chain-btc-address

112 阅读9分钟

BTC地址的生成规则

本文主要总结下BTC地址在原生状态下生成的基本的规则;不同的地址格式比较;BIP协议下地址的管理方法;

BTC地址的格式

Base58与Base64的区别:Base58主要用来生成btc地址,相比于Base64,Base58不使用数字"0",字母大写"O",字母大写"I",和字母小写"l",以及"+"和"/"符号:

  • 避免混淆,数字0和字母大写O,以及字母大写I和字母小写l会非常相似。
  • 不使用"+"和"/"的原因是非字母或数字的字符串作为帐号较难被接受。
  • 没有标点符号,通常不会被从中间分行,便于复制,大部分的软件支持双击选择整个字符串。
//Base58的字符串
const BTC_BASE58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"

但是... 这个Base58的计算量比base64的计算量多了很多。因为58不是2的整数倍,需要不断用除法去计算

原生地址生成方法

btc-addr-2.jpeg

地址生成流程

btc-key2.jpeg

BTC中的keys的生成关系:

  1. 使用随机数生成 256bits 的原生私钥(priv)
  2. 对私钥使用base58checkencode(0x80,priv)得到WIF格式的私钥,0x80是生成私钥WIF的前缀
  3. 对私钥使用ecdsa算法得到公钥对(x,y), 拼接 append(x,y) 得到原生公钥(pub,512bits),公钥是椭圆曲线上的一个点
  4. 公钥加前缀0x04,使用sha256,RIPEM160依次计算hash,得到160bits的公钥(pub_hash),0x04是公钥WIF格式前缀
  5. 对160bits公钥使用base58checkCode(0x00,pub_hash)编码得到用户的地址,0x00是主网地址的前缀

btc-base58.jpeg

Base58checkencode计算算法:

  1. 对 原数据(d) 加 版本前缀(version_d), 原始数据d和数据版本是Base58checknode算法的输入参数
  2. 对version_d数据取2次hash,sha256(sha256(vd)),并取结果的前4个字节做CRC
  3. 再次拼接vdc,(v是版本前缀,d是原数据,c是CRC)
  4. 最后对vdc编码,base58(vdc)

Base58CheckCode算法解析

// base58checkEncode计算的过程
1. Take the version byte and payload bytes, and concatenate them together (bytewise).//拼接版本和原生数据
2. Take the first four bytes of SHA256(SHA256(results of step 1)) // 取sha256(sha256(data))前4个字节做CRC
3. Concatenate the results of step 1 and the results of step 2 together (bytewise).// 版本+原生数据+CRC
4. Treating the results of step 3 - a series of bytes - as a single big-endian bignumber, convert to base-58 using normal mathematical steps (bignumber division) and the base-58 alphabet described below. The result should be normalized to not have any leading base-58 zeroes (character '1').// base58编码,字节是大段格式的
5. The leading character '1', which has a value of zero in base58, is reserved for representing an entire leading zero byte, as when it is in a leading position, has no value as a base-58 symbol. `There can be one or more leading '1's when necessary to represent one or more leading zero bytes`. Count the number of leading zero bytes that were the result of step 3 (for old Bitcoin addresses, there will always be at least one for the version/application byte; for new addresses, there will never be any). Each leading zero byte shall be represented by its own character '1' in the final result. // 开头的全部0x00字节替换为1,生成地址时主网增加的版本前缀是0x00,这个字节需要在base58编码时转换为"1"
6. Concatenate the 1's from step 5 with the results of step 4. This is the Base58Check result.

原生BTC地址的生成简单过程: 私钥 =>(ecdsa)=> 公钥对 =>(多次hash)=> btc地址;

BTC私钥的不同的格式

