Skip to content

Commit 424abf2

Browse files
committed
Fix taproot example to follow the suggestion in BIP341
1 parent 1f44f72 commit 424abf2

File tree

1 file changed

+101
-56
lines changed

1 file changed

+101
-56
lines changed

test/integration/taproot.md

Lines changed: 101 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -25,68 +25,108 @@ A simple keyspend example that is possible with the current API is below.
2525
- node >= v14
2626

2727
```js
28-
const crypto = require('crypto');
28+
// Run this whole file as async
29+
// Catch any errors at the bottom of the file
30+
// and exit the process with 1 error code
31+
(async () => {
32+
33+
// Order of the curve (N) - 1
34+
const N_LESS_1 = Buffer.from(
35+
'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140',
36+
'hex'
37+
);
38+
// 1 represented as 32 bytes BE
39+
const ONE = Buffer.from(
40+
'0000000000000000000000000000000000000000000000000000000000000001',
41+
'hex'
42+
);
2943

44+
const crypto = require('crypto');
3045
// bitcoinjs-lib v6
3146
const bitcoin = require('bitcoinjs-lib');
3247
// bip32 v3 wraps tiny-secp256k1
3348
const BIP32Wrapper = require('bip32').default;
3449
const RegtestUtils = require('regtest-client').RegtestUtils;
3550
// tiny-secp256k1 v2 is an ESM module, so we can't "require", and must import async
36-
import('tiny-secp256k1')
37-
.then(async (ecc) => {
38-
// End imports
39-
40-
// set up dependencies
41-
const APIPASS = process.env.APIPASS || 'satoshi';
42-
// docker run -d -p 8080:8080 junderw/bitcoinjs-regtest-server
43-
const APIURL = process.env.APIURL || 'http://127.0.0.1:8080/1';
44-
const regtestUtils = new RegtestUtils({ APIPASS, APIURL });
45-
46-
const bip32 = BIP32Wrapper(ecc);
47-
48-
const myKey = bip32.fromSeed(crypto.randomBytes(64), regtestUtils.network);
49-
// scriptPubkey
50-
const output = Buffer.concat([
51-
// witness v1, PUSH_DATA 32 bytes
52-
Buffer.from([0x51, 0x20]),
53-
// x-only pubkey (remove 1 byte y parity)
54-
myKey.publicKey.slice(1, 33),
55-
]);
56-
const address = bitcoin.address.fromOutputScript(
57-
output,
58-
regtestUtils.network
59-
);
60-
// amount from faucet
61-
const amount = 42e4;
62-
// amount to send
63-
const sendAmount = amount - 1e4;
64-
// get faucet
65-
const unspent = await regtestUtils.faucetComplex(output, amount);
66-
67-
const tx = createSigned(
68-
myKey,
69-
unspent.txId,
70-
unspent.vout,
71-
sendAmount,
72-
[output],
73-
[amount]
74-
);
75-
76-
const hex = tx.toHex();
77-
console.log('Valid tx sent from:');
78-
console.log(address);
79-
console.log('tx hex:');
80-
console.log(hex);
81-
await regtestUtils.broadcast(hex);
82-
await regtestUtils.verify({
83-
txId: tx.getId(),
84-
address,
85-
vout: 0,
86-
value: sendAmount,
87-
});
88-
})
89-
.catch(console.error);
51+
const ecc = await import('tiny-secp256k1');
52+
// wrap the bip32 library
53+
const bip32 = BIP32Wrapper(ecc);
54+
// set up dependencies
55+
const APIPASS = process.env.APIPASS || 'satoshi';
56+
// docker run -d -p 8080:8080 junderw/bitcoinjs-regtest-server
57+
const APIURL = process.env.APIURL || 'http://127.0.0.1:8080/1';
58+
const regtestUtils = new RegtestUtils({ APIPASS, APIURL });
59+
// End imports
60+
61+
const myKey = bip32.fromSeed(crypto.randomBytes(64), regtestUtils.network);
62+
63+
const output = createKeySpendOutput(myKey.publicKey);
64+
const address = bitcoin.address.fromOutputScript(
65+
output,
66+
regtestUtils.network
67+
);
68+
// amount from faucet
69+
const amount = 42e4;
70+
// amount to send
71+
const sendAmount = amount - 1e4;
72+
// get faucet
73+
const unspent = await regtestUtils.faucetComplex(output, amount);
74+
75+
const tx = createSigned(
76+
myKey,
77+
unspent.txId,
78+
unspent.vout,
79+
sendAmount,
80+
[output],
81+
[amount]
82+
);
83+
84+
const hex = tx.toHex();
85+
console.log('Valid tx sent from:');
86+
console.log(address);
87+
console.log('tx hex:');
88+
console.log(hex);
89+
await regtestUtils.broadcast(hex);
90+
await regtestUtils.verify({
91+
txId: tx.getId(),
92+
address,
93+
vout: 0,
94+
value: sendAmount,
95+
});
96+
97+
// Function for creating a tweaked p2tr key-spend only address
98+
// (This is recommended by BIP341)
99+
function createKeySpendOutput(publicKey) {
100+
// x-only pubkey (remove 1 byte y parity)
101+
const myXOnlyPubkey = publicKey.slice(1, 33);
102+
const commitHash = bitcoin.crypto.taggedHash('TapTweak', myXOnlyPubkey);
103+
const tweakResult = ecc.xOnlyPointAddTweak(myXOnlyPubkey, commitHash);
104+
if (tweakResult === null) throw new Error('Invalid Tweak');
105+
const { xOnlyPubkey: tweaked } = tweakResult;
106+
// scriptPubkey
107+
return Buffer.concat([
108+
// witness v1, PUSH_DATA 32 bytes
109+
Buffer.from([0x51, 0x20]),
110+
// x-only tweaked pubkey
111+
tweaked,
112+
]);
113+
}
114+
115+
// Function for signing for a tweaked p2tr key-spend only address
116+
// (Required for the above address)
117+
function signTweaked(messageHash, key) {
118+
const privateKey =
119+
key.publicKey[0] === 2
120+
? key.privateKey
121+
: ecc.privateAdd(ecc.privateSub(N_LESS_1, key.privateKey), ONE);
122+
const tweakHash = bitcoin.crypto.taggedHash(
123+
'TapTweak',
124+
key.publicKey.slice(1, 33)
125+
);
126+
const newPrivateKey = ecc.privateAdd(privateKey, tweakHash);
127+
if (newPrivateKey === null) throw new Error('Invalid Tweak');
128+
return ecc.signSchnorr(messageHash, newPrivateKey, Buffer.alloc(32));
129+
}
90130

91131
// Function for creating signed tx
92132
function createSigned(key, txid, vout, amountToSend, scriptPubkeys, values) {
@@ -102,10 +142,15 @@ function createSigned(key, txid, vout, amountToSend, scriptPubkeys, values) {
102142
values, // All previous values of all inputs
103143
bitcoin.Transaction.SIGHASH_DEFAULT // sighash flag, DEFAULT is schnorr-only (DEFAULT == ALL)
104144
);
105-
const signature = Buffer.from(key.signSchnorr(sighash));
145+
const signature = Buffer.from(signTweaked(sighash, key));
106146
// witness stack for keypath spend is just the signature.
107147
// If sighash is not SIGHASH_DEFAULT (ALL) then you must add 1 byte with sighash value
108148
tx.ins[0].witness = [signature];
109149
return tx;
110150
}
151+
152+
})().catch((err) => {
153+
console.error(err);
154+
process.exit(1);
155+
});
111156
```

0 commit comments

Comments
 (0)