1+ import * as assert from 'assert' ;
12import BIP32Factory from 'bip32' ;
3+ import * as bip39 from 'bip39' ;
24import ECPairFactory from 'ecpair' ;
35import * as ecc from 'tiny-secp256k1' ;
46import { describe , it } from 'mocha' ;
@@ -17,6 +19,78 @@ const bip32 = BIP32Factory(ecc);
1719const ECPair = ECPairFactory ( ecc ) ;
1820
1921describe ( 'bitcoinjs-lib (transaction with taproot)' , ( ) => {
22+ it ( 'can verify the BIP86 HD wallet vectors for taproot single sig (& sending example)' , async ( ) => {
23+ // Values taken from BIP86 document
24+ const mnemonic =
25+ 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' ;
26+ const xprv =
27+ 'xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu' ;
28+ const path = `m/86'/0'/0'/0/0` ; // Path to first child of receiving wallet on first account
29+ const internalPubkey = Buffer . from (
30+ 'cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115' ,
31+ 'hex' ,
32+ ) ;
33+ const expectedAddress =
34+ 'bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr' ;
35+
36+ // Verify the above (Below is no different than other HD wallets)
37+ const seed = await bip39 . mnemonicToSeed ( mnemonic ) ;
38+ const rootKey = bip32 . fromSeed ( seed ) ;
39+ assert . strictEqual ( rootKey . toBase58 ( ) , xprv ) ;
40+ const childNode = rootKey . derivePath ( path ) ;
41+ // Since internalKey is an xOnly pubkey, we drop the DER header byte
42+ const childNodeXOnlyPubkey = childNode . publicKey . slice ( 1 , 33 ) ;
43+ assert . deepEqual ( childNodeXOnlyPubkey , internalPubkey ) ;
44+
45+ // This is new for taproot
46+ // Note: we are using mainnet here to get the correct address
47+ // The output is the same no matter what the network is.
48+ const { address, output } = bitcoin . payments . p2tr ( {
49+ internalPubkey,
50+ } ) ;
51+ assert ( output ) ;
52+ assert . strictEqual ( address , expectedAddress ) ;
53+ // Used for signing, since the output and address are using a tweaked key
54+ // We must tweak the signer in the same way.
55+ const tweakedChildNode = childNode . tweak (
56+ bitcoin . crypto . taggedHash ( 'TapTweak' , childNodeXOnlyPubkey ) ,
57+ ) ;
58+
59+ // amount from faucet
60+ const amount = 42e4 ;
61+ // amount to send
62+ const sendAmount = amount - 1e4 ;
63+ // Send some sats to the address via faucet. Get the hash and index. (txid/vout)
64+ const { txId : hash , vout : index } = await regtestUtils . faucetComplex (
65+ output ,
66+ amount ,
67+ ) ;
68+ // Sent 420000 sats to taproot address
69+
70+ const psbt = new bitcoin . Psbt ( { network : regtest } )
71+ . addInput ( {
72+ hash,
73+ index,
74+ witnessUtxo : { value : amount , script : output } ,
75+ tapInternalKey : childNodeXOnlyPubkey ,
76+ } )
77+ . addOutput ( {
78+ value : sendAmount ,
79+ address : regtestUtils . RANDOM_ADDRESS ,
80+ } )
81+ . signInput ( 0 , tweakedChildNode )
82+ . finalizeAllInputs ( ) ;
83+
84+ const tx = psbt . extractTransaction ( ) ;
85+ await regtestUtils . broadcast ( tx . toHex ( ) ) ;
86+ await regtestUtils . verify ( {
87+ txId : tx . getId ( ) ,
88+ address : regtestUtils . RANDOM_ADDRESS ,
89+ vout : 0 ,
90+ value : sendAmount ,
91+ } ) ;
92+ } ) ;
93+
2094 it ( 'can create (and broadcast via 3PBP) a taproot key-path spend Transaction' , async ( ) => {
2195 const internalKey = bip32 . fromSeed ( rng ( 64 ) , regtest ) ;
2296 const p2pkhKey = bip32 . fromSeed ( rng ( 64 ) , regtest ) ;
0 commit comments