Segwitness 中btcd 代码实现详解
从btc源代码看segwitness的实现,以btcd代码为例:函数ExtractWitnessCommitment从块的coinbase交易中提取commitment。
func ExtractWitnessCommitment(tx *btcutil.Tx) ([]byte, bool) { // The witness commitment *must* be located within one of the coinbase // transaction's outputs. if !IsCoinBase(tx) { return nil, false } msgTx := tx.MsgTx() for i := len(msgTx.TxOut) - 1; i >= 0; i-- { // The public key script that contains the witness commitment // must shared a prefix with the WitnessMagicBytes, and be at // least 38 bytes. pkScript := msgTx.TxOut[i].PkScript if len(pkScript) >= CoinbaseWitnessPkScriptLength && bytes.HasPrefix(pkScript, WitnessMagicBytes) { // The witness commitment itself is a 32-byte hash // directly after the WitnessMagicBytes. The remaining // bytes beyond the 38th byte currently have no consensus // meaning. start := len(WitnessMagicBytes) end := CoinbaseWitnessPkScriptLength return msgTx.TxOut[i].PkScript[start:end], true } } return nil, false }
Commitment是 BIP:141中规定的一个结构,该结构包含一个WitnessMagicBytes :0x6a24aa21a9ed。如果coinbase交易的输出中包含这个头,即表示有采用segwitness协议。
1-byte - OP_RETURN (0x6a)
1-byte - Push the following 36 bytes (0x24)
4-byte - Commitment header (0xaa21a9ed)
32-byte - Commitment hash: Double-SHA256(witness root hash|witness reserved value)
Witness的结构定义如上所示,其中前六个字节是固定值,commitment hash是对witness root hash|witness reserved value求SHA256S双哈希得到。而witness root hash 是以wtxid为叶子节点的merke tree的根节点。witness reserved value是coinbase交易中交易输入中的一个值具体,计算merke root节点的代码如下:
func BuildMerkleTreeStore(transactions []*btcutil.Tx, witness bool) []*chainhash.Hash { // Calculate how many entries are required to hold the binary merkle // tree as a linear array and create an array of that size. nextPoT := nextPowerOfTwo(len(transactions)) arraySize := nextPoT*2 - 1 merkles := make([]*chainhash.Hash, arraySize) // Create the base transaction hashes and populate the array with them. for i, tx := range transactions { // If we're computing a witness merkle root, instead of the // regular txid, we use the modified wtxid which includes a // transaction's witness data within the digest. Additionally, // the coinbase's wtxid is all zeroes. switch { case witness && i == 0: var zeroHash chainhash.Hash merkles[i] = &zeroHash case witness: wSha := tx.MsgTx().WitnessHash() merkles[i] = &wSha default: merkles[i] = tx.Hash() } } // Start the array offset after the last transaction and adjusted to the // next power of two. offset := nextPoT for i := 0; i < arraySize-1; i += 2 { switch { // When there is no left child node, the parent is nil too. case merkles[i] == nil: merkles[offset] = nil // When there is no right child, the parent is generated by // hashing the concatenation of the left child with itself. case merkles[i+1] == nil: newHash := HashMerkleBranches(merkles[i], merkles[i]) merkles[offset] = newHash // The normal case sets the parent node to the double sha256 // of the concatentation of the left and right children. default: newHash := HashMerkleBranches(merkles[i], merkles[i+1]) merkles[offset] = newHash } offset++ } return merkles }
Segwitness定义了新的transactionid,每一笔交易都有两个交易id,一个是原来的txid,一个是新定义的Wtxid,对这个[nVersion][txins][txouts][nLockTime]序列化输出做SHA256双哈希,可以得到txid。而对[nVersion][marker][flag][txins][txouts][witness][nLockTime]的序列化输出做SHA256双哈希得到wtxid。函数ValidateWitnessCommitment是对winess的验证,即根据各个transaction计算的merkroot和上文提到的commitment hash做比较,如果相同则认为是合法的区块