区块链数字签名与校验源码分析
资源
源码分析
钱包
// Wallet stores private and public keys
type Wallet struct {
PrivateKey ecdsa.PrivateKey
PublicKey []byte
}
区块链钱包是私钥和公钥的集合。
地址
地址的计算过程可以由下图来表示:
也就是,系统通过钱包的公钥可以计算出钱包的地址。
// GetAddress returns wallet address
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
}
实际上,通过对地址进行Base58Decode,可以得到公钥的哈希结果(Publick key hash)。但得到公钥的哈希结果后,不能进一步逆向得到公钥(Public key)。
数字签名
数字签名的步骤可以分为两步:
- 对数据进行数字摘要算法得到数字摘要
- 通过私钥对数字摘要进行签名算法得到数字签名
(注:这里不讨论数字摘要算法和签名算法)
以下面的代码为例,对一笔交易的进行签名需要对这笔交易中所有的交易输入单独进行签名。
// TrimmedCopy creates a trimmed copy of Transaction to be used in signing
func (tx *Transaction) TrimmedCopy() Transaction {
var inputs []TXInput
var outputs []TXOutput
for _, vin := range tx.Vin {
inputs = append(inputs, TXInput{vin.Txid, vin.Vout, nil, nil})
}
for _, vout := range tx.Vout {
outputs = append(outputs, TXOutput{vout.Value, vout.PubKeyHash})
}
txCopy := Transaction{tx.ID, inputs, outputs}
return txCopy
}
// Sign signs each input of a Transaction
func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) {
if tx.IsCoinbase() {
return
}
// 这里检查形参是否合法
// 个人认为可有可无
for _, vin := range tx.Vin {
if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil {
log.Panic("ERROR: Previous transaction is not correct")
}
}
// 裁剪后的副本
txCopy := tx.TrimmedCopy()
for inID, vin := range txCopy.Vin {// 注意:每个交易输入都是独立进行数字签名的
prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
txCopy.Vin[inID].Signature = nil
txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash// 这一行不明白有什么意义
txCopy.ID = txCopy.Hash()// 得到了交易数据的数字摘要
txCopy.Vin[inID].PubKey = nil// 这一行不明白有什么意义
r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID)// 通过私钥对数字摘要进行签名
if err != nil {
log.Panic(err)
}
signature := append(r.Bytes(), s.Bytes()...)// 得到数字签名
tx.Vin[inID].Signature = signature// 单个交易输入签名完毕
}
}
数字签名校验
当我们期望校验一笔交易时,我们需要检验这笔交易的所有交易输入。而校验单笔交易输入需要三样东西:
- 数字签名
- 公钥
- 未进行数字摘要算法的原数据
所以,校验一笔交易输入合法性的步骤为:
- 通过与数字签名过程中相同的数字摘要算法对原数据进行计算,得到数字摘要1。
- 通过公钥解数字签名,得到数字摘要2。
- 判断数字摘要1与数字摘要2是否相同,相同则说明该输入交易确实由公钥持有人发送;否则,说明该输入交易不由公钥持有人发送或者输入交易的数据被篡改。
另外,源码表明数字签名的校验过程发生在矿工挖取新区块的过程。
// Verify verifies signatures of Transaction inputs
func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
if tx.IsCoinbase() {
return true
}
for _, vin := range tx.Vin {
if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil {
log.Panic("ERROR: Previous transaction is not correct")
}
}
txCopy := tx.TrimmedCopy()
curve := elliptic.P256()
for inID, vin := range tx.Vin {
// 校验者对数据进行数字摘要算法
prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
txCopy.Vin[inID].Signature = nil
txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash
txCopy.ID = txCopy.Hash()
txCopy.Vin[inID].PubKey = nil
// 数字摘要算法计算完毕,得到数字摘要1
// 对数字签名进行解密
// 得到数字摘要2
r := big.Int{}
s := big.Int{}
sigLen := len(vin.Signature)
r.SetBytes(vin.Signature[:(sigLen / 2)])
s.SetBytes(vin.Signature[(sigLen / 2):])
x := big.Int{}
y := big.Int{}
keyLen := len(vin.PubKey)
x.SetBytes(vin.PubKey[:(keyLen / 2)])
y.SetBytes(vin.PubKey[(keyLen / 2):])
rawPubKey := ecdsa.PublicKey{curve, &x, &y}
// 对比数字摘要1与数字摘要2
// 即可判断交易输入是否由公钥持有人发起
if ecdsa.Verify(&rawPubKey, txCopy.ID, &r, &s) == false {
return false
}
}
return true
}
一笔交易发生的过程(部分)
首先,系统会产生一笔普通交易。在这个过程中,系统需要使用支付方的私钥来对交易中所有的交易输入进行数字签名。
// NewUTXOTransaction creates a new transaction
func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction {
var inputs []TXInput
var outputs []TXOutput
wallets, err := NewWallets()
if err != nil {
log.Panic(err)
}
wallet := wallets.GetWallet(from)
pubKeyHash := HashPubKey(wallet.PublicKey)
acc, validOutputs := bc.FindSpendableOutputs(pubKeyHash, amount)
if acc < amount {
log.Panic("ERROR: Not enough funds")
}
// Build a list of inputs
for txid, outs := range validOutputs {
txID, err := hex.DecodeString(txid)
if err != nil {
log.Panic(err)
}
for _, out := range outs {
input := TXInput{txID, out, nil, wallet.PublicKey}
inputs = append(inputs, input)
}
}
// Build a list of outputs
outputs = append(outputs, *NewTXOutput(amount, to))
if acc > amount {
outputs = append(outputs, *NewTXOutput(acc-amount, from)) // a change
}
tx := Transaction{nil, inputs, outputs}
tx.ID = tx.Hash()
bc.SignTransaction(&tx, wallet.PrivateKey)
return &tx
}
然后,矿工对这笔新产生的交易进行数字签名校验,校验通过后进行挖矿,完成挖矿后将新区块添加到区块链中。
// MineBlock mines a new block with the provided transactions
func (bc *Blockchain) MineBlock(transactions []*Transaction) {
var lastHash []byte
for _, tx := range transactions {
if bc.VerifyTransaction(tx) != true {
log.Panic("ERROR: Invalid transaction")
}
}
err := bc.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
lastHash = b.Get([]byte("l"))
return nil
})
if err != nil {
log.Panic(err)
}
newBlock := NewBlock(transactions, lastHash)
err = bc.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
err := b.Put(newBlock.Hash, newBlock.Serialize())
if err != nil {
log.Panic(err)
}
err = b.Put([]byte("l"), newBlock.Hash)
if err != nil {
log.Panic(err)
}
bc.tip = newBlock.Hash
return nil
})
if err != nil {
log.Panic(err)
}
}