Skip to content

Commit f1a4b9d

Browse files
committed
Add example using BIP86 vector to verify the sending to and from a BIP86 generated taproot address
1 parent 02b112e commit f1a4b9d

File tree

1 file changed

+74
-0
lines changed

1 file changed

+74
-0
lines changed

test/integration/taproot.spec.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import * as assert from 'assert';
12
import BIP32Factory from 'bip32';
3+
import * as bip39 from 'bip39';
24
import ECPairFactory from 'ecpair';
35
import * as ecc from 'tiny-secp256k1';
46
import { describe, it } from 'mocha';
@@ -17,6 +19,78 @@ const bip32 = BIP32Factory(ecc);
1719
const ECPair = ECPairFactory(ecc);
1820

1921
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+
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

Comments
 (0)