1) 带校验以及前导0的base58编码方法源代码
#include <stdio.h> #include <stdlib.h> #include <string> #include <openssl/bn.h> #define DOMAIN_CHECK(c) ('0'<=(c)&&(c)<='9'||'a'<=(c)&&(c)<='f'||'A'<=(c)&&(c)<='F') #define BASE58TABLE "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" std::string base58encode(const std::string & hexstring) { std::string result = ""; BN_CTX * bnctx = BN_CTX_new(); BN_CTX_init(bnctx); BIGNUM * bn = BN_new(); BIGNUM * bn0= BN_new(); BIGNUM * bn58=BN_new(); BIGNUM * dv = BN_new(); BIGNUM * rem= BN_new(); BN_init(bn); BN_init(bn0); BN_init(bn58); BN_init(dv); BN_init(rem); BN_hex2bn(&bn, hexstring.c_str()); BN_hex2bn(&bn58, "3a");//58 BN_hex2bn(&bn0,"0"); while(BN_cmp(bn, bn0)>0){ BN_div(dv, rem, bn, bn58, bnctx); BN_copy(bn, dv); char base58char = BASE58TABLE[BN_get_word(rem)]; result += base58char; } // compute leading-zeros and prepend '1' (base58table[0]) const char * phexstr = hexstring.c_str(); int hexlen = hexstring.length(); for (int i = 1; i < hexlen; i += 2) { if (phexstr[i-1] == '0' && phexstr[i] == '0') result += BASE58TABLE[0]; else break; } // little endian -> big endian // most significant byte (the dv) is calulated at the end, // and appended at the end of string. // so convert it to big endian. std::string::iterator pbegin = result.begin(); std::string::iterator pend = result.end(); while(pbegin < pend) { char c = *pbegin; *(pbegin++) = *(--pend); *pend = c; } return result; } int main(int argc, char * argv []) { std::string hexstring = ""; FILE * fin = stdin; while(!feof(fin)) { char c = fgetc(fin); if (DOMAIN_CHECK(c)) hexstring +=c; } // check empty if (hexstring.length() == 0) return -1; // assert length %2 == 0 if (hexstring.length() % 2) hexstring.insert(hexstring.begin(), '0'); fprintf(stdout, "%s", base58encode(hexstring).c_str()); return 0; }
比特币地址是ECDSA算法的公钥
关于这个算法的三个特性:
1)私钥可以推导出公钥,但是公钥无法推导出私钥,
2)公钥有2个表示方式,压缩方式,前导为0x02,0x03;非压缩方式,前导为0x04
3)私钥对一个hash签名的结果是一个整数对(r,s),公钥可以验证是否有对应的私钥签发
0400112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff
echo -n 0400112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff | h2b | sha256sum | sed -e 's/ -//g' | h2b | openssl dgst -ripemd160
(stdin)= 77eaeaabc6da57ece603d2b56e4d21c865d5a5cd
再跳到下面的第2步,使用带hash-checking的base58编码
关于CBitcoinAddress的定义:
保存vchVersion字段和vchData字段(20个字节长度,ripemd160的hash结果),分别表示《版本号》和《RipeMD160表示的ID》内容。
版本号,可以是 0x00和0x05两种,但是base58编码在比特币系统中的应用还有其他几种类型,参考wiki
主要有几个步骤:
【版本】+【Ripemd160数据部分】
插播:至于怎么从公钥得到RipmeMd160的结果可以参考wiki,先sha256sum再ripemd160就可以了。
举例:
假如从公钥hash出来的id是1122334455667788990011223344556677889900,
05+1122334455667788990011223344556677889900 => 051122334455667788990011223344556677889900
HASH(ResultStep1)即:Hash256(Hash256(051122334455667788990011223344556677889900)) => 68787bf6caca67526183fdfb06d9a3efc0f2264cdfe94af017e5428d83ddf873
这是一个LE表示的uint256整数,提取前面的4个字节
得到:68787bf6
Base58(05112233445566778899001122334455667788990068787bf6) 也就是对一个(1+20+4)总共25个元素表示的大整数进行base58编码,这个整数肯定小于32字节的uint256。
得到: 33FcP5di8njrEcWNRvHhjx46fa2jrWPzYD
1)这个过程是可以反向解码校验的。
2)Hash256计算的对象都是256字节的大整数(小端表示的字节数据,博客中和wiki中都是用16进制表示的,要么用bignum库反序列化得到大整数,或者使用十六进制字符串反序列化 得到字节数组以后再计算)
3)举例的输入是随意的,实际0x05版本的比特币地址(也就是3前导的)是脚本地址,而不是公钥地址。
4)一个脚本实现的从CPubKeyID生成比特币地址
hex2bvec.sh 转换16进制字符串到字节数组
echo -n -e $(echo -n $(cat) | sed -e 's/\([a-fA-F0-9]\{2\}\)/\\x\1/g' )
echo -n 00$1$(echo -n 00$1 | hex2bvec.sh | sha256sum | sed -e 's/ -//g' | hex2bvec.sh | sha256sum | sed -e 's/\([0-9a-fA-F]\{8\}\).*/\1/g') | base58
OP_DUP OP_HASH160 c83dc3af1091303168fd1ab44e528d7c941ab49c OP_EQUALVERIFY OP_CHECKSIG | OK |
OP_DUP OP_HASH160 45d906fdc235c3ffb40acb4831a509e5bac4092c OP_EQUALVERIFY OP_CHECKSIG |
sh pubkey2addr.sh 45d906fdc235c3ffb40acb4831a509e5bac4092c 得到地址17NKcZNXqAbxWsTwB1UJHjc9mQG3yjGALA
也可以修改一个script2addr.sh
echo -n 00$1$(echo -n 00$1 | hex2bvec.sh | sha256sum | sed -e 's/ -//g' | hex2bvec.sh | sha256sum | sed -e 's/\([0-9a-fA-F]\{8\}\).*/\1/g') | base58
参考网页