111 lines
3.1 KiB
Markdown
111 lines
3.1 KiB
Markdown
|
# Taproot
|
||
|
|
||
|
A simple keyspend example that is possible with the current API is below.
|
||
|
|
||
|
## Current state of taproot support
|
||
|
|
||
|
- [x] segwit v1 address support via bech32m
|
||
|
- [x] segwit v1 sighash calculation on Transaction class
|
||
|
|
||
|
## TODO
|
||
|
|
||
|
- [ ] p2tr payment API to make script spends easier
|
||
|
- [ ] Support within the Psbt class
|
||
|
|
||
|
## Example
|
||
|
|
||
|
### Requirements
|
||
|
- npm dependencies
|
||
|
- bitcoinjs-lib v6.x.x
|
||
|
- bip32 v3.x.x
|
||
|
- tiny-secp256k1 v2.x.x
|
||
|
- regtest-client vx.x.x
|
||
|
- local regtest-server docker container running
|
||
|
- `docker run -d -p 8080:8080 junderw/bitcoinjs-regtest-server`
|
||
|
- node >= v14
|
||
|
|
||
|
```js
|
||
|
const crypto = require('crypto');
|
||
|
|
||
|
// bitcoinjs-lib v6
|
||
|
const bitcoin = require('bitcoinjs-lib');
|
||
|
// bip32 v3 wraps tiny-secp256k1
|
||
|
const BIP32Wrapper = require('bip32').default;
|
||
|
const RegtestUtils = require('regtest-client').RegtestUtils;
|
||
|
// tiny-secp256k1 v2 is an ESM module, so we can't "require", and must import async
|
||
|
import('tiny-secp256k1')
|
||
|
.then(async (ecc) => {
|
||
|
// End imports
|
||
|
|
||
|
// 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 });
|
||
|
|
||
|
const bip32 = BIP32Wrapper(ecc);
|
||
|
|
||
|
const myKey = bip32.fromSeed(crypto.randomBytes(64), regtestUtils.network);
|
||
|
// scriptPubkey
|
||
|
const output = Buffer.concat([
|
||
|
// witness v1, PUSH_DATA 32 bytes
|
||
|
Buffer.from([0x51, 0x20]),
|
||
|
// x-only pubkey (remove 1 byte y parity)
|
||
|
myKey.publicKey.slice(1, 33),
|
||
|
]);
|
||
|
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(
|
||
|
myKey,
|
||
|
unspent.txId,
|
||
|
unspent.vout,
|
||
|
sendAmount,
|
||
|
[output],
|
||
|
[amount]
|
||
|
);
|
||
|
|
||
|
const hex = tx.toHex();
|
||
|
console.log('Valid tx sent from:');
|
||
|
console.log(address);
|
||
|
console.log('tx hex:');
|
||
|
console.log(hex);
|
||
|
await regtestUtils.broadcast(hex);
|
||
|
await regtestUtils.verify({
|
||
|
txId: tx.getId(),
|
||
|
address,
|
||
|
vout: 0,
|
||
|
value: sendAmount,
|
||
|
});
|
||
|
})
|
||
|
.catch(console.error);
|
||
|
|
||
|
// Function for creating signed tx
|
||
|
function createSigned(key, txid, vout, amountToSend, scriptPubkeys, values) {
|
||
|
const tx = new bitcoin.Transaction();
|
||
|
tx.version = 2;
|
||
|
// Add input
|
||
|
tx.addInput(Buffer.from(txid, 'hex').reverse(), vout);
|
||
|
// Add output
|
||
|
tx.addOutput(scriptPubkeys[0], amountToSend);
|
||
|
const sighash = tx.hashForWitnessV1(
|
||
|
0, // which input
|
||
|
scriptPubkeys, // All previous outputs of all inputs
|
||
|
values, // All previous values of all inputs
|
||
|
bitcoin.Transaction.SIGHASH_DEFAULT // sighash flag, DEFAULT is schnorr-only (DEFAULT == ALL)
|
||
|
);
|
||
|
const signature = Buffer.from(key.signSchnorr(sighash));
|
||
|
// 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
|
||
|
tx.ins[0].witness = [signature];
|
||
|
return tx;
|
||
|
}
|
||
|
```
|