【比特币】地址生成

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),公钥可以验证是否有对应的私钥签发



1) 计算这个公钥对应的地址,

0400112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff

echo -n 0400112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff | h2b | sha256sum | sed -e 's/ -//g' | h2b | openssl dgst -ripemd160

(stdin)= 77eaeaabc6da57ece603d2b56e4d21c865d5a5cd


输出就是一个16进制字符串表示的20字节的大整数



再跳到下面的第2步,使用带hash-checking的base58编码


2) 如何从一个地址对象生成打印一个比特币地址出来。


关于CBitcoinAddress的定义:

保存vchVersion字段和vchData字段(20个字节长度,ripemd160的hash结果),分别表示《版本号》和《RipeMD160表示的ID》内容。

版本号,可以是 0x00和0x05两种,但是base58编码在比特币系统中的应用还有其他几种类型,参考wiki



主要有几个步骤:


2.1,序列化

【版本】+【Ripemd160数据部分】

插播:至于怎么从公钥得到RipmeMd160的结果可以参考wiki,先sha256sum再ripemd160就可以了。


举例:

假如从公钥hash出来的id是1122334455667788990011223344556677889900,

05+1122334455667788990011223344556677889900 => 051122334455667788990011223344556677889900


2.2,计算校验和

HASH(ResultStep1)即:Hash256(Hash256(051122334455667788990011223344556677889900)) => 68787bf6caca67526183fdfb06d9a3efc0f2264cdfe94af017e5428d83ddf873

这是一个LE表示的uint256整数,提取前面的4个字节

得到:68787bf6


2.3, 追加第二步的校验和到第一步的结果后面再进行base58编码

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' )

pubkey2addr.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

把比特币发送到某个地址,使用的是keyid,未经base58编码的公钥id,40个字节十六进制表示的20字节整数。

Output Scripts

OP_DUP OP_HASH160 c83dc3af1091303168fd1ab44e528d7c941ab49c OP_EQUALVERIFY OP_CHECKSIG  OK
OP_DUP OP_HASH160 45d906fdc235c3ffb40acb4831a509e5bac4092c OP_EQUALVERIFY OP_CHECKSIG
sh pubkey2addr.sh  c83dc3af1091303168fd1ab44e528d7c941ab49c 得到地址1KFn8qCwsNwo5fSC3qMHs6d3FEPTsojhKy

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


参考网页



你可能感兴趣的:(ecdsa,地址,比特币,虚拟货币,椭圆曲线数字签名算法)