GO实现BTC-V6-签名
本部分为GO实现BTC的最后一个部分 签名校验
http://www.okweex.com/3141.html 讲解文章
交易签名分析
我们对一笔交易签名,要填充每一个input的sig,N个input要有N个签名
签名内容包括:
1. 所引用的output的公钥哈希
2. 所新生成的output的公钥哈希
3. 所新生成的output的value
签名时需要找到这个input对应的output,或者说找到这笔交易
在创建交易最后,对交易进行一次签名。
疑问:
将所引用的output放到input的pubKey会覆盖原有的input的pubKey?
不会,因为我们在做签名的时候,会创建一个tx的副本,对这个副本进行替换,生成的Sig放回到原始的交易中
,这样这个交易的所有字段就都完整了。
txid 0x555三个交易签名,两个锁定脚本
签名详细图示
send转账的时候对本笔交易进行签名 用前交易prevTXs和私钥进行签名
当矿工校验的时候 也要找到 prevTXs 并且又了公钥pubkey和sign 就能比对是否有效
input中的pubkey就是引用的前output的pubkeyhash
bc.SignTransaction
func (bc *BlockChain) SignTransaction(tx *Transaction, privateKey *ecdsa.PrivateKey) { //1. 遍历账本找到所有应用交易 prevTXs := make(map[string]Transaction) //TODO tx.Sign(privateKey, prevTXs) }
在Transaction中添加签名函数
//第一个参数时私钥, //第二个参数时这个交易的input所引用的所有的交易 func (tx *Transaction) Sign(privKey *ecdsa.PrivateKey, prevTXs map[string]Transaction) { fmt.Printf("对交易进行签名...\n") //TODO }
在NewTransaction最后调用
//把查找引用交易的环节放到BlockChain中去,同时在BlockChain进行调用签名 //我们付款人再创建交易时,已经得到了所有引用的output的详细信息。 //但是我们不去使用,因为在矿工校验的时候,矿工是没有这部分信息的,矿工需要遍历账本找到所有引用交易 //我们为了统一操作,所以再次查询一次,进行签名。 bc.SignTransaction(&tx, privateKey)
实现SignTransaction函数
func (bc *BlockChain) SignTransaction(tx *Transaction, privateKey *ecdsa.PrivateKey) { //1. 遍历账本找到所有应用交易 prevTXs := make(map[string]Transaction) //遍历tx的inputs,通过id去查找所引用的交易 for _, input := range tx.TXInputs { //传入本次转账交易的 Input的TXID查找Transaction prevTx := bc.FindTransaction(input.TXID) if prevTx == nil { fmt.Printf("没有找到交易: %x\n", input.TXID) } else { //把找到的引用交易保存起来 //0x222 //0x333 // TXID -> Transaction prevTXs[string(input.TXID)] = prevTx } } tx.Sign(privateKey, prevTXs) }
实现FindTransaction函数
因为在本程序中内存中没用记录 交易ID和交易索引 所以每次都需要遍历
func (bc *BlockChain) FindTransaction(txid []byte) *Transaction { //遍历区块链的交易 //通过对比id来识别 it := bc.NewIterator() for { block := it.Next() for _, tx := range block.Transactions { //如果找到相同id交易,直接返回交易即可 if bytes.Equal(tx.TXid, txid) { fmt.Printf("找到了所引用交易: %x\n", tx.TXid) return tx } } if len(block.PrevBlockHash) == 0 { break } } return nil }
实现VerifyTransaction函数
//矿工校验流程
//1. 找到交易input所引用的所有的交易prevTXs
//2. 对交易进行校验
func (bc *BlockChain) VerifyTransaction(tx *Transaction) bool {
//校验的时候,如果是挖矿交易,直接返回true 无需校验
if tx.IsCoinbase() {
return true
}
prevTXs := make(map[string]Transaction)
//遍历tx的inputs,通过id去查找所引用的交易
for _, input := range tx.TXInputs {
prevTx := bc.FindTransaction(input.TXID)
if prevTx == nil {
fmt.Printf("没有找到交易: %x\n", input.TXID)
} else {
//把找到的引用交易保存起来
//0x222
//0x333
prevTXs[string(input.TXID)] = *prevTx
}
}
return tx.Verify(prevTXs)
}
在Transaction中添加Verify空实现
func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { fmt.Printf("对交易进行校验...\n") //TODO return true }
签名在创建交易时候签名
校验在挖矿之前进行校验交易,Coinbase无需校验
如果有矿工使用无效交易挖矿,挖出后经过其他矿工校验后 将无效
在AddBlock前对交易进行校验
//添加区块
func (bc *BlockChain) AddBlock(txs []*Transaction) {
//矿工得到交易时,第一时间对交易进行验证
//矿工如果不验证,即使挖矿成功,广播区块后,其他的验证矿工,仍然会校验每一笔交易
validTXs := []*Transaction{}
for _, tx := range txs {
if bc.VerifyTransaction(tx) {
fmt.Printf("--- 该交易有效: %x\n", tx.TXid)
validTXs = append(validTXs, tx)
} else {
fmt.Printf("发现无效的交易: %x\n", tx.TXid)
}
}
//..真正打包逻辑
}
签名与校验流程 多参考上图流程
Sign签名函数实现步骤
1. 拷贝一份交易txCopy,
>做相应裁剪:把每一个input的Sig和pubkey设置为nil
> output不做改变
txCopy := tx.TrimmedCopy()
2. 遍历txCopy.inputs, 把这个input所引用的output的公钥哈希拿过来,赋值给pubkey
3. 生成要签名的数据(哈希)
4. 对数据进行签名r, s
5. 拼接r,s为字节流,赋值给原始的交易的Signature字段
TrimmedCopy函数实现
//trim:裁剪 // >做相应裁剪:把每一个input的Sig和pubkey设置为nil // > output不做改变 func (tx *Transaction) TrimmedCopy() Transaction { var inputs []TXInput var outputs []TXOutput for _, input := range tx.TXInputs { input1 := TXInput{input.TXID, input.Index, nil, nil} inputs = append(inputs, input1) } outputs = tx.TXOutputs tx1 := Transaction{tx.TXid, inputs, outputs} return tx1 }
Sign函数实现
func (tx *Transaction) Sign(privKey *ecdsa.PrivateKey, prevTXs map[string]Transaction) { fmt.Printf("对交易进行签名...\n") //1. 拷贝一份交易txCopy, // >做相应裁剪:把每一个input的Sig和pubkey设置为nil // > output不做改变 txCopy := tx.TrimmedCopy() //2. 遍历txCopy.inputs, // > 把这个input所引用的output的公钥哈希拿过来,赋值给pubkey for i, input := range txCopy.TXInputs { //找到引用的交易 preTX := prevTXs[string(input.TXID)] output := preTX.TXOutputs[input.Index] //for循环迭代出来的数据是一个副本,对这个input进行修改,不会影响到原始数据 //所以我们这里需要使用下标方式修改 //input.PubKey = output.PubKeyHash txCopy.TXInputs[i].PubKey = output.PubKeyHash //签名要对数据的hash进行签名 //我们的数据都在交易中,我们要求交易的哈希 //Transaction的SetTXID函数就是对交易的哈希 //所以我们可以使用交易id作为我们的签名的内容 //3. 生成要签名的数据(哈希) txCopy.SetTXID() signData := txCopy.TXid //清理,原理同上 //input.PubKey = nil txCopy.TXInputs[i].PubKey = nil fmt.Printf("要签名的数据, signData: %x\n", signData) //4. 对数据进行签名r, s r, s, err := ecdsa.Sign(rand.Reader, privKey, signData) if err != nil { fmt.Printf("交易签名失败, err : %v\n", err) } //5. 拼接r,s为字节流 signature := append(r.Bytes(), s.Bytes()...) //6. 赋值给原始的交易的Signature字段 tx.TXInputs[i].Signature = signature } }
签名校验函数实现
其实签名和校验是一个道理 对数据还原 使用公钥和签名校验
func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { fmt.Printf("对交易进行校验...\n") //1. 拷贝修剪的副本 txCopy := tx.TrimmedCopy() //2. 遍历原始交易(注意,不是txCopy) for i, input := range tx.TXInputs { //3. 遍历原始交易的input所引用的前交易prevTX prevTX := prevTXs[string(input.TXID)] output := prevTX.TXOutputs[input.Index] //4. 找到output的公钥哈希,赋值给txCopy对应的input txCopy.TXInputs[i].PubKey = output.PubKeyHash //5. 还原签名的数据 txCopy.SetTXID() //清理动作,重要!!! txCopy.TXInputs[i].PubKey = nil verifyData := txCopy.TXid fmt.Printf("verifyData : %x\n", verifyData) //6. 校验 //还原签名为r,s signature := input.Signature //公钥字节流 pubKeyBytes := input.PubKey r := big.Int{} s := big.Int{} rData := signature[: len(signature)/2] sData := signature[len(signature)/2:] r.SetBytes(rData) s.SetBytes(sData) //type PublicKey struct { // elliptic.Curve // X, Y *big.Int //} //还原公钥为curve,X,Y x := big.Int{} y := big.Int{} xData := pubKeyBytes[: len(pubKeyBytes)/2] yData := pubKeyBytes[len(pubKeyBytes)/2:] x.SetBytes(xData) y.SetBytes(yData) curve := elliptic.P256() publicKey := ecdsa.PublicKey{curve, &x, &y} //数据,签名,公钥准备完毕,开始校验 //func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool { if !ecdsa.Verify(&publicKey, verifyData, &r, &s) { return false } } return true }
结构定义String()函数 DEMO
注意String返回值为string类型
Sprintf 拼接字符串返回不打印
package main import "fmt" type Test struct { str string } //给结构添加一个String() func (test *Test) String() string { res := fmt.Sprintf("hello world : %s\n", test.str) return res } func main() { //案例1 t1 := &Test{"您好"} fmt.Printf("%v\n",t1) //打印结果 hello world:您好 //连接案例2 res2 := strings.Join([]string{"1","2","3"},"+") //error fmt.Printf("%v",res2) //打印结果 1+2+3 }
交易的String函数
func (tx *Transaction) String() string { var lines []string lines = append(lines, fmt.Sprintf("--- Transaction %x:", tx.TXid)) for i, input := range tx.TXInputs { lines = append(lines, fmt.Sprintf(" Input %d:", i)) lines = append(lines, fmt.Sprintf(" TXID: %x", input.TXID)) lines = append(lines, fmt.Sprintf(" Out: %d", input.Index)) lines = append(lines, fmt.Sprintf(" Signature: %x", input.Signature)) lines = append(lines, fmt.Sprintf(" PubKey: %x", input.PubKey)) } for i, output := range tx.TXOutputs { lines = append(lines, fmt.Sprintf(" Output %d:", i)) lines = append(lines, fmt.Sprintf(" Value: %f", output.Value)) lines = append(lines, fmt.Sprintf(" Script: %x", output.PubKeyHash)) } //11111, 2222, 3333, 44444, 5555 //`11111 //2222 //3333 //44444 //5555` return strings.Join(lines, "\n") }
添加PrintTx命令
func (cli *CLI) PrintTx() { bc := NewBlockChain() if bc == nil { return } defer bc.db.Close() it := bc.NewIterator() for { block := it.Next() fmt.Printf("\n+++++++++++++++ 新的区块 +++++++++++++++++\n") for _, tx := range block.Transactions { fmt.Printf("tx : %v\n", tx) } if len(block.PrevBlockHash) == 0 { break } } }