@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/utils.js CHANGED
@@ -1,27 +1,208 @@
1
1
  import { schnorr, secp256k1 as secp } from '@noble/curves/secp256k1.js';
2
- import { bytesToNumberBE, numberToBytesBE } from '@noble/curves/utils.js';
2
+ import { abytes, bytesToNumberBE, numberToBytesBE } from '@noble/curves/utils.js';
3
3
  import { ripemd160 } from '@noble/hashes/legacy.js';
4
- import { sha256 } from '@noble/hashes/sha2.js';
4
+ import { sha256 as nobleSha256 } from '@noble/hashes/sha2.js';
5
+ import {} from '@noble/hashes/utils.js';
5
6
  import { utils as packedUtils, U32LE } from 'micro-packed';
6
- const Point = secp.Point;
7
- const Fn = Point.Fn;
8
- const CURVE_ORDER = Point.Fn.ORDER;
9
- export const hasEven = (y) => y % 2n === 0n;
10
- const isBytes = packedUtils.isBytes;
11
- const concatBytes = packedUtils.concatBytes;
12
- const equalBytes = packedUtils.equalBytes;
13
- export { concatBytes, equalBytes, isBytes, sha256 };
7
+ export { abytes, validateObject as vld } from '@noble/curves/utils.js';
8
+ export {} from '@noble/hashes/utils.js';
9
+ /**
10
+ * Validates that a value is a non-negative bigint.
11
+ * @param n - Value to validate.
12
+ * @param title - Label included in thrown errors.
13
+ * @returns The same bigint.
14
+ * @throws On wrong argument types. {@link TypeError}
15
+ * @example
16
+ * Validate a satoshi amount before transaction encoding.
17
+ * ```ts
18
+ * abigint(1n, 'amount');
19
+ * ```
20
+ */
21
+ export function abigint(n, title = 'value') {
22
+ if (typeof n !== 'bigint')
23
+ throw new TypeError(`"${title}" expected bigint, got type=${typeof n}`);
24
+ if (n < _0n)
25
+ throw new RangeError(`"${title}" expected non-negative bigint, got ${n}`);
26
+ return n;
27
+ }
28
+ import { validateObject as vld } from '@noble/curves/utils.js';
29
+ export function aarray(item, title, inner = () => { }) {
30
+ if (!Array.isArray(item))
31
+ throw new TypeError(`"${title}" expected array, got type=${typeof item}`);
32
+ for (let i = 0; i < item.length; i++)
33
+ inner(item[i], `${title}[${i}]`);
34
+ return item;
35
+ }
36
+ /**
37
+ * Asserts something is a string.
38
+ * @param value - Value to validate.
39
+ * @param title - Label included in thrown errors.
40
+ * @returns The validated string.
41
+ * @throws On wrong argument types. {@link TypeError}
42
+ * @example
43
+ * Validate a label string.
44
+ *
45
+ * ```ts
46
+ * astring('example', 'label');
47
+ * ```
48
+ */
49
+ export function astring(value, title = '') {
50
+ if (typeof value !== 'string') {
51
+ const prefix = title && `"${title}" `;
52
+ throw new TypeError(prefix + 'expected string, got type=' + typeof value);
53
+ }
54
+ return value;
55
+ }
56
+ export function validateObject(object, fields = {}, optFields = {}, _title = 'object') {
57
+ return vld(object, fields, optFields);
58
+ }
59
+ const Point = /* @__PURE__ */ (() => secp.Point)();
60
+ const Fn = /* @__PURE__ */ (() => Point.Fn)();
61
+ const CURVE_ORDER = /* @__PURE__ */ (() => Point.Fn.ORDER)();
62
+ // Be friendly to bad ECMAScript parsers by not using bigint literals.
63
+ // prettier-ignore
64
+ const _0n = /* @__PURE__ */ BigInt(0), _2n = /* @__PURE__ */ BigInt(2);
65
+ /**
66
+ * Checks whether a curve y-coordinate is even.
67
+ * @param y - y-coordinate to inspect
68
+ * @returns `true` when the coordinate is even.
69
+ * @example
70
+ * Check whether a point coordinate has even parity.
71
+ * ```ts
72
+ * hasEven(2n);
73
+ * ```
74
+ */
75
+ export const hasEven = (y) => y % _2n === _0n;
76
+ /**
77
+ * Checks whether a value is a Uint8Array.
78
+ * @param a - value to inspect
79
+ * @returns `true` when the value is a Uint8Array.
80
+ * @example
81
+ * Check whether an unknown value is already bytes.
82
+ * ```ts
83
+ * isBytes(new Uint8Array([1]));
84
+ * ```
85
+ */
86
+ export const isBytes = /* @__PURE__ */ (() => packedUtils.isBytes)();
87
+ /**
88
+ * Concatenates byte arrays into a single Uint8Array.
89
+ * @param arrays - byte arrays to concatenate
90
+ * @returns Concatenated byte array.
91
+ * @example
92
+ * Join several byte chunks before hashing or signing them.
93
+ * ```ts
94
+ * concatBytes(new Uint8Array([1]), new Uint8Array([2]));
95
+ * ```
96
+ */
97
+ export const concatBytes =
98
+ /* @__PURE__ */ (() => packedUtils.concatBytes)();
99
+ /**
100
+ * Compares two byte arrays for equality.
101
+ * @param a - first byte array
102
+ * @param b - second byte array
103
+ * @returns `true` when both arrays contain the same bytes.
104
+ * @example
105
+ * Compare two serialized values without converting them first.
106
+ * ```ts
107
+ * equalBytes(new Uint8Array([1]), new Uint8Array([1]));
108
+ * ```
109
+ */
110
+ export const equalBytes =
111
+ /* @__PURE__ */ (() => packedUtils.equalBytes)();
112
+ /**
113
+ * SHA-256 hash function.
114
+ * @param msg - bytes to hash
115
+ * @returns SHA-256 digest.
116
+ * @example
117
+ * Hash a byte array with SHA-256.
118
+ * ```ts
119
+ * sha256(new Uint8Array([1, 2, 3]));
120
+ * ```
121
+ */
122
+ export const sha256 = /* @__PURE__ */ (() => nobleSha256)();
123
+ /**
124
+ * HASH160 helper used by classic Bitcoin addresses.
125
+ * @param msg - bytes to hash
126
+ * @returns RIPEMD160(SHA256(msg)).
127
+ * @example
128
+ * Derive the HASH160 used by legacy address formats.
129
+ * ```ts
130
+ * hash160(new Uint8Array([1, 2, 3]));
131
+ * ```
132
+ */
14
133
  export const hash160 = (msg) => ripemd160(sha256(msg));
134
+ /**
135
+ * Double-SHA256 helper used by Bitcoin transaction ids.
136
+ * @param msgs - message parts to concatenate and hash
137
+ * @returns SHA256(SHA256(concat(msgs))).
138
+ * @example
139
+ * Compute the double-SHA256 used by txids and sighashes.
140
+ * ```ts
141
+ * sha256x2(new Uint8Array([1]), new Uint8Array([2]));
142
+ * ```
143
+ */
15
144
  export const sha256x2 = (...msgs) => sha256(sha256(concatBytes(...msgs)));
16
- export const randomPrivateKeyBytes = schnorr.utils.randomSecretKey;
17
- export const pubSchnorr = schnorr.getPublicKey;
18
- export const pubECDSA = secp.getPublicKey;
145
+ /**
146
+ * Generates a random secp256k1 private key.
147
+ * @returns Random 32-byte private key.
148
+ * @example
149
+ * Generate a fresh secp256k1 private key for signing.
150
+ * ```ts
151
+ * const privKey = randomPrivateKeyBytes();
152
+ * ```
153
+ */
154
+ export const randomPrivateKeyBytes = () => schnorr.utils.randomSecretKey();
155
+ /**
156
+ * Derives a BIP340 Schnorr public key from a private key.
157
+ * @param priv - private key bytes
158
+ * @returns X-only public key bytes.
159
+ * @example
160
+ * Derive the x-only public key used by Schnorr and Taproot.
161
+ * ```ts
162
+ * import { pubSchnorr, randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
163
+ * pubSchnorr(randomPrivateKeyBytes());
164
+ * ```
165
+ */
166
+ export const pubSchnorr = (priv) => schnorr.getPublicKey(priv);
167
+ /**
168
+ * Derives a secp256k1 ECDSA public key from a private key.
169
+ * @param privateKey - private key bytes
170
+ * @param isCompressed - whether to return the compressed form
171
+ * @returns Serialized public key bytes.
172
+ * @example
173
+ * Derive the normal secp256k1 public key for legacy or SegWit scripts.
174
+ * ```ts
175
+ * import { pubECDSA, randomPrivateKeyBytes } from '@scure/btc-signer/utils.js';
176
+ * pubECDSA(randomPrivateKeyBytes());
177
+ * ```
178
+ */
179
+ export const pubECDSA = (privateKey, isCompressed) => secp.getPublicKey(privateKey, isCompressed);
19
180
  // low-r signature grinding. Used to reduce tx size by 1 byte.
20
181
  // noble/secp256k1 does not support the feature: it is not used outside of BTC.
21
182
  // We implement it manually, because in BTC it's common.
22
183
  // Not best way, but closest to bitcoin implementation (easier to check)
23
- const hasLowR = (sig) => sig.r < CURVE_ORDER / 2n;
184
+ // Hoisted: the bound is constant; no need to redo the bigint division on every
185
+ // grinding-loop iteration. n/2 < 2^255, so r < n/2 guarantees the 32-byte DER r.
186
+ const LOW_R_BOUND = /* @__PURE__ */ (() => CURVE_ORDER / _2n)();
187
+ const hasLowR = (sig) => sig.r < LOW_R_BOUND;
188
+ /**
189
+ * Signs a 32-byte hash with ECDSA and returns DER encoding.
190
+ * @param hash - message hash to sign
191
+ * @param privateKey - signer private key
192
+ * @param lowR - whether to grind for low-R signatures
193
+ * @returns DER-encoded signature bytes.
194
+ * @throws If low-R grinding overflows or ECDSA signing fails validation. {@link Error}
195
+ * @example
196
+ * Hash a message first, then create the DER-encoded ECDSA signature.
197
+ * ```ts
198
+ * import { randomPrivateKeyBytes, sha256, signECDSA } from '@scure/btc-signer/utils.js';
199
+ * signECDSA(sha256(new Uint8Array([1, 2, 3])), randomPrivateKeyBytes());
200
+ * ```
201
+ */
24
202
  export function signECDSA(hash, privateKey, lowR = false) {
203
+ // signECDSA is the 32-byte sighash wrapper for BTC callers, so reject arbitrary-length
204
+ // messages here instead of silently signing them with prehash disabled.
205
+ abytes(hash, 32, 'hash');
25
206
  let sig = secp.Signature.fromBytes(secp.sign(hash, privateKey, { prehash: false }));
26
207
  if (lowR && !hasLowR(sig)) {
27
208
  const extraEntropy = new Uint8Array(32);
@@ -35,30 +216,89 @@ export function signECDSA(hash, privateKey, lowR = false) {
35
216
  }
36
217
  return sig.toBytes('der');
37
218
  }
38
- export const signSchnorr = schnorr.sign;
39
- export const tagSchnorr = schnorr.utils.taggedHash;
40
- export const PubT = {
219
+ /**
220
+ * BIP340 Schnorr signing function.
221
+ * @param message - 32-byte message digest
222
+ * @param secretKey - signer private key
223
+ * @param auxRand - optional auxiliary randomness
224
+ * @returns Schnorr signature bytes.
225
+ * @example
226
+ * Sign a 32-byte digest with the built-in BIP340 helper.
227
+ * ```ts
228
+ * import { randomPrivateKeyBytes, sha256, signSchnorr } from '@scure/btc-signer/utils.js';
229
+ * const msg = sha256(new Uint8Array([1, 2, 3]));
230
+ * signSchnorr(msg, randomPrivateKeyBytes());
231
+ * ```
232
+ */
233
+ export const signSchnorr = (message, secretKey, auxRand) => schnorr.sign(message, secretKey, auxRand);
234
+ /**
235
+ * Tagged-hash helper used by Schnorr and taproot constructions.
236
+ * @param tag - tagged-hash domain separator
237
+ * @param messages - message parts hashed under the tag
238
+ * @returns Tagged SHA-256 digest.
239
+ * @example
240
+ * Build the tagged hash used by Taproot leaves or tweaks.
241
+ * ```ts
242
+ * import { tagSchnorr } from '@scure/btc-signer/utils.js';
243
+ * tagSchnorr('TapLeaf', Uint8Array.of(0xc0), Uint8Array.of(0x51));
244
+ * ```
245
+ */
246
+ export const tagSchnorr = (tag, ...messages) => schnorr.utils.taggedHash(tag, ...messages);
247
+ /** Public key format tags used by validation helpers. */
248
+ export const PubT = /* @__PURE__ */ (() => Object.freeze({
41
249
  ecdsa: 0,
42
250
  schnorr: 1,
43
- };
251
+ }))();
252
+ /**
253
+ * Validates a public key against the expected Bitcoin key encoding.
254
+ * @param pub - public key bytes to validate
255
+ * @param type - expected public key format
256
+ * @returns The validated public key bytes.
257
+ * @throws On wrong argument types. {@link TypeError}
258
+ * @throws On wrong argument ranges or values. {@link RangeError}
259
+ * @example
260
+ * Reject keys that do not match the encoding required by the current script path.
261
+ * ```ts
262
+ * import {
263
+ * PubT,
264
+ * pubECDSA,
265
+ * randomPrivateKeyBytes,
266
+ * validatePubkey,
267
+ * } from '@scure/btc-signer/utils.js';
268
+ * validatePubkey(pubECDSA(randomPrivateKeyBytes()), PubT.ecdsa);
269
+ * ```
270
+ */
44
271
  export function validatePubkey(pub, type) {
45
272
  const len = pub.length;
46
273
  if (type === PubT.ecdsa) {
47
274
  if (len === 32)
48
- throw new Error('Expected non-Schnorr key');
275
+ throw new RangeError('Expected non-Schnorr key');
49
276
  Point.fromBytes(pub); // does assertValidity
50
277
  return pub;
51
278
  }
52
279
  else if (type === PubT.schnorr) {
53
280
  if (len !== 32)
54
- throw new Error('Expected 32-byte Schnorr key');
281
+ throw new RangeError('Expected 32-byte Schnorr key');
55
282
  schnorr.utils.lift_x(bytesToNumberBE(pub));
56
283
  return pub;
57
284
  }
58
285
  else {
59
- throw new Error('Unknown key type');
286
+ throw new TypeError('Unknown key type');
60
287
  }
61
288
  }
289
+ /**
290
+ * Computes the Taproot tweak scalar from an internal key and merkle root.
291
+ * @param a - internal key bytes
292
+ * @param b - optional merkle root bytes
293
+ * @returns Taproot tweak scalar.
294
+ * @throws If the tweak scalar is outside the curve order. {@link Error}
295
+ * @example
296
+ * Combine the internal key and Merkle root into the Taproot tweak scalar.
297
+ * ```ts
298
+ * import { pubSchnorr, randomPrivateKeyBytes, tapTweak } from '@scure/btc-signer/utils.js';
299
+ * tapTweak(pubSchnorr(randomPrivateKeyBytes()), new Uint8Array());
300
+ * ```
301
+ */
62
302
  export function tapTweak(a, b) {
63
303
  const u = schnorr.utils;
64
304
  const t = u.taggedHash('TapTweak', a, b);
@@ -67,8 +307,24 @@ export function tapTweak(a, b) {
67
307
  throw new Error('tweak higher than curve order');
68
308
  return tn;
69
309
  }
310
+ /**
311
+ * Tweaks a private key for Taproot key-path spending.
312
+ * @param privKey - internal private key bytes
313
+ * @param merkleRoot - optional taproot merkle root
314
+ * @returns Tweaked private key bytes.
315
+ * @throws If the Taproot tweak scalar is outside the curve order. {@link Error}
316
+ * @example
317
+ * Derive the tweaked Taproot key-path secret from the internal private key.
318
+ * ```ts
319
+ * import { randomPrivateKeyBytes, taprootTweakPrivKey } from '@scure/btc-signer/utils.js';
320
+ * taprootTweakPrivKey(randomPrivateKeyBytes());
321
+ * ```
322
+ */
70
323
  export function taprootTweakPrivKey(privKey, merkleRoot = Uint8Array.of()) {
71
324
  const u = schnorr.utils;
325
+ // BIP341 taproot_tweak_seckey starts with `seckey0 = int_from_bytes(seckey0)`, and
326
+ // BIP340 defines `int(x)` only for `x` as a 32-byte array, so reject other widths here.
327
+ abytes(privKey, 32, 'privKey');
72
328
  const seckey0 = bytesToNumberBE(privKey); // seckey0 = int_from_bytes(seckey0)
73
329
  const P = Point.BASE.multiply(seckey0); // P = point_mul(G, seckey0)
74
330
  // seckey = seckey0 if has_even_y(P) else SECP256K1_ORDER - seckey0
@@ -79,8 +335,28 @@ export function taprootTweakPrivKey(privKey, merkleRoot = Uint8Array.of()) {
79
335
  // bytes_from_int((seckey + t) % SECP256K1_ORDER)
80
336
  return numberToBytesBE(Fn.add(seckey, t), 32);
81
337
  }
338
+ /**
339
+ * Tweaks a Schnorr public key for Taproot key-path spending.
340
+ * @param pubKey - x-only internal public key
341
+ * @param h - taproot merkle root
342
+ * @returns Tweaked public key and output-key parity.
343
+ * @throws If the Taproot tweak scalar is outside the curve order. {@link Error}
344
+ * @example
345
+ * Derive the final Taproot output key from the internal key and Merkle root.
346
+ * ```ts
347
+ * import {
348
+ * pubSchnorr,
349
+ * randomPrivateKeyBytes,
350
+ * taprootTweakPubkey,
351
+ * } from '@scure/btc-signer/utils.js';
352
+ * taprootTweakPubkey(pubSchnorr(randomPrivateKeyBytes()), new Uint8Array());
353
+ * ```
354
+ */
82
355
  export function taprootTweakPubkey(pubKey, h) {
83
356
  const u = schnorr.utils;
357
+ // BIP341 taproot_tweak_pubkey feeds `pubkey` into `int_from_bytes(pubkey)`, and
358
+ // BIP340 defines `int(x)` only for `x` as a 32-byte array, so reject other widths here.
359
+ abytes(pubKey, 32, 'pubKey');
84
360
  const t = tapTweak(pubKey, h); // t = int_from_bytes(tagged_hash("TapTweak", pubkey + h))
85
361
  const P = u.lift_x(bytesToNumberBE(pubKey)); // P = lift_x(int_from_bytes(pubkey))
86
362
  const Q = P.add(Point.BASE.multiply(t)); // Q = point_add(P, point_mul(G, t))
@@ -89,27 +365,46 @@ export function taprootTweakPubkey(pubKey, h) {
89
365
  }
90
366
  // Another stupid decision, where lack of standard affects security.
91
367
  // Multisig needs to be generated with some key.
92
- // We are using approach from BIP 341/bitcoinjs-lib: SHA256(uncompressedDER(SECP256K1_GENERATOR_POINT))
368
+ // We are using the BIP 341/bitcoinjs-lib approach:
369
+ // SHA256(uncompressedDER(SECP256K1_GENERATOR_POINT))
93
370
  // It is possible to switch SECP256K1_GENERATOR_POINT with some random point;
94
371
  // but it's too complex to prove.
95
372
  // Also used by bitcoin-core and bitcoinjs-lib
96
- export const TAPROOT_UNSPENDABLE_KEY = sha256(Point.BASE.toBytes(false));
97
- export const NETWORK = {
373
+ // This is the fixed BIP 341 H example, not the privacy-preserving H + rG variant.
374
+ // Downstream helpers use exact-byte equality with it to recognize
375
+ // library-generated script-only outputs.
376
+ /** Standard unspendable internal key used for script-only Taproot outputs. */
377
+ export const TAPROOT_UNSPENDABLE_KEY = /* @__PURE__ */ (() => sha256(Point.BASE.toBytes(false)))();
378
+ /** Bitcoin mainnet network parameters. */
379
+ export const NETWORK = /* @__PURE__ */ Object.freeze({
98
380
  bech32: 'bc',
99
381
  pubKeyHash: 0x00,
100
382
  scriptHash: 0x05,
101
383
  wif: 0x80,
102
- };
103
- export const TEST_NETWORK = {
384
+ });
385
+ /** Bitcoin testnet network parameters. */
386
+ export const TEST_NETWORK = /* @__PURE__ */ Object.freeze({
104
387
  bech32: 'tb',
105
388
  pubKeyHash: 0x6f,
106
389
  scriptHash: 0xc4,
107
390
  wif: 0xef,
108
- };
391
+ });
109
392
  // Exported for tests, internal method
393
+ /**
394
+ * Lexicographically compares two byte arrays.
395
+ * @param a - first byte array
396
+ * @param b - second byte array
397
+ * @returns `-1`, `0`, or `1` depending on the ordering.
398
+ * @throws On wrong argument types. {@link TypeError}
399
+ * @example
400
+ * Compare two serialized keys using Bitcoin's byte ordering.
401
+ * ```ts
402
+ * compareBytes(new Uint8Array([1]), new Uint8Array([2]));
403
+ * ```
404
+ */
110
405
  export function compareBytes(a, b) {
111
406
  if (!isBytes(a) || !isBytes(b))
112
- throw new Error(`cmp: wrong type a=${typeof a} b=${typeof b}`);
407
+ throw new TypeError(`cmp: wrong type a=${typeof a} b=${typeof b}`);
113
408
  // -1 -> a<b, 0 -> a==b, 1 -> a>b
114
409
  const len = Math.min(a.length, b.length);
115
410
  for (let i = 0; i < len; i++)
@@ -118,8 +413,21 @@ export function compareBytes(a, b) {
118
413
  return Math.sign(a.length - b.length);
119
414
  }
120
415
  // Reverses key<->values
416
+ /**
417
+ * Reverses an object's keys and values.
418
+ * @param obj - object to reverse
419
+ * @returns Object with original values mapped back to keys.
420
+ * @throws If duplicate values would collide while reversing the object. {@link Error}
421
+ * @example
422
+ * Flip a lookup table so the values become keys.
423
+ * ```ts
424
+ * reverseObject({ a: 1, b: 2 });
425
+ * ```
426
+ */
121
427
  export function reverseObject(obj) {
122
- const res = {};
428
+ // Keep a raw dictionary shape so enum-like tables can reverse values like
429
+ // `toString` without colliding with inherited Object prototype properties.
430
+ const res = Object.create(null);
123
431
  for (const k in obj) {
124
432
  if (res[obj[k]] !== undefined)
125
433
  throw new Error('duplicate key');
@@ -127,4 +435,3 @@ export function reverseObject(obj) {
127
435
  }
128
436
  return res;
129
437
  }
130
- //# sourceMappingURL=utils.js.map