@did-btcr2/keypair 0.10.0 → 0.11.1
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/dist/cjs/pair.js +43 -49
- package/dist/cjs/pair.js.map +1 -1
- package/dist/cjs/public.js +21 -139
- package/dist/cjs/public.js.map +1 -1
- package/dist/cjs/secret.js +65 -82
- package/dist/cjs/secret.js.map +1 -1
- package/dist/esm/pair.js +43 -49
- package/dist/esm/pair.js.map +1 -1
- package/dist/esm/public.js +21 -139
- package/dist/esm/public.js.map +1 -1
- package/dist/esm/secret.js +65 -82
- package/dist/esm/secret.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/pair.d.ts +19 -4
- package/dist/types/pair.d.ts.map +1 -1
- package/dist/types/public.d.ts +7 -42
- package/dist/types/public.d.ts.map +1 -1
- package/dist/types/secret.d.ts +21 -20
- package/dist/types/secret.d.ts.map +1 -1
- package/dist/types/types.d.ts +3 -2
- package/dist/types/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/pair.ts +51 -56
- package/src/public.ts +25 -176
- package/src/secret.ts +72 -111
- package/src/types.ts +2 -2
package/src/public.ts
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
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 {
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
15
|
-
import { Secp256k1SecretKey } from './secret.js';
|
|
9
|
+
import { secp256k1, schnorr } from '@noble/curves/secp256k1.js';
|
|
10
|
+
import { equalBytes } from '@noble/curves/utils.js';
|
|
11
|
+
import { base58 } from '@scure/base';
|
|
16
12
|
import { CryptoOptions } from './types.js';
|
|
17
13
|
|
|
14
|
+
export const BIP340_PUBLIC_KEY_MULTIBASE_PREFIX: Bytes = new Uint8Array([0xe7, 0x01]);
|
|
15
|
+
|
|
18
16
|
/**
|
|
19
17
|
* Point Interface representing an (x, y) coordinate on the secp256k1 curve.
|
|
20
18
|
* @interface Point
|
|
@@ -104,11 +102,11 @@ export interface PublicKey {
|
|
|
104
102
|
encode(): string;
|
|
105
103
|
|
|
106
104
|
/**
|
|
107
|
-
*
|
|
108
|
-
* @param {
|
|
105
|
+
* Public key equality check.
|
|
106
|
+
* @param {PublicKey} other The public key to compare.
|
|
109
107
|
* @returns {boolean} True if the public keys are equal.
|
|
110
108
|
*/
|
|
111
|
-
equals(other:
|
|
109
|
+
equals(other: PublicKey): boolean;
|
|
112
110
|
}
|
|
113
111
|
|
|
114
112
|
/**
|
|
@@ -153,14 +151,14 @@ export class CompressedSecp256k1PublicKey implements PublicKey {
|
|
|
153
151
|
}
|
|
154
152
|
|
|
155
153
|
// Validate the point is on curve and in compressed form
|
|
156
|
-
if (!
|
|
154
|
+
if (!secp256k1.utils.isValidPublicKey(keyBytes)) {
|
|
157
155
|
throw new PublicKeyError(
|
|
158
156
|
'Invalid argument: not a valid secp256k1 compressed point',
|
|
159
157
|
'CONSTRUCTOR_ERROR', { keyBytes }
|
|
160
158
|
);
|
|
161
159
|
}
|
|
162
|
-
//
|
|
163
|
-
this.#bytes = keyBytes;
|
|
160
|
+
// Defensive copy — caller cannot mutate internal state
|
|
161
|
+
this.#bytes = new Uint8Array(keyBytes);
|
|
164
162
|
|
|
165
163
|
// Set multibase
|
|
166
164
|
this.#multibase.encoded = this.encode();
|
|
@@ -181,8 +179,7 @@ export class CompressedSecp256k1PublicKey implements PublicKey {
|
|
|
181
179
|
* @returns {Uint8Array} The 65-byte uncompressed public key (0x04, x, y).
|
|
182
180
|
*/
|
|
183
181
|
get uncompressed(): KeyBytes {
|
|
184
|
-
|
|
185
|
-
return uncompressed;
|
|
182
|
+
return secp256k1.Point.fromBytes(this.compressed).toBytes(false);
|
|
186
183
|
}
|
|
187
184
|
|
|
188
185
|
/**
|
|
@@ -240,8 +237,11 @@ export class CompressedSecp256k1PublicKey implements PublicKey {
|
|
|
240
237
|
* @returns {MultibaseObject} An object containing the multibase bytes, address and prefix.
|
|
241
238
|
*/
|
|
242
239
|
get multibase(): MultibaseObject {
|
|
243
|
-
|
|
244
|
-
|
|
240
|
+
return {
|
|
241
|
+
prefix : new Uint8Array(this.#multibase.prefix),
|
|
242
|
+
key : [...this.#multibase.key],
|
|
243
|
+
encoded : this.#multibase.encoded
|
|
244
|
+
};
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
/**
|
|
@@ -277,33 +277,7 @@ export class CompressedSecp256k1PublicKey implements PublicKey {
|
|
|
277
277
|
* @returns {KeyBytes} The decoded public key: prefix and public key bytes
|
|
278
278
|
*/
|
|
279
279
|
decode(): KeyBytes {
|
|
280
|
-
|
|
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;
|
|
280
|
+
return base58.decode(this.multibase.encoded.slice(1));
|
|
307
281
|
}
|
|
308
282
|
|
|
309
283
|
/**
|
|
@@ -311,25 +285,10 @@ export class CompressedSecp256k1PublicKey implements PublicKey {
|
|
|
311
285
|
* @returns {string} The public key encoded in base-58-btc multibase format.
|
|
312
286
|
*/
|
|
313
287
|
encode(): string {
|
|
314
|
-
// Convert public key bytes to an array
|
|
315
288
|
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
289
|
const publicKeyMultibase = Array.from(BIP340_PUBLIC_KEY_MULTIBASE_PREFIX);
|
|
327
|
-
|
|
328
|
-
// Push the public key bytes at the end of the prefix
|
|
329
290
|
publicKeyMultibase.push(...pk);
|
|
330
|
-
|
|
331
|
-
// Encode the bytes in base58btc format and return
|
|
332
|
-
return base58btc.encode(Uint8Array.from(publicKeyMultibase));
|
|
291
|
+
return 'z' + base58.encode(Uint8Array.from(publicKeyMultibase));
|
|
333
292
|
}
|
|
334
293
|
|
|
335
294
|
/**
|
|
@@ -344,13 +303,11 @@ export class CompressedSecp256k1PublicKey implements PublicKey {
|
|
|
344
303
|
// Default to schnorr scheme
|
|
345
304
|
opts ??= { scheme: 'schnorr' };
|
|
346
305
|
|
|
347
|
-
// If scheme is ecdsa, verify using ecdsa
|
|
348
306
|
if(opts.scheme === 'ecdsa') {
|
|
349
|
-
return
|
|
307
|
+
return secp256k1.verify(signature, data, this.compressed);
|
|
350
308
|
}
|
|
351
|
-
// If scheme is schnorr, verify using schnorr
|
|
352
309
|
else if(opts.scheme === 'schnorr') {
|
|
353
|
-
return
|
|
310
|
+
return schnorr.verify(signature, data, this.x);
|
|
354
311
|
}
|
|
355
312
|
|
|
356
313
|
// If scheme is neither ecdsa nor schnorr, throw an error
|
|
@@ -359,11 +316,11 @@ export class CompressedSecp256k1PublicKey implements PublicKey {
|
|
|
359
316
|
|
|
360
317
|
/**
|
|
361
318
|
* Compares this public key to another public key.
|
|
362
|
-
* @param {
|
|
319
|
+
* @param {PublicKey} other The other public key to compare
|
|
363
320
|
* @returns {boolean} True if the public keys are equal, false otherwise.
|
|
364
321
|
*/
|
|
365
|
-
equals(other:
|
|
366
|
-
return this.
|
|
322
|
+
equals(other: PublicKey): boolean {
|
|
323
|
+
return equalBytes(this.compressed, other.compressed);
|
|
367
324
|
}
|
|
368
325
|
|
|
369
326
|
/**
|
|
@@ -382,65 +339,6 @@ export class CompressedSecp256k1PublicKey implements PublicKey {
|
|
|
382
339
|
};
|
|
383
340
|
}
|
|
384
341
|
|
|
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
342
|
/**
|
|
445
343
|
* Static method to validate a public key.
|
|
446
344
|
* @param {Hex} pk The public key in hex (Uint8Array or string) format.
|
|
@@ -455,62 +353,13 @@ export class CompressedSecp256k1PublicKey implements PublicKey {
|
|
|
455
353
|
}
|
|
456
354
|
}
|
|
457
355
|
|
|
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
356
|
/**
|
|
485
357
|
* Creates a CompressedSecp256k1PublicKey object from a JSON representation.
|
|
486
358
|
* @param {PublicKeyObject} json The JSON object to initialize the CompressedSecp256k1PublicKey.
|
|
487
359
|
* @returns {CompressedSecp256k1PublicKey} The initialized CompressedSecp256k1PublicKey object.
|
|
488
360
|
*/
|
|
489
361
|
static fromJSON(json: PublicKeyObject): CompressedSecp256k1PublicKey {
|
|
490
|
-
json.point.
|
|
491
|
-
return new CompressedSecp256k1PublicKey(Uint8Array.from(json.point.x));
|
|
362
|
+
return new CompressedSecp256k1PublicKey(Uint8Array.from([json.point.parity, ...json.point.x]));
|
|
492
363
|
}
|
|
493
364
|
|
|
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
365
|
}
|
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,12 +8,18 @@ import {
|
|
|
11
8
|
SignatureBytes
|
|
12
9
|
} from '@did-btcr2/common';
|
|
13
10
|
import { sha256 } from '@noble/hashes/sha2';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
11
|
+
import { bytesToHex } from '@noble/hashes/utils';
|
|
12
|
+
import { secp256k1, schnorr } from '@noble/curves/secp256k1.js';
|
|
13
|
+
import { randomBytes } from '@noble/hashes/utils';
|
|
14
|
+
import { base58 } from '@scure/base';
|
|
18
15
|
import { CompressedSecp256k1PublicKey } from './public.js';
|
|
19
16
|
import { CryptoOptions } from './types.js';
|
|
17
|
+
import { equalBytes } from '@noble/curves/utils.js';
|
|
18
|
+
|
|
19
|
+
/** Fixed secret key header bytes per the Data Integrity BIP340 Cryptosuite spec: [0x81, 0x26] */
|
|
20
|
+
const BIP340_SECRET_KEY_MULTIBASE_PREFIX: Bytes = new Uint8Array([0x81, 0x26]);
|
|
21
|
+
/** Hash of the BIP-340 Multikey secret key prefix */
|
|
22
|
+
const BIP340_SECRET_KEY_MULTIBASE_PREFIX_HASH: string = bytesToHex(sha256(BIP340_SECRET_KEY_MULTIBASE_PREFIX));
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
25
|
* General SecretKey interface for the Secp256k1SecretKey class.
|
|
@@ -45,10 +48,10 @@ export interface SecretKey {
|
|
|
45
48
|
|
|
46
49
|
/**
|
|
47
50
|
* Checks if this secret key is equal to another secret key.
|
|
48
|
-
* @param {
|
|
51
|
+
* @param {SecretKey} other The other secret key to compare.
|
|
49
52
|
* @returns {boolean} True if the private keys are equal.
|
|
50
53
|
*/
|
|
51
|
-
equals(other:
|
|
54
|
+
equals(other: SecretKey): boolean;
|
|
52
55
|
|
|
53
56
|
/**
|
|
54
57
|
* Uses the secret key to compute the corresponding public key.
|
|
@@ -73,13 +76,13 @@ export interface SecretKey {
|
|
|
73
76
|
*/
|
|
74
77
|
export class Secp256k1SecretKey implements SecretKey {
|
|
75
78
|
/** @type {KeyBytes} The entropy for the secret key as a byte array */
|
|
76
|
-
|
|
79
|
+
#bytes?: KeyBytes;
|
|
77
80
|
|
|
78
81
|
/** @type {bigint} The entropy for the secret key as a bigint */
|
|
79
|
-
|
|
82
|
+
#seed?: bigint;
|
|
80
83
|
|
|
81
84
|
/** @type {string} The secret key as a secretKeyMultibase */
|
|
82
|
-
|
|
85
|
+
#multibase: string;
|
|
83
86
|
|
|
84
87
|
/**
|
|
85
88
|
* Instantiates an instance of Secp256k1SecretKey.
|
|
@@ -97,14 +100,14 @@ export class Secp256k1SecretKey implements SecretKey {
|
|
|
97
100
|
);
|
|
98
101
|
}
|
|
99
102
|
|
|
100
|
-
// If bytes and
|
|
103
|
+
// If bytes and length is 32, defensive-copy and derive seed
|
|
101
104
|
if (isBytes && entropy.length === 32) {
|
|
102
|
-
this.#bytes = entropy;
|
|
103
|
-
this.#seed = Secp256k1SecretKey.toSecret(
|
|
105
|
+
this.#bytes = new Uint8Array(entropy);
|
|
106
|
+
this.#seed = Secp256k1SecretKey.toSecret(this.#bytes);
|
|
104
107
|
}
|
|
105
108
|
|
|
106
|
-
// If
|
|
107
|
-
if (isSecret &&
|
|
109
|
+
// If bigint in valid range [1, n), convert to bytes
|
|
110
|
+
if (isSecret && entropy >= 1n && entropy < secp256k1.Point.Fn.ORDER) {
|
|
108
111
|
this.#bytes = Secp256k1SecretKey.toBytes(entropy);
|
|
109
112
|
this.#seed = entropy;
|
|
110
113
|
}
|
|
@@ -116,9 +119,9 @@ export class Secp256k1SecretKey implements SecretKey {
|
|
|
116
119
|
);
|
|
117
120
|
}
|
|
118
121
|
|
|
119
|
-
if(!this.#seed ||
|
|
122
|
+
if(!this.#seed || this.#seed < 1n || this.#seed >= secp256k1.Point.Fn.ORDER) {
|
|
120
123
|
throw new SecretKeyError(
|
|
121
|
-
'Invalid seed: must
|
|
124
|
+
'Invalid seed: must be valid bigint',
|
|
122
125
|
'CONSTRUCTOR_ERROR'
|
|
123
126
|
);
|
|
124
127
|
}
|
|
@@ -127,6 +130,16 @@ export class Secp256k1SecretKey implements SecretKey {
|
|
|
127
130
|
this.#multibase = this.encode();
|
|
128
131
|
}
|
|
129
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Zeros out secret key material from memory.
|
|
135
|
+
* The instance should not be used after calling this method.
|
|
136
|
+
*/
|
|
137
|
+
public destroy(): void {
|
|
138
|
+
if (this.#bytes) this.#bytes.fill(0);
|
|
139
|
+
this.#seed = undefined;
|
|
140
|
+
this.#multibase = '';
|
|
141
|
+
}
|
|
142
|
+
|
|
130
143
|
/**
|
|
131
144
|
* Get the secret key entropy as a byte array.
|
|
132
145
|
* @returns {KeyBytes} The secret key bytes as a Uint8Array
|
|
@@ -171,33 +184,19 @@ export class Secp256k1SecretKey implements SecretKey {
|
|
|
171
184
|
* @returns {string} The secret key in BIP340 multibase format.
|
|
172
185
|
*/
|
|
173
186
|
public encode(): string {
|
|
174
|
-
// Convert Uint8Array bytes to an Array
|
|
175
187
|
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
188
|
const mbaseBytes = Array.from(BIP340_SECRET_KEY_MULTIBASE_PREFIX);
|
|
185
|
-
|
|
186
|
-
// Push the secret key bytes at the end of the prefix
|
|
187
189
|
mbaseBytes.push(...secretKeyBytes);
|
|
188
|
-
|
|
189
|
-
// Encode the bytes in base58btc format and return
|
|
190
|
-
return base58btc.encode(Uint8Array.from(mbaseBytes));
|
|
190
|
+
return 'z' + base58.encode(Uint8Array.from(mbaseBytes));
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
/**
|
|
194
194
|
* Checks if this secret key is equal to another.
|
|
195
|
-
* @param {
|
|
195
|
+
* @param {SecretKey} other The other secret key
|
|
196
196
|
* @returns {boolean} True if the private keys are equal, false otherwise
|
|
197
197
|
*/
|
|
198
|
-
public equals(other:
|
|
199
|
-
|
|
200
|
-
return this.hex === other.hex;
|
|
198
|
+
public equals(other: SecretKey): boolean {
|
|
199
|
+
return equalBytes(this.bytes, other.bytes);
|
|
201
200
|
}
|
|
202
201
|
|
|
203
202
|
/**
|
|
@@ -205,33 +204,22 @@ export class Secp256k1SecretKey implements SecretKey {
|
|
|
205
204
|
* @returns {CompressedSecp256k1PublicKey} The computed public key
|
|
206
205
|
*/
|
|
207
206
|
public computePublicKey(): CompressedSecp256k1PublicKey {
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
}
|
|
207
|
+
return new CompressedSecp256k1PublicKey(secp256k1.getPublicKey(this.bytes));
|
|
208
|
+
}
|
|
226
209
|
|
|
227
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Safe JSON representation. Does not expose secret material.
|
|
212
|
+
* Called implicitly by JSON.stringify(). Use exportJSON() for full serialization.
|
|
213
|
+
*/
|
|
214
|
+
public toJSON(): { type: string } {
|
|
215
|
+
return { type: 'Secp256k1SecretKey' };
|
|
228
216
|
}
|
|
229
217
|
|
|
230
218
|
/**
|
|
231
|
-
*
|
|
219
|
+
* Exports the secret key as a JSON object. Contains sensitive material.
|
|
232
220
|
* @returns {SecretKeyObject} The secret key as a JSON object
|
|
233
221
|
*/
|
|
234
|
-
public
|
|
222
|
+
public exportJSON(): SecretKeyObject {
|
|
235
223
|
return {
|
|
236
224
|
bytes : Array.from(this.bytes),
|
|
237
225
|
seed : this.seed.toString(),
|
|
@@ -239,12 +227,22 @@ export class Secp256k1SecretKey implements SecretKey {
|
|
|
239
227
|
};
|
|
240
228
|
}
|
|
241
229
|
|
|
230
|
+
/** @override Prevents secret material from appearing in console.log */
|
|
231
|
+
public toString(): string {
|
|
232
|
+
return '[Secp256k1SecretKey]';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** @override Prevents secret material from appearing in Node.js inspect */
|
|
236
|
+
[Symbol.for('nodejs.util.inspect.custom')](): string {
|
|
237
|
+
return '[Secp256k1SecretKey]';
|
|
238
|
+
}
|
|
239
|
+
|
|
242
240
|
/**
|
|
243
241
|
* Checks if the secret key is valid.
|
|
244
242
|
* @returns {boolean} True if the secret key is valid, false otherwise
|
|
245
243
|
*/
|
|
246
244
|
public isValid(): boolean {
|
|
247
|
-
return
|
|
245
|
+
return secp256k1.utils.isValidSecretKey(this.bytes);
|
|
248
246
|
}
|
|
249
247
|
|
|
250
248
|
/**
|
|
@@ -252,16 +250,12 @@ export class Secp256k1SecretKey implements SecretKey {
|
|
|
252
250
|
* @returns {boolean} True if the public key is valid, false otherwise
|
|
253
251
|
*/
|
|
254
252
|
public hasValidPublicKey(): boolean {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (!tinysecp.isPoint(pk.compressed)) {
|
|
253
|
+
try {
|
|
254
|
+
this.computePublicKey();
|
|
255
|
+
return true;
|
|
256
|
+
} catch {
|
|
260
257
|
return false;
|
|
261
258
|
}
|
|
262
|
-
|
|
263
|
-
// Return true if the computed public key equals the provided public key
|
|
264
|
-
return true;
|
|
265
259
|
}
|
|
266
260
|
|
|
267
261
|
/**
|
|
@@ -276,14 +270,12 @@ export class Secp256k1SecretKey implements SecretKey {
|
|
|
276
270
|
// Set default options if not provided
|
|
277
271
|
opts ??= { scheme: 'schnorr' };
|
|
278
272
|
|
|
279
|
-
// Sign ecdsa and return
|
|
280
273
|
if(opts.scheme === 'ecdsa') {
|
|
281
|
-
return
|
|
274
|
+
return secp256k1.sign(data, this.bytes);
|
|
282
275
|
}
|
|
283
276
|
|
|
284
|
-
// Sign schnorr and return
|
|
285
277
|
if(opts.scheme === 'schnorr') {
|
|
286
|
-
return
|
|
278
|
+
return schnorr.sign(data, this.bytes);
|
|
287
279
|
}
|
|
288
280
|
|
|
289
281
|
throw new SecretKeyError(`Invalid scheme: ${opts.scheme}.`, 'SIGN_ERROR', opts);
|
|
@@ -296,7 +288,7 @@ export class Secp256k1SecretKey implements SecretKey {
|
|
|
296
288
|
*/
|
|
297
289
|
public static decode(multibase: string): Bytes {
|
|
298
290
|
// Decode the public key multibase string
|
|
299
|
-
const decoded =
|
|
291
|
+
const decoded = base58.decode(multibase.slice(1));
|
|
300
292
|
|
|
301
293
|
// If the public key bytes are not 35 bytes, throw an error
|
|
302
294
|
if(decoded.length !== 34) {
|
|
@@ -333,23 +325,6 @@ export class Secp256k1SecretKey implements SecretKey {
|
|
|
333
325
|
return new Secp256k1SecretKey(new Uint8Array(json.bytes));
|
|
334
326
|
}
|
|
335
327
|
|
|
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
328
|
/**
|
|
354
329
|
* Convert a bigint secret to secret key bytes.
|
|
355
330
|
* @param {KeyBytes} bytes The secret key bytes
|
|
@@ -372,7 +347,7 @@ export class Secp256k1SecretKey implements SecretKey {
|
|
|
372
347
|
);
|
|
373
348
|
|
|
374
349
|
// If bytes are not a valid secp256k1 secret key, throw error
|
|
375
|
-
if (!
|
|
350
|
+
if (!secp256k1.utils.isValidSecretKey(bytes)) {
|
|
376
351
|
throw new SecretKeyError(
|
|
377
352
|
'Invalid secret key: secret out of valid range',
|
|
378
353
|
'SET_PRIVATE_KEY_ERROR'
|
|
@@ -381,28 +356,13 @@ export class Secp256k1SecretKey implements SecretKey {
|
|
|
381
356
|
return new Uint8Array(bytes);
|
|
382
357
|
}
|
|
383
358
|
|
|
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
359
|
/**
|
|
395
360
|
* Creates a new Secp256k1SecretKey object from a bigint secret.
|
|
396
361
|
* @param {bigint} bint The secret bigint
|
|
397
362
|
* @returns {Secp256k1SecretKey} A new Secp256k1SecretKey object
|
|
398
363
|
*/
|
|
399
364
|
public static fromBigInt(bint: bigint): Secp256k1SecretKey {
|
|
400
|
-
|
|
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);
|
|
365
|
+
return new Secp256k1SecretKey(Secp256k1SecretKey.toBytes(bint));
|
|
406
366
|
}
|
|
407
367
|
|
|
408
368
|
/**
|
|
@@ -410,11 +370,12 @@ export class Secp256k1SecretKey implements SecretKey {
|
|
|
410
370
|
* @returns {KeyBytes} Uint8Array of 32 random bytes.
|
|
411
371
|
*/
|
|
412
372
|
public static random(): KeyBytes {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
373
|
+
let byteArray: Uint8Array;
|
|
374
|
+
// Retry until bytes fall in valid scalar range [1, n)
|
|
375
|
+
do {
|
|
376
|
+
byteArray = randomBytes(32);
|
|
377
|
+
} while (!secp256k1.utils.isValidSecretKey(byteArray));
|
|
378
|
+
return byteArray;
|
|
418
379
|
}
|
|
419
380
|
|
|
420
381
|
/**
|
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
|
|