Fix taproot example to follow the suggestion in BIP341

This commit is contained in:
junderw 2021-11-15 08:25:22 +09:00
parent 1f44f722d3
commit 424abf2376
No known key found for this signature in database
GPG key ID: B256185D3A971908

View file

@ -25,68 +25,108 @@ A simple keyspend example that is possible with the current API is below.
- node >= v14 - node >= v14
```js ```js
const crypto = require('crypto'); // Run this whole file as async
// Catch any errors at the bottom of the file
// and exit the process with 1 error code
(async () => {
// Order of the curve (N) - 1
const N_LESS_1 = Buffer.from(
'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140',
'hex'
);
// 1 represented as 32 bytes BE
const ONE = Buffer.from(
'0000000000000000000000000000000000000000000000000000000000000001',
'hex'
);
const crypto = require('crypto');
// bitcoinjs-lib v6 // bitcoinjs-lib v6
const bitcoin = require('bitcoinjs-lib'); const bitcoin = require('bitcoinjs-lib');
// bip32 v3 wraps tiny-secp256k1 // bip32 v3 wraps tiny-secp256k1
const BIP32Wrapper = require('bip32').default; const BIP32Wrapper = require('bip32').default;
const RegtestUtils = require('regtest-client').RegtestUtils; const RegtestUtils = require('regtest-client').RegtestUtils;
// tiny-secp256k1 v2 is an ESM module, so we can't "require", and must import async // tiny-secp256k1 v2 is an ESM module, so we can't "require", and must import async
import('tiny-secp256k1') const ecc = await import('tiny-secp256k1');
.then(async (ecc) => { // wrap the bip32 library
// End imports const bip32 = BIP32Wrapper(ecc);
// set up dependencies
const APIPASS = process.env.APIPASS || 'satoshi';
// docker run -d -p 8080:8080 junderw/bitcoinjs-regtest-server
const APIURL = process.env.APIURL || 'http://127.0.0.1:8080/1';
const regtestUtils = new RegtestUtils({ APIPASS, APIURL });
// End imports
// set up dependencies const myKey = bip32.fromSeed(crypto.randomBytes(64), regtestUtils.network);
const APIPASS = process.env.APIPASS || 'satoshi';
// docker run -d -p 8080:8080 junderw/bitcoinjs-regtest-server
const APIURL = process.env.APIURL || 'http://127.0.0.1:8080/1';
const regtestUtils = new RegtestUtils({ APIPASS, APIURL });
const bip32 = BIP32Wrapper(ecc); const output = createKeySpendOutput(myKey.publicKey);
const address = bitcoin.address.fromOutputScript(
output,
regtestUtils.network
);
// amount from faucet
const amount = 42e4;
// amount to send
const sendAmount = amount - 1e4;
// get faucet
const unspent = await regtestUtils.faucetComplex(output, amount);
const myKey = bip32.fromSeed(crypto.randomBytes(64), regtestUtils.network); const tx = createSigned(
// scriptPubkey myKey,
const output = Buffer.concat([ unspent.txId,
// witness v1, PUSH_DATA 32 bytes unspent.vout,
Buffer.from([0x51, 0x20]), sendAmount,
// x-only pubkey (remove 1 byte y parity) [output],
myKey.publicKey.slice(1, 33), [amount]
]); );
const address = bitcoin.address.fromOutputScript(
output,
regtestUtils.network
);
// amount from faucet
const amount = 42e4;
// amount to send
const sendAmount = amount - 1e4;
// get faucet
const unspent = await regtestUtils.faucetComplex(output, amount);
const tx = createSigned( const hex = tx.toHex();
myKey, console.log('Valid tx sent from:');
unspent.txId, console.log(address);
unspent.vout, console.log('tx hex:');
sendAmount, console.log(hex);
[output], await regtestUtils.broadcast(hex);
[amount] await regtestUtils.verify({
); txId: tx.getId(),
address,
vout: 0,
value: sendAmount,
});
const hex = tx.toHex(); // Function for creating a tweaked p2tr key-spend only address
console.log('Valid tx sent from:'); // (This is recommended by BIP341)
console.log(address); function createKeySpendOutput(publicKey) {
console.log('tx hex:'); // x-only pubkey (remove 1 byte y parity)
console.log(hex); const myXOnlyPubkey = publicKey.slice(1, 33);
await regtestUtils.broadcast(hex); const commitHash = bitcoin.crypto.taggedHash('TapTweak', myXOnlyPubkey);
await regtestUtils.verify({ const tweakResult = ecc.xOnlyPointAddTweak(myXOnlyPubkey, commitHash);
txId: tx.getId(), if (tweakResult === null) throw new Error('Invalid Tweak');
address, const { xOnlyPubkey: tweaked } = tweakResult;
vout: 0, // scriptPubkey
value: sendAmount, return Buffer.concat([
}); // witness v1, PUSH_DATA 32 bytes
}) Buffer.from([0x51, 0x20]),
.catch(console.error); // x-only tweaked pubkey
tweaked,
]);
}
// Function for signing for a tweaked p2tr key-spend only address
// (Required for the above address)
function signTweaked(messageHash, key) {
const privateKey =
key.publicKey[0] === 2
? key.privateKey
: ecc.privateAdd(ecc.privateSub(N_LESS_1, key.privateKey), ONE);
const tweakHash = bitcoin.crypto.taggedHash(
'TapTweak',
key.publicKey.slice(1, 33)
);
const newPrivateKey = ecc.privateAdd(privateKey, tweakHash);
if (newPrivateKey === null) throw new Error('Invalid Tweak');
return ecc.signSchnorr(messageHash, newPrivateKey, Buffer.alloc(32));
}
// Function for creating signed tx // Function for creating signed tx
function createSigned(key, txid, vout, amountToSend, scriptPubkeys, values) { function createSigned(key, txid, vout, amountToSend, scriptPubkeys, values) {
@ -102,10 +142,15 @@ function createSigned(key, txid, vout, amountToSend, scriptPubkeys, values) {
values, // All previous values of all inputs values, // All previous values of all inputs
bitcoin.Transaction.SIGHASH_DEFAULT // sighash flag, DEFAULT is schnorr-only (DEFAULT == ALL) bitcoin.Transaction.SIGHASH_DEFAULT // sighash flag, DEFAULT is schnorr-only (DEFAULT == ALL)
); );
const signature = Buffer.from(key.signSchnorr(sighash)); const signature = Buffer.from(signTweaked(sighash, key));
// witness stack for keypath spend is just the signature. // witness stack for keypath spend is just the signature.
// If sighash is not SIGHASH_DEFAULT (ALL) then you must add 1 byte with sighash value // If sighash is not SIGHASH_DEFAULT (ALL) then you must add 1 byte with sighash value
tx.ins[0].witness = [signature]; tx.ins[0].witness = [signature];
return tx; return tx;
} }
})().catch((err) => {
console.error(err);
process.exit(1);
});
``` ```