上文《比特币btcd代码之初体验》提到比特币除了主网外,还有Testnet以及Regtest网络。
Testnet是公开的测试网,所有开发都可以访问这个网络,为了避免有人恶意囤积上面的Testnet bitcoin,这个testnet每隔一段时期就会清空并以新的创始块重新开始。这也应该就是代码里面有时候会看到testnet3的原因。
Regtest则是本地的测试网络,这个网络不会公开出去,仅作为本地开发测试使用。
除此之外,还有Segnet即隔离见证的测试网络。而btcd代码里还看到了simnet这个测试网络选项,目测和Regtest一样是本地测试网络,但是具体有什么不同暂时未深入研究。
这篇文章主要从btcd相关代码出发,研究比特币的私钥、公钥以及地址如何生成,详情参见精通比特币第二版(中文版),简单地说:
第一步,也是最重要的一步,从一个随机数生成私钥。比特币软件使用操作系统底层的随机数生成器来产生256位的熵,即私钥。
第二步,通过椭圆曲线乘法可以从私钥计算得到公钥。
第三步,从公钥计算出比特币地址。
以下代码是从《在比特币上表白-使用Golang将誓言存在比特币的区块链上》拷贝过来的代码
package main
import (
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"fmt"
)
func GenerateBTC() (string, string, error) {
privKey, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
return "", "", err
}
privKeyWif, err := btcutil.NewWIF(privKey, &chaincfg.MainNetParams, false)
if err != nil {
return "", "", err
}
pubKeySerial := privKey.PubKey().SerializeUncompressed()
pubKeyAddress, err := btcutil.NewAddressPubKey(pubKeySerial, &chaincfg.MainNetParams)
if err != nil {
return "", "", err
}
return privKeyWif.String(), pubKeyAddress.EncodeAddress(), nil
}
func GenerateBTCTest() (string, string, error) {
privKey, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
return "", "", err
}
privKeyWif, err := btcutil.NewWIF(privKey, &chaincfg.TestNet3Params, false)
if err != nil {
return "", "", err
}
pubKeySerial := privKey.PubKey().SerializeUncompressed()
pubKeyAddress, err := btcutil.NewAddressPubKey(pubKeySerial, &chaincfg.TestNet3Params)
if err != nil {
return "", "", err
}
return privKeyWif.String(), pubKeyAddress.EncodeAddress(), nil
}
func main() {
wifKey, address, _ := GenerateBTCTest() // 测试地址
// wifKey, address, _ := GenerateBTC() // 正式地址
fmt.Println(address, wifKey)
}
分析如下:
随机数与私钥
privKey, err := btcec.NewPrivateKey(btcec.S256())
首先,在之前clone的btcd代码里,其中有一个模块叫btcec,它实现了比特币所需的椭圆曲线密码算法。S256返回的是一个实现了secp256k1标准所定义的一种特殊的椭圆曲线。再查看NewPrivateKey实现细节,发现是把一个椭圆曲线以及随机数生成器传入了crypto包。
这段代码直接返回了私钥和公钥,即上述的第一步和第二步。需要注意这代码里key是PrivateKey类型的对象,其有变量存放了公钥。
// github.com/btcsuite/btcd/btcec/privkey.go
// NewPrivateKey is a wrapper for ecdsa.GenerateKey that returns a PrivateKey
// instead of the normal ecdsa.PrivateKey.
func NewPrivateKey(curve elliptic.Curve) (*PrivateKey, error) {
key, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, err
}
return (*PrivateKey)(key), nil
}
这里刚好可以和比特币源码研读(1)--私钥 文章里做一对比,可以发现在C++版本里,其随机数生成的随机性应该更强,因为它同时采用了几个不同的随机源,而Golang版本则只用了软件提供的rand方法。这可能会有潜在的隐患,假如某一环境下软件的随机数分布不均衡,随机数算法中选取了某一段区域内的高概率随机数。那么原本理论上2^256个范围便会大大缩小,黑客从而提高了随机性碰撞的可能性。
WIF
privKeyWif, err := btcutil.NewWIF(privKey, &chaincfg.MainNetParams, false)
继续看代码,这句是把256位数字的私钥以钱包导入格式(WIF)来表示。同样的256位数字的私钥,通过不同编码可以有不同的表示,WIF是其中一种格式。
需要留意这里用到了与btcd项目同属的btcutil代码,可以把它看成是通用类的包。
地址
pubKeyAddress, err := btcutil.NewAddressPubKey(pubKeySerial, &chaincfg.MainNetParams)
和私钥的WIF类似,公钥可以通过一些算法来计算出其地址,详情需要参考《精通比特币》的讲解。
就这样,一个符合规则的比特币地址以及私钥就这样生成出来了,可以在https://btc.com上检查地址格式是否正确。
有疑问加站长微信联系(非本文作者)