@scure/btc-signer 2.2.0 → 2.4.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/p2p.d.ts CHANGED
@@ -44,4 +44,3 @@ export declare const elligatorSwift: Readonly<{
44
44
  getSharedSecret: (privateKeyA: TArg<Uint8Array>, publicKeyB: TArg<Uint8Array>) => TRet<Bytes>;
45
45
  getSharedSecretBip324: (privateKeyOurs: TArg<Uint8Array>, publicKeyTheirs: TArg<Uint8Array>, publicKeyOurs: TArg<Uint8Array>, initiating: boolean) => TRet<Uint8Array>;
46
46
  }>;
47
- //# sourceMappingURL=p2p.d.ts.map
package/p2p.js CHANGED
@@ -39,6 +39,9 @@ const MINUS_3_SQRT = Fp.sqrt(Fp.create(BigInt(-3)));
39
39
  const _3n = BigInt(3);
40
40
  const _4n = BigInt(4);
41
41
  const _7n = BigInt(7);
42
+ // Precomputed 1/2 mod p: turns the frequent "divide by 2" steps of XSwiftEC/XSwiftECInv
43
+ // into single multiplications instead of one modular inversion per call.
44
+ const INV_2 = Fp.inv(Fp.create(_2n));
42
45
  // This is the "lift_x(x) succeeds" predicate for field-normalized x values.
43
46
  // Raw x >= p would need the full BIP340 range check before reducing modulo p.
44
47
  const isValidX = (x) => FpIsSquare(Fp, Fp.add(Fp.mul(Fp.mul(x, x), x), _7n));
@@ -93,7 +96,7 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
93
96
  return; // [2 condition]
94
97
  if (ellCase & 1 && Fp.is0(r))
95
98
  return;
96
- v = Fp.div(Fp.add(Fp.neg(u), Fp.div(r, s)), _2n); // v = (-u + r / s) / 2
99
+ v = Fp.mul(Fp.add(Fp.neg(u), Fp.div(r, s)), INV_2); // v = (-u + r / s) / 2
97
100
  }
98
101
  const w = trySqrt(s);
99
102
  if (w === undefined)
@@ -102,7 +105,7 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
102
105
  const t0 = last & 1 ? Fp.add(_1n, MINUS_3_SQRT) : Fp.sub(_1n, MINUS_3_SQRT);
103
106
  const w0 = last === 0 || last === 5 ? Fp.neg(w) : w; // -w | w
104
107
  // w0 * (u * t0 / 2 + v)
105
- return Fp.mul(w0, Fp.add(Fp.div(Fp.mul(u, t0), _2n), v));
108
+ return Fp.mul(w0, Fp.add(Fp.mul(Fp.mul(u, t0), INV_2), v));
106
109
  },
107
110
  // Encode public key (point or x coordinate bigint) into 64-byte pseudorandom encoding
108
111
  // BIP324 samples encodings for x(P), so callers must pass a curve X coordinate in 0..p-1;
@@ -112,11 +115,19 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
112
115
  // so encode() must reject out-of-range x instead of silently reducing a different bigint modulo p.
113
116
  if (!Fp.isValid(x))
114
117
  throw new RangeError('elligatorSwift.encode: expected x coordinate in range 0..p-1');
118
+ // Off-curve x cannot round-trip: decode() only returns lift_x-able candidates, so
119
+ // the loop below would silently emit an encoding of a *different* public key.
120
+ if (!isValidX(x))
121
+ throw new RangeError('elligatorSwift.encode: expected x coordinate of a curve point');
115
122
  // 200k test cycles per keygen: avg=4 max=48
116
123
  // seems too much, but same as for reference implementation
