1
+ import * as assert from 'assert' ;
1
2
import BIP32Factory from 'bip32' ;
3
+ import * as bip39 from 'bip39' ;
2
4
import ECPairFactory from 'ecpair' ;
3
5
import * as ecc from 'tiny-secp256k1' ;
4
6
import { describe , it } from 'mocha' ;
@@ -17,6 +19,78 @@ const bip32 = BIP32Factory(ecc);
17
19
const ECPair = ECPairFactory ( ecc ) ;
18
20
19
21
describe ( '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
+
20
94
it ( 'can create (and broadcast via 3PBP) a taproot key-path spend Transaction' , async ( ) => {
21
95
const internalKey = bip32 . fromSeed ( rng ( 64 ) , regtest ) ;
22
96
const p2pkhKey = bip32 . fromSeed ( rng ( 64 ) , regtest ) ;
0 commit comments