比特币(btc)交易所钱包开发 - 2 - 创建地址

比特币地址和私钥与以太坊的异同

比特币的地址和私钥的关系和以太坊也是一样的,参见以太坊(eth)简介

但是在显示上有一些区别

  • 比特币的地址用的是Base58的序列化显示方式,这个方式是大小写敏感的,所以要注意比特币的地址是不能转化大小写的。
  • 比特币的私钥导出时一般使用WIF格式,为了给私钥加验证,比特币的私钥导出时在首位会加一个netID。这个netID对应不同的网络,例如比特币测试网络,比特币正式网络,莱特币等等;而在末尾会加一个数据校验,用来验证数据是否被损坏。

比特币及其衍生链的wif和地址填充数据

这里我们列举了几种链的数据的填充字节信息

1
2
3
4
5
6
var network = map[string]Network{
"rdd": {name: "reddcoin", symbol: "rdd", xpubkey: 0x3d, xprivatekey: 0xbd},
"dgb": {name: "digibyte", symbol: "dgb", xpubkey: 0x1e, xprivatekey: 0x80},
"btc": {name: "bitcoin", symbol: "btc", xpubkey: 0x00, xprivatekey: 0x80},
"ltc": {name: "litecoin", symbol: "ltc", xpubkey: 0x30, xprivatekey: 0xb0},
}

golang处理比特币数据的开源库

用golang处理比特币数据我们可以利用一个开源的库https://github.com/btcsuite

检测并生成可用的比特币地址

检测生成比特币地址的逻辑其实和以太坊是一样的,可参阅 以太坊(eth)交易所钱包开发 - 1 - 创建地址 。只不过生成私钥和获取地址的函数不一样。

首先我们看一下生成比特币私钥并转换为wif格式导出的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// CreatePrivateKey 生成一个二进制的私钥,并返回一个wif对象
func (network Network) CreatePrivateKey() (*btcutil.WIF, error) {
secret, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
return nil, err
}
// 注意,在返回wif对象的时候,传入了wif填充对象GetNetworkParams
return btcutil.NewWIF(secret, network.GetNetworkParams(), true)
}

// GetNetworkParams 根据map配置返回链填充数据
func (network Network) GetNetworkParams() *chaincfg.Params {
networkParams := &chaincfg.MainNetParams
networkParams.PubKeyHashAddrID = network.xpubkey
networkParams.PrivateKeyID = network.xprivatekey
return networkParams
}

// 获取私钥的wif字符串,Base58格式的字符串
// See DecodeWIF for a detailed breakdown of the format and requirements of
// a valid WIF string.
func (w *WIF) String() string {
// Precalculate size. Maximum number of bytes before base58 encoding
// is one byte for the network, 32 bytes of private key, possibly one
// extra byte if the pubkey is to be compressed, and finally four
// bytes of checksum.
encodeLen := 1 + btcec.PrivKeyBytesLen + 4
if w.CompressPubKey {
encodeLen++
}

a := make([]byte, 0, encodeLen)
// 首先填充netID, 也就是填充数据
a = append(a, w.netID)
// Pad and append bytes manually, instead of using Serialize, to
// avoid another call to make.
// 然后填充私钥二进制数据
a = paddedAppend(btcec.PrivKeyBytesLen, a, w.PrivKey.D.Bytes())
if w.CompressPubKey {
a = append(a, compressMagic)
}
// 对数据做校验填充
cksum := chainhash.DoubleHashB(a)[:4]
a = append(a, cksum...)
// 返回最终的Base58字符串
return base58.Encode(a)
}

之后我们看一下地址是怎么通过wif导出的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// NewAddressPubKey 获取一个public key
// address. The serializedPubKey parameter must be a valid pubkey and can be
// uncompressed, compressed, or hybrid.
func NewAddressPubKey(serializedPubKey []byte, net *chaincfg.Params) (*AddressPubKey, error) {
pubKey, err := btcec.ParsePubKey(serializedPubKey, btcec.S256())
if err != nil {
return nil, err
}

// Set the format of the pubkey. This probably should be returned
// from btcec, but do it here to avoid API churn. We already know the
// pubkey is valid since it parsed above, so it's safe to simply examine
// the leading byte to get the format.
pkFormat := PKFUncompressed
switch serializedPubKey[0] {
case 0x02, 0x03:
pkFormat = PKFCompressed
case 0x06, 0x07:
pkFormat = PKFHybrid
}

return &AddressPubKey{
pubKeyFormat: pkFormat,
pubKey: pubKey,
// 设置地址填充数据
pubKeyHashID: net.PubKeyHashAddrID,
}, nil
}

// EncodeAddress returns the string encoding of the public key as a
// pay-to-pubkey-hash. Note that the public key format (uncompressed,
// compressed, etc) will change the resulting address. This is expected since
// pay-to-pubkey-hash is a hash of the serialized public key which obviously
// differs with the format. At the time of this writing, most Bitcoin addresses
// are pay-to-pubkey-hash constructed from the uncompressed public key.
//
// Part of the Address interface.
func (a *AddressPubKey) EncodeAddress() string {
return encodeAddress(Hash160(a.serialize()), a.pubKeyHashID)
}

// encodeAddress returns a human-readable payment address given a ripemd160 hash
// and netID which encodes the bitcoin network and address type. It is used
// in both pay-to-pubkey-hash (P2PKH) and pay-to-script-hash (P2SH) address
// encoding.
func encodeAddress(hash160 []byte, netID byte) string {
// Format is 1 byte for a network and address class (i.e. P2PKH vs
// P2SH), 20 bytes for a RIPEMD160 hash, and 4 bytes of checksum.
return base58.CheckEncode(hash160[:ripemd160.Size], netID)
}

整合代码

具体实现的代码已经传到了github https://github.com/moremorefun/go-dc-wallet

功能入口在cmd/btc_address/main.go


坚持原创技术分享,您的支持鼓励我继续创作!