117
124
  while (true) {
118
- // random scalar 1..Fp.ORDER
119
- const u = Fp.create(Fp.fromBytes(secp256k1.utils.randomSecretKey()));
125
+ // Random field element 1..p-1: BIP324 samples u over the whole field (the previous
126
+ // secret-key sampler silently restricted u to 1..n-1); decode() remaps u = 0, so
127
+ // zero cannot round-trip and is skipped.
128
+ const u = Fp.create(Fp.fromBytes(randomBytes(32), true));
129
+ if (Fp.is0(u))
130
+ continue;
120
131
  const ellCase = randomBytes(1)[0] & 7; // [0..8)
121
132
  const t = elligatorSwift._inv(x, u, ellCase);
122
133
  if (!t)
@@ -148,10 +159,12 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
148
159
  let res = Fp.add(u, Fp.mul(Fp.mul(y, y), _4n)); // u + 4 * Y ** 2,
149
160
  if (isValidX(res))
150
161
  return Fp.toBytes(res);
151
- res = Fp.div(Fp.sub(Fp.div(Fp.neg(x), y), u), _2n); // (-X / Y - u) / 2
162
+ // X / Y is shared by the remaining candidates; computing it once saves an inversion.
163
+ const xDivY = Fp.div(x, y);
164
+ res = Fp.mul(Fp.sub(Fp.neg(xDivY), u), INV_2); // (-X / Y - u) / 2
152
165
  if (isValidX(res))
153
166
  return Fp.toBytes(res);
154
- res = Fp.div(Fp.sub(Fp.div(x, y), u), _2n); // (X / Y - u) / 2
167
+ res = Fp.mul(Fp.sub(xDivY, u), INV_2); // (X / Y - u) / 2
155
168
  if (isValidX(res))
156
169
  return Fp.toBytes(res);
157
170
  throw new Error('elligatorSwift: cannot decode public key');
@@ -178,6 +191,10 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
178
191
  getSharedSecretBip324: (privateKeyOurs, publicKeyTheirs, publicKeyOurs, initiating) => {
179
192
  // BIP324 Shared secret computation hashes "the exactly 64-byte public keys'
180
193
  // encodings sent over the wire", so both ElligatorSwift inputs must be 64 bytes here.
194
+ // Initiator/responder ordering decides the hash-input order, so require a real boolean
195
+ // instead of letting arbitrary truthy values pick a side.
196
+ if (typeof initiating !== 'boolean')
197
+ throw new TypeError('"initiating" expected boolean, got type=' + typeof initiating);
181
198
  const ours = abytes(publicKeyOurs, 64, 'publicKeyOurs');
182
199
  const theirs = abytes(publicKeyTheirs, 64, 'publicKeyTheirs');
183
200
  const ecdhPoint = elligatorSwift.getSharedSecret(privateKeyOurs, theirs);
@@ -185,4 +202,3 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
185
202
  return tagSchnorr('bip324_ellswift_xonly_ecdh', ...pubs, ecdhPoint);
186
203
  },
187
204
  });
