github.com/btcsuite/btcd@v0.24.0/txscript/taproot_test.go (about)

     1  // Copyright (c) 2013-2022 The btcsuite developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package txscript
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/hex"
    10  	"fmt"
    11  	prand "math/rand"
    12  	"testing"
    13  	"testing/quick"
    14  
    15  	"github.com/btcsuite/btcd/btcec/v2"
    16  	"github.com/btcsuite/btcd/btcec/v2/schnorr"
    17  	"github.com/btcsuite/btcd/btcutil"
    18  	"github.com/btcsuite/btcd/btcutil/hdkeychain"
    19  	"github.com/btcsuite/btcd/chaincfg"
    20  	secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  var (
    25  	testPubBytes, _ = hex.DecodeString("F9308A019258C31049344F85F89D5229B" +
    26  		"531C845836F99B08601F113BCE036F9")
    27  
    28  	// rootKey is the test root key defined in the test vectors:
    29  	// https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki
    30  	rootKey, _ = hdkeychain.NewKeyFromString(
    31  		"xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLi" +
    32  			"sriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu",
    33  	)
    34  
    35  	// accountPath is the base path for BIP86 (m/86'/0'/0').
    36  	accountPath = []uint32{
    37  		86 + hdkeychain.HardenedKeyStart, hdkeychain.HardenedKeyStart,
    38  		hdkeychain.HardenedKeyStart,
    39  	}
    40  	expectedExternalAddresses = []string{
    41  		"bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
    42  		"bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
    43  	}
    44  	expectedInternalAddresses = []string{
    45  		"bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7",
    46  	}
    47  )
    48  
    49  // TestControlBlockParsing tests that we're able to generate and parse a valid
    50  // control block.
    51  func TestControlBlockParsing(t *testing.T) {
    52  	t.Parallel()
    53  
    54  	var testCases = []struct {
    55  		controlBlockGen func() []byte
    56  		valid           bool
    57  	}{
    58  		// An invalid control block, it's only 5 bytes and needs to be
    59  		// at least 33 bytes.
    60  		{
    61  			controlBlockGen: func() []byte {
    62  				return bytes.Repeat([]byte{0x00}, 5)
    63  			},
    64  			valid: false,
    65  		},
    66  
    67  		// An invalid control block, it's greater than the largest
    68  		// accepted control block.
    69  		{
    70  			controlBlockGen: func() []byte {
    71  				return bytes.Repeat([]byte{0x00}, ControlBlockMaxSize+1)
    72  			},
    73  			valid: false,
    74  		},
    75  
    76  		// An invalid control block, it isn't a multiple of 32 bytes
    77  		// enough though it has a valid starting byte length.
    78  		{
    79  			controlBlockGen: func() []byte {
    80  				return bytes.Repeat([]byte{0x00}, ControlBlockBaseSize+34)
    81  			},
    82  			valid: false,
    83  		},
    84  
    85  		// A valid control block, of the largest possible size.
    86  		{
    87  			controlBlockGen: func() []byte {
    88  				privKey, _ := btcec.NewPrivateKey()
    89  				pubKey := privKey.PubKey()
    90  
    91  				yIsOdd := (pubKey.SerializeCompressed()[0] ==
    92  					secp.PubKeyFormatCompressedOdd)
    93  
    94  				ctrl := ControlBlock{
    95  					InternalKey:     pubKey,
    96  					OutputKeyYIsOdd: yIsOdd,
    97  					LeafVersion:     BaseLeafVersion,
    98  					InclusionProof: bytes.Repeat(
    99  						[]byte{0x00},
   100  						ControlBlockMaxSize-ControlBlockBaseSize,
   101  					),
   102  				}
   103  
   104  				ctrlBytes, _ := ctrl.ToBytes()
   105  				return ctrlBytes
   106  			},
   107  			valid: true,
   108  		},
   109  
   110  		// A valid control block, only has a single element in the
   111  		// proof as the tree only has a single element.
   112  		{
   113  			controlBlockGen: func() []byte {
   114  				privKey, _ := btcec.NewPrivateKey()
   115  				pubKey := privKey.PubKey()
   116  
   117  				yIsOdd := (pubKey.SerializeCompressed()[0] ==
   118  					secp.PubKeyFormatCompressedOdd)
   119  
   120  				ctrl := ControlBlock{
   121  					InternalKey:     pubKey,
   122  					OutputKeyYIsOdd: yIsOdd,
   123  					LeafVersion:     BaseLeafVersion,
   124  					InclusionProof: bytes.Repeat(
   125  						[]byte{0x00}, ControlBlockNodeSize,
   126  					),
   127  				}
   128  
   129  				ctrlBytes, _ := ctrl.ToBytes()
   130  				return ctrlBytes
   131  			},
   132  			valid: true,
   133  		},
   134  	}
   135  	for i, testCase := range testCases {
   136  		ctrlBlockBytes := testCase.controlBlockGen()
   137  
   138  		ctrlBlock, err := ParseControlBlock(ctrlBlockBytes)
   139  		switch {
   140  		case testCase.valid && err != nil:
   141  			t.Fatalf("#%v: unable to parse valid control block: %v", i, err)
   142  
   143  		case !testCase.valid && err == nil:
   144  			t.Fatalf("#%v: invalid control block should have failed: %v", i, err)
   145  		}
   146  
   147  		if !testCase.valid {
   148  			continue
   149  		}
   150  
   151  		// If we serialize the control block, we should get the exact same
   152  		// set of bytes as the input.
   153  		ctrlBytes, err := ctrlBlock.ToBytes()
   154  		if err != nil {
   155  			t.Fatalf("#%v: unable to encode bytes: %v", i, err)
   156  		}
   157  		if !bytes.Equal(ctrlBytes, ctrlBlockBytes) {
   158  			t.Fatalf("#%v: encoding mismatch: expected %x, "+
   159  				"got %x", i, ctrlBlockBytes, ctrlBytes)
   160  		}
   161  	}
   162  }
   163  
   164  // TestTaprootScriptSpendTweak tests that for any 32-byte hypothetical script
   165  // root, the resulting tweaked public key is the same as tweaking the private
   166  // key, then generating a public key from that. This test a quickcheck test to
   167  // assert the following invariant:
   168  //
   169  //   - taproot_tweak_pubkey(pubkey_gen(seckey), h)[1] ==
   170  //     pubkey_gen(taproot_tweak_seckey(seckey, h))
   171  func TestTaprootScriptSpendTweak(t *testing.T) {
   172  	t.Parallel()
   173  
   174  	// Assert that if we use this x value as the hash of the script root,
   175  	// then if we generate a tweaked public key, it's the same key as if we
   176  	// used that key to generate the tweaked
   177  	// private key, and then generated the public key from that.
   178  	f := func(x [32]byte) bool {
   179  		privKey, err := btcec.NewPrivateKey()
   180  		if err != nil {
   181  			return false
   182  		}
   183  
   184  		// Generate the tweaked public key using the x value as the
   185  		// script root.
   186  		tweakedPub := ComputeTaprootOutputKey(privKey.PubKey(), x[:])
   187  
   188  		// Now we'll generate the corresponding tweaked private key.
   189  		tweakedPriv := TweakTaprootPrivKey(*privKey, x[:])
   190  
   191  		// The public key for this private key should be the same as
   192  		// the tweaked public key we generate above.
   193  		return tweakedPub.IsEqual(tweakedPriv.PubKey()) &&
   194  			bytes.Equal(
   195  				schnorr.SerializePubKey(tweakedPub),
   196  				schnorr.SerializePubKey(tweakedPriv.PubKey()),
   197  			)
   198  	}
   199  
   200  	if err := quick.Check(f, nil); err != nil {
   201  		t.Fatalf("tweaked public/private key mapping is "+
   202  			"incorrect: %v", err)
   203  	}
   204  
   205  }
   206  
   207  // TestTaprootTweakNoMutation tests that the underlying private key passed into
   208  // TweakTaprootPrivKey is never mutated.
   209  func TestTaprootTweakNoMutation(t *testing.T) {
   210  	t.Parallel()
   211  
   212  	// Assert that given a random tweak, and a random private key, that if
   213  	// we tweak the private key it remains unaffected.
   214  	f := func(privBytes, tweak [32]byte) bool {
   215  		privKey, _ := btcec.PrivKeyFromBytes(privBytes[:])
   216  
   217  		// Now we'll generate the corresponding tweaked private key.
   218  		tweakedPriv := TweakTaprootPrivKey(*privKey, tweak[:])
   219  
   220  		// The tweaked private key and the original private key should
   221  		// NOT be the same.
   222  		if *privKey == *tweakedPriv {
   223  			t.Logf("private key was mutated")
   224  			return false
   225  		}
   226  
   227  		// We shuold be able to re-derive the private key from raw
   228  		// bytes and have that match up again.
   229  		privKeyCopy, _ := btcec.PrivKeyFromBytes(privBytes[:])
   230  		if *privKey != *privKeyCopy {
   231  			t.Logf("private doesn't match")
   232  			return false
   233  		}
   234  
   235  		return true
   236  	}
   237  
   238  	if err := quick.Check(f, nil); err != nil {
   239  		t.Fatalf("private key modified: %v", err)
   240  	}
   241  }
   242  
   243  // TestTaprootConstructKeyPath tests the key spend only taproot construction.
   244  func TestTaprootConstructKeyPath(t *testing.T) {
   245  	checkPath := func(branch uint32, expectedAddresses []string) {
   246  		path, err := derivePath(rootKey, append(accountPath, branch))
   247  		require.NoError(t, err)
   248  
   249  		for index, expectedAddr := range expectedAddresses {
   250  			extendedKey, err := path.Derive(uint32(index))
   251  			require.NoError(t, err)
   252  
   253  			pubKey, err := extendedKey.ECPubKey()
   254  			require.NoError(t, err)
   255  
   256  			tapKey := ComputeTaprootKeyNoScript(pubKey)
   257  
   258  			addr, err := btcutil.NewAddressTaproot(
   259  				schnorr.SerializePubKey(tapKey),
   260  				&chaincfg.MainNetParams,
   261  			)
   262  			require.NoError(t, err)
   263  
   264  			require.Equal(t, expectedAddr, addr.String())
   265  		}
   266  	}
   267  	checkPath(0, expectedExternalAddresses)
   268  	checkPath(1, expectedInternalAddresses)
   269  }
   270  
   271  func derivePath(key *hdkeychain.ExtendedKey, path []uint32) (
   272  	*hdkeychain.ExtendedKey, error) {
   273  
   274  	var (
   275  		currentKey = key
   276  		err        error
   277  	)
   278  	for _, pathPart := range path {
   279  		currentKey, err = currentKey.Derive(pathPart)
   280  		if err != nil {
   281  			return nil, err
   282  		}
   283  	}
   284  	return currentKey, nil
   285  }
   286  
   287  // TestTapscriptCommitmentVerification that given a valid control block, proof
   288  // we're able to both generate and validate validate script tree leaf inclusion
   289  // proofs.
   290  func TestTapscriptCommitmentVerification(t *testing.T) {
   291  	t.Parallel()
   292  
   293  	// make from 0 to 1 leaf
   294  	// ensure verifies properly
   295  	testCases := []struct {
   296  		numLeaves int
   297  
   298  		valid bool
   299  
   300  		treeMutateFunc func(*IndexedTapScriptTree)
   301  
   302  		ctrlBlockMutateFunc func(*ControlBlock)
   303  	}{
   304  		// A valid merkle proof of a single leaf.
   305  		{
   306  			numLeaves: 1,
   307  			valid:     true,
   308  		},
   309  
   310  		// A valid series of merkle proofs with an odd number of leaves.
   311  		{
   312  			numLeaves: 3,
   313  			valid:     true,
   314  		},
   315  
   316  		// A valid series of merkle proofs with an even number of leaves.
   317  		{
   318  			numLeaves: 4,
   319  			valid:     true,
   320  		},
   321  
   322  		// An invalid merkle proof, we modify the last byte of one of
   323  		// the leaves.
   324  		{
   325  			numLeaves: 4,
   326  			valid:     false,
   327  			treeMutateFunc: func(t *IndexedTapScriptTree) {
   328  				for _, leafProof := range t.LeafMerkleProofs {
   329  					leafProof.InclusionProof[0] ^= 1
   330  				}
   331  			},
   332  		},
   333  
   334  		{
   335  			// An invalid series of proofs, we modify the control
   336  			// block to not match the parity of the final output
   337  			// key commitment.
   338  			numLeaves: 2,
   339  			valid:     false,
   340  			ctrlBlockMutateFunc: func(c *ControlBlock) {
   341  				c.OutputKeyYIsOdd = !c.OutputKeyYIsOdd
   342  			},
   343  		},
   344  	}
   345  	for _, testCase := range testCases {
   346  		testName := fmt.Sprintf("num_leaves=%v, valid=%v, treeMutate=%v, "+
   347  			"ctrlBlockMutate=%v", testCase.numLeaves, testCase.valid,
   348  			testCase.treeMutateFunc == nil, testCase.ctrlBlockMutateFunc == nil)
   349  
   350  		t.Run(testName, func(t *testing.T) {
   351  			tapScriptLeaves := make([]TapLeaf, testCase.numLeaves)
   352  			for i := 0; i < len(tapScriptLeaves); i++ {
   353  				numLeafBytes := prand.Intn(1000)
   354  				scriptBytes := make([]byte, numLeafBytes)
   355  				if _, err := prand.Read(scriptBytes[:]); err != nil {
   356  					t.Fatalf("unable to read rand bytes: %v", err)
   357  				}
   358  				tapScriptLeaves[i] = NewBaseTapLeaf(scriptBytes)
   359  			}
   360  
   361  			scriptTree := AssembleTaprootScriptTree(tapScriptLeaves...)
   362  
   363  			if testCase.treeMutateFunc != nil {
   364  				testCase.treeMutateFunc(scriptTree)
   365  			}
   366  
   367  			internalKey, _ := btcec.NewPrivateKey()
   368  
   369  			rootHash := scriptTree.RootNode.TapHash()
   370  			outputKey := ComputeTaprootOutputKey(
   371  				internalKey.PubKey(), rootHash[:],
   372  			)
   373  
   374  			for _, leafProof := range scriptTree.LeafMerkleProofs {
   375  				ctrlBlock := leafProof.ToControlBlock(
   376  					internalKey.PubKey(),
   377  				)
   378  
   379  				if testCase.ctrlBlockMutateFunc != nil {
   380  					testCase.ctrlBlockMutateFunc(&ctrlBlock)
   381  				}
   382  
   383  				err := VerifyTaprootLeafCommitment(
   384  					&ctrlBlock, schnorr.SerializePubKey(outputKey),
   385  					leafProof.TapLeaf.Script,
   386  				)
   387  				valid := err == nil
   388  
   389  				if valid != testCase.valid {
   390  					t.Fatalf("test case mismatch: expected "+
   391  						"valid=%v, got valid=%v", testCase.valid,
   392  						valid)
   393  				}
   394  			}
   395  
   396  			// TODO(roasbeef): index correctness
   397  		})
   398  	}
   399  }