BIP0141+blockchain: add functions for extracting and validating witness commitment
This commit is contained in:
parent
d38ae9ca0b
commit
1b80b334bc
2 changed files with 162 additions and 3 deletions
|
@ -5,12 +5,42 @@
|
||||||
package blockchain
|
package blockchain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// CoinbaseWitnessDataLen is the required length of the only element within
|
||||||
|
// the coinbase's witness data if the coinbase transaction contains a
|
||||||
|
// witness commitment.
|
||||||
|
CoinbaseWitnessDataLen = 32
|
||||||
|
|
||||||
|
// CoinbaseWitnessPkScriptLength is the length of the public key script
|
||||||
|
// containing an OP_RETURN, the WitnessMagicBytes, and the witness
|
||||||
|
// commitment itself. In order to be a valid candidate for the output
|
||||||
|
// containing the witness commitment
|
||||||
|
CoinbaseWitnessPkScriptLength = 38
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// WitnessMagicBytes is the prefix marker within the public key script
|
||||||
|
// of a coinbase output to indicate that this output holds the witness
|
||||||
|
// commitment for a block.
|
||||||
|
WitnessMagicBytes = []byte{
|
||||||
|
txscript.OP_RETURN,
|
||||||
|
txscript.OP_DATA_36,
|
||||||
|
0xaa,
|
||||||
|
0x21,
|
||||||
|
0xa9,
|
||||||
|
0xed,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// nextPowerOfTwo returns the next highest power of two from a given number if
|
// nextPowerOfTwo returns the next highest power of two from a given number if
|
||||||
// it is not already a power of two. This is a helper function used during the
|
// it is not already a power of two. This is a helper function used during the
|
||||||
// calculation of a merkle tree.
|
// calculation of a merkle tree.
|
||||||
|
@ -66,7 +96,12 @@ func HashMerkleBranches(left *chainhash.Hash, right *chainhash.Hash) *chainhash.
|
||||||
// are calculated by concatenating the left node with itself before hashing.
|
// are calculated by concatenating the left node with itself before hashing.
|
||||||
// Since this function uses nodes that are pointers to the hashes, empty nodes
|
// Since this function uses nodes that are pointers to the hashes, empty nodes
|
||||||
// will be nil.
|
// will be nil.
|
||||||
func BuildMerkleTreeStore(transactions []*btcutil.Tx) []*chainhash.Hash {
|
//
|
||||||
|
// The additional bool parameter indicates if we are generating the merkle tree
|
||||||
|
// using witness transaction id's rather than regular transaction id's. This
|
||||||
|
// also presents an additional case wherein the wtxid of the coinbase transaction
|
||||||
|
// is the zeroHash.
|
||||||
|
func BuildMerkleTreeStore(transactions []*btcutil.Tx, witness bool) []*chainhash.Hash {
|
||||||
// Calculate how many entries are required to hold the binary merkle
|
// Calculate how many entries are required to hold the binary merkle
|
||||||
// tree as a linear array and create an array of that size.
|
// tree as a linear array and create an array of that size.
|
||||||
nextPoT := nextPowerOfTwo(len(transactions))
|
nextPoT := nextPowerOfTwo(len(transactions))
|
||||||
|
@ -75,7 +110,21 @@ func BuildMerkleTreeStore(transactions []*btcutil.Tx) []*chainhash.Hash {
|
||||||
|
|
||||||
// Create the base transaction hashes and populate the array with them.
|
// Create the base transaction hashes and populate the array with them.
|
||||||
for i, tx := range transactions {
|
for i, tx := range transactions {
|
||||||
merkles[i] = tx.Hash()
|
// 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
|
// Start the array offset after the last transaction and adjusted to the
|
||||||
|
@ -104,3 +153,113 @@ func BuildMerkleTreeStore(transactions []*btcutil.Tx) []*chainhash.Hash {
|
||||||
|
|
||||||
return merkles
|
return merkles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtractWitnessCommitment attempts to locate, and return the witness
|
||||||
|
// commitment for a block. The witness commitment is of the form:
|
||||||
|
// SHA256(witness root || witness nonce). The function additionally returns a
|
||||||
|
// boolean indicating if the witness root was located within any of the txOut's
|
||||||
|
// in the passed transaction. The witness commitment is stored as the data push
|
||||||
|
// for an OP_RETURN with special magic bytes to aide in location.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateWitnessCommitment validates the witness commitment (if any) found
|
||||||
|
// within the coinbase transaction of the passed block.
|
||||||
|
func ValidateWitnessCommitment(blk *btcutil.Block) error {
|
||||||
|
// If the block doesn't have any transactions at all, then we won't be
|
||||||
|
// able to extract a commitment from the non-existent coinbase
|
||||||
|
// transaction. So we exit early here.
|
||||||
|
if len(blk.Transactions()) == 0 {
|
||||||
|
str := "cannot validate witness commitment of block without " +
|
||||||
|
"transactions"
|
||||||
|
return ruleError(ErrNoTransactions, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
coinbaseTx := blk.Transactions()[0]
|
||||||
|
if len(coinbaseTx.MsgTx().TxIn) == 0 {
|
||||||
|
return ruleError(ErrNoTxInputs, "transaction has no inputs")
|
||||||
|
}
|
||||||
|
|
||||||
|
witnessCommitment, witnessFound := ExtractWitnessCommitment(coinbaseTx)
|
||||||
|
|
||||||
|
// If we can't find a witness commitment in any of the coinbase's
|
||||||
|
// outputs, then the block MUST NOT contain any transactions with
|
||||||
|
// witness data.
|
||||||
|
if !witnessFound {
|
||||||
|
for _, tx := range blk.Transactions() {
|
||||||
|
msgTx := tx.MsgTx()
|
||||||
|
if msgTx.HasWitness() {
|
||||||
|
str := fmt.Sprintf("block contains transaction with witness" +
|
||||||
|
" data, yet no witness commitment present")
|
||||||
|
return ruleError(ErrUnexpectedWitness, str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point the block contains a witness commitment, so the
|
||||||
|
// coinbase transaction MUST have exactly one witness element within
|
||||||
|
// its witness data and that element must be exactly
|
||||||
|
// CoinbaseWitnessDataLen bytes.
|
||||||
|
coinbaseWitness := coinbaseTx.MsgTx().TxIn[0].Witness
|
||||||
|
if len(coinbaseWitness) != 1 {
|
||||||
|
str := fmt.Sprintf("the coinbase transaction has %d items in "+
|
||||||
|
"its witness stack when only one is allowed",
|
||||||
|
len(coinbaseWitness))
|
||||||
|
return ruleError(ErrInvalidWitnessCommitment, str)
|
||||||
|
}
|
||||||
|
witnessNonce := coinbaseWitness[0]
|
||||||
|
if len(witnessNonce) != CoinbaseWitnessDataLen {
|
||||||
|
str := fmt.Sprintf("the coinbase transaction witness nonce "+
|
||||||
|
"has %d bytes when it must be %d bytes",
|
||||||
|
len(witnessNonce), CoinbaseWitnessDataLen)
|
||||||
|
return ruleError(ErrInvalidWitnessCommitment, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, with the preliminary checks out of the way, we can check if
|
||||||
|
// the extracted witnessCommitment is equal to:
|
||||||
|
// SHA256(witnessMerkleRoot || witnessNonce). Where witnessNonce is the
|
||||||
|
// coinbase transaction's only witness item.
|
||||||
|
witnessMerkleTree := BuildMerkleTreeStore(blk.Transactions(), true)
|
||||||
|
witnessMerkleRoot := witnessMerkleTree[len(witnessMerkleTree)-1]
|
||||||
|
|
||||||
|
var witnessPreimage [chainhash.HashSize * 2]byte
|
||||||
|
copy(witnessPreimage[:], witnessMerkleRoot[:])
|
||||||
|
copy(witnessPreimage[chainhash.HashSize:], witnessNonce)
|
||||||
|
|
||||||
|
computedCommitment := chainhash.DoubleHashB(witnessPreimage[:])
|
||||||
|
if !bytes.Equal(computedCommitment, witnessCommitment) {
|
||||||
|
str := fmt.Sprintf("witness commitment does not match: "+
|
||||||
|
"computed %v, coinbase includes %v", computedCommitment,
|
||||||
|
witnessCommitment)
|
||||||
|
return ruleError(ErrWitnessCommitmentMismatch, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
// TestMerkle tests the BuildMerkleTreeStore API.
|
// TestMerkle tests the BuildMerkleTreeStore API.
|
||||||
func TestMerkle(t *testing.T) {
|
func TestMerkle(t *testing.T) {
|
||||||
block := btcutil.NewBlock(&Block100000)
|
block := btcutil.NewBlock(&Block100000)
|
||||||
merkles := blockchain.BuildMerkleTreeStore(block.Transactions())
|
merkles := blockchain.BuildMerkleTreeStore(block.Transactions(), false)
|
||||||
calculatedMerkleRoot := merkles[len(merkles)-1]
|
calculatedMerkleRoot := merkles[len(merkles)-1]
|
||||||
wantMerkle := &Block100000.Header.MerkleRoot
|
wantMerkle := &Block100000.Header.MerkleRoot
|
||||||
if !wantMerkle.IsEqual(calculatedMerkleRoot) {
|
if !wantMerkle.IsEqual(calculatedMerkleRoot) {
|
||||||
|
|
Loading…
Reference in a new issue