PageRenderTime 112ms CodeModel.GetById 44ms RepoModel.GetById 1ms app.codeStats 0ms

/test/integration/taproot.md

https://github.com/bitcoinjs/bitcoinjs-lib
Markdown | 156 lines | 138 code | 18 blank | 0 comment | 0 complexity | 19835a8e3dcbfd5961d7ccdf67366269 MD5 | raw file
  1. # Taproot
  2. A simple keyspend example that is possible with the current API is below.
  3. ## Current state of taproot support
  4. - [x] segwit v1 address support via bech32m
  5. - [x] segwit v1 sighash calculation on Transaction class
  6. ## TODO
  7. - [ ] p2tr payment API to make script spends easier
  8. - [ ] Support within the Psbt class
  9. ## Example
  10. ### Requirements
  11. - npm dependencies
  12. - bitcoinjs-lib v6.x.x
  13. - bip32 v3.x.x
  14. - tiny-secp256k1 v2.x.x
  15. - regtest-client vx.x.x
  16. - local regtest-server docker container running
  17. - `docker run -d -p 8080:8080 junderw/bitcoinjs-regtest-server`
  18. - node >= v14
  19. ```js
  20. // Run this whole file as async
  21. // Catch any errors at the bottom of the file
  22. // and exit the process with 1 error code
  23. (async () => {
  24. // Order of the curve (N) - 1
  25. const N_LESS_1 = Buffer.from(
  26. 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140',
  27. 'hex'
  28. );
  29. // 1 represented as 32 bytes BE
  30. const ONE = Buffer.from(
  31. '0000000000000000000000000000000000000000000000000000000000000001',
  32. 'hex'
  33. );
  34. const crypto = require('crypto');
  35. // bitcoinjs-lib v6
  36. const bitcoin = require('bitcoinjs-lib');
  37. // bip32 v3 wraps tiny-secp256k1
  38. const BIP32Wrapper = require('bip32').default;
  39. const RegtestUtils = require('regtest-client').RegtestUtils;
  40. // tiny-secp256k1 v2 is an ESM module, so we can't "require", and must import async
  41. const ecc = await import('tiny-secp256k1');
  42. // wrap the bip32 library
  43. const bip32 = BIP32Wrapper(ecc);
  44. // set up dependencies
  45. const APIPASS = process.env.APIPASS || 'satoshi';
  46. // docker run -d -p 8080:8080 junderw/bitcoinjs-regtest-server
  47. const APIURL = process.env.APIURL || 'http://127.0.0.1:8080/1';
  48. const regtestUtils = new RegtestUtils({ APIPASS, APIURL });
  49. // End imports
  50. const myKey = bip32.fromSeed(crypto.randomBytes(64), regtestUtils.network);
  51. const output = createKeySpendOutput(myKey.publicKey);
  52. const address = bitcoin.address.fromOutputScript(
  53. output,
  54. regtestUtils.network
  55. );
  56. // amount from faucet
  57. const amount = 42e4;
  58. // amount to send
  59. const sendAmount = amount - 1e4;
  60. // get faucet
  61. const unspent = await regtestUtils.faucetComplex(output, amount);
  62. const tx = createSigned(
  63. myKey,
  64. unspent.txId,
  65. unspent.vout,
  66. sendAmount,
  67. [output],
  68. [amount]
  69. );
  70. const hex = tx.toHex();
  71. console.log('Valid tx sent from:');
  72. console.log(address);
  73. console.log('tx hex:');
  74. console.log(hex);
  75. await regtestUtils.broadcast(hex);
  76. await regtestUtils.verify({
  77. txId: tx.getId(),
  78. address,
  79. vout: 0,
  80. value: sendAmount,
  81. });
  82. // Function for creating a tweaked p2tr key-spend only address
  83. // (This is recommended by BIP341)
  84. function createKeySpendOutput(publicKey) {
  85. // x-only pubkey (remove 1 byte y parity)
  86. const myXOnlyPubkey = publicKey.slice(1, 33);
  87. const commitHash = bitcoin.crypto.taggedHash('TapTweak', myXOnlyPubkey);
  88. const tweakResult = ecc.xOnlyPointAddTweak(myXOnlyPubkey, commitHash);
  89. if (tweakResult === null) throw new Error('Invalid Tweak');
  90. const { xOnlyPubkey: tweaked } = tweakResult;
  91. // scriptPubkey
  92. return Buffer.concat([
  93. // witness v1, PUSH_DATA 32 bytes
  94. Buffer.from([0x51, 0x20]),
  95. // x-only tweaked pubkey
  96. tweaked,
  97. ]);
  98. }
  99. // Function for signing for a tweaked p2tr key-spend only address
  100. // (Required for the above address)
  101. function signTweaked(messageHash, key) {
  102. const privateKey =
  103. key.publicKey[0] === 2
  104. ? key.privateKey
  105. : ecc.privateAdd(ecc.privateSub(N_LESS_1, key.privateKey), ONE);
  106. const tweakHash = bitcoin.crypto.taggedHash(
  107. 'TapTweak',
  108. key.publicKey.slice(1, 33)
  109. );
  110. const newPrivateKey = ecc.privateAdd(privateKey, tweakHash);
  111. if (newPrivateKey === null) throw new Error('Invalid Tweak');
  112. return ecc.signSchnorr(messageHash, newPrivateKey, Buffer.alloc(32));
  113. }
  114. // Function for creating signed tx
  115. function createSigned(key, txid, vout, amountToSend, scriptPubkeys, values) {
  116. const tx = new bitcoin.Transaction();
  117. tx.version = 2;
  118. // Add input
  119. tx.addInput(Buffer.from(txid, 'hex').reverse(), vout);
  120. // Add output
  121. tx.addOutput(scriptPubkeys[0], amountToSend);
  122. const sighash = tx.hashForWitnessV1(
  123. 0, // which input
  124. scriptPubkeys, // All previous outputs of all inputs
  125. values, // All previous values of all inputs
  126. bitcoin.Transaction.SIGHASH_DEFAULT // sighash flag, DEFAULT is schnorr-only (DEFAULT == ALL)
  127. );
  128. const signature = Buffer.from(signTweaked(sighash, key));
  129. // witness stack for keypath spend is just the signature.
  130. // If sighash is not SIGHASH_DEFAULT (ALL) then you must add 1 byte with sighash value
  131. tx.ins[0].witness = [signature];
  132. return tx;
  133. }
  134. })().catch((err) => {
  135. console.error(err);
  136. process.exit(1);
  137. });
  138. ```