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中的keys的生成关系:
- 使用随机数生成 256bits 的原生私钥(priv)
- 对私钥使用
base58checkencode(0x80,priv)
得到WIF
格式的私钥,0x80
是生成私钥WIF的前缀 - 对私钥使用
ecdsa
算法得到公钥对(x,y), 拼接append(x,y)
得到原生公钥(pub,512bits),公钥是椭圆曲线上的一个点 - 公钥加前缀
0x04
,使用sha256
,RIPEM160
依次计算hash,得到160bits的公钥(pub_hash),0x04
是公钥WIF格式前缀 - 对160bits公钥使用
base58checkCode(0x00,pub_hash)
编码得到用户的地址,0x00
是主网地址的前缀
Base58checkencode计算算法:
- 对 原数据(d) 加 版本前缀(version_d), 原始数据d和数据版本是Base58checknode算法的输入参数
- 对version_d数据取2次hash,
sha256(sha256(vd))
,并取结果的前4个字节做CRC - 再次拼接vdc,(v是版本前缀,d是原数据,c是CRC)
- 最后对vdc编码,
base58(vdc)
// 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私钥的不同的格式
type | prefix | description | private key example | note |
---|---|---|---|---|
Raw | None | 32 bytes | - | 256bits; 32个Byte; 64个Hex |
Hex | None | 64 hexadecimal digits | 1e99423a4ed27608a15a2616a2b0e9e52ced330ac530edcc32c8ffc6a526aedd | hex=64 |
WIF | 5 | Base58Check encoding: Base58 with version prefix of 0x80 and 32-bit checksum | 5J3mBbAH58CpQ3Y5RNJpUKPE62SQ5tfcvU2JpbnkeyhfsYB1Jcn | version + data |
WIF-compressed | K or L | As above, with added suffix 0x01 before encoding | KxFC1jmwwCoACiCAWZ3eXa96mBM6tb3TYzGmf6YwgdGWZgawvrtJ | 在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 Address | 0x05 | 3 |
Private Key WIF | 十六进制为 0x80 十进制时为 128 | 5 ; K or L (K/L是压缩格式) |
Public Key WIF | 十六进制为 0x04 | -,没有这种格式 |
BIP32 Extended Private Key | 0488ADE4 | xprv ,tprv 是测试网 |
BIP32 Extended Public Key | 0488B21E | xpub ,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 + x
是hex
格式; => 主要用来节省公钥存储空间,压缩公钥依然能表示曲线上的一个点;
压缩私钥推导压缩公钥的算法实现(上面的解析有待校验)....?
BTC 地址生成的流程解析
- 使用版本0x04拼接公钥对:pub =
0x04+x+y
- 对拼接的结果进行2次hash:pub_hash = ripemd160(sha256(pub)),得到160bit的hash
- 使用主网地址前缀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 结构如图:
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钱包结构