@did-btcr2/keypair 0.5.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/src/secret.ts ADDED
@@ -0,0 +1,410 @@
1
+ import {
2
+ BIP340_SECRET_KEY_MULTIBASE_PREFIX,
3
+ BIP340_SECRET_KEY_MULTIBASE_PREFIX_HASH,
4
+ Bytes,
5
+ CURVE,
6
+ Entropy,
7
+ Hex,
8
+ KeyBytes,
9
+ SecretKeyError,
10
+ SecretKeyObject
11
+ } from '@did-btcr2/common';
12
+ import { sha256 } from '@noble/hashes/sha2';
13
+ import { getRandomValues } from 'crypto';
14
+ import { base58btc } from 'multiformats/bases/base58';
15
+ import * as tinysecp from 'tiny-secp256k1';
16
+ import { SchnorrKeyPair } from './pair.js';
17
+ import { PublicKey } from './public.js';
18
+
19
+ /**
20
+ * Interface for the SecretKey class.
21
+ * @interface ISecretKey
22
+ * @type {ISecretKey}
23
+ */
24
+ export interface ISecretKey {
25
+ /**
26
+ * Get the secret key bytes.
27
+ * @readonly @type {KeyBytes} The secret key bytes.
28
+ */
29
+ bytes: KeyBytes;
30
+
31
+ /**
32
+ * Getter returns the secret key bytes in bigint format.
33
+ * Setter allows alternative method of using a bigint seed for the entropy.
34
+ * @type {bigint} The secret key seed.
35
+ */
36
+ seed: bigint;
37
+
38
+ /**
39
+ * Get the secret key as a hex string.
40
+ * @readonly @type {Hex} The secret key as a hex string.
41
+ */
42
+ hex: Hex;
43
+
44
+ /**
45
+ * Checks if this secret key is equal to another secret key.
46
+ *
47
+ * @returns {boolean} True if the private keys are equal.
48
+ */
49
+ equals(other: SecretKey): boolean;
50
+
51
+ /**
52
+ * Uses the secret key to compute the corresponding public key.
53
+ * @returns {KeyBytes} A new PublicKey object.
54
+ */
55
+ computePublicKey(): KeyBytes;
56
+
57
+ /**
58
+ * Checks if the secret key is valid.
59
+ * @returns {boolean} Whether the secret key is valid.
60
+ */
61
+ isValid(): boolean;
62
+
63
+
64
+ /**
65
+ * JSON representation of a SecretKey object.
66
+ * @returns {SecretKeyObject} The SecretKey as a JSON object.
67
+ */
68
+ json(): SecretKeyObject;
69
+ }
70
+
71
+ /**
72
+ * Encapsulates a secp256k1 secret key
73
+ * Provides get methods for different formats (raw, secret, point).
74
+ * Provides helpers methods for comparison, serialization and publicKey generation.
75
+ * @class SecretKey
76
+ * @type {SecretKey}
77
+ *
78
+ */
79
+ export class SecretKey implements ISecretKey {
80
+ /** @type {KeyBytes} The entropy for the secret key as a byte array */
81
+ private _bytes?: KeyBytes;
82
+
83
+ /** @type {bigint} The entropy for the secret key as a bigint */
84
+ private _seed?: bigint;
85
+
86
+ /** @type {string} The secret key as a secretKeyMultibase */
87
+ private _multibase: string;
88
+
89
+ /**
90
+ * Instantiates an instance of SecretKey.
91
+ * @param {Entropy} entropy bytes (Uint8Array) or secret (bigint)
92
+ * @throws {SecretKeyError} If entropy is not provided, not a valid 32-byte secret key or not a valid bigint seed
93
+ */
94
+ constructor(entropy: Entropy) {
95
+ // If entropy not valid bytes or bigint seed, throw an error
96
+ const isBytes = entropy instanceof Uint8Array;
97
+ const isSecret = typeof entropy === 'bigint';
98
+ if(!isBytes && !isSecret) {
99
+ throw new SecretKeyError(
100
+ 'Invalid entropy: must be a valid byte array (32) or bigint',
101
+ 'CONSTRUCTOR_ERROR'
102
+ );
103
+ }
104
+
105
+ // If bytes and bytes are not length 32
106
+ if (isBytes && entropy.length === 32) {
107
+ this._bytes = entropy;
108
+ this._seed = SecretKey.toSecret(entropy);
109
+ }
110
+
111
+ // If secret and secret is not a valid bigint, throw error
112
+ if (isSecret && !(entropy < 1n || entropy >= CURVE.n)) {
113
+ this._bytes = SecretKey.toBytes(entropy);
114
+ this._seed = entropy;
115
+ }
116
+
117
+ if(!this._bytes || this._bytes.length !== 32) {
118
+ throw new SecretKeyError(
119
+ 'Invalid bytes: must be a valid 32-byte secret key',
120
+ 'CONSTRUCTOR_ERROR'
121
+ );
122
+ }
123
+
124
+ if(!this._seed || (this._seed < 1n || this._seed >= CURVE.n)) {
125
+ throw new SecretKeyError(
126
+ 'Invalid seed: must must be valid bigint',
127
+ 'CONSTRUCTOR_ERROR'
128
+ );
129
+ }
130
+
131
+ // Set the secret key multibase
132
+ this._multibase = this.encode();
133
+ }
134
+
135
+ /**
136
+ * Get the secret key entropy as a byte array.
137
+ * @returns {KeyBytes} The secret key bytes as a Uint8Array
138
+ */
139
+ get bytes(): Uint8Array {
140
+ // Return a copy of the secret key bytes
141
+ const bytes = new Uint8Array(this._bytes!);
142
+ return bytes;
143
+ }
144
+
145
+ /**
146
+ * Get the secret key entropy as a bigint.
147
+ * @returns {bigint} The secret key as a bigint
148
+ */
149
+ get seed(): bigint {
150
+ // Memoize the secret and return
151
+ const seed = BigInt(this._seed!) as bigint;
152
+ return seed;
153
+ }
154
+
155
+ /**
156
+ * Returns the raw secret key as a hex string.
157
+ * @returns {Hex} The secret key as a hex string
158
+ */
159
+ get hex(): Hex {
160
+ // Convert the raw secret key bytes to a hex string
161
+ return Buffer.from(this.bytes).toString('hex');
162
+ }
163
+
164
+
165
+ /**
166
+ * Encode the secret key bytes as a secretKeyMultibase string.
167
+ * @returns {string} The secret key in base58btc multibase format
168
+ */
169
+ get multibase(): string {
170
+ const multibase = this._multibase;
171
+ return multibase;
172
+ }
173
+
174
+ /**
175
+ * Encodes the secret key bytes to BIP340 multibase format.
176
+ * @returns {string} The secret key in BIP340 multibase format.
177
+ */
178
+ public encode(): string {
179
+ // Convert Uint8Array bytes to an Array
180
+ const secretKeyBytes = this.bytes.toArray();
181
+
182
+ if(secretKeyBytes.length !== 32) {
183
+ throw new SecretKeyError(
184
+ 'Invalid secret key: must be a valid 32-byte secret key',
185
+ 'ENCODE_MULTIBASE_ERROR'
186
+ );
187
+ }
188
+ // Convert prefix to an array
189
+ const mbaseBytes = BIP340_SECRET_KEY_MULTIBASE_PREFIX.toArray();
190
+
191
+ // Push the secret key bytes at the end of the prefix
192
+ mbaseBytes.push(...secretKeyBytes);
193
+
194
+ // Encode the bytes in base58btc format and return
195
+ return base58btc.encode(mbaseBytes.toUint8Array());
196
+ }
197
+
198
+ /**
199
+ * Checks if this secret key is equal to another.
200
+ * @param {SecretKey} other The other secret key
201
+ * @returns {boolean} True if the private keys are equal, false otherwise
202
+ */
203
+ public equals(other: SecretKey): boolean {
204
+ // Compare the hex strings of the private keys
205
+ return this.hex === other.hex;
206
+ }
207
+
208
+ /**
209
+ * Computes the public key from the secret key bytes.
210
+ * @returns {KeyBytes} The computed public key
211
+ */
212
+ public computePublicKey(): KeyBytes {
213
+ // Derive the public key from the secret key
214
+ const publicKeyBytes = tinysecp.pointFromScalar(this.bytes, true);
215
+
216
+ // If no public key, throw error
217
+ if (!publicKeyBytes) {
218
+ throw new SecretKeyError(
219
+ 'Invalid compute: failed to derive public key',
220
+ 'COMPUTE_PUBLIC_KEY_ERROR'
221
+ );
222
+ }
223
+
224
+ // If public key is not compressed, throw error
225
+ if(publicKeyBytes.length !== 33) {
226
+ throw new SecretKeyError(
227
+ 'Invalid compute: public key not compressed format',
228
+ 'COMPUTE_PUBLIC_KEY_ERROR'
229
+ );
230
+ }
231
+
232
+ return publicKeyBytes;
233
+ }
234
+
235
+ /**
236
+ * Converts the secret key to a JSON object.
237
+ * @returns {SecretKeyObject} The secret key as a JSON object
238
+ */
239
+ public json(): SecretKeyObject {
240
+ return {
241
+ bytes : this.bytes.toArray(),
242
+ seed : this.seed.toString(),
243
+ hex : this.hex,
244
+ };
245
+ }
246
+
247
+ /**
248
+ * Checks if the secret key is valid.
249
+ * @returns {boolean} True if the secret key is valid, false otherwise
250
+ */
251
+ public isValid(): boolean {
252
+ return tinysecp.isPrivate(this.bytes);
253
+ }
254
+
255
+ /**
256
+ * Checks if the public key is a valid secp256k1 point.
257
+ * @param {PublicKey} pk The public key to validate
258
+ * @returns {boolean} True if the public key is valid, false otherwise
259
+ */
260
+ public isValidPair(pk: PublicKey): boolean {
261
+ // If the public key is not valid, return false
262
+ if (!tinysecp.isPoint(pk.compressed)) {
263
+ return false;
264
+ }
265
+
266
+ // Else return true
267
+ return true;
268
+ }
269
+
270
+ /**
271
+ * Decodes the multibase string to the 34-byte secret key (2 byte prefix + 32 byte key).
272
+ * @param {string} multibase The multibase string to decode
273
+ * @returns {Bytes} The decoded secret key.
274
+ */
275
+ public static decode(multibase: string): Bytes {
276
+ // Decode the public key multibase string
277
+ const decoded = base58btc.decode(multibase);
278
+
279
+ // If the public key bytes are not 35 bytes, throw an error
280
+ if(decoded.length !== 34) {
281
+ throw new SecretKeyError(
282
+ 'Invalid argument: must be 34 byte secretKeyMultibase',
283
+ 'DECODE_MULTIBASE_ERROR'
284
+ );
285
+ }
286
+
287
+ // Grab the prefix bytes
288
+ const prefix = decoded.slice(0, 2);
289
+
290
+ // Compute the prefix hash
291
+ const prefixHash = Buffer.from(sha256(prefix)).toString('hex');
292
+
293
+ // If the prefix hash does not equal the BIP340 prefix hash, throw an error
294
+ if (prefixHash !== BIP340_SECRET_KEY_MULTIBASE_PREFIX_HASH) {
295
+ throw new SecretKeyError(
296
+ `Invalid prefix: malformed multibase prefix ${prefix}`,
297
+ 'DECODE_MULTIBASE_ERROR'
298
+ );
299
+ }
300
+
301
+ // Return the decoded key bytes
302
+ return decoded;
303
+ }
304
+
305
+ /**
306
+ * Creates a SecretKey object from a JSON object.
307
+ * @param {SecretKeyObject} json The JSON object containing the secret key bytes
308
+ * @returns {SecretKey} A new SecretKey object
309
+ */
310
+ public static fromJSON(json: SecretKeyObject): SecretKey {
311
+ return new SecretKey(new Uint8Array(json.bytes));
312
+ }
313
+
314
+ /**
315
+ * Converts a SecretKey or KeyBytes to a Pair.
316
+ * @param {KeyBytes} bytes
317
+ * @returns {SchnorrKeyPair} The SchnorrKeyPair object containing the public and private keys
318
+ * @throws {SecretKeyError} If the secret key is not valid
319
+ */
320
+ public static toKeyPair(bytes: KeyBytes): SchnorrKeyPair {
321
+ // Create a new SecretKey from the bytes
322
+ const secretKey = new SecretKey(bytes);
323
+
324
+ // Compute the public key from the secret key
325
+ const publicKey = secretKey.computePublicKey();
326
+
327
+ // Create a new Pair from the public key and secret key
328
+ return new SchnorrKeyPair({ publicKey, secretKey });
329
+ }
330
+
331
+ /**
332
+ * Convert a bigint secret to secret key bytes.
333
+ * @param {KeyBytes} bytes The secret key bytes
334
+ * @returns {bigint} The secret key bytes as a bigint secret
335
+ */
336
+ public static toSecret(bytes: KeyBytes): bigint {
337
+ return bytes.reduce((acc, byte) => (acc << 8n) | BigInt(byte), 0n);
338
+ }
339
+
340
+ /**
341
+ * Convert a secret key bytes to a bigint secret.
342
+ * @param {bigint} secret The secret key secret.
343
+ * @returns {KeyBytes} The secret key secret as secret key bytes.
344
+ */
345
+ public static toBytes(secret: bigint): KeyBytes {
346
+ // Ensure it’s a valid 32-byte value in [1, n-1] and convert bigint to Uint8Array
347
+ const bytes = Uint8Array.from(
348
+ { length: 32 },
349
+ (_, i) => Number(secret >> BigInt(8 * (31 - i)) & BigInt(0xff))
350
+ );
351
+
352
+ // If bytes are not a valid secp256k1 secret key, throw error
353
+ if (!tinysecp.isPrivate(bytes)) {
354
+ throw new SecretKeyError(
355
+ 'Invalid secret key: secret out of valid range',
356
+ 'SET_PRIVATE_KEY_ERROR'
357
+ );
358
+ }
359
+ return new Uint8Array(bytes);
360
+ }
361
+
362
+ /**
363
+ * Creates a new SecretKey object from a bigint secret.
364
+ * @param {bigint} secret The secret bigint
365
+ * @returns {SecretKey} A new SecretKey object
366
+ */
367
+ public static fromSecret(secret: bigint): SecretKey {
368
+ // Convert the secret bigint to a hex string
369
+ const hexsecret = secret.toString(16).padStart(64, '0');
370
+ // Convert the hex string to a Uint8Array
371
+ const privateKeyBytes = new Uint8Array(hexsecret.match(/.{2}/g)!.map(byte => parseInt(byte, 16)));
372
+ // Return a new SecretKey object
373
+ return new SecretKey(privateKeyBytes);
374
+ }
375
+
376
+ /**
377
+ * Generates random secret key bytes.
378
+ * @returns {KeyBytes} Uint8Array of 32 random bytes.
379
+ */
380
+ public static random(): KeyBytes {
381
+ // Generate empty 32-byte array
382
+ const byteArray = new Uint8Array(32);
383
+
384
+ // Use the getRandomValues function to fill the byteArray with random values
385
+ return getRandomValues(byteArray);
386
+ }
387
+
388
+
389
+ /**
390
+ * Creates a new SecretKey from random secret key bytes.
391
+ * @returns {SecretKey} A new SecretKey object
392
+ */
393
+ public static generate(): SecretKey {
394
+ // Generate empty 32-byte array
395
+ const randomBytes = this.random();
396
+
397
+ // Use the getRandomValues function to fill the byteArray with random values
398
+ return new SecretKey(randomBytes);
399
+ }
400
+
401
+ /**
402
+ * Generates a public key from the given secret key bytes.
403
+ * @param {KeyBytes} bytes The secret key bytes
404
+ * @returns {KeyBytes} The computed public key bytes
405
+ */
406
+ public static getPublicKey(bytes: KeyBytes): KeyBytes {
407
+ // Create a new SecretKey from the bytes and compute the public key
408
+ return new SecretKey(bytes).computePublicKey();
409
+ }
410
+ }