typeprefixdescriptionprivate key examplenote
RawNone32 bytes-256bits; 32个Byte; 64个Hex
HexNone64 hexadecimal digits1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aeddhex=64
WIF5Base58Check encoding: Base58 with version prefix of 0x80 and 32-bit checksum5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcnversion + data
WIF-compressedK or LAs above, with added suffix 0x01 before encodingKxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ在encoding之前原数据增加后缀0x01: version + data + (0x01)
  • RAW,是原生的字节,btc使用椭圆曲线加密算法计算公钥对,私钥是随机数
  • Hex,对原生字节转16进制表示,一般算法可见
  • WIF(Wallet import format): en.bitcoin.it/wiki/Wallet…;钱包之间导入导出的格式,使用base58加密处理
  • WIF-compressed,用于推导生成被压缩格式的公钥 (compressed public keys)

BTC的公、私钥、地址的格式比较

种类Hex版本前缀base58格式前缀
Bitcoin Address,主网0x00是版本前缀1
Bitcoin Testnet Address,测试网0x6F的版本前缀m or n
Pay-to-Script-Hash Address0x053
Private Key WIF十六进制为 0x80 十进制时为 1285 ; K or L (K/L是压缩格式)
Public Key WIF十六进制为 0x04-,没有这种格式
BIP32 Extended Private Key0488ADE4xprv,tprv是测试网
BIP32 Extended Public Key0488B21Expub,tpub
  • priv-WIF(私钥WIF),使用前缀0x80对私钥base58checkencode编码,base58checkencode(0x80,priv)得到base58格式;
  • priv-WIF-Compressed(压缩私钥WIF),使用0x01后缀编码base58checkencode(0x80,priv+0x01),=>主要用来用于推导pub-WIF-Compressed(压缩公钥),算法还没有实现...
  • pub-WIF(公钥WIF),原生公钥(x,y)是曲线上的一点,使用0x04前缀拼接为WIF格式,0x04+x+y得到hex格式
  • pub-WIF-Compressed(压缩公钥WIF),x可以推导出y,即y=f(x),但是y是有符号的,关于x轴对称; 0x02表示正数,0x03表示负数,压缩公钥02/03 + xhex格式; => 主要用来节省公钥存储空间,压缩公钥依然能表示曲线上的一个点;

压缩私钥推导压缩公钥的算法实现(上面的解析有待校验)....?

btc-address.jpeg

BTC 地址生成的流程解析

  1. 使用版本0x04拼接公钥对:pub = 0x04+x+y
  2. 对拼接的结果进行2次hash:pub_hash = ripemd160(sha256(pub)),得到160bit的hash
  3. 使用主网地址前缀0x00进行base58checkencode: address = base58checkencode(0x00,pub_hash)

原生地址规则源码demo


// 加密算法包
// package esdca
// PublicKey represents an ECDSA public key.
type PublicKey struct {
    elliptic.Curve // esdca 曲线
    X, Y *big.Int  // 曲线坐标
}

// PrivateKey represents an ECDSA private key.
type PrivateKey struct {
    PublicKey
    D *big.Int  //私钥
}
// 使用椭圆曲线和随机数计算BTC公/私钥
// GenerateKey generates a public and private key pair.
func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) {
    k, err := randFieldElement(c, rand)
    if err != nil {
        return nil, err
    }
    priv := new(PrivateKey)
    priv.PublicKey.Curve = c //曲线
    priv.D = k  // 私钥
    priv.PublicKey.X, priv.PublicKey.Y = c.ScalarBaseMult(k.Bytes()) // 公钥
    return priv, nil
}

// 自己的公私钥结构
type MyWallet struct {
    PrivateKey []byte
    PublicKey  []byte
}
// 公私钥生成的算法
func newKeyPair() ([]byte, []byte) {
    curve := elliptic.P256()
    /*使用ecdsa生成公私钥*/
    private, err := ecdsa.GenerateKey(curve, rand.Reader)
    if err != nil {
        log.Panic(err)
    }
    //获取私钥
    d := private.D.Bytes()
    b := make([]byte, 0, privKeyBytesLen)
    // 私钥bytes
    priKet := paddedAppend(privKeyBytesLen, b, d)
    // (X,Y) 坐标合并为公钥对
    pubKey := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...)
    fmt.Println("=======================", len(priKet), len(pubKey))
    return priKet, pubKey
}

