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 }