@did-btcr2/keypair 0.10.0 → 0.11.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/src/public.ts CHANGED
@@ -1,18 +1,17 @@
1
1
  import {
2
- BIP340_PUBLIC_KEY_MULTIBASE_PREFIX,
3
- BIP340_PUBLIC_KEY_MULTIBASE_PREFIX_HASH,
4
2
  Bytes,
5
- CURVE,
6
3
  Hex,
7
4
  KeyBytes,
8
5
  MultibaseObject,
9
6
  PublicKeyError,
10
7
  PublicKeyObject
11
8
  } from '@did-btcr2/common';
12
- import { sha256 } from '@noble/hashes/sha2';
13
- import { base58btc } from 'multiformats/bases/base58';
14
- import * as tinysecp from 'tiny-secp256k1';
15
- import { Secp256k1SecretKey } from './secret.js';
9
+
10
+ /** Fixed public key header bytes per the Data Integrity BIP340 Cryptosuite spec: [0xe7, 0x01] */
11
+ export const BIP340_PUBLIC_KEY_MULTIBASE_PREFIX: Bytes = new Uint8Array([0xe7, 0x01]);
12
+ import { secp256k1, schnorr } from '@noble/curves/secp256k1.js';
13
+ import { timingSafeEqual } from 'crypto';
14
+ import { base58 } from '@scure/base';
16
15
  import { CryptoOptions } from './types.js';
17
16
 
18
17
  /**
@@ -104,11 +103,11 @@ export interface PublicKey {
104
103
  encode(): string;
105
104
 
106
105
  /**
107
- * CompressedSecp256k1PublicKey key equality check. Checks if `this` public key is equal to `other` public key.
108
- * @param {CompressedSecp256k1PublicKey} other The public key to compare.
106
+ * Public key equality check.
107
+ * @param {PublicKey} other The public key to compare.
109
108
  * @returns {boolean} True if the public keys are equal.
110
109
  */
111
- equals(other: CompressedSecp256k1PublicKey): boolean;
110
+ equals(other: PublicKey): boolean;
112
111
  }
113
112
 
114
113
  /**
@@ -153,14 +152,14 @@ export class CompressedSecp256k1PublicKey implements PublicKey {
153
152
  }
154
153
 
155
154
  // Validate the point is on curve and in compressed form
156
- if (!tinysecp.isPoint(keyBytes)) {
155
+ if (!secp256k1.utils.isValidPublicKey(keyBytes)) {
157
156
  throw new PublicKeyError(
158
157
  'Invalid argument: not a valid secp256k1 compressed point',
159
158
  'CONSTRUCTOR_ERROR', { keyBytes }
160
159
  );
161
160
  }
162
- // Set the bytes
163
- this.#bytes = keyBytes;
161
+ // Defensive copy — caller cannot mutate internal state
162
+ this.#bytes = new Uint8Array(keyBytes);
164
163
 
165
164
  // Set multibase
166
165
  this.#multibase.encoded = this.encode();
@@ -181,8 +180,7 @@ export class CompressedSecp256k1PublicKey implements PublicKey {
181
180
  * @returns {Uint8Array} The 65-byte uncompressed public key (0x04, x, y).
182
181
  */
183
182
  get uncompressed(): KeyBytes {
184
- const uncompressed = this.liftX();
185
- return uncompressed;
183
+ return secp256k1.Point.fromBytes(this.compressed).toBytes(false);
186
184
  }
187
185
 
188
186
  /**
@@ -240,8 +238,11 @@ export class CompressedSecp256k1PublicKey implements PublicKey {
240
238
  * @returns {MultibaseObject} An object containing the multibase bytes, address and prefix.
241
239
  */
242
240
  get multibase(): MultibaseObject {
243
- const multibase = this.#multibase;
244
- return multibase;
241
+ return {
242
+ prefix : new Uint8Array(this.#multibase.prefix),
243
+ key : [...this.#multibase.key],
244
+ encoded : this.#multibase.encoded
245
+ };
245
246
  }
246
247
 
247
248
  /**
@@ -277,33 +278,7 @@ export class CompressedSecp256k1PublicKey implements PublicKey {
277
278
  * @returns {KeyBytes} The decoded public key: prefix and public key bytes
278
279
  */
279
280
  decode(): KeyBytes {
280
- // Decode the public key multibase string
281
- const decoded = base58btc.decode(this.multibase.encoded);
282
-
283
- // If the public key bytes are not 35 bytes, throw an error
284
- if(decoded.length !== 35) {
285
- throw new PublicKeyError(
286
- 'Invalid argument: must be 35 byte publicKeyMultibase',
287
- 'DECODE_MULTIBASE_ERROR'
288
- );
289
- }
290
-
291
- // Grab the prefix bytes
292
- const prefix = decoded.slice(0, 2);
293
-
294
- // Compute the prefix hash
295
- const prefixHash = Buffer.from(sha256(prefix)).toString('hex');
296
-
297
- // If the prefix hash does not equal the BIP340 prefix hash, throw an error
298
- if (prefixHash !== BIP340_PUBLIC_KEY_MULTIBASE_PREFIX_HASH) {
299
- throw new PublicKeyError(
300
- `Invalid prefix: malformed multibase prefix ${prefix}`,
301
- 'DECODE_MULTIBASE_ERROR'
302
- );
303
- }
304
-
305
- // Return the decoded public key bytes
306
- return decoded;
281
+ return base58.decode(this.multibase.encoded.slice(1));
307
282
  }
308
283
 
309
284
  /**
@@ -311,25 +286,10 @@ export class CompressedSecp256k1PublicKey implements PublicKey {
311
286
  * @returns {string} The public key encoded in base-58-btc multibase format.
312
287
  */
313
288
  encode(): string {
314
- // Convert public key bytes to an array
315
289
  const pk = Array.from(this.compressed);
316
-
317
- // Ensure the public key is 33-byte secp256k1 compressed public key
318
- if (pk.length !== 33) {
319
- throw new PublicKeyError(
320
- 'Invalid argument: must be 33-byte (compressed) public key',
321
- 'ENCODE_MULTIBASE_ERROR'
322
- );
323
- }
324
-
325
- // Convert prefix to an array
326
290
  const publicKeyMultibase = Array.from(BIP340_PUBLIC_KEY_MULTIBASE_PREFIX);
327
-
328
- // Push the public key bytes at the end of the prefix
329
291
  publicKeyMultibase.push(...pk);
330
-
331
- // Encode the bytes in base58btc format and return
332
- return base58btc.encode(Uint8Array.from(publicKeyMultibase));
292
+ return 'z' + base58.encode(Uint8Array.from(publicKeyMultibase));
333
293
  }
334
294
 
335
295
  /**
@@ -344,13 +304,11 @@ export class CompressedSecp256k1PublicKey implements PublicKey {
344
304
  // Default to schnorr scheme
345
305
  opts ??= { scheme: 'schnorr' };
346
306
 
347
- // If scheme is ecdsa, verify using ecdsa
348
307
  if(opts.scheme === 'ecdsa') {
349
- return tinysecp.verify(data, this.compressed, signature);
308
+ return secp256k1.verify(signature, data, this.compressed);
350
309
  }
351
- // If scheme is schnorr, verify using schnorr
352
310
  else if(opts.scheme === 'schnorr') {
353
- return tinysecp.verifySchnorr(data, this.x, signature);
311
+ return schnorr.verify(signature, data, this.x);
354
312
  }
355
313
 
356
314
  // If scheme is neither ecdsa nor schnorr, throw an error
@@ -359,11 +317,11 @@ export class CompressedSecp256k1PublicKey implements PublicKey {
359
317
 
360
318
  /**
361
319
  * Compares this public key to another public key.
362
- * @param {CompressedSecp256k1PublicKey} other The other public key to compare
320
+ * @param {PublicKey} other The other public key to compare
363
321
  * @returns {boolean} True if the public keys are equal, false otherwise.
364
322
  */
365
- equals(other: CompressedSecp256k1PublicKey): boolean {
366
- return this.hex === other.hex;
323
+ equals(other: PublicKey): boolean {
324
+ return timingSafeEqual(this.compressed, other.compressed);
367
325
  }
368
326
 
369
327
  /**
@@ -382,65 +340,6 @@ export class CompressedSecp256k1PublicKey implements PublicKey {
382
340
  };
383
341
  }
384
342
 
385
- /**
386
- * Computes modular exponentiation: (base^exp) % mod.
387
- * Used for computing modular square roots.
388
- * @param {bigint} base The base value
389
- * @param {bigint} exp The exponent value
390
- * @param {bigint} mod The modulus value
391
- * @returns {bigint} The result of the modular exponentiation
392
- */
393
- modPow(base: bigint, exp: bigint, mod: bigint): bigint {
394
- let result = 1n;
395
- while (exp > 0n) {
396
- if (exp & 1n) result = (result * base) % mod;
397
- base = (base * base) % mod;
398
- exp >>= 1n;
399
- }
400
- return result;
401
- };
402
-
403
- /**
404
- * Computes `sqrt(a) mod p` using Tonelli-Shanks algorithm.
405
- * This finds `y` such that `y^2 ≡ a mod p`.
406
- * @param {bigint} a The value to find the square root of
407
- * @param {bigint} p The prime modulus
408
- * @returns {bigint} The square root of `a` mod `p`
409
- */
410
- sqrtMod(a: bigint, p: bigint): bigint {
411
- return this.modPow(a, (p + 1n) >> 2n, p);
412
- };
413
-
414
- /**
415
- * Lifts a 32-byte x-only coordinate into a full secp256k1 point (x, y).
416
- * @param xBytes 32-byte x-coordinate
417
- * @returns {Uint8Array} 65-byte uncompressed public key (starts with `0x04`)
418
- */
419
- liftX(): Uint8Array {
420
- // Ensure x-coordinate is 32 bytes
421
- if (this.x.length !== 32) {
422
- throw new PublicKeyError('Invalid argument: x-coordinate length must be 32 bytes', 'LIFT_X_ERROR');
423
- }
424
-
425
- // Convert x from Uint8Array → BigInt
426
- const x = BigInt('0x' + Buffer.from(this.x).toString('hex'));
427
- if (x <= 0n || x >= CURVE.p) {
428
- throw new PublicKeyError('Invalid conversion: x out of range as BigInt', 'LIFT_X_ERROR');
429
- }
430
-
431
- // Compute y² = x³ + 7 mod p
432
- const ySquared = BigInt((x ** 3n + CURVE.b) % CURVE.p);
433
-
434
- // Compute y (do not enforce parity)
435
- const y = this.sqrtMod(ySquared, CURVE.p);
436
-
437
- // Convert x and y to Uint8Array
438
- const yBytes = Buffer.from(y.toString(16).padStart(64, '0'), 'hex');
439
-
440
- // Return 65-byte uncompressed public key: `0x04 || x || y`
441
- return new Uint8Array(Buffer.concat([Buffer.from([0x04]), Buffer.from(this.x), yBytes]));
442
- };
443
-
444
343
  /**
445
344
  * Static method to validate a public key.
446
345
  * @param {Hex} pk The public key in hex (Uint8Array or string) format.
@@ -455,62 +354,13 @@ export class CompressedSecp256k1PublicKey implements PublicKey {
455
354
  }
456
355
  }
457
356
 
458
- /**
459
- * Returns the point of the public key.
460
- * @param {Hex} pk The public key in hex (Uint8Array or string) format.
461
- * @returns {Point} The point of the public key.
462
- * @throws {PublicKeyError} If the public key is not a valid hex string or byte array.
463
- */
464
- static point(pk: Hex): Point {
465
- // If the public key is a hex string, convert it to a CompressedSecp256k1PublicKey object and return the point
466
- if(typeof pk === 'string' && /^[0-9a-fA-F]+$/.test(pk)) {
467
- const publicKey = new CompressedSecp256k1PublicKey(Buffer.from(pk, 'hex'));
468
- return publicKey.point;
469
- }
470
-
471
- // If the public key is a byte array or ArrayBuffer, convert it to a CompressedSecp256k1PublicKey object and return the point
472
- if(pk instanceof Uint8Array || ArrayBuffer.isView(pk)) {
473
- const publicKey = new CompressedSecp256k1PublicKey(pk as KeyBytes);
474
- return publicKey.point;
475
- }
476
-
477
- // If the public key is neither a hex string nor a byte array, throw an error
478
- throw new PublicKeyError(
479
- 'Invalid publicKey: must be a hex string or byte array',
480
- 'POINT_ERROR', { publicKey: pk }
481
- );
482
- }
483
-
484
357
  /**
485
358
  * Creates a CompressedSecp256k1PublicKey object from a JSON representation.
486
359
  * @param {PublicKeyObject} json The JSON object to initialize the CompressedSecp256k1PublicKey.
487
360
  * @returns {CompressedSecp256k1PublicKey} The initialized CompressedSecp256k1PublicKey object.
488
361
  */
489
362
  static fromJSON(json: PublicKeyObject): CompressedSecp256k1PublicKey {
490
- json.point.x.unshift(json.point.parity);
491
- return new CompressedSecp256k1PublicKey(Uint8Array.from(json.point.x));
363
+ return new CompressedSecp256k1PublicKey(Uint8Array.from([json.point.parity, ...json.point.x]));
492
364
  }
493
365
 
494
- /**
495
- * Computes the deterministic public key for a given secret key.
496
- * @param {Secp256k1SecretKey | KeyBytes} sk The Secp256k1SecretKey object or the secret key bytes
497
- * @returns {CompressedSecp256k1PublicKey} A new CompressedSecp256k1PublicKey object
498
- */
499
- static fromSecretKey(sk: Secp256k1SecretKey | KeyBytes): CompressedSecp256k1PublicKey {
500
- // If the secret key is a Secp256k1SecretKey object, get the raw bytes else use the bytes
501
- const bytes = sk instanceof Secp256k1SecretKey ? sk.bytes : sk;
502
-
503
- // Throw error if the secret key is not 32 bytes
504
- if(bytes.length !== 32) {
505
- throw new PublicKeyError('Invalid arg: must be 32 byte secret key', 'FROM_SECRET_KEY_ERROR');
506
- }
507
-
508
- // Compute the public key from the secret key
509
- const secret = sk instanceof Secp256k1SecretKey
510
- ? sk
511
- : new Secp256k1SecretKey(sk);
512
-
513
- // Return a new CompressedSecp256k1PublicKey object
514
- return secret.computePublicKey();
515
- }
516
366
  }
package/src/secret.ts CHANGED
@@ -1,8 +1,5 @@
1
1
  import {
2
- BIP340_SECRET_KEY_MULTIBASE_PREFIX,
3
- BIP340_SECRET_KEY_MULTIBASE_PREFIX_HASH,
4
2
  Bytes,
5
- CURVE,
6
3
  Hex,
7
4
  HexString,
8
5
  KeyBytes,
@@ -11,13 +8,18 @@ import {
11
8
  SignatureBytes
12
9
  } from '@did-btcr2/common';
13
10
  import { sha256 } from '@noble/hashes/sha2';
14
- import { getRandomValues, randomBytes } from 'crypto';
15
- import { base58btc } from 'multiformats/bases/base58';
16
- import * as tinysecp from 'tiny-secp256k1';
17
- import { SchnorrKeyPair } from './pair.js';
11
+ import { bytesToHex } from '@noble/hashes/utils';
12
+ import { secp256k1, schnorr } from '@noble/curves/secp256k1.js';
13
+ import { getRandomValues, timingSafeEqual } from 'crypto';
14
+ import { base58 } from '@scure/base';
18
15
  import { CompressedSecp256k1PublicKey } from './public.js';
19
16
  import { CryptoOptions } from './types.js';
20
17
 
18
+ /** Fixed secret key header bytes per the Data Integrity BIP340 Cryptosuite spec: [0x81, 0x26] */
19
+ const BIP340_SECRET_KEY_MULTIBASE_PREFIX: Bytes = new Uint8Array([0x81, 0x26]);
20
+ /** Hash of the BIP-340 Multikey secret key prefix */
21
+ const BIP340_SECRET_KEY_MULTIBASE_PREFIX_HASH: string = bytesToHex(sha256(BIP340_SECRET_KEY_MULTIBASE_PREFIX));
22
+
21
23
  /**
22
24
  * General SecretKey interface for the Secp256k1SecretKey class.
23
25
  * @interface SecretKey
@@ -45,10 +47,10 @@ export interface SecretKey {
45
47
 
46
48
  /**
47
49
  * Checks if this secret key is equal to another secret key.
48
- * @param {Secp256k1SecretKey} other The other secret key to compare.
50
+ * @param {SecretKey} other The other secret key to compare.
49
51
  * @returns {boolean} True if the private keys are equal.
50
52
  */
51
- equals(other: Secp256k1SecretKey): boolean;
53
+ equals(other: SecretKey): boolean;
52
54
 
53
55
  /**
54
56
  * Uses the secret key to compute the corresponding public key.
@@ -73,13 +75,13 @@ export interface SecretKey {
73
75
  */
74
76
  export class Secp256k1SecretKey implements SecretKey {
75
77
  /** @type {KeyBytes} The entropy for the secret key as a byte array */
76
- readonly #bytes?: KeyBytes;
78
+ #bytes?: KeyBytes;
77
79
 
78
80
  /** @type {bigint} The entropy for the secret key as a bigint */
79
- readonly #seed?: bigint;
81
+ #seed?: bigint;
80
82
 
81
83
  /** @type {string} The secret key as a secretKeyMultibase */
82
- readonly #multibase: string;
84
+ #multibase: string;
83
85
 
84
86
  /**
85
87
  * Instantiates an instance of Secp256k1SecretKey.
@@ -97,14 +99,14 @@ export class Secp256k1SecretKey implements SecretKey {
97
99
  );
98
100
  }
99
101
 
100
- // If bytes and bytes are not length 32
102
+ // If bytes and length is 32, defensive-copy and derive seed
101
103
  if (isBytes && entropy.length === 32) {
102
- this.#bytes = entropy;
103
- this.#seed = Secp256k1SecretKey.toSecret(entropy);
104
+ this.#bytes = new Uint8Array(entropy);
105
+ this.#seed = Secp256k1SecretKey.toSecret(this.#bytes);
104
106
  }
105
107
 
106
- // If secret and secret is not a valid bigint, throw error
107
- if (isSecret && !(entropy < 1n || entropy >= CURVE.n)) {
108
+ // If bigint in valid range [1, n), convert to bytes
109
+ if (isSecret && entropy >= 1n && entropy < secp256k1.Point.Fn.ORDER) {
108
110
  this.#bytes = Secp256k1SecretKey.toBytes(entropy);
109
111
  this.#seed = entropy;
110
112
  }
@@ -116,9 +118,9 @@ export class Secp256k1SecretKey implements SecretKey {
116
118
  );
117
119
  }
118
120
 
119
- if(!this.#seed || (this.#seed < 1n || this.#seed >= CURVE.n)) {
121
+ if(!this.#seed || this.#seed < 1n || this.#seed >= secp256k1.Point.Fn.ORDER) {
120
122
  throw new SecretKeyError(
121
- 'Invalid seed: must must be valid bigint',
123
+ 'Invalid seed: must be valid bigint',
122
124
  'CONSTRUCTOR_ERROR'
123
125
  );
124
126
  }
@@ -127,6 +129,16 @@ export class Secp256k1SecretKey implements SecretKey {
127
129
  this.#multibase = this.encode();
128
130
  }
129
131
 
132
+ /**
133
+ * Zeros out secret key material from memory.
134
+ * The instance should not be used after calling this method.
135
+ */
136
+ public destroy(): void {
137
+ if (this.#bytes) this.#bytes.fill(0);
138
+ this.#seed = undefined;
139
+ this.#multibase = '';
140
+ }
141
+
130
142
  /**
131
143
  * Get the secret key entropy as a byte array.
132
144
  * @returns {KeyBytes} The secret key bytes as a Uint8Array
@@ -171,33 +183,19 @@ export class Secp256k1SecretKey implements SecretKey {
171
183
  * @returns {string} The secret key in BIP340 multibase format.
172
184
  */
173
185
  public encode(): string {
174
- // Convert Uint8Array bytes to an Array
175
186
  const secretKeyBytes = Array.from(this.bytes);
176
-
177
- if(secretKeyBytes.length !== 32) {
178
- throw new SecretKeyError(
179
- 'Invalid secret key: must be a valid 32-byte secret key',
180
- 'ENCODE_MULTIBASE_ERROR'
181
- );
182
- }
183
- // Convert prefix to an array
184
187
  const mbaseBytes = Array.from(BIP340_SECRET_KEY_MULTIBASE_PREFIX);
185
-
186
- // Push the secret key bytes at the end of the prefix
187
188
  mbaseBytes.push(...secretKeyBytes);
188
-
189
- // Encode the bytes in base58btc format and return
190
- return base58btc.encode(Uint8Array.from(mbaseBytes));
189
+ return 'z' + base58.encode(Uint8Array.from(mbaseBytes));
191
190
  }
192
191
 
193
192
  /**
194
193
  * Checks if this secret key is equal to another.
195
- * @param {Secp256k1SecretKey} other The other secret key
194
+ * @param {SecretKey} other The other secret key
196
195
  * @returns {boolean} True if the private keys are equal, false otherwise
197
196
  */
198
- public equals(other: Secp256k1SecretKey): boolean {
199
- // Compare the hex strings of the private keys
200
- return this.hex === other.hex;
197
+ public equals(other: SecretKey): boolean {
198
+ return timingSafeEqual(this.bytes, other.bytes);
201
199
  }
202
200
 
203
201
  /**
@@ -205,33 +203,22 @@ export class Secp256k1SecretKey implements SecretKey {
205
203
  * @returns {CompressedSecp256k1PublicKey} The computed public key
206
204
  */
207
205
  public computePublicKey(): CompressedSecp256k1PublicKey {
208
- // Derive the public key from the secret key
209
- const publicKeyBytes = tinysecp.pointFromScalar(this.bytes, true);
210
-
211
- // If no public key, throw error
212
- if (!publicKeyBytes) {
213
- throw new SecretKeyError(
214
- 'Invalid compute: failed to derive public key',
215
- 'COMPUTE_PUBLIC_KEY_ERROR'
216
- );
217
- }
218
-
219
- // If public key is not compressed, throw error
220
- if(publicKeyBytes.length !== 33) {
221
- throw new SecretKeyError(
222
- 'Invalid compute: public key not compressed format',
223
- 'COMPUTE_PUBLIC_KEY_ERROR'
224
- );
225
- }
206
+ return new CompressedSecp256k1PublicKey(secp256k1.getPublicKey(this.bytes));
207
+ }
226
208
 
227
- return new CompressedSecp256k1PublicKey(publicKeyBytes);
209
+ /**
210
+ * Safe JSON representation. Does not expose secret material.
211
+ * Called implicitly by JSON.stringify(). Use exportJSON() for full serialization.
212
+ */
213
+ public toJSON(): { type: string } {
214
+ return { type: 'Secp256k1SecretKey' };
228
215
  }
229
216
 
230
217
  /**
231
- * Converts the secret key to a JSON object.
218
+ * Exports the secret key as a JSON object. Contains sensitive material.
232
219
  * @returns {SecretKeyObject} The secret key as a JSON object
233
220
  */
234
- public toJSON(): SecretKeyObject {
221
+ public exportJSON(): SecretKeyObject {
235
222
  return {
236
223
  bytes : Array.from(this.bytes),
237
224
  seed : this.seed.toString(),
@@ -239,12 +226,22 @@ export class Secp256k1SecretKey implements SecretKey {
239
226
  };
240
227
  }
241
228
 
229
+ /** @override Prevents secret material from appearing in console.log */
230
+ public toString(): string {
231
+ return '[Secp256k1SecretKey]';
232
+ }
233
+
234
+ /** @override Prevents secret material from appearing in Node.js inspect */
235
+ [Symbol.for('nodejs.util.inspect.custom')](): string {
236
+ return '[Secp256k1SecretKey]';
237
+ }
238
+
242
239
  /**
243
240
  * Checks if the secret key is valid.
244
241
  * @returns {boolean} True if the secret key is valid, false otherwise
245
242
  */
246
243
  public isValid(): boolean {
247
- return tinysecp.isPrivate(this.bytes);
244
+ return secp256k1.utils.isValidSecretKey(this.bytes);
248
245
  }
249
246
 
250
247
  /**
@@ -252,16 +249,12 @@ export class Secp256k1SecretKey implements SecretKey {
252
249
  * @returns {boolean} True if the public key is valid, false otherwise
253
250
  */
254
251
  public hasValidPublicKey(): boolean {
255
- // Compute the public key from the secret key and compress it
256
- const pk = this.computePublicKey();
257
-
258
- // If the public key is not valid, return false
259
- if (!tinysecp.isPoint(pk.compressed)) {
252
+ try {
253
+ this.computePublicKey();
254
+ return true;
255
+ } catch {
260
256
  return false;
261
257
  }
262
-
263
- // Return true if the computed public key equals the provided public key
264
- return true;
265
258
  }
266
259
 
267
260
  /**
@@ -276,14 +269,12 @@ export class Secp256k1SecretKey implements SecretKey {
276
269
  // Set default options if not provided
277
270
  opts ??= { scheme: 'schnorr' };
278
271
 
279
- // Sign ecdsa and return
280
272
  if(opts.scheme === 'ecdsa') {
281
- return tinysecp.sign(data, this.bytes);
273
+ return secp256k1.sign(data, this.bytes);
282
274
  }
283
275
 
284
- // Sign schnorr and return
285
276
  if(opts.scheme === 'schnorr') {
286
- return tinysecp.signSchnorr(data, this.bytes, randomBytes(32));
277
+ return schnorr.sign(data, this.bytes);
287
278
  }
288
279
 
289
280
  throw new SecretKeyError(`Invalid scheme: ${opts.scheme}.`, 'SIGN_ERROR', opts);
@@ -296,7 +287,7 @@ export class Secp256k1SecretKey implements SecretKey {
296
287
  */
297
288
  public static decode(multibase: string): Bytes {
298
289
  // Decode the public key multibase string
299
- const decoded = base58btc.decode(multibase);
290
+ const decoded = base58.decode(multibase.slice(1));
300
291
 
301
292
  // If the public key bytes are not 35 bytes, throw an error
302
293
  if(decoded.length !== 34) {
@@ -333,23 +324,6 @@ export class Secp256k1SecretKey implements SecretKey {
333
324
  return new Secp256k1SecretKey(new Uint8Array(json.bytes));
334
325
  }
335
326
 
336
- /**
337
- * Converts a Secp256k1SecretKey or KeyBytes to a SchnorrKeyPair.
338
- * @param {KeyBytes} bytes The secret key bytes
339
- * @returns {SchnorrKeyPair} The SchnorrKeyPair object containing the public and private keys
340
- * @throws {SecretKeyError} If the secret key is not valid
341
- */
342
- public static toKeyPair(bytes: KeyBytes): SchnorrKeyPair {
343
- // Create a new Secp256k1SecretKey from the bytes
344
- const secretKey = new Secp256k1SecretKey(bytes);
345
-
346
- // Compute the public key from the secret key
347
- const publicKey = secretKey.computePublicKey();
348
-
349
- // Create a new Pair from the public key and secret key
350
- return new SchnorrKeyPair({ publicKey, secretKey });
351
- }
352
-
353
327
  /**
354
328
  * Convert a bigint secret to secret key bytes.
355
329
  * @param {KeyBytes} bytes The secret key bytes
@@ -372,7 +346,7 @@ export class Secp256k1SecretKey implements SecretKey {
372
346
  );
373
347
 
374
348
  // If bytes are not a valid secp256k1 secret key, throw error
375
- if (!tinysecp.isPrivate(bytes)) {
349
+ if (!secp256k1.utils.isValidSecretKey(bytes)) {
376
350
  throw new SecretKeyError(
377
351
  'Invalid secret key: secret out of valid range',
378
352
  'SET_PRIVATE_KEY_ERROR'
@@ -381,28 +355,13 @@ export class Secp256k1SecretKey implements SecretKey {
381
355
  return new Uint8Array(bytes);
382
356
  }
383
357
 
384
- /**
385
- * Creates a new Secp256k1SecretKey object from random bytes.
386
- * @param {KeyBytes} bytes The secret key bytes
387
- * @returns {Secp256k1SecretKey} A new Secp256k1SecretKey object
388
- */
389
- public static fromBytes(bytes: KeyBytes): Secp256k1SecretKey {
390
- // Return a new Secp256k1SecretKey object
391
- return new Secp256k1SecretKey(bytes);
392
- }
393
-
394
358
  /**
395
359
  * Creates a new Secp256k1SecretKey object from a bigint secret.
396
360
  * @param {bigint} bint The secret bigint
397
361
  * @returns {Secp256k1SecretKey} A new Secp256k1SecretKey object
398
362
  */
399
363
  public static fromBigInt(bint: bigint): Secp256k1SecretKey {
400
- // Convert the secret bigint to a hex string
401
- const hexsecret = bint.toString(16).padStart(64, '0');
402
- // Convert the hex string to a Uint8Array
403
- const bytes = new Uint8Array(hexsecret.match(/.{2}/g)!.map(byte => parseInt(byte, 16)));
404
- // Return a new Secp256k1SecretKey object
405
- return new Secp256k1SecretKey(bytes);
364
+ return new Secp256k1SecretKey(Secp256k1SecretKey.toBytes(bint));
406
365
  }
407
366
 
408
367
  /**
@@ -410,11 +369,12 @@ export class Secp256k1SecretKey implements SecretKey {
410
369
  * @returns {KeyBytes} Uint8Array of 32 random bytes.
411
370
  */
412
371
  public static random(): KeyBytes {
413
- // Generate empty 32-byte array
414
372
  const byteArray = new Uint8Array(32);
415
-
416
- // Use the getRandomValues function to fill the byteArray with random values
417
- return getRandomValues(byteArray);
373
+ // Retry until bytes fall in valid scalar range [1, n)
374
+ do {
375
+ getRandomValues(byteArray);
376
+ } while (!secp256k1.utils.isValidSecretKey(byteArray));
377
+ return byteArray;
418
378
  }
419
379
 
420
380
  /**
package/src/types.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Hex, KeyBytes } from '@did-btcr2/common';
2
- import { CompressedSecp256k1PublicKey } from './public.js';
3
- import { Secp256k1SecretKey } from './secret.js';
2
+ import type { CompressedSecp256k1PublicKey } from './public.js';
3
+ import type { Secp256k1SecretKey } from './secret.js';
4
4
 
5
5
  export type CryptoOptions = { scheme: 'ecdsa' | 'schnorr' }
6
6