// 私钥WIF
func (w MyWallet) GetPriWIF() (wif string) {
    // 私钥使用0x80前缀计算base58checkencode
    wif = b58checkencode(versionPriRaw, w.PrivateKey)
    return wif
}
// 私钥WIF压缩
func (w MyWallet) GetPriWIFCompressed() (wif string) {
    // 在encoding之前先对原数据增加后缀0x01
    pbytes := append(w.PrivateKey, []byte{0x01}...)
    wif = b58checkencode(versionPriWIF, pbytes)
    return wif
}

// 使用公钥生成地址
func (w MyWallet) GetAddress() (address string) {
    // 公钥增加前缀0x04
    pub_bytes := append([]byte{versionPubRaw}, w.PublicKey...)
    /* SHA256 Hash */
    fmt.Println("2 - Perform SHA-256 hashing on the public key")
    sha256_h := sha256.New()
    sha256_h.Reset()
    sha256_h.Write(pub_bytes)
    pub_hash_1 := sha256_h.Sum(nil)
    fmt.Println(byte2Hex(pub_hash_1))
    fmt.Println("=======================")
    /* RIPEMD-160 Hash */
    fmt.Println("3 - Perform RIPEMD-160 hashing on the result of SHA-256")
    ripemd160_h := ripemd160.New()
    ripemd160_h.Reset()
    ripemd160_h.Write(pub_hash_1)
    pub_hash_2 := ripemd160_h.Sum(nil)
    fmt.Println(byte2Hex(pub_hash_2))
    fmt.Println("=======================")
    /*address = b58checkencode(0x00, pub_hash_2)*/
    // 使用主网地址计算base58checkencode
    address = b58checkencode(versionMainNet, pub_hash_2)
    return address
}

// base58checkencode 计算算法
func b58checkencode(ver uint8, b []byte) (s string) {
    // 拼接前缀
    bcpy := append([]byte{ver}, b...)
    /* Create a new SHA256 context */
    sha256H := sha256.New()
    /* 第一遍SHA256 Hash #1 */
    sha256H.Reset()
    sha256H.Write(bcpy)
    hash1 := sha256H.Sum(nil)
    /* 第二遍计算SHA256 Hash #2 */
    sha256H.Reset()
    sha256H.Write(hash1)
    hash2 := sha256H.Sum(nil)
    /* 拼接后缀CRC*/
    bcpy = append(bcpy, hash2[0:4]...)
    /* Encode base58 string */
    // base58编码
    s = b58encode(bcpy)
    /* For number of leading 0's in bytes, prepend 1 */
    // 如果前缀是0x00,增加1
    for _, v := range bcpy {
        if v != 0 {
            break
        }
        s = "1" + s
    }
    return s
}

BIP地址管理方法

BIP(Bitcoin Improvement Proposals),是btc优化协议..这里只介绍跟钱包相关的协议;BTC官方地址使用WIF格式管理key,ETH官方采用KDF(keystore)形式管理key...

地址管理的使用的主要协议:BIP32,39,44,43...

BIP32,实现HD钱包

BIP32 结构如图:

bip-32.jpeg

HD wallet(Hierachical Deterministic Wallets)分层确定性钱包:钱包的管理本质是key的管理,依据之前的BTC原生key生成规则解析; key的管理可以分为:确定性钱包、非确定性钱包

  • 确定性钱包:每个密钥对都是随机生成的..可以参考官方地址生成的方法
  • 非确定性钱包:所有的密钥由一个主密钥派生生成...这就是BIP32协议的内容
//  缺省的钱包机构
//   * m/iH/0/k
//     corresponds to the k'th keypair of the external chain of account  number i of the HDW derived from master m.
//   * m/iH/1/k
//     corresponds to the k'th keypair of the internal chain of account number i of the HDW derived from master m.

BIP39,管理助记词; 使用助记词生成主私钥

BIP44,多币种多账户的钱包; 对分层的钱包定义了分层

BIP43,多用途的HD钱包结构

参考资料