MIRACL用户手册(译) 下载本文

MIRACL用户手册

译:叶道全 yedaoq@126.com

摘要

Miracl库包含100余个例程,涉及多倍精度运算(multiprecision arithmetic)的各个方面。定义了两种新的数据类型——表示大整数的big类型和表示有理数的flash(short for floating-slash)类型。大整数例程基于Knuth算法(在他的著作“The Art of Computer Programming”第四章中提出)。floating-slash(不固定斜杠?)算法基于圆整小数,最初由D.Matula和P.Kornerup提出。所有例程都针对速度和效率进行了全面的优化,同时也是标准的,可移植的C程序。另外,对于某些时间要求非常严格的算法,Miracl也针对流行的Intel 80x86系列处理器提供了汇编语言实现。Miracl还提供了C++接口。

Miracl的所有源代码都包含于此。

第二章 安装

通过Microsft C/C++、Borlands Turbo C/C++、Watcom C以及DJGPP GNU编译器,MIRACL库已经成功安装到VAX11/780,各种UNIX工作站(Sun,SPARC、Next以及IBM RS/6000),IBM PC等机器上。还有ARM机器和Apple Macintosh。最近MIRACL也已经在Itanium和AMD 64位处理器上运行过了。

MIRACL分发包中包含了库中所有模块的完整源代码以及各自的示例程序。大部分是用标准的ANSI C编写的,可用任意规范的ANSI C编译器进行编译。一些模块包含大量的内联汇编代码,用于优化在某些特定编译器/处理器组合上的性能。通过条件编译,它们可以透明地调用,并且不会影响到其它编译器。批处理文件xxdoit.xxx包含在多种编译器上生成库文件和示例程序的命令。请打开并检查与你的配置相关的文件。

分发包包含了部分流行的编译器的预编译库文件:ready-to-run版本,它们可立即使用,为了节省空间,其中并没有包含所有的示例程序。

要生成一个库,必须使用编译器,文本编译器,链接器,库管理实用程序(librarian utility)以及汇编器(assembler,可选),请阅读编译器文档以获取更多细节。mrmuldv.any文件包含了时间关键(time-critical)

的例程muldiv,muldvd,muldvd2和muldvm的汇编语言版本,它们可能需要根据配置作一些改动。当编译器不支持一个可用于保存两个单字长整数乘积的双倍长度类型时,这些模块尤为必要。许多现代编译器支持这一点(通常称为long long),在这种情况下,一般使用这些模块的C版本mrmuldv.ccc(直接拷贝到mrmuldv.c)就足够了。更多细节请仔细阅读手册以及mrmuldv.any的注释。

必须指定硬件/编译器规格文件mirdef.h。为了协助此过程,提供了此头文件的五个示例:mirdef.h16(16位处理器)、mirdef.h32(32位处理器)、mirdef.haf(工作在16位模式的32位处理器)以及mirdef.hpc(工作在16位环境中的伪32位)。

请注意完整的32位版本是最快的,但只是在使用一个32位处理器及一个32位编译器的时候如此。在Unix环境中使用gcc和g++时尽量用mirdef.gcc。

config.c用于协助配置过程。在目标处理器上编译和运行它,它会自动生成mirdef.h文件,并给出常规的配置建议。它还生成一个miracl.lst文件,其中包含建立相关库时应包含的MIRACL模块列表。强烈建议尝试此程序。编译它时请务必关掉所有编译器优化选项。

mirdef.h文件包含一些可选定义:当无法满足muldvd,muldvd2和muldvm在mrmuldv.c中的版本时,定义MR NOFULLWIDTH;若要在程序中使用flash变量,定义MR_FLASH;MR_LITTLE_ENDIAN 与 MR_BIG_ENDIAN,必须定义其中之一,config.c会自动决定哪一个适用于你的处理器。

省略MR_FLASH时,big变量可以更大,并且生成的库也更小。定义MR_STRIPPED_DOWN将忽略错误消息,可以进一步节约生成的代码空间,请小心使用!

如果不想要任何汇编器,请定义MR_NOASM。这将以内联的方式生成四个时间关键例程的标准C代码。这稍快一些——节省了函数调用的开销——这也是最优编译器得考虑的一点(原文:and also gives an optimising compiler something to chew on)。

使用Microsoft Visual C++时,msvisual.txt中提供了一些有用的建议。Linux操作系统对应的是linux.txt,Borland编译器用户则可以查看borland.txt。

预生成库或.txt文件中提供的建议不可用的时候,通过下列步骤多半能成功地生成MIRACL库: 1. 在目标处理器上编译并运行config.c。

2. 将生成的mirdef.tst重命名为mirdef.h。

3. 根据config程序的建议,从mrmuldv.any中提取一个合适的mrmuldv.c或将标准C版本的mrmuldv.ccc拷贝到mrmuldv.c)。如果是纯汇编,则可以命名为mrmuldv.s或mrmuldv.asm。

4. 如果选择了快速KCM或Comba模乘算法,则编译并运行mex.c实用程序(在任意工作站上)。使用它自动生成mrcomba.c或mrkcm.c,其中需要一个处理器/编译器规格文件xxx.mcs,同时编译器必须支持内联汇编。

5. 确保编译器能访问所有MIRACL头文件。通常标志-I或/I允许访问当前目录中的所有头文件。 6. 编译miracl.lst文件中列出的MIRACL模块并创建库文件,通常是miracl.a或miracl.lib。这可以通过将miracl.lst编译为一个合适的批处理文件或make文件来实现。在UNIX上可以像这样简单:

gcc -I. -c -O2 mr*.c ar rc miracl.a mr*.o

7. 若要使用MIRACL的C++包装,编译所需的模块,例如zzn.cpp和(或)big.cpp等。

8. 将MIRACL库和所需的任意C++模块编译并链接到你的应用程序(原文:Compile and link your application code to any C++ modules it requires and to the MIRACL library)。

记住MIRACL是可移植软件,它可以移植到任意支持某个ANSI C编译器的计算机上。

请注意MIRACL是一个C库,而不是C++。它总是应该作为一个C库来生成,否则你可能得到编译器错误。为在C程序中包含MIRACL例程,请在程序的开始处include头文件miracl.h(在stdio.h之后)。虽然大部分情况下使用C++包装更为可取,你也还是可以通过以下方式在C++程序中直接调用MIRACL例程:

extern \{

#include \}

2.1 优化

在MIRACL的上下文中,这意味着更快的速度。配置MIRACL时需要作的一个关键决策就是确定要使用的底层类型(经常是int类型)。如config所要求的,通常应定义尽可能大的底层类型。如果你有一个64位处理器,你就应该可以指定一个64位的底层类型。在某些场合,使用一个双精度浮点型的底层类型可能更快。

显然纯C的MIRACL产品是最慢的(当然它依然相当地快),它也是最容易入门的。这需要一种宽度是底层类型的两倍的整数类型。在这样的背景下,请注意目前大多数的编译都支持一种long long(有时称为__int64)类型,其尺寸是int的两倍。

如果你的处理器是低端的RISC类型并且不支持整数乘除指令,或需要使用非常大的模数,则

Karatsuba-Montgomery-Comba快速模乘技术可以使乘幂加密系统(exponentiation cryptosystems)更快。config程序将同样针对此点为你提供引导。

有时用汇编语言实现mrmuldv模块会更快。这不需要双倍尺寸的数据类型。如果够幸运你的编译器也支持自动调用内联汇编,则将更变更快。可以在miracl.h中查看哪些编译器支持这种方法。

为获得终极速度,可以使用mrkcm.c,mrcomba.c中实现的非常(extreme,极品)技术,kcmcomba.txt中有关于如何用mex实用程序自动生成这些文件的操作指南。

2.2 从版本3升级

版本4引入了MIRACL实例指针(MIRACL Instance Pointer,mip)。之前的版本使用一些全局和静态变量存储内部状态信息。这主要有两个问题。首先是为了避免冲突,这些全局变量取了一些令人费解的名字;其次是它使得开发多线程应用程序变得更困难。所以从版本4开始,这些变量声明到一个miracl结构中,作为实例变量来引用,必须通过指向miracl结构的指针来访问它们。现在全局指针是MIRACL维护的唯一全局/静态变量。它的值由负责初始化MIRACL库的mirsys例程返回。

C++程序员应注意miracl与Miracl命名上的区别,mip可以通过对Miracl实例取址来获得: Miracl precision = 50; ...

mip = &precision; ... etc

2.3 多线程编程

从版本4.4开始,通过若干特性,MIRACL对多线程编程提供了完整的支持。

要解决的问题是MIRACL需要通过mip指针来访问许多实例相关的信息。理想的状态是没有全局变量,但MIRACL有一个这样的指针。不幸的是每个使用MIRACL的线程都需要一个属于自己的mip,用来指向它自己的独立状态信息。这是多线程的一个众所周知的焦点(原文:This is a well-known issue that arises with threads)。

第一个解决方案是修改MIRACL,将mip作为所有MIRACL函数的参数来传递,用来替代全局变量。通过在mirdef.h中定义MR_GENERIC_MT,MIRACL将自动更改以支持此方式。现在(几乎所有)MIRACL例程都改为第一个参数是mip。一些简单的函数例外,它们不需要mip参数——参考手册中用星号来标识它们。brent_mt.c是一个使用以这种方式建立的MIRACL库工作的示例程序。请注意这种解决方案不适用于使用MIRACL的C++包装的应用程序,只适用于直接访问MIRACL例程的C程序。

另一种可选方案是使用Key,这是一种线程特定的“全局”变量。Key不是C/C++标准的一部分,而是操作系统的特殊扩展,通过特殊的函数调用来实现。MIRACL对Microsoft Windows和UNIX操作系统提供了支持。对于前者,Key称为线程本地存储(Thread-Local Storage);对于后者,MIRACL对POSIX标准接口提供了多线程支持。一个对Windows和Unix都非常有用的参考是[Walmsley]。对多线程的这种支持实现在模块mrcore.c中(位于文件的起始处的初始化例程mirsys中)。

对于Windows,在mirdef.h中定义MR_WINDOWS_MT。对Unix则定义MR_UNIX_MT。在两种场合都有一定的程序涵义(源文:In either case there are some programming implications.)。

在第一种场合中,用于保存mip的Key必须由程序的主线程初始化和销毁(最后)。这些功能通过调用相应的特殊例程mr_init_threading和mr_end_threading来完成。

在C++程序中,这些函数可能与全局变量的构造函数和析构函数相关联[Walmsley]——这将确保它们会在主线程创建新的线程分支前的一个适合的时候被调用。它们必须在线程显式(或隐式)地调用missys(通过创建线程特定的Miracl实例)之前被调用。

强烈建议程序在开发时不要实现对多线程的支持。只有程序经过完整的测试和调用,它才应转换到一个线程中(原文:should it be converted into a thread)。

线程编程可能要求其它的操作系统的特殊机制——在链接特殊的库或访问特殊的堆函数方面。这里值得指出的是MIRACL堆访问都是通过mralloc.c模块完成的。

threadwn.cpp程序是一个Windows C++多线程示例。阅读其中的注释——它可以编译和从Windows命令提示符运行。类似地threaduc.cpp是一个Unix的多线程示例。

2.4 受限环境

在版本5的中,有一个对在非常小和受限的环境中的MIRACL实现的新支持。使用config实用程序,它现在支持各种时空交换(time/space trade-offs),最主要的革新是在一个不支持堆的环境中生成和使用MIRACL。通常big变量的空间从堆中分配,但通过在配置头文件中指定MR_STATIC,可以生成一个总是尝试从静态内存或栈,而不是堆中分配内存的版本。

这带来的主要负面影响是big变量的最大尺寸必须在编译时确定(生成库的时候)。如往常一样,在这个过程中最好让config实用程序引导你创建一个合适的配置头文件mirdef.h。

对于C程序员,使用下列方式从栈中为big变量分配内存: big x, y, z;

char mem[MR_BIG_RESERVE(3)]; memset(mem, 0, MR_BIG_RESERVE(3));

为三个big变量分配的空间都在栈上并且被清零,然后每个变量应如下初始化: x = mirvar_mem(mem, 0); y = mirvar_mem(mem, 1);

z = mirvar_mem(mem, 2);

从单个内存块中为多个big变量分配所有空间是有意义的,那样可以更快的初始化,而且可以对变量对齐进行完整的控制——编译器有时会出错。请注意big初始化函数mirvar在这种模式中不再有效,分配操作应像上面描述的那样实现。

最后,可以选择性地在函数末尾调用memset来在离开前清空内存块——出于保密原因,这可能很重要。请参考示例程序brent.c。

这种机制在实现一个使用椭圆曲线的非常小的程序时可能非常有用。椭圆曲线要求的big数字要比其它加密技术的小得多。从栈中为椭圆曲线的点分配内存:

epoint *x, *y, *z;

char mem[MR_ECP_RESERVE(3)]; memset(mem, 0, MR_ECP_RESERVE(3)); 初始化这些点:

x = epoint_init_mem(mem, 0); y = epoint_init_mem(mem, 1); z = epoint_init_mem(mem, 2);

同样,在离开函数前清空相关内存是明智的。

此机制对C++编程同样支持得很好,它与第7章描述的栈分配方法联合工作。pk-demo.cpp是一个使用示例。

在一些极端场合中,可能要求从栈中分配所有内存。这允许使用和重用最多的内存,并且避免宝贵的RAM出现碎片。对于C程序,这可以通过在mirdef.h中定义MR_GENERIC_MT来实现。

这种场合中的典型mirdef.h头文件可能是这个样子的: /*

* MIRACL compiler/hardware definitions - mirdef.h * Copyright (c) 1988-2005 Shamus Software Ltd.

*/

#define MR_LITTLE_ENDIAN #define MIRACL 32 #define mr_utype int #define MR_IBITS 32 #define MR_LBITS 32

#define mr_unsign32 unsigned int #define mr_dltype __int64

#define mr_unsign64 unsigned __int64 #define MR_STATIC 7 #define MR_ALWAYS_BINARY #define MR_NOASM

#define MAXBASE ((mr_small)1<<(MIRACL-1)) #define MR_BITSINCHAR 8 #define MR_SHORT_OF_MEMORY #define MR_GENERIC_MT #define MR_STRIPPED_DOWN

关于使用这种类型的头文件的示例程序,请查看ecsgen_s.c,ecsign_s.c,ecsver_s.c,ecsgen2s.c,ecsign2s.c和ecsver2s.c。这些程序使用Microsoft C++在Pentium上实现了非常小而快速的ECDSA密码生成,数字签名以及校验算法。ecdh.c是另一个很棒的示例,它使用导航预行计算(precomputation)来加速一个EC Diffie-Hellman实现。

注意:不使用堆有一些小问题:结构不能再是可变大小的,因此在这种模式中MIRACL的许多特性都不再可用。例如中国剩余定理(Chinese remainder theorem)程序要求的导航预行计算(precomputation)就无

法支持。但在一个受限环境中,有理由假定诸如导航预行计算(precomputation)将脱机实现,以使将程序固化到ROM中成为可能。

MIRACL模块经过了周密的设计以使对于给定的任务,应用程序都只需要从库中引入最少的模块。这可以帮助应用程序保持最小的尺寸。但是如果程序的尺寸是一个大话题(big issue),则可以手工从模块中删除不需要的函数来达到额外的节约。

2.5 平台特定的信息

2.5.5 Microsoft Visual C++

在Microsoft C++6.0上创建MIRACL应用程序的步骤如下:

1. 创建一个Win32 Console Application的空工程。

2. 选择菜单“Project”——“Add to Project”——“File”。

3. 选择“Library files(.lib)”文件类型,查找预编译的MIRACL库文件ms32.lib,将它添加到工程。 4. 添加一个工程的主文件(例如limlee.cpp)。

5. 如果是C++主工程文件,则再添加其它所需的文件,如big.cpp、zzn.cpp等。主文件的头部注释中列出了所需的文件。

6. 选择菜单“Project”——“Setting”。选中C/C++ Tab页,选择“Preprocesser”目录,在“Additional Include Directoryies”中指定MIRACL头文件路径。

7. 确保应用程序运行目录中的所有程序所需文件均可用,如*.key,*.dss或*.ecs等文件。 8. 生成并运行程序。

在Microsoft C++6.0上创建MIRACL库的步骤:

1. 编译并运行config.c实用程序,将生成的mirdef.tst重命名为mirdef.h。注意Microsoft C拥有一个64位整数类型__int64。

2. 新一个“Win32 Static Library”类型的工程。

3. 添加适当的mr*.c文件,它们在miracl.lst中列出。

4. 选择菜单“Project”——“Setting”。选中C/C++ Tab页,选择“Preprocesser”目录,在“Additional Include Directoryies”中指定MIRACL头文件路径。

5. 编译工程生成MIRACL库。

在Microsoft C++.NET上创建MIRACL应用程序的步骤:

1. 创建一个Win32 Console Application的空工程。

2. 去掉工程中的空主文件,用MIRACL提供的文件(如limlee.cpp)将其替换。请注意现在主函数称为_tmain。

3. 选择菜单“Project”——“Add to Project”——“File”。选择“All files”文件类型,查找预编译的MIRACL库文件ms32.lib,将它添加到工程。

4. 如果是C++主工程文件,则则再添加其它所需的文件,如big.cpp、zzn.cpp等。主文件的头部注释中列出了所需的文件。

5. 选择菜单“Project”——“Properties”。打开C++标签页,到“Precompiled Headers”,选择“Not using precompiled headers”。再打开“General”标签页,指定MIRACL头文件的路径。

6. 生成并运行程序。

在Microsoft C++.NET上创建MIRACL库的步骤:

创建一个“Win32 Project”类型的Visual C++工程。 选择“Application Settings“,选择”Static Library“。 添加相应的mr*.c文件。

选择“Project”——“Properties”。打开C++标签页,到“Precompiled Headers”,选择“Not using precompiled headers”。再打开“General”标签页,指定MIRACL头文件的路径。

生成工程。

若要在基于MFC Win32工程中使用MIRACL,这里有一些相关建议:

1. 使用config.c实例程序生成mirdef.h,并在其中定义MR_NO_STANDARD_IO,再生成MIRACL库。 2. 如果使用C++ MIRACL类,别忘了在工程设置中将big.cpp等MIRACL实现文件设置为“not using pre-compiled headers”。

3. 别忘了MIRACL是一个C库,若要直接调用MIRACL例程,必须: extern \{

#include \}

使用C++时,通过C++包装来访问MIRACL可能更合适。

4. 要显示一个big值,先将它转换为string。见big.h头部的注释。

对于Visual C++8.0,预编译的ms32.lib无法工作,必须首先生成一个新的库文件。

第三章 接口

/*

* Program to calculate factorials. */

#include

#include \void main() {

下面是一个一个计算并输出1000!(1000的阶乘)的程序:

/* calculate factorial of number */ big nf; /* declare \int n;

miracl *mip = mirsys(5000, 10); /* base 10, 5000 digits per big */

nf = mirvar(1); /* initialise big variable nf=1 */ printf(\printf(\scanf(\getchar(); while (n > 1)

premult(nf, n--, nf); /* nf=n!=n*(n-1)*...2*1 */ printf(\

otnum(nf, stdout); /* output result */ }

若要在程序中使用MIRACL,则必须先包含声明: #include “miracl.h”

这告诉编译在继续编译前将C头文件miracl.h包含到当前主程序中来。此文件包含用户可以使用的所有MIRACL例程。mirach.h中的子头文件mirdef.h包含硬件/编译器特定的细节。

在主程序中,必须调用mirsys来初始化MIRACL系统,通过mirsys指定基数和big、flash变量的最大尺寸,同时它还将初始化随机数系统,并创建一些工作区变量(big类型)供内部使用。返回的值是Miracl实例指针(Miracl Instance Pointer,mip)。此指针可用于访问与当前MIRACL实例相关的各种内部参数。例如设置ERCON标记可以写为:

mip->ERCON = TRUE;

对mirsys的调用还会初始化MIRACL包集成的错误跟踪系统。Whenever an error is detected the sequence of routine calls down to the routine which generated the error is reported, as well as the error itself。一条典型的错误消息类似:

MIRACL error from routine powltr

called from isprime called from your program Raising integer to a negative power

这些错误报告能够协助我们在开发过程中更方便地调试这些例程。一个相关的实例变量是TRACER,初始为OFF,若设置为ON,将导致对MIRACL例程中的程序流程的跟踪输出到屏幕上。

实例标记ERNUM初始为0,用于记录最近发生的MIRACL发生的错误的编号。如果标记ERCON设置为FALSE(默认),错误消息将直接输出到stdout并通过系统调用exit(0)终止进程。如果你的系统不支持exit例程,则编程人员必须提供一个替代品。若ERCOM设置为TRUE,则不会发出错误消息,取而代之由编程人员来负责检查和处理错误。在这种情况下,程序将继续执行。编程人员可以选择处理错误并将ERNUM重围为0。但错误经常是致命的,如果ERNUM非零,接下来的所有MIRACL例程调用都将“失败(fall-through)”并立即退出。可能的错误列表请参见miracl.h。

用户程序中的每个big或flash变量都通过调用mirvar初始化,它也允许对变量赋一个整数作为初值。 miracl.h中声明的所有的算术和数论例程均在在这些变量上使用。这些例程在参数使用上(几乎总是)具有完全的灵活性。例如调用multiply(x,y,z)将把x和y的乘积保存到z中,调用multiply(x,y,x)、multiply(y,y,x)或multiply(x,x,x)都是同样有效的。请注意按照约定第一个参数通常是例程的输入。

所提供的例程不仅可以用于对big和flash进行算术运算,也可以在内置的整形和双精度浮点型变量上进行运算。

类型转换例程也有提供,细节请参考参考手册中的有关文档。

输入输入到文件或I/O设备由例程innum,otnum,cinnum,cotnum处理。前两个使用mirsys中指定的固定基数。后两者与可由用户动态更改的实例变量IOBASE联合使用。一个简单的规则是若程序是CPU边界

的(原文:A simple rule is that if the program is CPU bound),或涉及基数的变动,则初始应将基数设置为MAXBASE(或0——当完全宽度的基数可行时),并使用cinnum和cotnum。另一方面,如果程序是I/O边界的(原文:If the program is I/O bound),或需要访问数字中的特定位(通过getdig,putdig和numdig),则使用innum和otnum。

例程instr,otstr,cinstr和cotstr以类似的方式支持从字符串输入或输出到字符串。输入例程可用于为big或flash设置一大的常量值。通过输出到字符串,可在实际输出文件或I/O设备之前完成格式化。

最大可表示基数为256的值,基数小于等于60的数使用0-9,A-Z以及a-z中的符号。

64进制数字强制使用base64编码。如有必要,base64数字输出时将在结尾后以 ‘=’符号填充(不会有其它格式)。输入时,空白字符会被跳过,并忽略填充部分。不要对flash数字使用base64。不要使用base64输出负数(会忽略符号)。

如果基数大于64且不等于64,则将使用ASCII码0-255。

基数256在将一行文件当作大整数来解析时有用,如在第8章描述的公钥加密程序中。例程big_to_bytes和bytes_to_big允许直接在内部的big格式和二进制之间进行转换。

字符串通常是以零结尾的。但在使用基数256时会产生一个问题。在这种情况下0-255中的任何一个数字都可出现在一个数中,所以零不应当作字符串的终止符。输入时应采用别的方式来指定字符串中数字的数量。

通过在调用innum或instr之前设置实例变量INPLEN(例如置为25),输入将在送入25个字符后终止。INPLEN初始为0,并且每次相关操作都会在返回之前将它重置为0。

例如,先初始化一个使用400字节长的big变量的MIRACL实例: miracl *mip = mirsys(400,256); 使用此基数,内部计算将非常高效。

将一个ASCII字符串作为一个256进制数输入,它以0结尾,因为不需要INPLEN: innum(x, stdin);

现在再输入一个1024位的随机值: mip->INPLEN=128;

innum(y, stdin); 以16进制查看输出: mip->IOBASE = 16; cotnum(w, stdout); 以64进制查看输出: mip->IOBASE = 64; cotnum(w, stdout);

有理数可以使用小数点形式(例如0.3333)或分数形式(例如1/3)来输入。也可以通过设备实例变量RPOINT为ON或OFF来选择以哪一个形式输出。

第四章 内部表示

大部分语言的编译器通常提供一到两种浮点类型用于表达所有实数,与一到多种整数类型一起用来描述所有数字。这些内置数据类型与底层的计算机架构密切相关,它们通常合理地设计为对出现频率更高的较小数运算得更快,而不是与那些出现频率领很低的大数字一样慢。浮点类型允许使用相对来说小范围的二进制数字(例如32位)来以足够的精度在一个大的动态范围内表示实数。整数类型直接使用相同的二进制值来表示一个限值内的所有整数,或用二进制补码来表示负数。

传统的计算机运算方式存在几个缺陷:

1. 浮点和整数类型是不相容的。整数集虽然是无穷的,但依然是有理数的一个子集,而有理数则是实数的一个子集。因此每个整数都有一个等价的浮点表示。不幸的是这两种表示通常是不一样的。例如整数类型会用等价的二进制来表示一个小的正整数,而浮点类型则会用分开的尾数和幂来表示。这意味着需要用转换程序来从一种形式转换为另一种形式。

2. 大部分有理数都无法被精确的表示(例如1/3,译注:貌似1/3是无理数啊)。事实上浮点系统只能精确地表示那些分母是底层表示的基数的因子的整数倍的有理数。例如我们熟悉的十进制系统只能精确地表示分母是2或5的倍数的有理数;1/20是0.05,而1/21是0.0476190476190...

3. 浮点的圆整是依赖于基数的,而且也是不明显的错误的一个根源。

4. 一个事实是整数和浮点数据类型的尺寸由计算机架构决定,这使语言设计者难以保持它们的语言能够真正可移植。

5. 数字只能以固定的依赖机器的精度来表示。在许多应用程序中这是严重的缺陷,例如在新兴的正在成长的公钥加密算法领域。

6. 基数依赖的现象是不易考虑的。例如很难从传统的整形中访问十进制数的某一位。

这里描述了一组用于直接操作多倍精度有理数的C例程,多倍精度整数是其一个兼容子集。同时也可以进行近似的实数运算。

两个新的数据类型是big和flash。前者用于存储多倍精度整数,后者以\不固定斜杠)的形式用分子和分母来存储小数。两者的格式都是一个固定长度的数字数组,外加一个包含符号和长度信息的独立32位整数。

需要用一种内置类型来储存数字,这个类型定义为mr_small,它可以是int、long甚至double。 struct bigtype {

mr_unsign32 L; mr_small *d; };

typedef struct bigtype *big; typedef struct bigtype *flash; 定义变量:

big x, y[10], z[10][10];

要注意一个big变量仅仅是一个指针,big(或flash)变量实体所需要的内存是从堆(也可以从栈)中分配的。因此使用前必须进行初始化。

分母长度为0意味着实际上分母是1,分子长度为0意味着实际上分母是1。零本身定义为一个首元素是0的数字。

flash中的斜杠并不在一个固定位置,它依赖于分子和分母的相对尺寸。

操作flash时,会将它拆分为分别等于分子和分母的两个big值。big的操作由提取并操作其中的每个mr_small元素组合而成。为避免可能的溢出,每个元素中的值通常限定在一个比完整的变量所能表示的最大值小的范围,例如在16位机上是0-32767(2的15次方 - 1),但基于2的16次方进行编程也是可以的,C语言不会报告整数溢出错误。

系统初始化时用户必须指定一个固定的尺寸(以字或字节为单位)和基数,这两个值将应用于所有big和flash变量。基数的上限与机器字长有关,可以使用不超过上限的任意值作为基数。

当使用一个偏小的基数b时,系统实际上将使用b*n作为基数(这能够更高的效率),n是一个满足使b*n能被单个机器字表示的最大整数。

当使用一个完整宽度的基数时,程序通常运行得最快。若使用这个基数,请在调用mirsys时指定基数为0。需要注意的是对某些时间关键的例程,这种模式在部分常用的编译器/处理器组合中是由大量的汇编代码支持的。例如,对于使用Borland/Turbo C和80x86处理器,可以查看mrarth1.c中的源程序。

将符号和分子、分母的大小信息编码到单个字中是可行的,C语言包含位操作的完整结构。

第五章 实现

big类型的算术运算的实现并没有太大的创造性,使用的算法是Knuth[Knuth81]。但是做了一些速度优化的工作。

在耗时的乘除法内部,典型的实现是对操作数的每一位相乘,加上上一次操作的“进位”,将结果分为一个数字和对下一个操作的“进位”。以10进制乘法为为例,考虑8723536221 * 9:

为了正确地处理数字5所在的列,需要计算5 * 9 = 45,加上前一列的进位3,得到48,将其中的8作为

结果,4作为对下一列的进位。

基本的原始操作是计算(a * b + c) / m的商和余数。在上例中: a = 5, b = 9, c = 3, m = 10

这个操作具有惊人的广泛用途,并且它处算法的最里层循环中,因此它的高效实现是非常必要的。 对这个MAD(Multiply, Add and Divide)操作,高级语言在实现时通常具有三个难点: 1. 慢。

2. 商和余数不能同时作为一次除法的结果,因此计算过程执行两次,一次用来获得商,一次用来获得余数。 3. 即使操作结果是两个单值,但中间结果ab + c可能是双倍长度的。很庆幸地C语言拥有long整形可以用来临时保存这个乘积。

因为这些原因,最好用汇编语言来实现这些关键操作。即使C语言的long类型不是双倍长度(在32位机器上,大部分C编译器的int和long都是32位),在机器语言层也通常能处理一个临时的双倍长度的结果。有关详细资料可以查看mrmuldv.any文件。

MIRACL对big和flash类型使用固定长度的数组,这是为了避免可变长度方式下需要进行内存分配和碎片收集的困难度和时间消耗。这也意味着在对big值进行计算时,所有的结果和中间数者必须小于等于在mirsys中指定的固定尺寸。

在实践中,稳态计算中涉及的数字的尺寸大多相近。除非两个数相乘导致产生一个双倍长度的中间结果,但这也通常会接下来立即在一次除法操作中缩小。这种情况的一个典型的例子是Pollard-Brentl因式分解。

请注意上面提到的问题的另一个微观表现。只是为了拷贝这些偶然出现的中间值,而为每个变量分配所需大小的两倍的存储是很可惜的。因此,MIRACL库提供了一个特殊的Multiply,Add,Divide例程(名为mad)。它已经被证明在内存受限的机器上实现大型程序(像Pomerance-Silverman-Montgomery因式分解)时非常有用。

除了基本的算法运算,以下例程也有提供:

1. 生成和判断质数,使用概率质数判断方法[Knuth81]。

2. 生成随机big和flash,基于subtract-with-borrow生成器[Marsaglia]。但是请注意内部实现的的基本

随机数生成器并不具有秘密安全性(cryptographicly secure,保密性?)。在真实的加密应用程序中,它是不够可靠的。mrstrong.c中提供了一个强保密生成器。

3. 计算幂和根。

4. 实现了普通的和扩展的欧几里得最大公约数(Euclidean GCD)算法。

5. 实现了中国剩余定理[Knuth81],以及计算雅可比符号(Jacobi Symbol)[Reisel]。 6. 超大数的乘法,使用快速傅里叶变换(Fast Fourier Transform)[Pollard71]。

在进行大规模模运算(modular arithmetic)时,一个时间上的关键操作是\模乘\,就是对两个数进行乘法之后再除以一个固定的n,取其余数。一个明显的解决方案是用之前提到的mad例程来完成,但Montgomery提出了一种替代方案,这要求要求先将两个数转换为特殊的n-residue样式,在n-residue样式下可以通过一个完全没有用到除法的特殊例程更快的完成模乘操作,然后再将结果转换为普通格式。请注意n-residue的模加与模减操作与普通的类似,使用相同的例程。 鉴于要求对变量进行n-residue格式转换,Montgomery算法只应考虑用于对同一个模数进行大量模运算的计算场合。这在C++环境中使用更为方便,因为屏蔽了这些细节。

MIRACL库中的许多要进行大量模运算的例程在内部使用了蒙哥马利算法,如高度优化的模幂(modular exponentiation)函数powmod,以及实现GF(P)椭圆曲线运算的函数,可以在参考手册中找到相关信息。

为了达到最快的模运算,同时还必须使用汇编,针对特定的模数或特定尺寸的模数进行优化等方法。有相当数量的技术可供使用:

首先是Comba和KCM方法,其实现分别在mrcomba.c和mrkcm.c文件中。这些文件通过向模板文件mrcomba.tpl和mrkcm.tpl中插入.mcs文件中的宏来创建。这是通过宏展开程序mex自动完成的。在目标机器上编译并运行config.c以自动创建合适的mirdef.h和获取继续操作的建议。同时也请阅读kcmcomba.txt。为使嵌入的程序获得尽可能快的性能,当没有与您的编译器/处理器对应的x.mcs文件时,建议你自己创建一个。

另外有两种在一定程序上属于实验性的技术,分别实现在mr87v.c和mr87f.c文件中,仅适合于80x86系列处理器和Borland C++编译器。

在满足条件时,相应的代码将被自动调用。

请务必注意上述四种技术要求编译器支持内联汇编,而且后两者仅用Borland C++ V4.5编译器在80x86

系列处理器上进行过测试。

第一种方法的思路是对乘法和归约过程(multiplication and reduction process)中隐含的循环进行完全的分解和重组,由[Comba]首先提出,[Scott96]对其进行了修改,见mrcomba.tpl。这种方法必须使用固定宽度的模数并在编译时确定(在mirdef.h中定义MR_COMBA宏为模数尺寸,以字为单位)。对中小尺寸的模数这种方法工作得很好,尤其是在GF(p)椭圆曲线加密算法中。若要获得比这还要快的速度,归约过程可对某些具有特别简单的样式(particularly simple form)的模数进行优化。这可以通过手工向mrcomba.tpl插入相应的代码来完成。针对模数p = 2 ^ 192 - 2 ^ 64 - 1的示例代码在例程comba_redc中给出,要运行此特殊的代码,必须在mirdef.h中定义MR_SPECIAL。

这项技术可以与Karatsuba的快速乘法[Knuth81]联合用于提高对大模数的模乘运算速度。可在mirdef.h中定义MR_KCM以调用此Karatsuba-Comba-Montgomery (KCM)方法。以机器字为单位的模数尺寸限定为必须等于MR_KCM*2n,n可为(合理范围内的)任意正整数,这是使用Karatsuba算法的一个后果。例如若在32位机器上定义MR_KCM为8,则可用的模数尺寸为512,1024,2048位等。

另一个选择是利用浮点协处理器(floating point co-processor),如果存在的话。它的乘法指令通常要比整数单元的要快[Rubin]。Intel Pentium处理器内嵌的协处理器可以用三个周期完成一次乘法,而一次整数乘法需要10个周期。不过对Pentium Pro,II或III情况可能不同。同时协处理器有8个额外的寄存器,可直接操作64位数字。这个特性给程序员提供了一些额外的可以加以利用的灵活性。一些实验性的代码在mr87f.c和mr87v.c中提供,可通过在mirdef.h中定义MR_PENTIUM加以利用。使用config.c来生成mirdef.h——这时底层类型必须选择double。模块mr87v.c实现了简洁的循环代码(compact looping code),可工作于小于某个最大值的任意模数。模块mr87f.c展开了这些循环以获得更快的速度,但也因此更为宠大并且要求固定尺寸的模数。请注意这些运行方式与完全宽度基数(full-width base)是不兼容的,它们通常在基数为2^28或2^29(config.c会为你计算出这个值)的数字上工作得最好。同时也请注意即使这些方法可以加速Pentium上的模幂运算,但它也可能在大部分的其它80x86处理器上更慢,所以请小心使用。在一次测试中,对一个2048位的数字求2048位的幂再对一个2048位的模数取模的操作在60MHz的Pentium上耗时2.4秒。

图5说明了在Pentium Pro 200MHz处理器上使用Borland C 32位编译器编译时,各个方法所需的相对时间。基线对应mrarth2.c和mrmonty.c中用汇编直接实现的方法。Comba和KCM的汇编实现在文件ms86.mcs中。x轴表示模数尺寸,y轴表示时间。请注意在计算(ab mod n)时:

1. 假定a、b、n是随机生成的,拥有相同的位长度,并且没有特殊的格式。 2. 假定Comb(见[HAC]和brick.c)等优化技术均未应用。

图中8192位的模数相关的时间是准确的。与更小的模数相关的时间是以一个递增的放大比例显示的(比例以8为单位递增)。因此对4096位模数,应将其时间除以8,对2048位模数,应将其时间除以64,等等。对一些很大的模数进行完全的展开是不切实际的,因此图中并未提供相关算法的相关时间。

请注意Comba是针对512位及以下尺寸的模数进行过优化的。这意味着它是快速GF(p)椭圆曲线算法、1024位RSA加密算法(要求两个512位乘幂和一个中国剩余定理的程序)的实现的最佳选择。但这个结论是与处理器相关的,不适用于全部。而且Comba会产生大量的代码,这在某些程序中是需要慎重考虑的。在一些环境中(例如高速缓存非常小),使用非展开的汇编实现可能更明智。

从5.20版本开始,MIRACL直接用C提供了一个新类型,称为zzn2,它实际上由两个n-residue格式的big组成。

typedef struct {

big a; big b; } zzn2;

其中a和b分别表示实部和虚部,一个zzn2的值为a+ib,i为平方非剩余(quadratic non-residue)的虚平方根(imaginary square root)。一个zzn2变量是一个与某个素数模数相关的二次展开域的元素的表示(原文:A zzn2 variable is a representation of an element of a quadratic extension field with respect to a prime modulus p)。例如若 p ≡ 3 (mod 4),则i可取-1的平方根,and the analogy to complex numbers with their real and imaginary parts becomes clear。这在实现密码配对时尤其有用。作为一个使用范例,请参考cardona.cpp中一个解三次方程的例子。实例变量qnr中保存了一个默认的平方非剩余值,目前仅支持-1和-2。

为协助程序员产生针对非标准环境(例如嵌入式控制器)中的处理器的代码,动态内存分配的代码总是从mralloc.c中调用。默认情况下这将调用C运行库函数calloc和free。但很容易更改为其它内存分配机制。出于同样的原因,所有屏幕/键盘的输入输出都通过标准库函数fputc和fgetc执行,通过截取对这些函数的调用,I/O可以重定向到非标准设备。

第六章 Floating-Slash数字

一种直观的表示有理数的方式是通过简化的分数,用消去公因数的分子和分母来表示。这样的数字可以用直观的方式进行加减乘除,其结果也可以通过消去分子、分母的最大公约数(Greatest Common Divisor)来简化。MIRACL包含了一个高效的GCD子例程,该例程使用古典欧几里得(Euclidean)算法的一个针对多倍精度数的Lehmers变种。

另一种可选方案是使用有限连分数(finite continued fraction,[ Knuth81])来表示有理数。任何有理数p/q都可以表示为:

上式可以简洁的表示为:p/q = [a0/a1/a2/.../an],ai为正整数,通常很小。例如277/642 = [0/2/3/6/1/3/3]。

请注意连分数中的ai,它们是在对q和p进行Euclidean GCD计算时的次生产物”商“,因此很容易找出。 当我们用固定长度来表示有理数时,一个问题是一些操作的结果可能超过了固定的长度。这就需要一些方案来作截断或圆整。虽然并没有直观的截断大分数的方式,但截断一个连分数表达式却很简单。作为其结果得到的稍小的分数称为原分数的一个最佳有理近似或渐近分数(a best rational approximation, or a convergent)。

考虑截断277/642 = [0/2/3/6/1/3/3],简单地去掉CF表达式中最后一个元素,得到[0/2/3/6/1/3/3] = 85/197,这与277/642仅相差0.0018%。继续截断至22/51,19/44,3/7,1/2直至0/1,随着分数越来越小,误差逐渐增大。

上面提到的圆整称为“中间数圆整(Mediant rounding)“。若p/q和r/s是两个相邻的可表达数,则它们的中间数是不可表达数p+r/q+s。处于中间数与p/q之间的数圆整为p/q,处于中间数与r/s之间的数圆整为r/s,中间数本身圆整为p/q或r/s中较简单者。

从理论上说这是一种非常好的圆整方式,比浮点运算中相当随意且依赖基数的方式要好得多。此处(MIRACL)使用的即是这种方式。Matula和Kornerup描述了有关floating-slash运算的完整的理论基础[Matula85]。应注意到MIRACL的flash实际上是Matula和Kornerup分析的固定斜杠系统和非固定斜杠系统(fixed- and floating-slash systems)的混合产物,它的斜杠只能以字为单位浮动,而不是以位为单位。但由于精度的提升,flash的规格参数与floating-slash比较接近。

MIRACL例程mround实现了中间数圆整。若某个算术运算结果为p/q,则先对p和q应用Euclidean GCD算法,但此处目的不是为了获得最大公约数,而是为了获得商数来创建p/q的渐近分数,此过程直到渐近分数的尺寸大到无法用flash表示为止。完整的算法如下(Kornerup and Matula [Korn83]):

给定p>=0,q>=1,

从i = 0开始,依次递增。当

bi?1 >0时,用bi?2除以bi?1,得到商ai和余数bi,因此:

然后计算:

译注:因 当

xi / yi大到无法放到一个flash表达式中时,将xi-1/ yi-1作为圆整结果。

由于圆整过程必须应用到所有的运算结果上,并且也因为它可能会相当慢,因此我们作了许多对它的实现的优化工作。采用了Lehmer的思路[Knuth81]——尽可能久的只操作数字中最重要的部分,因此迭代过程中大多只需要作单精度运算。另外也特别留意了避免圆整结果超出flash表达式的限制[Scott89a]。诸如log(x),exp(x),sin(x),cos(x),tan(x)等初始函数的计算中使用的基本运算过程采用了Brent [Brent76]描述的快速算法。

大多时候程序都能够保证给出的结果是精确的,这可以通过检查实例变量EXACT来确认,EXACT初始为TRUE,只有发生了圆整操作,才会被置为FALSE。

使用可变的flash类型来逼近实数的运算的一个缺陷是可表达的值之间的间隙不固定(Matula and Kornerup[Matula85])。

考虑一个分子分母乘积不超过256的floating-slash系统,显然小于1/1的第一个可表示数是15/16,间隙为1/16;比0/1大的下一个分数是1/255,间隙为1/255。通常,对一个k位的floating-slash系统,间隙大小从2^(-k)至2^(-k/2)不等。在实践中这意味着一个处于某个比较大的间隙中的实数的表示精度将只有平常的一半。幸运的是这样的间隙非常少见,而且精度会越来越高,仅在简分数附近出现(原文:Fortunately such large gaps are rare, and increasingly so for higher precision, occurring only near simple fractions)。但这也说明实际结果中,小数部分只有一半是能够完全信任的。此问题的一个局部解决方案是直接用连分数来表示有理数,这在间隙尺寸上能够提供更好的一致性(Kornerup and Matula [Korn85]),但是很难用高级语言实现。

flash类型上的算术运算无疑要比同等尺寸的多倍精度浮点类型慢(例如[Brent78])。flash的优势在于它精确表示有理数的能力,以及对它们的精确运算。由于中间数圆整优先选择简分数(原文:prefer a simple fraction over a complex one)的倾向,即使需要圆整,通常也能得到正确的结果。例如roots程序先计算2的平方根,再对结果求平方,得到的结果依然为2——尽管内部做过多次圆整。

警告:不要将flash运算与内置的double运算混合。它们无法和平相处,如果决定用flash运算,就自始至终使用它,开始的时候就将所有常量转换为flash类型。若能将常量表示为分数则更好:

x = Flash(5,8) //x = 5/8 上式比x = 0.625更好。

第七章 C++接口

许多MIRACL包的用户对下述情况很失望,对一个flash变更x,其运算: t = x?x?1

必须使用下列顺序代码: fmul(x, x, t); fadd(t, x, t); fincr(t, 1, 1, t); 而不能简单的写为: t=x*x +x + 1;

当然某些人可以用MIRACL库来写一个有特殊目的的可解释这样的指令的C编译器(有关这种方式的一个示例,参见Cherry and Morris [Cherry])。但并没有必要采用这么极端的措施。C++是C的一个超集,它自然地继承了C并获得了广泛的认同。C++对C的增强主要在于使它成为一种面向对象语言。通过将big和flash定义为一种“类型“(C++术语),可以重载常用的数学运算符,因而编译器能够自动将连接big或flash变量的操作符替换为对适当的MIRACL例程的调用。此外C++还可以通过定义在类中的构造器/析构器自动进行初始化和清除工作。这可以使程序员从重复调用mirvar来初始化big和flash变量的单调工作中解脱出来。确实,

2只要正确地定义和创建了类型,就可以像使用内置的double和int类型那样来使用新的数据类型。使用C++还可以帮助用户屏蔽MIRACL的内部工作。

MIRACL库通过头文件big.h,flash.h,zzn.h,gf2m.h,ecn.h和ec2.h提供了C++的接口。功能的实现在相关的文件big.cpp,flash.cpp,zzn.cpp,gf2m.cpp,ecn.cpp和ec2.cpp中,它们应链接到任何需要使用它们的应用程序中。中国剩余定理也巧妙地实现为了一个类,其实现在文件crt.h和crt.cpp中,decode.cpp中则有一个使用示例。使用预先计算(precomputation,[ HAC])的Comb快速模幂算法实现在brick.h中,brick.cpp中有使用示例。GF(p)椭圆曲线等值在文件ebrick.h和ebrick.cpp中,GF(2^m)椭圆曲线等值则在ebrick2.h和ebrick2.cpp中。

/*

* Program to calculate factorials. */

#include

#include %using namespace std;

Miracl precision(500,10); // This makes sure that MIRACL // is initialised before main() // is called void main() {

/* calculate factorial of number */ Big nf = 1; /* declare \int n;

cout << \cout << \

cin >> n; while (n > 1)

nf *= (n--); /* nf = n! = n*(n-1)*(n-2)*....3*2*1 */ cout << \}

请注意对用于设置big变量精度的伪类Miracl的使用。它声明在全局范围内以确保MIRACL会在main调用前初始化(注意这不适用于多线程环境)。编译和链接此程序时,别忘了链接实现Big类型的文件big.cpp。

与内部Big类型相互转换很重要: 将十六进制字符串转换为Big: Big x; char c[100]; ...

mip->IOBASE = 16; x = c;

将Big转换为十六进制字符串: mip->IOBASE = 16; c << x;

若要与二进制相互转换,请使用from_binary()和to_binary()友员。 int len; char c[100]; ...

Big x = from_binary(len, c);

// creates Big x from len bytes of binary in c len = to_binary(x, 100, c, FALSE);

// converts Big x to len bytes binary in c[100] len = to_binary(x, 100, c, TRUE);

// converts Big x to len bytes binary in c[100] // (right justified with leading zeros)

在许多示例程序尤其是因式分解程序中,需要进行所有的算术运算。为避免乏味地在每个操作后都要进行降阶(reduction,mod n),使用了一个新的C++类ZZn,它定义在zzn.h中。ZZn的算子将自动进行降价。modulo(n)函数用于设置模数。GF2m类使用类似的方式来处理定义于GF(2^m)上的各个域元素。此时用modulo(m,a,b,c)来设置模数,它同时也指定了一个三项式

tm+ta+1(b = c =0)或五项式(tanomial)

tm+ta+tb+tc+1。详细内容参见IEEE P1363文档:http://grouper.ieee.org/groups/1363/draft.html

ZZn内部使用Montgomery表示法(见zzn.h),但作为C++的一个典型特性,这对程序员是透明的。 ecn.h中定义的类ECn使操作GF(p)椭圆曲线上的点成为一件简单的事情,同样屏蔽了所有麻烦的细节。ec2.h中定义的EC2完成与GF(2^m)相关的同样事情。

几乎所有的MIRACL功能都可以通过C++来访问。

大部分的示例程序的C++版本包含在分发包中,后缀名为.cpp。

用C++维护大对象的一个问题是编译器产生代码来创建/销毁/拷贝临时对象的倾向。默认情况下MIRACL从堆中获取内存给Big和Flash,这可能相当耗时,并且最终这些对象还要清除。从栈中分配内存可能要快一点,尤其对于相对较小的big数。这可以在编译时通过定义开关BIGS=m来做到。例如在Microsoft C++编译器上使用下列命令行:

C:\\miracl>cl /O2 /GX /DBIGS=50 brent.cpp big.cpp zzn.cpp miracl.lib 请注意m的值应小于主程序中调用mirsys(n,0)或Miracl precision=n时n的值。

在有限域上运算时,有效数字总是小于某个固定的模数。例如在有限域(mod n)中,ZZn可以处理关于模数n(512位,通过Modulo(n)指定)的值。在这种情况下可以定义ZZNS=16,从而所有的元素的大小都是16*32=512位并且从栈中创建(这在与Comba机制联合使用时工作得尤其好)。

类似地,当工作在域GF(2^283)上时,可以定义GF2MS=9,从而域上的所有元素都存储在从栈中分配的

固定的9个字中。

在后两种情况中,调用mirsys(n,0)或Miracl precision=n指定的精度值n应至少比ZZNS=m或GF2MS=m中指定的m大2。

开发时或当对象非常大时,不推荐使用这些方法。它仅与C++程序有关。见示例程序ibe_dec和dl.cpp中 有关使用此机制的注释。但同时它的好处也是明显的,程序运行效率可能提升一倍。

最后,这里有一个实现相对来说较复杂的加密协议的C++程序,注意其中对的域元素变量(大写字母)的转换。

/*

* Gunthers’s ID based key exchange - Finite field version * See RFC 1824

* r^r variant (with Perfect Forward Security) */

#include #include #include %using namespace std; Miracl precision = 100; char *IDa = \char *IDb = \// Hash function Big H(char *ID) {

// hash character string to 160-bit big number int b;

Big h; char s[20]; sha sh; shs_init(&sh);

while (*ID != 0) shs_process(&sh, *ID++); shs_hash(&sh, s); h = from_binary(20, s); return h; }

int main() {

int bits;

ifstream common(\Big p,q,g,x,k,ra,rb,sa,sb,ta,tb,wa,wb; ZZn G,Y,Ra,Rb,Ua,Ub,Va,Vb,Key; ZZn A[4]; Big b[4]; long seed;

miracl *mip = &precision;

cout << \cin >> seed; irand(seed);

// get common data. Its in hex. G^q mod p = 1 common >> bits; mip->IOBASE = 16;

common >> p >> q >> g; mip->IOBASE = 10; modulo(p); // set modulus G = (ZZn) g;

cout << \// CA generates its secret and public keys x = rand(q); // CA secret key, 0 < x < q Y = pow(G, x); // CA public key, Y=G^x cout << \// Visit to CA - a k = rand(q); Ra = pow(G, k); ra = (Big) Ra % q;

sa = (H(IDa) + (k * ra) % q); sa = (sa * inverse(x, q)) % q; // Visit to CA - b k = rand(q); Rb = pow(G, k); rb = (Big) Rb % q;

sb = (H(IDb) + (k * rb) % q); sb = (sb * inverse(x, q)) % q;

cout << \// offline calculation - a wa = rand(q);

Va = pow(G, wa); ta = rand(q); Ua = pow(Y, ta); // offline calculation - b wb = rand(q); Vb = pow(G, wb); tb = rand(q); Ub = pow(Y, tb); // Swap ID, R, U, V

cout << \// calculate key a

// Key = Vb^wa.Ub^sa.G^[(H(IDa)*tb)%q].Rb^[(rb*ta)%q] mod p rb = (Big) Rb % q;

A[0] = Vb; A[1] = Ub; A[2] = G; A[3] = Rb; b[0] = wa; b[1] = sa; b[2] = (H(IDb) * ta) % q; b[3] = (rb * ta) % q;

Key = pow(4, A, b); // extended exponentiation cout << \// calculate key - b ra = (Big) Ra % q;

A[0] = Va; A[1] = Ua; A[2] = G; A[3] = Ra; b[0] = wb; b[1] = sb; b[2] = (H(IDa) * tb) % q; b[3] = (ra * tb) % q;

Key = pow(4, A, b); // extended exponentiation

cout << \return 0; }

MIRACL演化出了一个复杂的类继承体系(见下图),尽可能多的类型直接在C/汇编的内核上直接建立。请注意对多项式,幂级数和扩展域的支持。

第九章 实例变量

这里列出了miracl.h中miracl结构的所有成员,它们都通过mip(Miracl Instance Pointer,Miracl实例指针)来访问。

BOOL EXACT 初始为TRUE,flash运算中发生圆整时置为FALSE。 int INPLEN 输入字符串的长度,输入二进制数据时使用。

int IOBASE 输入、输出时使用的“可打印”基数,在程序中可任意更改,必须大于等于2,小于等于256。 int IOBSIZ I/O缓冲区大小。

BOOL ERCON 错误发生时,默认产生一条错误消息并中止程序,用户可将此值置TRUE来处理错误。 int ERNUM 最后一次发生的错误的值。 char IOBUFF[] I/O缓冲区。

int NTRY isprime进行概率素数测试(probabalistic primality test)时迭代的次数,初始为6。 int *PRIMES 指向小素数表的指针。

BOOL RPOINT 置TRUE时数字使用小数形式输出,否则使用分数形式输出(默认)。 BOOL TRACER 置为ON时输出调试信息,默认为OFF。

第十一章 硬件/编译器接口

硬件/编译器细节通过mirdef.h向MIRACL指定。

移植到新的硬件环境中时必须编辑此文件,mrmuldv.any中一些时间关键的例程的汇编版本也要重新生成,不过大多数情况下C版本的mrmuldv.ccc可以直接拷贝到mrmuldv.c中。

可以的话最好使用交互式的config.c程序自动生成的mirdef.h文件。