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