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
}
}
}



