Golang|区块链数字签名与校验源码分析

区块链数字签名与校验源码分析

资源

go实现区块链
剖析区块链(六): 核心技术之数字签名

源码分析

钱包

// Wallet stores private and public keys
type Wallet struct {
	PrivateKey ecdsa.PrivateKey
	PublicKey  []byte
}

区块链钱包是私钥和公钥的集合。

地址

地址的计算过程可以由下图来表示:
Base58
也就是,系统通过钱包的公钥可以计算出钱包的地址。

// 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)
	}
}
以下是使用Go语言实现区块链的基本步骤: 1. 定义区块结构体 ```go type Block struct { Index int Timestamp string Data string PrevHash string Hash string } ``` 2. 创建区块链结构体 ```go type Blockchain struct { chain []*Block } ``` 3. 实现创建区块的方法 ```go func (bc *Blockchain) createBlock(data string) *Block { prevBlock := bc.chain[len(bc.chain)-1] newBlock := &Block{ Index: prevBlock.Index + 1, Timestamp: time.Now().String(), Data: data, PrevHash: prevBlock.Hash, } newBlock.Hash = calculateHash(newBlock) bc.chain = append(bc.chain, newBlock) return newBlock } ``` 4. 实现计算区块哈希值的方法 ```go func calculateHash(block *Block) string { record := strconv.Itoa(block.Index) + block.Timestamp + block.Data + block.PrevHash h := sha256.New() h.Write([]byte(record)) hashed := h.Sum(nil) return hex.EncodeToString(hashed) } ``` 5. 实现验证区块链的方法 ```go func (bc *Blockchain) validateChain() bool { for i := 1; i < len(bc.chain); i++ { currentBlock := bc.chain[i] prevBlock := bc.chain[i-1] if currentBlock.Hash != calculateHash(currentBlock) { return false } if currentBlock.PrevHash != prevBlock.Hash { return false } } return true } ``` 6. 创建区块链实例并添加创世块 ```go func main() { bc := Blockchain{[]*Block{createGenesisBlock()}} bc.createBlock("Data for block 2") bc.createBlock("Data for block 3") fmt.Println(bc.validateChain()) } ``` 以上是使用Go语言实现区块链的基本步骤,其中包括定义区块结构体、创建区块链结构体、实现创建区块的方法、实现计算区块哈希值的方法、实现验证区块链的方法以及创建区块链实例并添加创世块。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值