@@ -25,68 +25,108 @@ A simple keyspend example that is possible with the current API is below.
25
25
- node >= v14
26
26
27
27
``` 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
+ );
29
43
44
+ const crypto = require (' crypto' );
30
45
// bitcoinjs-lib v6
31
46
const bitcoin = require (' bitcoinjs-lib' );
32
47
// bip32 v3 wraps tiny-secp256k1
33
48
const BIP32Wrapper = require (' bip32' ).default ;
34
49
const RegtestUtils = require (' regtest-client' ).RegtestUtils ;
35
50
// 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
+ }
90
130
91
131
// Function for creating signed tx
92
132
function createSigned (key , txid , vout , amountToSend , scriptPubkeys , values ) {
@@ -102,10 +142,15 @@ function createSigned(key, txid, vout, amountToSend, scriptPubkeys, values) {
102
142
values, // All previous values of all inputs
103
143
bitcoin .Transaction .SIGHASH_DEFAULT // sighash flag, DEFAULT is schnorr-only (DEFAULT == ALL)
104
144
);
105
- const signature = Buffer .from (key . signSchnorr (sighash));
145
+ const signature = Buffer .from (signTweaked (sighash, key ));
106
146
// witness stack for keypath spend is just the signature.
107
147
// If sighash is not SIGHASH_DEFAULT (ALL) then you must add 1 byte with sighash value
108
148
tx .ins [0 ].witness = [signature];
109
149
return tx;
110
150
}
151
+
152
+ })().catch ((err ) => {
153
+ console .error (err);
154
+ process .exit (1 );
155
+ });
111
156
```
0 commit comments