@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 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
- - ๐Ÿ“ก No network code: simplified audits and offline usage
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
- ```ts
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?: <RawTransactionBytesOrHex>,
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?: P.U32LE,
458
- redeemScript?: Bytes,
459
- witnessScript?: Bytes,
460
- bip32Derivation?: [Bytes, {fingerprint: number; path: number[]}]; // [PubKey, DeriviationPath]
461
- finalScriptSig?: Bytes,
462
- finalScriptWitness?: Bytes[],
463
- porCommitment?: Bytes,
464
- sequence?: number,
465
- requiredTimeLocktime?: number,
466
- requiredHeightLocktime?: number,
467
- tapKeySig?: Bytes,
468
- tapScriptSig?: [Bytes, Bytes][]; // [PubKeySchnorr, LeafHash]
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?: [{version: number; internalKey: Bytes; merklePath: Bytes[]}, Bytes];
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[2], {
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 - NOT btc. If you need btc representation, use Decimal:
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, {fingerprint: number; path: number[]}]; // [PubKey, DeriviationPath]
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 compressed = hex.decode(
569
- '030000000000000000000000000000000000000000000000000000000000000001'
570
- );
571
- const script = btc.p2pkh(compressed).script;
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 txP2WPKH = new btc.Transaction();
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(secp256k1.getPublicKey(privKey, true)).script,
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, 'cbb94443b19861df0824914fa654212facc071854e0df6f7388b482a6394526d');
782
+ deepStrictEqual(txP2WPKH.id, 'e4db0a196f378a6648deb221a2771fde577892479a2d52abbe8cf3d31d2f140f');
636
783
  deepStrictEqual(
637
784
  txP2WPKH.hex,
638
- '010000000001033edaa6c4e0740ae334dbb5857dd8c6faf6ea5196760652ad7033ed9031c261c00000000000ffffffff0d9ae8a4191b3ba5a2b856c21af0f7a4feb97957ae80725ef38a933c906519a20000000000ffffffffc7a4a37d38c2b0de3d3b3e8d8e8a331977c12532fc2a4632df27a89c311ee2fa0000000000ffffffff03e8030000000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac881300000000000017a914a860f76561c85551594c18eecceffaee8c4822d7876b24000000000000160014e8df018c7e326cc253faac7e46cdc51e68542c4202473044022024e7b1a6ae19a95c69c192745db09cc54385a80cc7684570cfbf2da84cbbfa0802205ad55efb2019a1aa6edc03cf243989ea428c4d216699cbae2cfaf3c26ddef5650121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f0247304402204415ef16f341e888ca2483b767b47fcf22977b6d673c3f7c6cae2f6b4bc2ac08022055be98747345b02a6f40edcc2f80390dcef4efe57b38c1bb7d16bdbca710abfd0121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f02473044022069769fb5c97a7dd9401dbd3f6d32a38fe82bc8934c49c7c4cd3b39c6d120080c02202c181604203dc45c10e5290ded103195fae117d7fb0db19cdc411e73a76da6cb0121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f00000000'
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 = secp256k1.getPublicKey(privKey, true);
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
- // NOTE: 'selected' will 'undefined' if there is not enough funds
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.js](./test/bip327-musig2.test.js) as well:
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 = randomBytes(32); // Alice generates a random 32-byte secret key
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 = randomBytes(32); // Bob generates a random 32-byte secret key
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 = randomBytes(32); // Carol generates a random 32-byte secret key
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
- // Verify the final signature
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', priv),
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) => (P.isBytes(i) ? hex.encode(i) : i)),
1331
+ ).map((i) => (btc.utils.isBytes(i) ? hex.encode(i) : i)),
1063
1332
  [
1064
- 'OP_2',
1333
+ 2,
1065
1334
  '030000000000000000000000000000000000000000000000000000000000000001',
1066
1335
  '030000000000000000000000000000000000000000000000000000000000000002',
1067
1336
  '030000000000000000000000000000000000000000000000000000000000000003',
1068
- 'OP_3',
1337
+ 3,
1069
1338
  'CHECKMULTISIG',
1070
1339
  ]
1071
1340
  );
1072
1341
  deepStrictEqual(
1073
1342
  hex.encode(
1074
1343
  btc.Script.encode([
1075
- 'OP_2',
1344
+ 2,
1076
1345
  hex.decode('030000000000000000000000000000000000000000000000000000000000000001'),
1077
1346
  hex.decode('030000000000000000000000000000000000000000000000000000000000000002'),
1078
1347
  hex.decode('030000000000000000000000000000000000000000000000000000000000000003'),
1079
- 'OP_3',
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
- - micro-bmark, micro-should and jsbt are used for benchmarking / testing / build tooling and developed by the same author
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: