@scure/btc-signer 2.0.1 โ 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +334 -64
- package/index.d.ts +15 -6
- package/index.js +16 -7
- package/musig2.d.ts +212 -69
- package/musig2.js +352 -99
- package/net.d.ts +355 -0
- package/net.js +875 -0
- package/p2p.d.ts +17 -8
- package/p2p.js +63 -11
- package/package.json +17 -17
- package/payment.d.ts +406 -41
- package/payment.js +570 -69
- package/psbt.d.ts +2958 -560
- package/psbt.js +475 -119
- package/script.d.ts +311 -133
- package/script.js +313 -90
- package/src/_type_test.ts +69 -0
- package/src/index.ts +34 -11
- package/src/musig2.ts +424 -155
- package/src/net.ts +1106 -0
- package/src/p2p.ts +76 -24
- package/src/payment.ts +882 -235
- package/src/psbt.ts +648 -229
- package/src/script.ts +397 -139
- package/src/transaction.ts +667 -196
- package/src/utils.ts +392 -47
- package/src/utxo.ts +182 -83
- package/transaction.d.ts +242 -32
- package/transaction.js +531 -121
- package/utils.d.ts +296 -25
- package/utils.js +337 -30
- package/utxo.d.ts +438 -76
- package/utxo.js +150 -59
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/musig2.d.ts.map +0 -1
- package/musig2.js.map +0 -1
- package/p2p.d.ts.map +0 -1
- package/p2p.js.map +0 -1
- package/payment.d.ts.map +0 -1
- package/payment.js.map +0 -1
- package/psbt.d.ts.map +0 -1
- package/psbt.js.map +0 -1
- package/script.d.ts.map +0 -1
- package/script.js.map +0 -1
- package/transaction.d.ts.map +0 -1
- package/transaction.js.map +0 -1
- package/utils.d.ts.map +0 -1
- package/utils.js.map +0 -1
- package/utxo.d.ts.map +0 -1
- package/utxo.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Audited & minimal library for creating, signing & decoding Bitcoin transactions.
|
|
|
4
4
|
|
|
5
5
|
- ๐ [**Audited**](#security) by an independent security firm
|
|
6
6
|
- โ๏ธ Create transactions, inputs, outputs, sign them
|
|
7
|
-
- ๐ก
|
|
7
|
+
- ๐ก Optional network helper; core signer works offline
|
|
8
8
|
- ๐ UTXO selection with different strategies
|
|
9
9
|
- ๐ป Classic & SegWit: P2PK, P2PKH, P2WPKH, P2SH, P2WSH, P2MS
|
|
10
10
|
- ๐งช Schnorr & Taproot BIP340/BIP341: P2TR, P2TR-NS, P2TR-MS
|
|
@@ -49,6 +49,40 @@ For React Native, you may need a [polyfill for crypto.getRandomValues](https://g
|
|
|
49
49
|
import * as btc from '@scure/btc-signer';
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
### Quickstart
|
|
53
|
+
|
|
54
|
+
Create a Taproot address, build a transaction spending one of its UTXOs, sign and finalize it:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import * as btc from '@scure/btc-signer';
|
|
58
|
+
import { hex } from '@scure/base';
|
|
59
|
+
|
|
60
|
+
const privKey = hex.decode('a1547d0a01c9acb2b8a4128f97bbcd74d9a5750a9a3d1571ee3d9840b41d1fbb');
|
|
61
|
+
const pubKey = btc.utils.pubSchnorr(privKey);
|
|
62
|
+
|
|
63
|
+
// Taproot payment for a single key. Also check out p2wpkh, p2sh, p2wsh & others below
|
|
64
|
+
const spend = btc.p2tr(pubKey);
|
|
65
|
+
console.log(spend.address);
|
|
66
|
+
// bc1pkw4e67whvet7q6xa854tstt7kjn9f7n3gqvyjqznezqwux0gnzhsrtlvy3
|
|
67
|
+
|
|
68
|
+
const tx = new btc.Transaction();
|
|
69
|
+
tx.addInput({
|
|
70
|
+
...spend, // adds tapInternalKey & other fields required for signing
|
|
71
|
+
txid: '75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858',
|
|
72
|
+
index: 0,
|
|
73
|
+
witnessUtxo: { script: spend.script, amount: 100_000n }, // amounts are always bigint sats
|
|
74
|
+
});
|
|
75
|
+
tx.addOutputAddress(spend.address!, 90_000n); // leftover 10k sats become the fee
|
|
76
|
+
tx.sign(privKey);
|
|
77
|
+
tx.finalize();
|
|
78
|
+
|
|
79
|
+
console.log(tx.id);
|
|
80
|
+
// 927bc3441c7b376a83913b534bf10430139d9f64d83cbc38eb3a4e24cce54dc5
|
|
81
|
+
console.log(tx.hex); // ready to broadcast, e.g. via net.ts EsploraProvider
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The rest of the docs cover every payment type, PSBT workflows, UTXO selection and more:
|
|
85
|
+
|
|
52
86
|
- [Payments](#payments)
|
|
53
87
|
- [P2PK Pay To Public Key](#p2pk-pay-to-public-key)
|
|
54
88
|
- [P2PKH Public Key Hash](#p2pkh-public-key-hash)
|
|
@@ -69,6 +103,7 @@ import * as btc from '@scure/btc-signer';
|
|
|
69
103
|
- [Basic transaction sign](#basic-transaction-sign)
|
|
70
104
|
- [BIP174 PSBT multi-sig example](#bip174-psbt-multi-sig-example)
|
|
71
105
|
- [UTXO selection](#utxo-selection)
|
|
106
|
+
- [Network](#network)
|
|
72
107
|
- [MuSig2](#musig2)
|
|
73
108
|
- [P2P, ElligatorSwift, BIP324](#p2p-elligatorswift-bip324)
|
|
74
109
|
- [Ordinals and custom scripts](#ordinals-and-custom-scripts)
|
|
@@ -107,6 +142,10 @@ import { deepStrictEqual, throws } from 'assert';
|
|
|
107
142
|
Legacy script, doesn't have an address. Must be wrapped in P2SH / P2WSH / P2SH-P2WSH. Not recommended.
|
|
108
143
|
|
|
109
144
|
```ts
|
|
145
|
+
import * as btc from '@scure/btc-signer';
|
|
146
|
+
import { hex } from '@scure/base';
|
|
147
|
+
import { deepStrictEqual } from 'node:assert';
|
|
148
|
+
|
|
110
149
|
const uncompressed = hex.decode(
|
|
111
150
|
'04ad90e5b6bc86b3ec7fac2c5fbda7423fc8ef0d58df594c773fa05e2c281b2bfe877677c668bd13603944e34f4818ee03cadd81a88542b8b4d5431264180e2c28'
|
|
112
151
|
);
|
|
@@ -124,11 +163,16 @@ deepStrictEqual(btc.p2pk(uncompressed), {
|
|
|
124
163
|
Classic (pre-SegWit) address.
|
|
125
164
|
|
|
126
165
|
```ts
|
|
166
|
+
import * as btc from '@scure/btc-signer';
|
|
167
|
+
import { hex } from '@scure/base';
|
|
168
|
+
import { deepStrictEqual } from 'node:assert';
|
|
169
|
+
|
|
127
170
|
const PubKey = hex.decode('030000000000000000000000000000000000000000000000000000000000000001');
|
|
128
171
|
deepStrictEqual(btc.p2pkh(PubKey), {
|
|
129
172
|
type: 'pkh',
|
|
130
173
|
address: '134D6gYy8DsR5m4416BnmgASuMBqKvogQh',
|
|
131
174
|
script: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
|
|
175
|
+
hash: hex.decode('168b992bcfc44050310b3a94bd0771136d0b28d1'),
|
|
132
176
|
});
|
|
133
177
|
// P2SH-P2PKH
|
|
134
178
|
deepStrictEqual(btc.p2sh(btc.p2pkh(PubKey)), {
|
|
@@ -136,6 +180,7 @@ deepStrictEqual(btc.p2sh(btc.p2pkh(PubKey)), {
|
|
|
136
180
|
address: '3EPhLJ1FuR2noj6qrTs4YvepCvB6sbShoV',
|
|
137
181
|
script: hex.decode('a9148b530b962725af3bb7c818f197c619db3f71495087'),
|
|
138
182
|
redeemScript: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
|
|
183
|
+
hash: hex.decode('8b530b962725af3bb7c818f197c619db3f714950'),
|
|
139
184
|
});
|
|
140
185
|
// P2WSH-P2PKH
|
|
141
186
|
deepStrictEqual(btc.p2wsh(btc.p2pkh(PubKey)), {
|
|
@@ -143,6 +188,7 @@ deepStrictEqual(btc.p2wsh(btc.p2pkh(PubKey)), {
|
|
|
143
188
|
address: 'bc1qhxtthndg70cthfasy8y4qlk9h7r3006azn9md0fad5dg9hh76nkqaufnuz',
|
|
144
189
|
script: hex.decode('0020b996bbcda8f3f0bba7b021c9507ec5bf8717bf5d14cbb6bd3d6d1a82defed4ec'),
|
|
145
190
|
witnessScript: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
|
|
191
|
+
hash: hex.decode('b996bbcda8f3f0bba7b021c9507ec5bf8717bf5d14cbb6bd3d6d1a82defed4ec'),
|
|
146
192
|
});
|
|
147
193
|
// P2SH-P2WSH-P2PKH
|
|
148
194
|
deepStrictEqual(btc.p2sh(btc.p2wsh(btc.p2pkh(PubKey))), {
|
|
@@ -151,6 +197,7 @@ deepStrictEqual(btc.p2sh(btc.p2wsh(btc.p2pkh(PubKey))), {
|
|
|
151
197
|
script: hex.decode('a9148a3d36fb710a9c7cae06cfcdf39792ff5773e8f187'),
|
|
152
198
|
redeemScript: hex.decode('0020b996bbcda8f3f0bba7b021c9507ec5bf8717bf5d14cbb6bd3d6d1a82defed4ec'),
|
|
153
199
|
witnessScript: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
|
|
200
|
+
hash: hex.decode('8a3d36fb710a9c7cae06cfcdf39792ff5773e8f1'),
|
|
154
201
|
});
|
|
155
202
|
```
|
|
156
203
|
|
|
@@ -163,11 +210,16 @@ Uses bech32 address.
|
|
|
163
210
|
Can't be wrapped in [P2WSH](#p2wsh-witness-script-hash).
|
|
164
211
|
|
|
165
212
|
```ts
|
|
213
|
+
import * as btc from '@scure/btc-signer';
|
|
214
|
+
import { hex } from '@scure/base';
|
|
215
|
+
import { deepStrictEqual } from 'node:assert';
|
|
216
|
+
|
|
166
217
|
const PubKey = hex.decode('030000000000000000000000000000000000000000000000000000000000000001');
|
|
167
218
|
deepStrictEqual(btc.p2wpkh(PubKey), {
|
|
168
219
|
type: 'wpkh',
|
|
169
220
|
address: 'bc1qz69ej270c3q9qvgt822t6pm3zdksk2x35j2jlm',
|
|
170
221
|
script: hex.decode('0014168b992bcfc44050310b3a94bd0771136d0b28d1'),
|
|
222
|
+
hash: hex.decode('168b992bcfc44050310b3a94bd0771136d0b28d1'),
|
|
171
223
|
});
|
|
172
224
|
// P2SH-P2WPKH
|
|
173
225
|
deepStrictEqual(btc.p2sh(btc.p2wpkh(PubKey)), {
|
|
@@ -175,6 +227,7 @@ deepStrictEqual(btc.p2sh(btc.p2wpkh(PubKey)), {
|
|
|
175
227
|
address: '3BCuRViGCTXmQjyJ9zjeRUYrdZTUa38zjC',
|
|
176
228
|
script: hex.decode('a91468602f2db7b7d7cdcd2639ab6bf7f5bfe828e53f87'),
|
|
177
229
|
redeemScript: hex.decode('0014168b992bcfc44050310b3a94bd0771136d0b28d1'),
|
|
230
|
+
hash: hex.decode('68602f2db7b7d7cdcd2639ab6bf7f5bfe828e53f'),
|
|
178
231
|
});
|
|
179
232
|
```
|
|
180
233
|
|
|
@@ -185,6 +238,10 @@ Classic (pre-SegWit) script address. Useful for multisig and other advanced use-
|
|
|
185
238
|
Required tx input fields to make it spendable: `redeemScript`
|
|
186
239
|
|
|
187
240
|
```ts
|
|
241
|
+
import * as btc from '@scure/btc-signer';
|
|
242
|
+
import { hex } from '@scure/base';
|
|
243
|
+
import { deepStrictEqual } from 'node:assert';
|
|
244
|
+
|
|
188
245
|
const PubKey = hex.decode('030000000000000000000000000000000000000000000000000000000000000001');
|
|
189
246
|
// Wrap P2PKH in P2SH
|
|
190
247
|
deepStrictEqual(btc.p2sh(btc.p2pkh(PubKey)), {
|
|
@@ -192,6 +249,7 @@ deepStrictEqual(btc.p2sh(btc.p2pkh(PubKey)), {
|
|
|
192
249
|
address: '3EPhLJ1FuR2noj6qrTs4YvepCvB6sbShoV',
|
|
193
250
|
script: hex.decode('a9148b530b962725af3bb7c818f197c619db3f71495087'),
|
|
194
251
|
redeemScript: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
|
|
252
|
+
hash: hex.decode('8b530b962725af3bb7c818f197c619db3f714950'),
|
|
195
253
|
});
|
|
196
254
|
```
|
|
197
255
|
|
|
@@ -203,12 +261,17 @@ In SegWit, signature is removed from tx hash calculation.
|
|
|
203
261
|
Required tx input fields to make it spendable: `witnessScript`
|
|
204
262
|
|
|
205
263
|
```ts
|
|
264
|
+
import * as btc from '@scure/btc-signer';
|
|
265
|
+
import { hex } from '@scure/base';
|
|
266
|
+
import { deepStrictEqual } from 'node:assert';
|
|
267
|
+
|
|
206
268
|
const PubKey = hex.decode('030000000000000000000000000000000000000000000000000000000000000001');
|
|
207
269
|
deepStrictEqual(btc.p2wsh(btc.p2pkh(PubKey)), {
|
|
208
270
|
type: 'wsh',
|
|
209
271
|
address: 'bc1qhxtthndg70cthfasy8y4qlk9h7r3006azn9md0fad5dg9hh76nkqaufnuz',
|
|
210
272
|
script: hex.decode('0020b996bbcda8f3f0bba7b021c9507ec5bf8717bf5d14cbb6bd3d6d1a82defed4ec'),
|
|
211
273
|
witnessScript: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
|
|
274
|
+
hash: hex.decode('b996bbcda8f3f0bba7b021c9507ec5bf8717bf5d14cbb6bd3d6d1a82defed4ec'),
|
|
212
275
|
});
|
|
213
276
|
```
|
|
214
277
|
|
|
@@ -219,6 +282,10 @@ Not really script type, but construction of P2WSH inside P2SH.
|
|
|
219
282
|
Required tx input fields to make it spendable: `redeemScript`, `witnessScript`
|
|
220
283
|
|
|
221
284
|
```ts
|
|
285
|
+
import * as btc from '@scure/btc-signer';
|
|
286
|
+
import { hex } from '@scure/base';
|
|
287
|
+
import { deepStrictEqual } from 'node:assert';
|
|
288
|
+
|
|
222
289
|
const PubKey = hex.decode('030000000000000000000000000000000000000000000000000000000000000001');
|
|
223
290
|
deepStrictEqual(btc.p2sh(btc.p2wsh(btc.p2pkh(PubKey))), {
|
|
224
291
|
type: 'sh',
|
|
@@ -226,6 +293,7 @@ deepStrictEqual(btc.p2sh(btc.p2wsh(btc.p2pkh(PubKey))), {
|
|
|
226
293
|
script: hex.decode('a9148a3d36fb710a9c7cae06cfcdf39792ff5773e8f187'),
|
|
227
294
|
redeemScript: hex.decode('0020b996bbcda8f3f0bba7b021c9507ec5bf8717bf5d14cbb6bd3d6d1a82defed4ec'),
|
|
228
295
|
witnessScript: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
|
|
296
|
+
hash: hex.decode('8a3d36fb710a9c7cae06cfcdf39792ff5773e8f1'),
|
|
229
297
|
});
|
|
230
298
|
```
|
|
231
299
|
|
|
@@ -236,6 +304,10 @@ Classic / segwit (pre-taproot) M-of-N Multisig. Doesn't have an address, must be
|
|
|
236
304
|
Duplicate public keys are not accepted to reduce mistakes. Use flag `allowSamePubkeys` to override the behavior, for cases like `2-of-[A,A,B,C]`, which can be signed by `A or (B and C)`.
|
|
237
305
|
|
|
238
306
|
```ts
|
|
307
|
+
import * as btc from '@scure/btc-signer';
|
|
308
|
+
import { hex } from '@scure/base';
|
|
309
|
+
import { deepStrictEqual } from 'node:assert';
|
|
310
|
+
|
|
239
311
|
const PubKeys = [
|
|
240
312
|
hex.decode('030000000000000000000000000000000000000000000000000000000000000001'),
|
|
241
313
|
hex.decode('030000000000000000000000000000000000000000000000000000000000000002'),
|
|
@@ -249,6 +321,7 @@ deepStrictEqual(btc.p2sh(btc.p2ms(2, PubKeys)), {
|
|
|
249
321
|
redeemScript: hex.decode(
|
|
250
322
|
'5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae'
|
|
251
323
|
),
|
|
324
|
+
hash: hex.decode('9d91c6de4eacde72a7cc86bff98d1915b3c7818f'),
|
|
252
325
|
});
|
|
253
326
|
// Multisig 2-of-3 wrapped in P2WSH
|
|
254
327
|
deepStrictEqual(btc.p2wsh(btc.p2ms(2, PubKeys)), {
|
|
@@ -258,6 +331,7 @@ deepStrictEqual(btc.p2wsh(btc.p2ms(2, PubKeys)), {
|
|
|
258
331
|
witnessScript: hex.decode(
|
|
259
332
|
'5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae'
|
|
260
333
|
),
|
|
334
|
+
hash: hex.decode('74ee2b4ceec10839a489c07d4a538384394681e3dcd88f3ee87a85199908aa5e'),
|
|
261
335
|
});
|
|
262
336
|
// Multisig 2-of-3 wrapped in P2SH-P2WSH
|
|
263
337
|
deepStrictEqual(btc.p2sh(btc.p2wsh(btc.p2ms(2, PubKeys))), {
|
|
@@ -268,6 +342,7 @@ deepStrictEqual(btc.p2sh(btc.p2wsh(btc.p2ms(2, PubKeys))), {
|
|
|
268
342
|
witnessScript: hex.decode(
|
|
269
343
|
'5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae'
|
|
270
344
|
),
|
|
345
|
+
hash: hex.decode('ab70ab84b12b891364b4b2a14ca813cac308b242'),
|
|
271
346
|
});
|
|
272
347
|
// Useful util: wraps P2MS in P2SH or P2WSH
|
|
273
348
|
deepStrictEqual(btc.p2sh(btc.p2ms(2, PubKeys)), btc.multisig(2, PubKeys));
|
|
@@ -289,6 +364,10 @@ to sign multi-sig wallets, and there is no BIP/PSBT fields for that yet.
|
|
|
289
364
|
Required tx input fields to make it spendable: `tapInternalKey`, `tapMerkleRoot`, `tapLeafScript`
|
|
290
365
|
|
|
291
366
|
```ts
|
|
367
|
+
import * as btc from '@scure/btc-signer';
|
|
368
|
+
import { hex } from '@scure/base';
|
|
369
|
+
import { deepStrictEqual } from 'node:assert';
|
|
370
|
+
|
|
292
371
|
const PubKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
|
|
293
372
|
// Key Path Spend (owned of private key for PubKey can spend)
|
|
294
373
|
deepStrictEqual(btc.p2tr(PubKey), {
|
|
@@ -338,6 +417,10 @@ This is fast for cases like 15-of-20, but extremely slow for cases like 5-of-20.
|
|
|
338
417
|
Duplicate public keys are not accepted to reduce mistakes. Use flag `allowSamePubkeys` to override the behavior, for cases like `2-of-[A,A,B,C]`, which can be signed by `A or (B and C)`.
|
|
339
418
|
|
|
340
419
|
```ts
|
|
420
|
+
import * as btc from '@scure/btc-signer';
|
|
421
|
+
import { hex } from '@scure/base';
|
|
422
|
+
import { deepStrictEqual } from 'node:assert';
|
|
423
|
+
|
|
341
424
|
const PubKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
|
|
342
425
|
const PubKey2 = hex.decode('0202020202020202020202020202020202020202020202020202020202020202');
|
|
343
426
|
const PubKey3 = hex.decode('1212121212121212121212121212121212121212121212121212121212121212');
|
|
@@ -371,6 +454,10 @@ Duplicate public keys are not accepted to reduce mistakes. Use flag `allowSamePu
|
|
|
371
454
|
**Experimental**, use at your own risk.
|
|
372
455
|
|
|
373
456
|
```ts
|
|
457
|
+
import * as btc from '@scure/btc-signer';
|
|
458
|
+
import { hex } from '@scure/base';
|
|
459
|
+
import { deepStrictEqual } from 'node:assert';
|
|
460
|
+
|
|
374
461
|
const PubKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
|
|
375
462
|
const PubKey2 = hex.decode('0202020202020202020202020202020202020202020202020202020202020202');
|
|
376
463
|
const PubKey3 = hex.decode('1212121212121212121212121212121212121212121212121212121212121212');
|
|
@@ -395,6 +482,10 @@ deepStrictEqual(clean(btc.p2tr(undefined, btc.p2tr_ms(2, [PubKey, PubKey2, PubKe
|
|
|
395
482
|
Specific case of `p2tr_ns(1, [pubkey])`, which is the same as the BTC descriptor: `tr($H,pk(PUBKEY))`
|
|
396
483
|
|
|
397
484
|
```ts
|
|
485
|
+
import * as btc from '@scure/btc-signer';
|
|
486
|
+
import { hex } from '@scure/base';
|
|
487
|
+
import { deepStrictEqual } from 'node:assert';
|
|
488
|
+
|
|
398
489
|
const PubKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
|
|
399
490
|
// P2PK for taproot
|
|
400
491
|
const clean = (x) => ({ type: x.type, address: x.address, script: hex.encode(x.script) });
|
|
@@ -410,6 +501,10 @@ deepStrictEqual(clean(btc.p2tr(undefined, [btc.p2tr_pk(PubKey)])), {
|
|
|
410
501
|
Ephemeral anchors are supported. [Check out docs](https://bitcoinops.org/en/topics/ephemeral-anchors/).
|
|
411
502
|
|
|
412
503
|
```ts
|
|
504
|
+
import * as btc from '@scure/btc-signer';
|
|
505
|
+
import { hex } from '@scure/base';
|
|
506
|
+
import { deepStrictEqual } from 'node:assert';
|
|
507
|
+
|
|
413
508
|
const p2aScript = hex.decode('51024e73');
|
|
414
509
|
const decoded = btc.OutScript.decode(p2aScript);
|
|
415
510
|
deepStrictEqual(decoded, { type: 'p2a', script: p2aScript });
|
|
@@ -428,7 +523,7 @@ If you have use-case where they are needed, create a github issue.
|
|
|
428
523
|
|
|
429
524
|
PSBTv2 features tx_modifiable and taproot+bip32 are not supported yet.
|
|
430
525
|
|
|
431
|
-
```
|
|
526
|
+
```text
|
|
432
527
|
// Decode
|
|
433
528
|
Transaction.fromRaw(raw: Bytes, opts: TxOpts = {}); // Raw tx
|
|
434
529
|
Transaction.fromPSBT(psbt: Bytes, opts: TxOpts = {}); // PSBT tx
|
|
@@ -448,32 +543,42 @@ Use `getInput` and `inputsLength` to read information about inputs: they return
|
|
|
448
543
|
This is necessary to avoid accidental modification of internal structures without calling methods (addInput/updateInput) that will verify correctness.
|
|
449
544
|
|
|
450
545
|
```ts
|
|
546
|
+
import * as btc from '@scure/btc-signer';
|
|
547
|
+
import { hex } from '@scure/base';
|
|
548
|
+
import { deepStrictEqual, throws } from 'node:assert';
|
|
549
|
+
|
|
550
|
+
const tx = new btc.Transaction();
|
|
551
|
+
type Bytes = Uint8Array | string;
|
|
552
|
+
type RawTransactionBytesOrHex = Uint8Array | string;
|
|
553
|
+
type DerivationPath = { fingerprint: number; path: number[] };
|
|
554
|
+
type TapScriptSigKey = { pubKey: Bytes; leafHash: Bytes };
|
|
555
|
+
type TapLeafScriptKey = { version: number; internalKey: Bytes; merklePath: Bytes[] };
|
|
451
556
|
type TransactionInput = {
|
|
452
|
-
txid?: Bytes
|
|
453
|
-
index?: number
|
|
454
|
-
nonWitnessUtxo?:
|
|
455
|
-
witnessUtxo?: {script?: Bytes; amount: bigint}
|
|
557
|
+
txid?: Bytes;
|
|
558
|
+
index?: number;
|
|
559
|
+
nonWitnessUtxo?: RawTransactionBytesOrHex;
|
|
560
|
+
witnessUtxo?: { script?: Bytes; amount: bigint };
|
|
456
561
|
partialSig?: [Bytes, Bytes][]; // [PubKey, Signature]
|
|
457
|
-
sighashType?:
|
|
458
|
-
redeemScript?: Bytes
|
|
459
|
-
witnessScript?: Bytes
|
|
460
|
-
bip32Derivation?: [Bytes,
|
|
461
|
-
finalScriptSig?: Bytes
|
|
462
|
-
finalScriptWitness?: Bytes[]
|
|
463
|
-
porCommitment?: Bytes
|
|
464
|
-
sequence?: number
|
|
465
|
-
requiredTimeLocktime?: number
|
|
466
|
-
requiredHeightLocktime?: number
|
|
467
|
-
tapKeySig?: Bytes
|
|
468
|
-
tapScriptSig?: [
|
|
562
|
+
sighashType?: number;
|
|
563
|
+
redeemScript?: Bytes;
|
|
564
|
+
witnessScript?: Bytes;
|
|
565
|
+
bip32Derivation?: [Bytes, DerivationPath | undefined][]; // [PubKey, DeriviationPath]
|
|
566
|
+
finalScriptSig?: Bytes;
|
|
567
|
+
finalScriptWitness?: Bytes[];
|
|
568
|
+
porCommitment?: Bytes;
|
|
569
|
+
sequence?: number;
|
|
570
|
+
requiredTimeLocktime?: number;
|
|
571
|
+
requiredHeightLocktime?: number;
|
|
572
|
+
tapKeySig?: Bytes;
|
|
573
|
+
tapScriptSig?: [TapScriptSigKey, Bytes][]; // [PubKeySchnorr, LeafHash]
|
|
469
574
|
// [ControlBlock, ScriptWithVersion]
|
|
470
|
-
tapLeafScript?: [
|
|
471
|
-
tapInternalKey?: Bytes
|
|
472
|
-
tapMerkleRoot?: Bytes
|
|
575
|
+
tapLeafScript?: [TapLeafScriptKey, Bytes][];
|
|
576
|
+
tapInternalKey?: Bytes;
|
|
577
|
+
tapMerkleRoot?: Bytes;
|
|
473
578
|
};
|
|
474
579
|
|
|
475
|
-
tx.addInput(input: TransactionInput): number;
|
|
476
|
-
tx.updateInput(idx: number, input: TransactionInput);
|
|
580
|
+
// tx.addInput(input: TransactionInput): number;
|
|
581
|
+
// tx.updateInput(idx: number, input: TransactionInput);
|
|
477
582
|
|
|
478
583
|
// Input
|
|
479
584
|
tx.addInput({ txid: new Uint8Array(32), index: 0 });
|
|
@@ -494,7 +599,7 @@ tx.addInput({
|
|
|
494
599
|
txid: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
495
600
|
index: 0,
|
|
496
601
|
});
|
|
497
|
-
deepStrictEqual(tx.inputs[
|
|
602
|
+
deepStrictEqual(tx.inputs[1], {
|
|
498
603
|
txid: new Uint8Array(32),
|
|
499
604
|
index: 0,
|
|
500
605
|
sequence: btc.DEFAULT_SEQUENCE,
|
|
@@ -542,9 +647,11 @@ for (let i = 0; i < tx.inputsLength; i++) {
|
|
|
542
647
|
|
|
543
648
|
### Outputs
|
|
544
649
|
|
|
545
|
-
`addOutputAddress` uses bigint amounts, which means satoshis
|
|
650
|
+
`addOutputAddress` uses bigint amounts, which means satoshis, not BTC. If you need BTC representation, use `Decimal`:
|
|
546
651
|
|
|
547
652
|
```ts
|
|
653
|
+
import * as btc from '@scure/btc-signer';
|
|
654
|
+
|
|
548
655
|
const amountSatoshi = btc.Decimal.decode('1.5'); // 1.5 btc in satoshi
|
|
549
656
|
```
|
|
550
657
|
|
|
@@ -552,23 +659,33 @@ Use `getOutput` and `outputsLength` to read outputs information. This methods re
|
|
|
552
659
|
This is necessary to avoid accidental modification of internal structures without calling methods (addOutput/updateOutput) that will verify correctness.
|
|
553
660
|
|
|
554
661
|
```ts
|
|
662
|
+
import * as btc from '@scure/btc-signer';
|
|
663
|
+
import { hex } from '@scure/base';
|
|
664
|
+
import { deepStrictEqual, throws } from 'node:assert';
|
|
665
|
+
|
|
666
|
+
const tx = new btc.Transaction();
|
|
667
|
+
type Bytes = Uint8Array | string;
|
|
668
|
+
type DerivationPath = { fingerprint: number; path: number[] };
|
|
555
669
|
type TransactionOutput = {
|
|
556
|
-
script?: Bytes
|
|
557
|
-
amount?: bigint
|
|
558
|
-
redeemScript?: Bytes
|
|
559
|
-
witnessScript?: Bytes
|
|
560
|
-
bip32Derivation?: [Bytes,
|
|
561
|
-
tapInternalKey?: Bytes
|
|
670
|
+
script?: Bytes;
|
|
671
|
+
amount?: bigint;
|
|
672
|
+
redeemScript?: Bytes;
|
|
673
|
+
witnessScript?: Bytes;
|
|
674
|
+
bip32Derivation?: [Bytes, DerivationPath | undefined][]; // [PubKey, DeriviationPath]
|
|
675
|
+
tapInternalKey?: Bytes;
|
|
562
676
|
};
|
|
563
677
|
|
|
564
|
-
tx.addOutput(o: TransactionOutput): number;
|
|
565
|
-
tx.updateOutput(idx: number, output: TransactionOutput);
|
|
566
|
-
tx.addOutputAddress(address: string, amount: bigint, network = NETWORK): number;
|
|
678
|
+
// tx.addOutput(o: TransactionOutput): number;
|
|
679
|
+
// tx.updateOutput(idx: number, output: TransactionOutput);
|
|
680
|
+
// tx.addOutputAddress(address: string, amount: bigint, network = NETWORK): number;
|
|
567
681
|
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
);
|
|
571
|
-
const
|
|
682
|
+
const pubKey = hex.decode('030000000000000000000000000000000000000000000000000000000000000001');
|
|
683
|
+
const bip1 = [pubKey, { fingerprint: 5, path: [1, 2, 3] }];
|
|
684
|
+
const pubKey2 = hex.decode('030000000000000000000000000000000000000000000000000000000000000002');
|
|
685
|
+
const bip2 = [pubKey2, { fingerprint: 6, path: [4, 5, 6] }];
|
|
686
|
+
const pubKey3 = hex.decode('030000000000000000000000000000000000000000000000000000000000000003');
|
|
687
|
+
const bip3 = [pubKey3, { fingerprint: 7, path: [7, 8, 9] }];
|
|
688
|
+
const script = btc.p2pkh(pubKey).script;
|
|
572
689
|
tx.addOutput({ script, amount: 100n });
|
|
573
690
|
deepStrictEqual(tx.outputs[0], {
|
|
574
691
|
script,
|
|
@@ -616,15 +733,45 @@ for (let i = 0; i < tx.outputsLength; i++) {
|
|
|
616
733
|
### Basic transaction sign
|
|
617
734
|
|
|
618
735
|
```ts
|
|
736
|
+
import * as btc from '@scure/btc-signer';
|
|
737
|
+
import { hex } from '@scure/base';
|
|
738
|
+
import { pubECDSA } from '@scure/btc-signer/utils.js';
|
|
739
|
+
import { deepStrictEqual } from 'node:assert';
|
|
740
|
+
|
|
619
741
|
const privKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
|
|
620
|
-
const
|
|
742
|
+
const pubKey = pubECDSA(privKey);
|
|
743
|
+
const TX_TEST_OUTPUTS = [
|
|
744
|
+
['1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP', 10n],
|
|
745
|
+
['3H3Kc7aSPP4THLX68k4mQMyf1gvL6AtmDm', 50n],
|
|
746
|
+
['bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq', 93n],
|
|
747
|
+
] as const;
|
|
748
|
+
const TX_TEST_INPUTS = [
|
|
749
|
+
{
|
|
750
|
+
txid: hex.decode('c061c23190ed3370ad5206769651eaf6fac6d87d85b5db34e30a74e0c4a6da3e'),
|
|
751
|
+
index: 0,
|
|
752
|
+
amount: 550n,
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
txid: hex.decode('a21965903c938af35e7280ae5779b9fea4f7f01ac256b8a2a53b1b19a4e89a0d'),
|
|
756
|
+
index: 0,
|
|
757
|
+
amount: 600n,
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
txid: hex.decode('fae21e319ca827df32462afc3225c17719338a8e8d3e3b3ddeb0c2387da3a4c7'),
|
|
761
|
+
index: 0,
|
|
762
|
+
amount: 600n,
|
|
763
|
+
},
|
|
764
|
+
];
|
|
765
|
+
const RAW_TX_HEX =
|
|
766
|
+
'01000000033edaa6c4e0740ae334dbb5857dd8c6faf6ea5196760652ad7033ed9031c261c00000000000ffffffff0d9ae8a4191b3ba5a2b856c21af0f7a4feb97957ae80725ef38a933c906519a20000000000ffffffffc7a4a37d38c2b0de3d3b3e8d8e8a331977c12532fc2a4632df27a89c311ee2fa0000000000ffffffff030a000000000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac320000000000000017a914a860f76561c85551594c18eecceffaee8c4822d7875d00000000000000160014e8df018c7e326cc253faac7e46cdc51e68542c4200000000';
|
|
767
|
+
const txP2WPKH = new btc.Transaction({ version: 1 });
|
|
621
768
|
for (const inp of TX_TEST_INPUTS) {
|
|
622
769
|
txP2WPKH.addInput({
|
|
623
770
|
txid: inp.txid,
|
|
624
771
|
index: inp.index,
|
|
625
772
|
witnessUtxo: {
|
|
626
773
|
amount: inp.amount,
|
|
627
|
-
script: btc.p2wpkh(
|
|
774
|
+
script: btc.p2wpkh(pubKey).script,
|
|
628
775
|
},
|
|
629
776
|
});
|
|
630
777
|
}
|
|
@@ -632,16 +779,23 @@ for (const [address, amount] of TX_TEST_OUTPUTS) txP2WPKH.addOutputAddress(addre
|
|
|
632
779
|
deepStrictEqual(hex.encode(txP2WPKH.unsignedTx), RAW_TX_HEX);
|
|
633
780
|
txP2WPKH.sign(privKey);
|
|
634
781
|
txP2WPKH.finalize();
|
|
635
|
-
deepStrictEqual(txP2WPKH.id, '
|
|
782
|
+
deepStrictEqual(txP2WPKH.id, 'e4db0a196f378a6648deb221a2771fde577892479a2d52abbe8cf3d31d2f140f');
|
|
636
783
|
deepStrictEqual(
|
|
637
784
|
txP2WPKH.hex,
|
|
638
|
-
'
|
|
785
|
+
'010000000001033edaa6c4e0740ae334dbb5857dd8c6faf6ea5196760652ad7033ed9031c261c00000000000ffffffff0d9ae8a4191b3ba5a2b856c21af0f7a4feb97957ae80725ef38a933c906519a20000000000ffffffffc7a4a37d38c2b0de3d3b3e8d8e8a331977c12532fc2a4632df27a89c311ee2fa0000000000ffffffff030a000000000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac320000000000000017a914a860f76561c85551594c18eecceffaee8c4822d7875d00000000000000160014e8df018c7e326cc253faac7e46cdc51e68542c4202483045022100d04801283249fc9a80f71d8fe8d9f6dc0e84afc0e59df2733f04ff659e095a8802206ce71c598d8f75b7cb2102b252b7bd04c6228c0826da0ed27104f8cca829869d0121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f02473044022026ef492099b86572a965b28d11a40bf9b1e9fe5a2aeab22cbca1b354988910e30220416508f564e67932cb1c38bef7a3c3f0a95470fc81c6cb359a9268a49f6449850121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f0247304402202cf37bbcf2c098e48ffc204d0ab688465b43642546d2e0414f5b8e4bdfae9420022006ccfb2415c7a941b6d5c07651fd80b9656bdea5fe793530cf2c604920c72d100121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f00000000'
|
|
639
786
|
);
|
|
640
787
|
```
|
|
641
788
|
|
|
642
789
|
### BIP174 PSBT multi-sig example
|
|
643
790
|
|
|
791
|
+
> `npm install @scure/base @scure/bip32`
|
|
792
|
+
|
|
644
793
|
```ts
|
|
794
|
+
import * as btc from '@scure/btc-signer';
|
|
795
|
+
import { hex } from '@scure/base';
|
|
796
|
+
import * as bip32 from '@scure/bip32';
|
|
797
|
+
import { deepStrictEqual } from 'node:assert';
|
|
798
|
+
|
|
645
799
|
const testnet = {
|
|
646
800
|
wif: 0xef,
|
|
647
801
|
bip32: {
|
|
@@ -828,8 +982,14 @@ a lot of outputs close to dust.
|
|
|
828
982
|
#### Example
|
|
829
983
|
|
|
830
984
|
```ts
|
|
985
|
+
import * as btc from '@scure/btc-signer';
|
|
986
|
+
import { hex } from '@scure/base';
|
|
987
|
+
import { pubECDSA } from '@scure/btc-signer/utils.js';
|
|
988
|
+
import { deepStrictEqual } from 'node:assert';
|
|
989
|
+
|
|
831
990
|
const privKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
|
|
832
|
-
const pubKey =
|
|
991
|
+
const pubKey = pubECDSA(privKey);
|
|
992
|
+
const regtest = { bech32: 'bcrt', pubKeyHash: 0x6f, scriptHash: 0xc4 };
|
|
833
993
|
const spend = btc.p2wpkh(pubKey, regtest);
|
|
834
994
|
const utxo = [
|
|
835
995
|
{
|
|
@@ -874,7 +1034,8 @@ const selected = btc.selectUTXO(utxo, outputs, 'default', {
|
|
|
874
1034
|
createTx: true, // create tx with selected inputs/outputs
|
|
875
1035
|
network: regtest,
|
|
876
1036
|
});
|
|
877
|
-
//
|
|
1037
|
+
// selectUTXO returns undefined if there is not enough funds.
|
|
1038
|
+
if (!selected) throw new Error('expected enough funds');
|
|
878
1039
|
deepStrictEqual(selected.fee, 394n); // estimated fee
|
|
879
1040
|
deepStrictEqual(selected.change, true); // change address used
|
|
880
1041
|
deepStrictEqual(selected.outputs, [
|
|
@@ -899,23 +1060,119 @@ deepStrictEqual(tx.id, 'b702078d65edd65a84b2a97a669df5631b06f42a67b0d7090e540b02
|
|
|
899
1060
|
deepStrictEqual(tx.fee, 394n);
|
|
900
1061
|
```
|
|
901
1062
|
|
|
1063
|
+
## Network
|
|
1064
|
+
|
|
1065
|
+
Bitcoin nodes can't be used as source-of-truth to construct transactions & get UTXOs.
|
|
1066
|
+
An extra indexer is required. Our `net.js` submodule allows to easily fetch UTXOs,
|
|
1067
|
+
balances, and other data for an address.
|
|
1068
|
+
See [README-fullnode.md](./README-fullnode.md) for details and
|
|
1069
|
+
guide on running a full node with an indexer.
|
|
1070
|
+
|
|
1071
|
+
```ts
|
|
1072
|
+
import * as btc from '@scure/btc-signer';
|
|
1073
|
+
import { EsploraProvider } from '@scure/btc-signer/net.js';
|
|
1074
|
+
import { pubECDSA } from '@scure/btc-signer/utils.js';
|
|
1075
|
+
const net = new EsploraProvider(fetch, 'http://127.0.0.1:3000');
|
|
1076
|
+
// Methods: `height`, `blockInfo`, `fee`, `balance`, `txCount`, `sendTx`, `waitForTx`, `txInfo`,
|
|
1077
|
+
// `unspent`, `transfers`, `history`, `historyMulti`.
|
|
1078
|
+
// Transient backend failures (429/5xx, dropped connections) are retried with backoff on GETs.
|
|
1079
|
+
// Long scans accept `signal` (AbortSignal) and `onProgress`; raw-tx fan-out is
|
|
1080
|
+
// bounded by `concurrency` (default 8).
|
|
1081
|
+
|
|
1082
|
+
// Get latest block.
|
|
1083
|
+
async function latestBlock() {
|
|
1084
|
+
const height = await net.height();
|
|
1085
|
+
const block = await net.blockInfo(height);
|
|
1086
|
+
return { number: block.number, hash: block.hash, timestamp: new Date(block.timestamp) };
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Get per-address transaction history.
|
|
1090
|
+
async function addressTransactions(address: string) {
|
|
1091
|
+
const txs = await net.transfers(address, { limit: 10 });
|
|
1092
|
+
return txs.map((tx) => ({ txid: tx.txid, block: tx.block, fee: tx.info.fee }));
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// Stream history instead of buffering it: rows arrive newest-first while pages
|
|
1096
|
+
// are fetched, and stopping early also stops fetching.
|
|
1097
|
+
async function streamHistory(address: string) {
|
|
1098
|
+
for await (const tx of net.history(address, { onProgress: (p) => console.log(p.percent) })) {
|
|
1099
|
+
if (tx.block !== undefined && tx.block < 800_000) break;
|
|
1100
|
+
console.log(tx.txid, tx.transfers);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// Merged history for a set of addresses (HD wallets): one txid-deduplicated
|
|
1105
|
+
// stream; each row lists the watched addresses participating in it.
|
|
1106
|
+
async function walletHistory(addresses: string[]) {
|
|
1107
|
+
for await (const tx of net.historyMulti(addresses)) console.log(tx.txid, tx.addresses);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// Broadcast, then wait for confirmation.
|
|
1111
|
+
async function sendAndWait(rawTx: string) {
|
|
1112
|
+
const txid = await net.sendTx(rawTx);
|
|
1113
|
+
return await net.waitForTx(txid, { confirmations: 2, timeoutMs: 3_600_000 });
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// Get UTXOs, select inputs, and sign. Call with a key that controls funded UTXOs.
|
|
1117
|
+
async function signSpend(privKey: Uint8Array, to: string, amount: bigint) {
|
|
1118
|
+
const spend = btc.p2wpkh(pubECDSA(privKey));
|
|
1119
|
+
const unspent = await net.unspent(spend.address!);
|
|
1120
|
+
const feePerByte = await net.fee(2);
|
|
1121
|
+
const selected = btc.selectUTXO(unspent.utxo, [{ address: to, amount }], 'default', {
|
|
1122
|
+
feePerByte,
|
|
1123
|
+
changeAddress: spend.address!,
|
|
1124
|
+
});
|
|
1125
|
+
if (!selected) throw new Error(`not enough funds for ${spend.address}`);
|
|
1126
|
+
const { tx } = selected;
|
|
1127
|
+
tx.sign(privKey);
|
|
1128
|
+
tx.finalize();
|
|
1129
|
+
return { txid: tx.id, raw: tx.hex };
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// For wrapped or script spends, add caller-owned metadata before selection:
|
|
1133
|
+
// const wrapped = btc.p2sh(btc.p2wpkh(pubECDSA(privKey)));
|
|
1134
|
+
// const base = await net.unspent(wrapped.address!);
|
|
1135
|
+
// const utxo = base.utxo.map((u) => ({ ...u, redeemScript: wrapped.redeemScript }));
|
|
1136
|
+
// const selected = btc.selectUTXO(utxo, outputs, 'default', opts);
|
|
1137
|
+
```
|
|
1138
|
+
|
|
1139
|
+
First argument is `fetch` API-compatible transport.
|
|
1140
|
+
We suggest using `micro-ftch` package - a wrapper, which supports kill-switch,
|
|
1141
|
+
logging, timeouts, concurrency limits, replay fixtures, and other useful network controls:
|
|
1142
|
+
|
|
1143
|
+
```ts
|
|
1144
|
+
import { ftch } from 'micro-ftch';
|
|
1145
|
+
let NETWORK_ENABLED = true;
|
|
1146
|
+
const fetcher = ftch(fetch, {
|
|
1147
|
+
isValidRequest: () => NETWORK_ENABLED,
|
|
1148
|
+
timeout: 10_000,
|
|
1149
|
+
concurrencyLimit: 4,
|
|
1150
|
+
});
|
|
1151
|
+
const net = new EsploraProvider(fetcher, 'http://127.0.0.1:3000')
|
|
1152
|
+
```
|
|
1153
|
+
|
|
902
1154
|
## MuSig2
|
|
903
1155
|
|
|
904
1156
|
MuSig2 implementation conforming to [BIP-327](https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki)
|
|
905
|
-
is available in `@scure/btc-signer/musig2.js`. Check out [bip327-musig2.test.
|
|
1157
|
+
is available in `@scure/btc-signer/musig2.js`. Check out [bip327-musig2.test.ts](./test/bip327-musig2.test.ts) as well:
|
|
1158
|
+
|
|
1159
|
+
> `npm install @noble/curves`
|
|
906
1160
|
|
|
907
1161
|
```ts
|
|
1162
|
+
import * as btc from '@scure/btc-signer';
|
|
908
1163
|
import * as musig2 from '@scure/btc-signer/musig2.js';
|
|
1164
|
+
import { schnorr } from '@noble/curves/secp256k1.js';
|
|
1165
|
+
import { deepStrictEqual } from 'node:assert';
|
|
909
1166
|
// MuSig2 Multi-signature for Alice, Bob, and Carol
|
|
910
1167
|
// 1. Key Generation (for each signer: Alice, Bob, Carol)
|
|
911
1168
|
// - Alice's key generation
|
|
912
|
-
const aliceSecretKey =
|
|
1169
|
+
const aliceSecretKey = btc.utils.randomPrivateKeyBytes(); // Alice generates a random 32-byte secret key
|
|
913
1170
|
const alicePublicKey = musig2.IndividualPubkey(aliceSecretKey); // Alice derives her individual public key from her secret key
|
|
914
1171
|
// - Bob's key generation
|
|
915
|
-
const bobSecretKey =
|
|
1172
|
+
const bobSecretKey = btc.utils.randomPrivateKeyBytes(); // Bob generates a random 32-byte secret key
|
|
916
1173
|
const bobPublicKey = musig2.IndividualPubkey(bobSecretKey); // Bob derives his individual public key from his secret key
|
|
917
1174
|
// - Carol's key generation
|
|
918
|
-
const carolSecretKey =
|
|
1175
|
+
const carolSecretKey = btc.utils.randomPrivateKeyBytes(); // Carol generates a random 32-byte secret key
|
|
919
1176
|
const carolPublicKey = musig2.IndividualPubkey(carolSecretKey); // Carol derives her individual public key from her secret key
|
|
920
1177
|
|
|
921
1178
|
// 2. Key Aggregation (All signers participate by sharing public keys)
|
|
@@ -953,9 +1210,7 @@ const partialSignatures = [alicePartialSignature, bobPartialSignature, carolPart
|
|
|
953
1210
|
const finalSignature = session.partialSigAgg(partialSignatures); // Aggregate partial signatures to create the final signature
|
|
954
1211
|
|
|
955
1212
|
// 7. Signature Verification (Anyone can verify the final signature)
|
|
956
|
-
|
|
957
|
-
import { schnorr } from '@noble/curves/secp256k1';
|
|
958
|
-
schnorr.verify(finalSignature, msg, aggregatePublicKey);
|
|
1213
|
+
deepStrictEqual(schnorr.verify(finalSignature, msg, aggregatePublicKey), true);
|
|
959
1214
|
```
|
|
960
1215
|
|
|
961
1216
|
## Ordinals and custom scripts
|
|
@@ -1011,6 +1266,8 @@ for (const k of [alice, bob]) {
|
|
|
1011
1266
|
|
|
1012
1267
|
## Utils
|
|
1013
1268
|
|
|
1269
|
+
> `npm install @scure/base`
|
|
1270
|
+
|
|
1014
1271
|
### secp256k1 keys
|
|
1015
1272
|
|
|
1016
1273
|
```ts
|
|
@@ -1027,11 +1284,15 @@ const pub = pubSchnorr(priv);
|
|
|
1027
1284
|
Returns common addresses from privateKey
|
|
1028
1285
|
|
|
1029
1286
|
```ts
|
|
1287
|
+
import * as btc from '@scure/btc-signer';
|
|
1288
|
+
import { hex } from '@scure/base';
|
|
1289
|
+
import { deepStrictEqual } from 'node:assert';
|
|
1290
|
+
|
|
1030
1291
|
const privKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
|
|
1031
1292
|
deepStrictEqual(btc.getAddress('pkh', privKey), '1C6Rc3w25VHud3dLDamutaqfKWqhrLRTaD'); // P2PKH (legacy address)
|
|
1032
1293
|
deepStrictEqual(btc.getAddress('wpkh', privKey), 'bc1q0xcqpzrky6eff2g52qdye53xkk9jxkvrh6yhyw'); // SegWit V0 address
|
|
1033
1294
|
deepStrictEqual(
|
|
1034
|
-
btc.getAddress('tr',
|
|
1295
|
+
btc.getAddress('tr', privKey),
|
|
1035
1296
|
'bc1p33wm0auhr9kkahzd6l0kqj85af4cswn276hsxg6zpz85xe2r0y8syx4e5t'
|
|
1036
1297
|
); // TapRoot KeyPathSpend
|
|
1037
1298
|
```
|
|
@@ -1041,6 +1302,10 @@ deepStrictEqual(
|
|
|
1041
1302
|
Encoding/decoding of WIF privateKeys. Only compressed keys are supported for now.
|
|
1042
1303
|
|
|
1043
1304
|
```ts
|
|
1305
|
+
import * as btc from '@scure/btc-signer';
|
|
1306
|
+
import { hex } from '@scure/base';
|
|
1307
|
+
import { deepStrictEqual } from 'node:assert';
|
|
1308
|
+
|
|
1044
1309
|
const privKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
|
|
1045
1310
|
deepStrictEqual(btc.WIF().encode(privKey), 'KwFfNUhSDaASSAwtG7ssQM1uVX8RgX5GHWnnLfhfiQDigjioWXHH');
|
|
1046
1311
|
deepStrictEqual(
|
|
@@ -1054,29 +1319,33 @@ deepStrictEqual(
|
|
|
1054
1319
|
Encoding/decoding bitcoin scripts
|
|
1055
1320
|
|
|
1056
1321
|
```ts
|
|
1322
|
+
import * as btc from '@scure/btc-signer';
|
|
1323
|
+
import { hex } from '@scure/base';
|
|
1324
|
+
import { deepStrictEqual } from 'node:assert';
|
|
1325
|
+
|
|
1057
1326
|
deepStrictEqual(
|
|
1058
1327
|
btc.Script.decode(
|
|
1059
1328
|
hex.decode(
|
|
1060
1329
|
'5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae'
|
|
1061
1330
|
)
|
|
1062
|
-
).map((i) => (
|
|
1331
|
+
).map((i) => (btc.utils.isBytes(i) ? hex.encode(i) : i)),
|
|
1063
1332
|
[
|
|
1064
|
-
|
|
1333
|
+
2,
|
|
1065
1334
|
'030000000000000000000000000000000000000000000000000000000000000001',
|
|
1066
1335
|
'030000000000000000000000000000000000000000000000000000000000000002',
|
|
1067
1336
|
'030000000000000000000000000000000000000000000000000000000000000003',
|
|
1068
|
-
|
|
1337
|
+
3,
|
|
1069
1338
|
'CHECKMULTISIG',
|
|
1070
1339
|
]
|
|
1071
1340
|
);
|
|
1072
1341
|
deepStrictEqual(
|
|
1073
1342
|
hex.encode(
|
|
1074
1343
|
btc.Script.encode([
|
|
1075
|
-
|
|
1344
|
+
2,
|
|
1076
1345
|
hex.decode('030000000000000000000000000000000000000000000000000000000000000001'),
|
|
1077
1346
|
hex.decode('030000000000000000000000000000000000000000000000000000000000000002'),
|
|
1078
1347
|
hex.decode('030000000000000000000000000000000000000000000000000000000000000003'),
|
|
1079
|
-
|
|
1348
|
+
3,
|
|
1080
1349
|
'CHECKMULTISIG',
|
|
1081
1350
|
])
|
|
1082
1351
|
),
|
|
@@ -1089,6 +1358,10 @@ deepStrictEqual(
|
|
|
1089
1358
|
Encoding / decoding of output scripts
|
|
1090
1359
|
|
|
1091
1360
|
```ts
|
|
1361
|
+
import * as btc from '@scure/btc-signer';
|
|
1362
|
+
import { hex } from '@scure/base';
|
|
1363
|
+
import { deepStrictEqual } from 'node:assert';
|
|
1364
|
+
|
|
1092
1365
|
deepStrictEqual(
|
|
1093
1366
|
btc.OutScript.decode(
|
|
1094
1367
|
hex.decode(
|
|
@@ -1166,6 +1439,9 @@ Bitcoin is more complex than ETH / SOL despite having less features:
|
|
|
1166
1439
|
|
|
1167
1440
|
The library has been independently audited:
|
|
1168
1441
|
|
|
1442
|
+
- at version 2.2.0, in Apr 2026, by ourselves (self-audited)
|
|
1443
|
+
- Scope: everything
|
|
1444
|
+
- [Changes since audit](https://github.com/paulmillr/scure-btc-signer/compare/2.2.0..main)
|
|
1169
1445
|
- at version 0.3.0, in Feb 2023, by [cure53](https://cure53.de)
|
|
1170
1446
|
- PDFs: [online](https://cure53.de/audit-report_micro-btc-signer.pdf), [offline](./audit/2023-02-21-cure53-audit-report.pdf)
|
|
1171
1447
|
- [Changes since audit](https://github.com/paulmillr/scure-btc-signer/compare/0.3.0..main).
|
|
@@ -1195,15 +1471,9 @@ For this package, there are 4 dependencies; and a few dev dependencies:
|
|
|
1195
1471
|
- [noble-curves](https://github.com/paulmillr/noble-curves) provides secp256k1 elliptic curve
|
|
1196
1472
|
- [scure-base](https://github.com/paulmillr/scure-base) provides base58 and bech32
|
|
1197
1473
|
- [micro-packed](https://github.com/paulmillr/micro-packed) is responsible for binary encoding
|
|
1198
|
-
-
|
|
1474
|
+
- jsbt is used for benchmarking / testing / build tooling and developed by the same author
|
|
1199
1475
|
- prettier, fast-check and typescript are used for code quality / test generation / ts compilation. It's hard to audit their source code thoroughly and fully because of their size
|
|
1200
1476
|
|
|
1201
|
-
## Contributing & testing
|
|
1202
|
-
|
|
1203
|
-
- `npm install && npm run build && npm test` will build the code and run tests.
|
|
1204
|
-
- `npm run lint` / `npm run format` will run linter / fix linter issues.
|
|
1205
|
-
- `npm run build:release` will build single file
|
|
1206
|
-
|
|
1207
1477
|
## Learning & documentation
|
|
1208
1478
|
|
|
1209
1479
|
There are several nice resources on the topic:
|