Wallet结构如下:
type Wallet struct {
PrivateKey ecdsa.PrivateKey
PublicKey []byte
}
type Wallets struct {
Wallets map[string]*Wallet
}
func NewWallet() *Wallet {
private, public := newKeyPair()
wallet := Wallet{private, public}
return &wallet
}
func newKeyPair() (ecdsa.PrivateKey, []byte) {
curve := elliptic.P256()
private, err := ecdsa.GenerateKey(curve, rand.Reader)
pubKey := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...)
return *private, pubKey
}
钱包就是一个密钥对。Wallets代表钱包集合,Wallets可以被存储到文件中,同时也可以从文件加载到内存中。NewWallet函数用于生成一个新的钱包。newKeyPair函数使用椭圆曲线算法生成私钥,紧接着通过私钥生成公钥。需要注意一点:椭圆曲线算法中,公钥是曲线上的点集合。因此,公钥由X,Y坐标混合而成。比特币中,这些坐标被组合在一起,生成公钥。
现在,创建一个地址:
func (w Wallet) GetAddress() []byte {
pubKeyHash := HashPubKey(w.PublicKey)
versionedPayload := append([]byte{version}, pubKeyHash...)
checksum := checksum(versionedPayload)
fullPayload := append(versionedPayload, checksum...)
address := Base58Encode(fullPayload)
return address
}
func HashPubKey(pubKey []byte) []byte {
publicSHA256 := sha256.Sum256(pubKey)
RIPEMD160Hasher := ripemd160.New()
_, err := RIPEMD160Hasher.Write(publicSHA256[:])
publicRIPEMD160 := RIPEMD160Hasher.Sum(nil)
return publicRIPEMD160
}
func checksum(payload []byte) []byte {
firstSHA := sha256.Sum256(payload)
secondSHA := sha256.Sum256(firstSHA[:])
return secondSHA[:addressChecksumLen]
}
通过Base58将公钥转换成可读格式,包括以下步骤:
- 使用 RIPEMD160(SHA256(PubKey))对公钥进行两次哈希,生成pubKeyHash
- 将版本信息追加到pubKeyHash之前,生成versionedPayload,此时versionedPayload=version+pubKeyHash
- 使用SHA256(SHA256(versionedPayload))进行两次哈希得到一个hash值,取该值的前n个字节最终生成checksum
- 将checksum追加到versionedPayload之后,生成编码前的地址,此时地址= version+pubKeyHash+checksum
- 最后使用Base58对version+pubKeyHash+checksum编码生成最终的地址。
最终,我们得到一个真实的比特币地址,甚至可以在blockchain.info上查询该地址的余额。当然,无论查询多少次,该地址的余额都会是0。好的公钥加密算法是非常关键,它使产生相同私钥的概率尽可能低,低到不可能发生。同时,比特币地址生成算法使用一组开放的算法集合,因此不需要连接到比特币网络就可以生成比特币地址。
现在,修改TXI和TXO结构,在其中加入地址:
type TXInput struct {
Txid []byte
Vout int
Signature []byte
PubKey []byte
}
func (in *TXInput) UsesKey(pubKeyHash []byte) bool {
lockingHash := HashPubKey(in.PubKey)
return bytes.Compare(lockingHash, pubKeyHash) == 0
}
type TXOutput struct {
Value int
PubKeyHash []byte
}
func (out *TXOutput) Lock(address []byte) {
pubKeyHash := Base58Decode(address)
pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4]
out.PubKeyHash = pubKeyHash
}
func (out *TXOutput) IsLockedWithKey(pubKeyHash []byte) bool {
return bytes.Compare(out.PubKeyHash, pubKeyHash) == 0
}
由于我们不打算实现一个脚本语言,因此不再使用ScriptPubKey和ScriptSig。ScriptSig被Signature和PubKey代替;ScriptPubKey被PubKeyHash代替。通过下面的方法中实现比特币中TXO的加解锁和TXI的签名逻辑:
UsesKey方法核查TXI是否可以使用特定的未哈希的公钥来解锁一个TXO。IsLockedWithKey核查TXO是否被特定的哈希后的公钥锁定。UsesKey和IsLockedWithKey是互补函数,FindUnspentTransactions函数使用这两个函数来构建交易间的关系。
Lock方法是对TXO进行加锁。当发给某人货币时,仅仅知道他们的地址,因此该方法唯一入参就是地址信息。从地址中从解码出哈希后的公钥,将其保存到PubKeyHash中。
现在,让我们看看是否能正常工作:
$ blockchain_go createwallet
Your new address: 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt
$ blockchain_go createwallet
Your new address: 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h
$ blockchain_go createwallet
Your new address: 1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2sy
$ blockchain_go createblockchain -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt
0000005420fbfdafa00c093f56e033903ba43599fa7cd9df40458e373eee724d
Done!
$ blockchain_go getbalance -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt
Balance of '13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt': 10
$ blockchain_go send -from 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h -to 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt -amount 5
2017/09/12 13:08:56 ERROR: Not enough funds
$ blockchain_go send -from 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt -to 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h -amount 6
00000019afa909094193f64ca06e9039849709f5948fbac56cae7b1b8f0ff162
Success!
$ blockchain_go getbalance -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt
Balance of '13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt': 4
$ blockchain_go getbalance -address 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h
Balance of '15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h': 6
$ blockchain_go getbalance -address 1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2sy
Balance of '1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2sy': 0