188
- //# sourceMappingURL=p2p.js.map
package/package.json CHANGED
@@ -1,46 +1,43 @@
1
1
  {
2
2
  "name": "@scure/btc-signer",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "Audited & minimal library for Bitcoin. Handle transactions, Schnorr, Taproot, UTXO & PSBT",
5
5
  "files": [
6
6
  "*.js",
7
7
  "*.d.ts",
8
- "*.js.map",
9
- "*.d.ts.map",
10
8
  "src",
11
9
  "!_type_test.*"
12
10
  ],
13
11
  "dependencies": {
14
- "@noble/curves": "~2.2.0",
15
- "@noble/hashes": "~2.2.0",
16
- "@scure/base": "~2.2.0",
17
- "micro-packed": "~0.9.0"
12
+ "@noble/curves": "2.4.0",
13
+ "@noble/hashes": "2.4.0",
14
+ "@scure/base": "2.4.0",
15
+ "micro-packed": "0.11.0"
18
16
  },
19
17
  "devDependencies": {
20
- "@paulmillr/jsbt": "0.5.0",
21
- "@scure/bip32": "2.0.0",
22
- "prettier": "3.6.2",
23
- "typescript": "6.0.2"
18
+ "@paulmillr/jsbt": "0.7.1",
19
+ "bismar": "0.1.8",
20
+ "@scure/bip32": "2.4.0",
21
+ "micro-ftch": "1.2.0",
22
+ "prettier": "3.9.6",
23
+ "typescript": "6.0.3"
24
24
  },
25
25
  "scripts": {
26
+ "benchmark:size": "bismar -bsm",
26
27
  "build": "tsc",
27
- "build:clean": "rm -f *.{js,d.ts,js.map,d.ts.map}",
28
- "build:release": "npx --no @paulmillr/jsbt esbuild test/build",
29
- "check": "npx --no @paulmillr/jsbt check package.json",
30
- "check:readme": "npx --no @paulmillr/jsbt readme package.json",
31
- "check:treeshake": "npx --no @paulmillr/jsbt treeshake package.json test/build/out-treeshake",
32
- "check:jsdoc": "npx --no @paulmillr/jsbt tsdoc package.json",
28
+ "build:clean": "rm -f *.{js,d.ts}",
29
+ "check": "jsbt-check",
30
+ "benchmark": "node benchmark/index.ts",
31
+ "benchmark:network": "node benchmark/network.ts",
33
32
  "format": "prettier --write src test/*.test.*",
34
- "test": "node --no-warnings test/index.ts",
35
- "test:bun": "bun test/index.ts",
36
- "test:deno": "deno --allow-env --allow-read test/index.ts",
37
- "test:node20": "cd test; npx tsc; node compiled/test/index.js",
33
+ "test": "node test/index.ts",
38
34
  "test:slow": "node test/slow.test.ts",
39
35
  "test:extended": "node --experimental-loader ./test/bitcoinjs-test/esm-loader.js ./test/bitcoinjs-test/index.test.js"
40
36
  },
41
37
  "exports": {
42
38
  ".": "./index.js",
43
39
  "./musig2.js": "./musig2.js",
40
+ "./net.js": "./net.js",
44
41
  "./p2p.js": "./p2p.js",
45
42
  "./payment.js": "./payment.js",
46
43
  "./psbt.js": "./psbt.js",
package/payment.d.ts CHANGED
@@ -91,7 +91,8 @@ export type CustomScript = Coder<OptScript, CustomScriptOut | undefined> & {
91
91
  export declare const OutScript: TRet<P.CoderType<NonNullable<OutP2AType | OutPKType | OutPKHType | OutSHType | OutWSHType | OutWPKHType | OutMSType | OutTRType | OutTRNSType | OutTRMSType | OutUnknownType | undefined>>>;
92
92
  /** Type of the output-script coder. */
93
93
  export type OutScriptType = typeof OutScript;
94
- type OutScriptValue = ReturnType<OutScriptType['decode']> | undefined;
94
+ export declare const _WitnessOutScript: OutScriptType;
95
+ type AddressValue = NonNullable<ReturnType<OutScriptType['decode']>>;
95
96
  /**
96
97
  * Validates that nested redeem and witness scripts match their wrappers.
97
98
  * @param script - top-level output script
@@ -186,6 +187,7 @@ export type P2SHReturn<T extends P2Ret> = T extends {
186
187
  * Wraps a child script inside P2SH.
187
188
  * @param child - child payment descriptor to wrap
188
189
  * @param network - address network parameters
190
+ * @param allowNonCanonicalScript - whether to create an address for a non-minimal child script
189
191
  * @returns P2SH descriptor preserving witness metadata when present.
190
192
  * @throws If the wrapped script combination is invalid or unsupported. {@link Error}
191
193
  * @example
@@ -196,7 +198,7 @@ export type P2SHReturn<T extends P2Ret> = T extends {
196
198
  * p2sh(p2wsh(p2pk(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'))));
197
199
  * ```
198
200
  */
199
- export declare const p2sh: <T extends P2Ret>(child: TArg<T>, network?: BTC_NETWORK) => TRet<Extends<P2SHReturn<T>, P2Ret>>;
201
+ export declare const p2sh: <T extends P2Ret>(child: TArg<T>, network?: BTC_NETWORK, allowNonCanonicalScript?: boolean) => TRet<Extends<P2SHReturn<T>, P2Ret>>;
200
202
  /** Pay-to-witness-script-hash descriptor. */
201
203
  export type P2WSH = {
202
204
  /** Payment-script tag for pay-to-witness-script-hash outputs. */
@@ -214,6 +216,7 @@ export type P2WSH = {
214
216
  * Wraps a child script inside native SegWit P2WSH.
215
217
  * @param child - child payment descriptor to wrap
216
218
  * @param network - address network parameters
219
+ * @param allowNonCanonicalScript - whether to create an address for a non-minimal child script
217
220
  * @returns P2WSH descriptor.
218
221
  * @throws If the wrapped script combination is invalid or unsupported. {@link Error}
219
222
  * @example
@@ -224,7 +227,7 @@ export type P2WSH = {
224
227
  * p2wsh(p2pk(hex.decode('0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')));
225
228
  * ```
226
229
  */
227
- export declare const p2wsh: (child: TArg<P2Ret>, network?: BTC_NETWORK) => TRet<Extends<P2WSH, P2Ret>>;
230
+ export declare const p2wsh: (child: TArg<P2Ret>, network?: BTC_NETWORK, allowNonCanonicalScript?: boolean) => TRet<Extends<P2WSH, P2Ret>>;
228
231
  /** Pay-to-witness-public-key-hash descriptor. */
229
232
  export type P2WPKH = {
230
233
  /** Payment-script tag for pay-to-witness-public-key-hash outputs. */
@@ -380,6 +383,7 @@ export type P2TRRet<T> = T extends TaprootScriptTree ? P2TR_TREE : P2TR;
380
383
  * @param customScripts - optional custom script codecs for taproot leaves
381
384
  * @returns Taproot descriptor with optional script-path metadata.
382
385
  * @throws If the internal key or taproot script tree is invalid. {@link Error}
386
+ * @throws If a numeric script value is outside its supported range. {@link RangeError}
383
387
  * @example
384
388
  * Combine script leaves into a final taproot output descriptor and address.
385
389
  * ```ts
@@ -393,23 +397,27 @@ export type P2TRRet<T> = T extends TaprootScriptTree ? P2TR_TREE : P2TR;
393
397
  */
394
398
  export declare function p2tr(internalPubKey: TArg<Bytes | string>, tree?: undefined, network?: BTC_NETWORK, allowUnknownOutputs?: boolean, customScripts?: TArg<CustomScript[]>): TRet<Extends<P2TR, P2Ret>>;
395
399
  export declare function p2tr(internalPubKey: TArg<Bytes | string | undefined>, tree: TArg<TaprootScriptTree>, network?: BTC_NETWORK, allowUnknownOutputs?: boolean, customScripts?: TArg<CustomScript[]>): TRet<Extends<P2TR_TREE, P2Ret>>;
400
+ /** Maximum number of combinations materialized by one default helper call. */
401
+ export declare const MAX_COMBINATIONS = 4096;
396
402
  /**
397
403
  * Returns all size-`m` combinations from a list.
398
404
  * @param m - size of each combination
399
405
  * @param list - input items to combine
406
+ * @param maxCombinations - maximum result rows to materialize
400
407
  * @returns Array of combinations.
401
408
  * @throws If the combination size or input list is invalid. {@link Error}
409
+ * @throws If the requested result exceeds the materialization limit. {@link RangeError}
402
410
  * @example
403
411
  * Enumerate all size-two subsets of a short list.
404
412
  * ```ts
405
413
  * combinations(2, [1, 2, 3]);
406
414
  * ```
407
415
  */
408
- export declare function combinations<T>(m: number, list: T[]): T[][];
416
+ export declare function combinations<T>(m: number, list: T[], maxCombinations?: number): T[][];
409
417
  /**
410
418
  * M-of-N multi-leaf wallet via p2tr_ns. If m == n, single script is emitted.
411
- * Takes O(n^2) if m != n. 99-of-100 is ok, 5-of-100 is not.
412
- * It materializes C(n, m) leaves, so middle-of-the-range thresholds blow up combinatorially.
419
+ * It materializes C(n, m) leaves up to {@link MAX_COMBINATIONS}; middle-of-the-range thresholds
420
+ * above that bound are rejected before allocation.
413
421
  * `2-of-[A,B,C] => [A,B] | [A,C] | [B,C]`
414
422
  */
415
423
  export type P2TR_NS = {
@@ -425,6 +433,7 @@ export type P2TR_NS = {
425
433
  * @param allowSamePubkeys - whether duplicate keys are allowed
426
434
  * @returns Array of taproot leaf descriptors.
427
435
  * @throws If the taproot multisig parameters are invalid. {@link Error}
436
+ * @throws If the requested leaf set exceeds the materialization limit. {@link RangeError}
428
437
  * @example
429
438
  * Build the leaf set for an M-of-N taproot `CHECKSIGVERIFY` policy.
430
439
  * ```ts
@@ -443,6 +452,7 @@ export type P2TR_PK = P2TR_NS;
443
452
  * @param pubkey - Schnorr public key
444
453
  * @returns Taproot single-key leaf descriptor.
445
454
  * @throws If the taproot single-key leaf cannot be encoded. {@link Error}
455
+ * @throws If the delegated leaf policy exceeds its supported range. {@link RangeError}
446
456
  * @example
447
457
  * Build a single-key tapscript leaf.
448
458
  * ```ts
@@ -482,6 +492,7 @@ export declare function p2tr_ms(m: number, pubkeys: TArg<Bytes[]>, allowSamePubk
482
492
  * @param network - address network parameters
483
493
  * @returns Encoded Bitcoin address.
484
494
  * @throws If the requested address type is unknown. {@link Error}
495
+ * @throws If a key-derived script value is outside its supported range. {@link RangeError}
485
496
  * @example
486
497
  * Pick the output type first, then derive the matching address from the private key.
487
498
  * ```ts
@@ -561,9 +572,5 @@ export declare function WIF(network?: BTC_NETWORK): TRet<Coder<Bytes, string>>;
561
572
  * coder.encode(p2wpkh(pubECDSA(randomPrivateKeyBytes())));
562
573
  * ```
563
574
  */
564
- export declare function Address(network?: BTC_NETWORK): {
565
- encode(from: Exclude<OutScriptValue, undefined>): string;
566
- decode(address: string): OutScriptValue;
567
- };
575
+ export declare function Address(network?: BTC_NETWORK): TRet<P.Coder<AddressValue, string>>;
568
576
  export {};
569
- //# sourceMappingURL=payment.d.ts.map