比特币系统采用的公钥密码学方案和ECDSA签名算法介绍—第二部分:代码实现(C语言)

当前位置:首页 > 币圈百科 > 比特币系统采用的公钥密码学方案和ECDSA签名算法介绍—第二部分:代码实现(C语言)

比特币系统采用的公钥密码学方案和ECDSA签名算法介绍—第二部分:代码实现(C语言)

2022-12-03币圈百科220

(原理请参考:《比特币系统采用的公钥密码学方案和ECDSA签名算法介绍——第一部分:原理》 )

依赖库:openssl-1.01h,参考文档:3358 openssl . source archive . com/

为了降低代码复杂度,本文使用OpenSSL库实现了大整数(BIGNUM)的运算,比特币系统中

整数字节顺序:(首先看比特币-qt源代码时会有帮助)

大整数(超过66的64位及以下的变长整数采用little endian编码,如变量、紧凑整数等。64位或更少的传统整数使用系统定义的编码。如int64_t、itn32_t等。

一、哈希函数:

目前比特币-qt中使用的哈希函数主要是Hash256和Hash160。(代码中保留了Hash512,但没有使用)

Hash256是对原始数据进行两次SHA256运算,生成一个256位的哈希结果。应用:block_header、Transactions、base58check等。

Hash160是对原始数据的推进进行SHA256运算,然后对生成的哈希值进行RIPEMD160运算,生成160位的哈希结果。应用:比特币地址。

示例代码:

# define hash 256 _ size(32)# define hash 160 _ size(20)size _ t hash 256(const unsigned char * begin,size _ t size,unsigned char to[]){ unsigned char hash[if(NULL==begin | | size==0)返回0;SHA256(begin,size,(unsigned char *)hash[0]);SHA256(hash[0],sizeof hash,(unsigned char *)to[0]);返回HASH256 _ SIZE} SIZE _ t has h160(const unsigned char * begin,size_t size,unsigned char to[]){ unsigned char hash[hash 256 _ SIZE];if(NULL==begin || size==0)返回0;SHA256(begin,size,(unsigned char *)hash[0]);RIPEMD160(hash[0],sizeof hash,to[0]);返回HASH160 _ SIZE}

二。Base58代码的编码特点:输出结果为不易混淆的字母和数字,方便鼠标操作(鼠标双击可选择完整文本)。应用:比特币的地址和私钥导出。

编码步骤:将原始数据看作一个大整数E(1) r=E模58(2)将r附加到输出缓冲区,(3) E=E/58,若E 0,则返回(1)(4) 检查原始数据是否有前导的'0',如有,则将base58编码的0添加到输出缓冲区中(5) 字符串反转(转换成大端的格式)

示例代码:

static const char * PSZ base 58=' 123456789 abcdefghjklmnpqrstuvwxyzabcdefghijkmnopqrstuvwxyz ';size _ t base 58 encode(const unsigned char * begin,size_t size,char * to){ size _ t CB=0;BN _ CTX * CTX=NULL;无符号字符c;无符号char *pzero=(无符号char *)开始;无符号char *pend=(无符号char *)(开始大小);char * p=to//bool fSign=0;BIGNUM bn,dv,rem,bn58,bn0if((NULL==begin) || (size==0))返回0;//无效参数CB=尺寸* 138/100 1;//输出的大小将小于(138/100 * sizeof(src))//**应为输出缓冲区分配足够的内存if(NULL==to)返回CB;BN _ init(bn58);BN _ init(bn0);BN_set_word(bn58,58);BN _ zero(bn0);BN _ init(BN);BN _ init(dv);BN _ init(rem);BN_bin2bn(begin,size,BN);CTX=BN _ CTX新();if(NULL==ctx)返回0;//前面的都是初始化工作,下面是正式的算法while(BN_cmp(bn,bn0) 0){if(!BN_div(dv,rem,BN,bn58,CTX))破位;BN=DVC=BN _ get _ word(rem);*(p)=PSZ基数58[c];}//添加前导的0 while(*(p零)==0){ *(p)=PSZ基数58[0];if(pzero pend)中断;} * p=CB=p-to;反向字节((无符号字符*)到,CB);//输出为big endian integerBN _ CTX _自由(CTX);返回CB;}

解码步骤:

static const int 8 _ t b58 digits[]={-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,3 size _ t base 58 decode(const char * begin,size_t size无符号char *p=(无符号char *)开始;无符号char * pend=p size size _ t cbBIGNUM BN,bncharBIGNUM bn58,bn0BN _ CTX * CTX=BN _ CTX _新();if(NULL==ctx)返回0;BN _ init(bn58);BN _ init(bn0);BN _ init(BN);BN _ init(BN char);BN_set_word(bn58,58);BN _ zero(bn0);while(p pend){ c=* p;if(c0x 80)goto label _ errexit;if(-1==b58位数[c])goto label _ errexit;BN_set_word(bnchar,b58位数[c]);如果(!BN_mul(bn,BN,bn58,CTX))goto label _ errexit;BN_add(bn,BN,BN char);p;} CB=BN _ num _ bytes(BN);if(NULL==to)返回CB;BN_bn2bin(bn,to);BN _ CTX _自由(CTX);返回CB;label_errexit:if(NULL!=CTX)BN _ CTX自由(CTX);返回0;}

三、私钥生成比特币系统中的私钥是一个256位(比特)的随机整数。 取值范围在[1,n-1]之间,其中,n(order)=FFFFFFFF fffffffffffffffffff baa edce 6 af 48 a 03 b bfd 25 e 8c d 0364141生成步骤:1、初始化伪随机数种子2、生成一个256位的随机数3、验证随机数范围是否在[1,n-1],如超出范围,则返回2

示例代码:

BOOL ECKey _ Check(const unsigned char vch[32]);uint 32 _ t ECKey _ GeneratePrivKey(EC _ KEY * pkey,unsigned char vch[32]){ RandAndSeed();//初始化随机数种子,具体实现请参考附件中的源文件,或比特币-qt的源码do{RAND_bytes(vch,32);//利用openssl库生成一个256位的随机数}while(!ECKey _ Check(vch));//检查取值范围是否合法如果(pkey!=空)返回ECKey_GenKeypair(pkey,vch);//生成密钥对返回32;//私钥的字节数} BOOL ECKey _ Check(const unsigned char vch[32]){ static const unsigned char order[32]={0xFF、0xFF、0xFF、0xFF、0xFF、0xFF、0xFF、0xFF、0xFF、0xFF、0xFF、0xFF、0x ff、0x xf、0xBA、0xAE、0xDC、0xE6、0xAF、0x48、0xA0、0x3B、0xBF、0xD2、0x5E、0x8C、0xD0、0x36私钥BIGNUM b order//G在钉扎力下的序号orderBIGNUM bn0//0BN _ init(bn);BN _ init(bnOrder);BN _ init(bn0);BN _ zero(bn0);BN_bin2bn(vch,32,BN);//将二进制序列转化为一个大整数BN_bin2bn(order,32,bnOrder);//** 0 [私钥] 0)返回0;//私钥必须小于订单返回1;}

四、公钥的生成

公钥的计算公式:Q=dGQ:公钥d:私钥g:椭圆曲线基点

//**初始化EC_KEY,告知openssl库将使用secp256k1椭圆曲线。EC _ KEY * ECKey _ new(){ return EC _ KEY _ new _ by _ curve _ name(NID _ secp 256k 1);} uint 32 _ t ECKey _ genkey对(EC _ KEY * pkey,unsigned char vch[hash 256 _ SIZE]){//* * pkey=ECKey _ new();const uint 32 _ t key _ SIZE=hash 256 _ SIZE;const EC _ GROUP * groupBIGNUM bnBIGNUM * privkey=bnBN _ CTX * CTX=NULL;EC _ POINT * pubkey=NULLif(NULL==pkey)返回0;BN _ init(BN);//BIGNUM使用前最好先初始化,否则窗子明瓦环境下有时会出错group=EC _ KEY _ get 0 _ group(pkey);//组结构体中存储了G值和运算规则pubkey=EC _ POINT _ new(group);//给公共密钥分配内存,公钥为曲线上的一个点CTX=BN _ CTX新();if(NULL==蓑衣网小编2022pubkey | | NULL==CTX)goto label _ errexit;if(BN_bin2bn(vch,key_size,bn)) //将私钥(二进制形式)转化为一个大整数{if(EC_POINT_mul(group,pubkey,privkey,NULL,NULL,ctx)) //pubkey=privkey*G{//将密钥存储于EC_KEY结构体中,便于导出为所需的格式EC_KEY_set_private_key(pkey,privkey);EC_KEY_set_public_key(pkey,pubkey);} BN _ clear _ free(BN);} EC _ POINT _ free(pubkey);BN _ CTX _自由(CTX);return key _ size label _ errexit:if(NULL!=pubkey)EC _ POINT _ free(pubkey);if(NULL!=CTX)BN _ CTX自由(CTX);返回0;}

公钥提取:

uint 32 _ t ECKey _ get pubkey(EC _ KEY * pkey,无符号char * pubkey, BOOL fCompressed){unsigned char * p_pubkey = pubkey;uint32_t cb;EC_KEY_set_conv_form(pkey, fCompressed?POINT_CONVERSION_COMPRESSED:POINT_CONVERSION_UNCOMPRESSED); // 公钥采用压缩还是非压缩格式输出cb = i2o_ECPublicKey(pkey, NULL);if(0==cb || cb>65) return 0;if(NULL == pubkey) return cb;// 此处不要直接用pubkey,openssl库会自动修改传递进去的指针蓑衣网小编2022cb = i2o_ECPublicKey(pkey, &p_pubkey);return cb;}

五、 比特币地址的生成和私钥的导出

比特币地址的生成:步骤:1、H0 = Hash160(公钥)2、在H0前添加版本号: 0×00, ?–> extH03、生成校验值H1 = Hash256(extH0)4、将H1的前四个字节 复制到extH0之后, ext_pubkey ={0×0, H0, };5、对ext_pubkey进行base58编码,输出的结果即为比特币地址

//**************************************************//** Base58check编码生成比特币地址uint32_t PubkeyToAddr(const unsigned char * pubkey, size_t size, char *to){struct{unsigned char version;unsigned char vch[20];unsigned char checksum[4];}ext_pubkey;unsigned char hash[32]={0};printf("ext_pubkey size: %d\n", sizeof(ext_pubkey));ext_pubkey.version = 0x00;Hash160(pubkey, size, &ext_pubkey.vch[0]);Hash256(&ext_pubkey.version, 1+20, hash);memcpy(ext_pubkey.checksum, hash, 4);return Base58Encode((unsigned char *)&ext_pubkey.version, sizeof(ext_pubkey), to);}

私钥导出格式:

蓑衣网小编2022步骤:1、将私钥转化为256位的二进制格式V2、在V前添加版本号: 0×80, (如果使用压缩标志,则在V后再添加一个0×01),–>extV3、生成校验值H1 = Hash256(extV)4、将H1的前四个字节 复制到extV之后, ext_privkey ={0×80, V, (0×01), };5、对ext_privkey进行base58编码,输出的结果即为比特币地址

uint32_t PrivkeyToWIF(const unsigned char vch[HASH256_SIZE], char *to, BOOL fCompressed){struct{unsigned char version;unsigned char vch[HASH256_SIZE];unsigned char checksum[5];}ext_privkey;unsigned char hash[HASH256_SIZE];uint32_t offset = fCompressed?1:0;ext_privkey.version = 0x80;memcpy(ext_privkey.vch, vch, 32);if(offset) ext_privkey.checksum[0] = 0x01;Hash256(&ext_privkey.version, 1 + HASH256_SIZE +offset, hash);memcpy(&ext_privkey.checksum[offset], hash, 4 );return Base58Encode(&ext_privkey.version, 1 + HASH256_SIZE + offset + 4, to);}

六、签名和验证

签名步骤:1、H = Hash256(M)2、调用openssl的ECDSA_do_sign函数,生成签名sig = (r,s)。3、检查s值是否大于order/2,如果大于,则利用 s + (-s) = order, -s = order –s, 这一性质将签名改写为(r, -s)的形式。这两种形式的签名是等效的,但如果s > order/2,由于验证方的实现方式不可预知,验证方有可能将其视为一个更大的正整数而不是-s,这会导致签名验证失败。

size_t ECKey_Sign(EC_KEY *pkey, const unsigned char hash[HASH256_SIZE], unsigned char **to){//** if (*to) == NULL, the caller should use OPENSSL_free(*to) to free the memorysize_t cb = 0;ECDSA_SIG *sig = NULL;BN_CTX * ctx = NULL;BIGNUM order; // The order of GBIGNUM halforder; // get sign/unsign markunsigned char *output = NULL;const EC_GROUP * group = EC_KEY_get0_group(pkey); // secp256k1: Gif(NULL == group) return 0;sig = ECDSA_do_sign((unsigned char *)&hash[0], HASH256_SIZE, pkey);if(NULL == sig) return 0;//** sig = (r,s) = (r,-s)//** s must less than order/2, otherwise, some app may parse '-s' as a large unsigned positive integerctx = BN_CTX_new();if(NULL == ctx) goto label_exit;//** allocate memory for bignumBN_init(&order);BN_init(&halforder);// get the order of GEC_GROUP_get_order(group, &order, ctx); // secp256k1: nBN_rshift1(&halforder, &order);if(BN_cmp(sig->s, &halforder)>0){// if s > order/2, then output -s. (-s = (order - s))BN_sub(sig->s, &order, sig->s);}BN_CTX_free(ctx);output = *to;cb = ECDSA_size(pkey);if(NULL == output){output = (unsigned char *)OPENSSL_malloc(cb);if(NULL == output) goto label_exit;}if(NULL == *to) *to = output;//** i2d_ECDSA_SIG DER encode content of ECDSA_SIG object//** (note: this function modifies *pp (*pp += length of the DER encoded signature)).//** do not pass the address of 'to' directlycb = i2d_ECDSA_SIG(sig, &output);label_exit:ECDSA_SIG_free(sig);return cb;}

验证签名:

BOOL ECKey_Verify(EC_KEY *pkey, const unsigned char hash[HASH256_SIZE], const unsigned char *sig, size_t size){if(ECDSA_verify(0, (unsigned char *)&hash[0], HASH256_SIZE, sig, size, pkey) != 1) // -1 = error, 0 = bad sig, 1 = goodreturn FALSE;return TRUE;}

附件:

1. “sample.h”

2.“ecc_sample1.c”

3. VS6下需要下载gnu兼容的

cprogram

2014.6.11

chehw


BTC addr:1ChEiWRkJJMBZVQojqjvW5fTB7hhUng129

比特币系统采用的公钥密码学方案和ECDSA签名算法介绍—第二部分:代码实现(C语言) | 分享给朋友: