Fix taproot example to follow the suggestion in BIP341
This commit is contained in:
parent
1f44f722d3
commit
424abf2376
1 changed files with 96 additions and 51 deletions
|
@ -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);
|
||||||
|
});
|
||||||
```
|
```
|
Loading…
Reference in a new issue