@@ -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
3146const bitcoin = require (' bitcoinjs-lib' );
3247// bip32 v3 wraps tiny-secp256k1
3348const BIP32Wrapper = require (' bip32' ).default ;
3449const 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
92132function 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