本部分为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
		}
	}
}