@@ -10,6 +10,7 @@ import (
1010 "github.com/btcsuite/btcd/blockchain"
1111 "github.com/btcsuite/btcd/btcec/v2"
1212 "github.com/btcsuite/btcd/btcec/v2/schnorr"
13+ "github.com/btcsuite/btcd/btcutil/hdkeychain"
1314 "github.com/btcsuite/btcd/chaincfg/chainhash"
1415 "github.com/btcsuite/btcd/txscript"
1516 "github.com/btcsuite/btcd/wire"
@@ -1296,3 +1297,217 @@ func TestCopySpendTemplate(t *testing.T) {
12961297
12971298 require .True (t , newAsset .DeepEqual (spendTemplate ))
12981299}
1300+
1301+ // TestExternalKeyPubKey tests that the public key can be derived from an
1302+ // external key.
1303+ func TestExternalKeyPubKey (t * testing.T ) {
1304+ t .Parallel ()
1305+
1306+ dummyXPub := func () hdkeychain.ExtendedKey {
1307+ xpubStr := "xpub6BynCcnXLYNnnMUZARkHxbP9pG6h5rES8Zb8aHtGwmFX" +
1308+ "9DdjJiyT9PNwkSMZfS3CvGRpvV21SkLRM6xhtshvA3DnJbQsvjD" +
1309+ "yySWGArynQNf"
1310+ xpub , err := hdkeychain .NewKeyFromString (xpubStr )
1311+ require .NoError (t , err , "failed to create xpub from string" )
1312+ return * xpub
1313+ }
1314+
1315+ dummyXPubTestnet := func () hdkeychain.ExtendedKey {
1316+ xpubStr := "tpubDDfTBtwwqxXuCej7pKYfbXeCW3inAtv1cw4knmvYTTHk" +
1317+ "w3NoKaeCNH5XdY6n6fnBPc1gWEgeurfmBVzJLfBB1hGU64LsHFz" +
1318+ "Jv4ASqaHyALH"
1319+ xpub , err := hdkeychain .NewKeyFromString (xpubStr )
1320+ require .NoError (t , err , "failed to create xpub from string" )
1321+ return * xpub
1322+ }
1323+
1324+ testCases := []struct {
1325+ name string
1326+ externalKey ExternalKey
1327+ expectedPubKey string
1328+ expectError bool
1329+ expectedError string
1330+ }{
1331+ {
1332+ name : "valid BIP-86 external key" ,
1333+ externalKey : ExternalKey {
1334+ XPub : dummyXPub (),
1335+ MasterFingerprint : 0x12345678 ,
1336+ DerivationPath : []uint32 {
1337+ 86 + hdkeychain .HardenedKeyStart ,
1338+ 0 + hdkeychain .HardenedKeyStart ,
1339+ 0 + hdkeychain .HardenedKeyStart , 0 , 0 ,
1340+ },
1341+ },
1342+ expectError : false ,
1343+
1344+ // The pubkey was generated with "chantools derivekey
1345+ // --rootkey xpub... --path "m/0/0" --neuter" command.
1346+ expectedPubKey : "02c0ca6c5d4dc4899de975f17f1023e424a" +
1347+ "93a7ba6339cbaf514689f75d51787cc" ,
1348+ },
1349+ {
1350+ name : "invalid derivation path length" ,
1351+ externalKey : ExternalKey {
1352+ XPub : dummyXPub (),
1353+ MasterFingerprint : 0x12345678 ,
1354+ DerivationPath : []uint32 {
1355+ 86 + hdkeychain .HardenedKeyStart ,
1356+ 0 + hdkeychain .HardenedKeyStart ,
1357+ 0 + hdkeychain .HardenedKeyStart ,
1358+ },
1359+ },
1360+ expectError : true ,
1361+ expectedError : "derivation path must have exactly 5 " +
1362+ "components" ,
1363+ },
1364+ {
1365+ name : "invalid BIP-86 derivation path" ,
1366+ externalKey : ExternalKey {
1367+ XPub : dummyXPub (),
1368+ MasterFingerprint : 0x12345678 ,
1369+ DerivationPath : []uint32 {
1370+ 44 + hdkeychain .HardenedKeyStart ,
1371+ 0 + hdkeychain .HardenedKeyStart ,
1372+ 0 + hdkeychain .HardenedKeyStart , 0 , 0 ,
1373+ },
1374+ },
1375+ expectError : true ,
1376+ expectedError : "xpub must be derived from BIP-0086 " +
1377+ "(Taproot) derivation path" ,
1378+ },
1379+ {
1380+ name : "valid BIP-86 external key, custom coin_type" ,
1381+ externalKey : ExternalKey {
1382+ XPub : dummyXPub (),
1383+ MasterFingerprint : 0x12345678 ,
1384+ DerivationPath : []uint32 {
1385+ 86 + hdkeychain .HardenedKeyStart ,
1386+ 42 + hdkeychain .HardenedKeyStart ,
1387+ 0 + hdkeychain .HardenedKeyStart , 0 , 0 ,
1388+ },
1389+ },
1390+ expectError : false ,
1391+
1392+ // The pubkey was generated with "chantools derivekey
1393+ // --rootkey xpub... --path m/0/0 --neuter" command.
1394+ expectedPubKey : "02c0ca6c5d4dc4899de975f17f1023e424a" +
1395+ "93a7ba6339cbaf514689f75d51787cc" ,
1396+ },
1397+ {
1398+ name : "valid BIP-86 external key, custom account" ,
1399+ externalKey : ExternalKey {
1400+ XPub : dummyXPub (),
1401+ MasterFingerprint : 0x12345678 ,
1402+ DerivationPath : []uint32 {
1403+ 86 + hdkeychain .HardenedKeyStart ,
1404+ 0 + hdkeychain .HardenedKeyStart ,
1405+ 42 + hdkeychain .HardenedKeyStart , 0 , 0 ,
1406+ },
1407+ },
1408+ expectError : false ,
1409+
1410+ // The pubkey was generated with "chantools derivekey
1411+ // --rootkey xpub... --path m/0/0 --neuter" command.
1412+ expectedPubKey : "02c0ca6c5d4dc4899de975f17f1023e424a" +
1413+ "93a7ba6339cbaf514689f75d51787cc" ,
1414+ },
1415+ {
1416+ name : "valid BIP-86 external key, change output" ,
1417+ externalKey : ExternalKey {
1418+ XPub : dummyXPub (),
1419+ MasterFingerprint : 0x12345678 ,
1420+ DerivationPath : []uint32 {
1421+ 86 + hdkeychain .HardenedKeyStart ,
1422+ 0 + hdkeychain .HardenedKeyStart ,
1423+ 0 + hdkeychain .HardenedKeyStart , 1 , 0 ,
1424+ },
1425+ },
1426+ expectError : false ,
1427+
1428+ // The pubkey was generated with "chantools derivekey
1429+ // --rootkey xpub... --path m/1/0 --neuter" command.
1430+ expectedPubKey : "02ce0e73519634aaf1a34cc17afb517a697" +
1431+ "95c063386030f1b1b724410a84aa709" ,
1432+ },
1433+ {
1434+ name : "valid BIP-86 external key, change=2" ,
1435+ externalKey : ExternalKey {
1436+ XPub : dummyXPub (),
1437+ MasterFingerprint : 0x12345678 ,
1438+ DerivationPath : []uint32 {
1439+ 86 + hdkeychain .HardenedKeyStart ,
1440+ 0 + hdkeychain .HardenedKeyStart ,
1441+ 0 + hdkeychain .HardenedKeyStart , 2 , 0 ,
1442+ },
1443+ },
1444+ expectError : false ,
1445+
1446+ // The pubkey was generated with "chantools derivekey
1447+ // --rootkey xpub... --path m/2/0 --neuter" command.
1448+ expectedPubKey : "0278b9669141d21f0598cc44a427c5d03a3" +
1449+ "5d6aaed5555931a99a1659dfea4ebcf" ,
1450+ },
1451+ {
1452+ name : "valid BIP-86 external key, index=2" ,
1453+ externalKey : ExternalKey {
1454+ XPub : dummyXPub (),
1455+ MasterFingerprint : 0x12345678 ,
1456+ DerivationPath : []uint32 {
1457+ 86 + hdkeychain .HardenedKeyStart ,
1458+ 0 + hdkeychain .HardenedKeyStart ,
1459+ 0 + hdkeychain .HardenedKeyStart , 0 , 2 ,
1460+ },
1461+ },
1462+ expectError : false ,
1463+
1464+ // The pubkey was generated with "chantools derivekey
1465+ // --rootkey xpub... --path "m/0/2" --neuter" command.
1466+ expectedPubKey : "0375e49d472c25d1138a5526b9b7a0198e1" +
1467+ "d692cc3fd0133f260aca446e1244ff9" ,
1468+ },
1469+ {
1470+ name : "valid BIP-86 external key, testnet" ,
1471+ externalKey : ExternalKey {
1472+ XPub : dummyXPubTestnet (),
1473+ MasterFingerprint : 0x12345678 ,
1474+ DerivationPath : []uint32 {
1475+ 86 + hdkeychain .HardenedKeyStart ,
1476+ 1 + hdkeychain .HardenedKeyStart ,
1477+ 0 + hdkeychain .HardenedKeyStart , 0 , 0 ,
1478+ },
1479+ },
1480+ expectError : false ,
1481+
1482+ // The pubkey was generated with "chantools derivekey
1483+ // --testnet --rootkey xpub... --path "m/0/0" --neuter".
1484+ expectedPubKey : "0280a3fcbeb7f770af6dd45cb0f4d02e104" +
1485+ "4eafe0d8b05bcaec79dc0478c7fa0da" ,
1486+ },
1487+ }
1488+
1489+ for _ , tc := range testCases {
1490+ t .Run (tc .name , func (tt * testing.T ) {
1491+ pubKey , err := tc .externalKey .PubKey ()
1492+
1493+ if tc .expectError {
1494+ require .Error (tt , err , tc .name )
1495+ if tc .expectedError != "" {
1496+ require .Contains (
1497+ tt , err .Error (),
1498+ tc .expectedError ,
1499+ )
1500+ }
1501+
1502+ return
1503+ }
1504+
1505+ require .NoError (tt , err )
1506+ require .IsType (tt , btcec.PublicKey {}, pubKey )
1507+ pubKeyHex := hex .EncodeToString (
1508+ pubKey .SerializeCompressed (),
1509+ )
1510+ require .Equal (tt , tc .expectedPubKey , pubKeyHex )
1511+ })
1512+ }
1513+ }
0 commit comments