ox 0.7.2 → 0.8.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/core/Keystore.ts CHANGED
@@ -12,14 +12,14 @@ import * as Bytes from './Bytes.js'
12
12
  import type * as Errors from './Errors.js'
13
13
  import * as Hash from './Hash.js'
14
14
  import type * as Hex from './Hex.js'
15
+ import type { OneOf } from './internal/types.js'
15
16
 
16
- /** Base Key. */
17
- type BaseKey<
17
+ /** Base Derivation Options. */
18
+ type BaseDeriveOpts<
18
19
  kdf extends string = string,
19
20
  kdfparams extends Record<string, unknown> = Record<string, unknown>,
20
21
  > = {
21
22
  iv: Bytes.Bytes
22
- key: () => string
23
23
  kdfparams: kdfparams
24
24
  kdf: kdf
25
25
  }
@@ -33,16 +33,19 @@ export type Keystore = {
33
33
  iv: string
34
34
  }
35
35
  mac: string
36
- } & Pick<Key, 'kdf' | 'kdfparams'>
36
+ } & Pick<DeriveOpts, 'kdf' | 'kdfparams'>
37
37
  id: string
38
38
  version: 3
39
39
  }
40
40
 
41
41
  /** Key. */
42
- export type Key = Pbkdf2Key | ScryptKey
42
+ export type Key = (() => Hex.Hex) | Hex.Hex
43
43
 
44
- /** PBKDF2 Key. */
45
- export type Pbkdf2Key = BaseKey<
44
+ /** Derivation Options. */
45
+ export type DeriveOpts = Pbkdf2DeriveOpts | ScryptDeriveOpts
46
+
47
+ /** PBKDF2 Derivation Options. */
48
+ export type Pbkdf2DeriveOpts = BaseDeriveOpts<
46
49
  'pbkdf2',
47
50
  {
48
51
  c: number
@@ -52,8 +55,8 @@ export type Pbkdf2Key = BaseKey<
52
55
  }
53
56
  >
54
57
 
55
- /** Scrypt Key. */
56
- export type ScryptKey = BaseKey<
58
+ /** Scrypt Derivation Options. */
59
+ export type ScryptDeriveOpts = BaseDeriveOpts<
57
60
  'scrypt',
58
61
  {
59
62
  dklen: number
@@ -80,11 +83,11 @@ export type ScryptKey = BaseKey<
80
83
  * // JSON keystore.
81
84
  * const keystore = { crypto: { ... }, id: '...', version: 3 }
82
85
  *
83
- * // Derive key from password.
84
- * const key = Keystore.pbkdf2({ password: 'testpassword' })
86
+ * // Derive the key using your password.
87
+ * const key = Keystore.toKey(keystore, { password: 'hunter2' })
85
88
  *
86
89
  * // Decrypt the private key.
87
- * const privateKey = await Keystore.decrypt(keystore, key)
90
+ * const privateKey = Keystore.decrypt(keystore, key)
88
91
  * // @log: "0x..."
89
92
  * ```
90
93
  *
@@ -93,13 +96,13 @@ export type ScryptKey = BaseKey<
93
96
  * @param options - Decryption options.
94
97
  * @returns Decrypted private key.
95
98
  */
96
- export async function decrypt<as extends 'Hex' | 'Bytes' = 'Hex'>(
99
+ export function decrypt<as extends 'Hex' | 'Bytes' = 'Hex'>(
97
100
  keystore: Keystore,
98
101
  key: Key,
99
102
  options: decrypt.Options<as> = {},
100
- ): Promise<decrypt.ReturnType<as>> {
103
+ ): decrypt.ReturnType<as> {
101
104
  const { as = 'Hex' } = options
102
- const key_ = Bytes.from(`0x${key.key()}`)
105
+ const key_ = Bytes.from(typeof key === 'function' ? key() : key)
103
106
 
104
107
  const encKey = Bytes.slice(key_, 0, 16)
105
108
  const macKey = Bytes.slice(key_, 16, 32)
@@ -110,7 +113,10 @@ export async function decrypt<as extends 'Hex' | 'Bytes' = 'Hex'>(
110
113
  if (!Bytes.isEqual(mac, Bytes.from(`0x${keystore.crypto.mac}`)))
111
114
  throw new Error('corrupt keystore')
112
115
 
113
- const data = ctr(encKey, key.iv).decrypt(ciphertext)
116
+ const data = ctr(
117
+ encKey,
118
+ Bytes.from(`0x${keystore.crypto.cipherparams.iv}`),
119
+ ).decrypt(ciphertext)
114
120
 
115
121
  if (as === 'Hex') return Bytes.toHex(data) as never
116
122
  return data as never
@@ -143,10 +149,10 @@ export declare namespace decrypt {
143
149
  * const privateKey = Secp256k1.randomPrivateKey()
144
150
  *
145
151
  * // Derive key from password.
146
- * const key = Keystore.pbkdf2({ password: 'testpassword' })
152
+ * const [key, opts] = Keystore.pbkdf2({ password: 'testpassword' })
147
153
  *
148
154
  * // Encrypt the private key.
149
- * const encrypted = await Keystore.encrypt(privateKey, key)
155
+ * const encrypted = Keystore.encrypt(privateKey, key, opts)
150
156
  * // @log: {
151
157
  * // @log: "crypto": {
152
158
  * // @log: "cipher": "aes-128-ctr",
@@ -173,29 +179,29 @@ export declare namespace decrypt {
173
179
  * @param options - Encryption options.
174
180
  * @returns Encrypted keystore.
175
181
  */
176
- export async function encrypt(
182
+ export function encrypt(
177
183
  privateKey: Bytes.Bytes | Hex.Hex,
178
184
  key: Key,
179
- options: encrypt.Options = {},
180
- ): Promise<Keystore> {
181
- const { id = crypto.randomUUID() } = options
185
+ options: encrypt.Options,
186
+ ): Keystore {
187
+ const { id = crypto.randomUUID(), kdf, kdfparams, iv } = options
182
188
 
183
- const key_ = Bytes.from(`0x${key.key()}`)
189
+ const key_ = Bytes.from(typeof key === 'function' ? key() : key)
184
190
  const value_ = Bytes.from(privateKey)
185
191
 
186
192
  const encKey = Bytes.slice(key_, 0, 16)
187
193
  const macKey = Bytes.slice(key_, 16, 32)
188
194
 
189
- const ciphertext = ctr(encKey, key.iv).encrypt(value_)
195
+ const ciphertext = ctr(encKey, iv).encrypt(value_)
190
196
  const mac = Hash.keccak256(Bytes.concat(macKey, ciphertext))
191
197
 
192
198
  return {
193
199
  crypto: {
194
200
  cipher: 'aes-128-ctr',
195
201
  ciphertext: Bytes.toHex(ciphertext).slice(2),
196
- cipherparams: { iv: Bytes.toHex(key.iv).slice(2) },
197
- kdf: key.kdf,
198
- kdfparams: key.kdfparams,
202
+ cipherparams: { iv: Bytes.toHex(iv).slice(2) },
203
+ kdf,
204
+ kdfparams,
199
205
  mac: Bytes.toHex(mac).slice(2),
200
206
  } as Keystore['crypto'],
201
207
  id,
@@ -204,7 +210,7 @@ export async function encrypt(
204
210
  }
205
211
 
206
212
  export declare namespace encrypt {
207
- type Options = {
213
+ type Options = DeriveOpts & {
208
214
  /** UUID. */
209
215
  id?: string | undefined
210
216
  }
@@ -217,7 +223,7 @@ export declare namespace encrypt {
217
223
  * ```ts twoslash
218
224
  * import { Keystore } from 'ox'
219
225
  *
220
- * const key = Keystore.pbkdf2({ password: 'testpassword' })
226
+ * const [key, opts] = Keystore.pbkdf2({ password: 'testpassword' })
221
227
  * ```
222
228
  *
223
229
  * @param options - PBKDF2 options.
@@ -229,11 +235,10 @@ export function pbkdf2(options: pbkdf2.Options) {
229
235
  const salt = options.salt ? Bytes.from(options.salt) : Bytes.random(32)
230
236
  const key = Bytes.toHex(
231
237
  pbkdf2_noble(sha256, password, salt, { c: iterations, dkLen: 32 }),
232
- ).slice(2)
238
+ )
233
239
 
234
- return defineKey({
240
+ return defineKey(() => key, {
235
241
  iv,
236
- key: () => key,
237
242
  kdfparams: {
238
243
  c: iterations,
239
244
  dklen: 32,
@@ -241,7 +246,7 @@ export function pbkdf2(options: pbkdf2.Options) {
241
246
  salt: Bytes.toHex(salt).slice(2),
242
247
  },
243
248
  kdf: 'pbkdf2',
244
- }) satisfies Pbkdf2Key
249
+ }) satisfies [Key, Pbkdf2DeriveOpts]
245
250
  }
246
251
 
247
252
  export declare namespace pbkdf2 {
@@ -264,7 +269,7 @@ export declare namespace pbkdf2 {
264
269
  * ```ts twoslash
265
270
  * import { Keystore } from 'ox'
266
271
  *
267
- * const key = await Keystore.pbkdf2Async({ password: 'testpassword' })
272
+ * const [key, opts] = await Keystore.pbkdf2Async({ password: 'testpassword' })
268
273
  * ```
269
274
  *
270
275
  * @param options - PBKDF2 options.
@@ -279,11 +284,10 @@ export async function pbkdf2Async(options: pbkdf2.Options) {
279
284
  c: iterations,
280
285
  dkLen: 32,
281
286
  }),
282
- ).slice(2)
287
+ )
283
288
 
284
- return defineKey({
289
+ return defineKey(() => key, {
285
290
  iv,
286
- key: () => key,
287
291
  kdfparams: {
288
292
  c: iterations,
289
293
  dklen: 32,
@@ -291,7 +295,7 @@ export async function pbkdf2Async(options: pbkdf2.Options) {
291
295
  salt: Bytes.toHex(salt).slice(2),
292
296
  },
293
297
  kdf: 'pbkdf2',
294
- }) satisfies Pbkdf2Key
298
+ }) satisfies [Key, Pbkdf2DeriveOpts]
295
299
  }
296
300
 
297
301
  export declare namespace pbkdf2Async {
@@ -305,26 +309,22 @@ export declare namespace pbkdf2Async {
305
309
  * ```ts twoslash
306
310
  * import { Keystore } from 'ox'
307
311
  *
308
- * const key = Keystore.scrypt({ password: 'testpassword' })
312
+ * const [key, opts] = Keystore.scrypt({ password: 'testpassword' })
309
313
  * ```
310
314
  *
311
315
  * @param options - Scrypt options.
312
316
  * @returns Scrypt key.
313
317
  */
314
318
  export function scrypt(options: scrypt.Options) {
315
- const { iv, n = 262_144, password } = options
316
-
317
- const p = 8
318
- const r = 1
319
+ const { iv, n = 262_144, password, p = 8, r = 1 } = options
319
320
 
320
321
  const salt = options.salt ? Bytes.from(options.salt) : Bytes.random(32)
321
322
  const key = Bytes.toHex(
322
323
  scrypt_noble(password, salt, { N: n, dkLen: 32, r, p }),
323
- ).slice(2)
324
+ )
324
325
 
325
- return defineKey({
326
+ return defineKey(() => key, {
326
327
  iv,
327
- key: () => key,
328
328
  kdfparams: {
329
329
  dklen: 32,
330
330
  n,
@@ -333,7 +333,7 @@ export function scrypt(options: scrypt.Options) {
333
333
  salt: Bytes.toHex(salt).slice(2),
334
334
  },
335
335
  kdf: 'scrypt',
336
- }) satisfies ScryptKey
336
+ }) satisfies [Key, ScryptDeriveOpts]
337
337
  }
338
338
 
339
339
  export declare namespace scrypt {
@@ -342,6 +342,10 @@ export declare namespace scrypt {
342
342
  iv?: Bytes.Bytes | Hex.Hex | undefined
343
343
  /** Cost factor. @default 262_144 */
344
344
  n?: number | undefined
345
+ /** Parallelization factor. @default 8 */
346
+ p?: number | undefined
347
+ /** Block size. @default 1 */
348
+ r?: number | undefined
345
349
  /** Password to derive key from. */
346
350
  password: string
347
351
  /** Salt to use for key derivation. @default `Bytes.random(32)` */
@@ -356,7 +360,7 @@ export declare namespace scrypt {
356
360
  * ```ts twoslash
357
361
  * import { Keystore } from 'ox'
358
362
  *
359
- * const key = await Keystore.scryptAsync({ password: 'testpassword' })
363
+ * const [key, opts] = await Keystore.scryptAsync({ password: 'testpassword' })
360
364
  * ```
361
365
  *
362
366
  * @param options - Scrypt options.
@@ -371,11 +375,10 @@ export async function scryptAsync(options: scrypt.Options) {
371
375
  const salt = options.salt ? Bytes.from(options.salt) : Bytes.random(32)
372
376
  const key = Bytes.toHex(
373
377
  await scryptAsync_noble(password, salt, { N: n, dkLen: 32, r, p }),
374
- ).slice(2)
378
+ )
375
379
 
376
- return defineKey({
380
+ return defineKey(() => key, {
377
381
  iv,
378
- key: () => key,
379
382
  kdfparams: {
380
383
  dklen: 32,
381
384
  n,
@@ -384,29 +387,157 @@ export async function scryptAsync(options: scrypt.Options) {
384
387
  salt: Bytes.toHex(salt).slice(2),
385
388
  },
386
389
  kdf: 'scrypt',
387
- }) satisfies ScryptKey
390
+ }) satisfies [Key, ScryptDeriveOpts]
388
391
  }
389
392
 
390
393
  export declare namespace scryptAsync {
391
394
  type Options = scrypt.Options
392
395
  }
393
396
 
397
+ /**
398
+ * Extracts a Key from a JSON Keystore to use for decryption.
399
+ *
400
+ * @example
401
+ * ```ts twoslash
402
+ * // @noErrors
403
+ * import { Keystore } from 'ox'
404
+ *
405
+ * // JSON keystore.
406
+ * const keystore = { crypto: { ... }, id: '...', version: 3 }
407
+ *
408
+ * const key = Keystore.toKey(keystore, { password: 'hunter2' }) // [!code focus]
409
+ *
410
+ * const decrypted = Keystore.decrypt(keystore, key)
411
+ * ```
412
+ *
413
+ * @param keystore - JSON Keystore
414
+ * @param options - Options
415
+ * @returns Key
416
+ */
417
+ export function toKey(keystore: Keystore, options: toKey.Options): Key {
418
+ const { crypto } = keystore
419
+ const { password } = options
420
+ const { cipherparams, kdf, kdfparams } = crypto
421
+ const { iv } = cipherparams
422
+ const { c, n, p, r, salt } = kdfparams as OneOf<
423
+ Pbkdf2DeriveOpts['kdfparams'] | ScryptDeriveOpts['kdfparams']
424
+ >
425
+
426
+ const [key] = (() => {
427
+ switch (kdf) {
428
+ case 'scrypt':
429
+ return scrypt({
430
+ iv: Bytes.from(`0x${iv}`),
431
+ n,
432
+ p,
433
+ r,
434
+ salt: Bytes.from(`0x${salt}`),
435
+ password,
436
+ })
437
+ case 'pbkdf2':
438
+ return pbkdf2({
439
+ iv: Bytes.from(`0x${iv}`),
440
+ iterations: c,
441
+ password,
442
+ salt: Bytes.from(`0x${salt}`),
443
+ })
444
+ default:
445
+ throw new Error('unsupported kdf')
446
+ }
447
+ })()
448
+
449
+ return key
450
+ }
451
+
452
+ export declare namespace toKey {
453
+ type Options = {
454
+ /** Password to derive key from. */
455
+ password: string
456
+ }
457
+ }
458
+
459
+ /**
460
+ * Extracts a Key asynchronously from a JSON Keystore to use for decryption.
461
+ *
462
+ * @example
463
+ * ```ts twoslash
464
+ * // @noErrors
465
+ * import { Keystore } from 'ox'
466
+ *
467
+ * // JSON keystore.
468
+ * const keystore = { crypto: { ... }, id: '...', version: 3 }
469
+ *
470
+ * const key = await Keystore.toKeyAsync(keystore, { password: 'hunter2' }) // [!code focus]
471
+ *
472
+ * const decrypted = Keystore.decrypt(keystore, key)
473
+ * ```
474
+ *
475
+ * @param keystore - JSON Keystore
476
+ * @param options - Options
477
+ * @returns Key
478
+ */
479
+ export async function toKeyAsync(
480
+ keystore: Keystore,
481
+ options: toKeyAsync.Options,
482
+ ): Promise<Key> {
483
+ const { crypto } = keystore
484
+ const { password } = options
485
+ const { cipherparams, kdf, kdfparams } = crypto
486
+ const { iv } = cipherparams
487
+ const { c, n, p, r, salt } = kdfparams as OneOf<
488
+ Pbkdf2DeriveOpts['kdfparams'] | ScryptDeriveOpts['kdfparams']
489
+ >
490
+
491
+ const [key] = await (async () => {
492
+ switch (kdf) {
493
+ case 'scrypt':
494
+ return await scryptAsync({
495
+ iv: Bytes.from(`0x${iv}`),
496
+ n,
497
+ p,
498
+ r,
499
+ salt: Bytes.from(`0x${salt}`),
500
+ password,
501
+ })
502
+ case 'pbkdf2':
503
+ return await pbkdf2({
504
+ iv: Bytes.from(`0x${iv}`),
505
+ iterations: c,
506
+ password,
507
+ salt: Bytes.from(`0x${salt}`),
508
+ })
509
+ default:
510
+ throw new Error('unsupported kdf')
511
+ }
512
+ })()
513
+
514
+ return key
515
+ }
516
+
517
+ export declare namespace toKeyAsync {
518
+ type Options = {
519
+ /** Password to derive key from. */
520
+ password: string
521
+ }
522
+ }
523
+
394
524
  ///////////////////////////////////////////////////////////////////////////
395
525
 
396
526
  /** @internal */
397
- function defineKey<const key extends defineKey.Value>(
398
- key: key,
399
- ): key & { iv: Bytes.Bytes } {
400
- const iv = key.iv ? Bytes.from(key.iv) : Bytes.random(16)
401
- return { ...key, iv }
527
+ function defineKey<
528
+ const key extends Key,
529
+ const options extends defineKey.Options,
530
+ >(key: key, options: options): [key, options & { iv: Bytes.Bytes }] {
531
+ const iv = options.iv ? Bytes.from(options.iv) : Bytes.random(16)
532
+ return [key, { ...options, iv }] as never
402
533
  }
403
534
 
404
535
  /** @internal */
405
536
  declare namespace defineKey {
406
- type Value<
537
+ type Options<
407
538
  kdf extends string = string,
408
539
  kdfparams extends Record<string, unknown> = Record<string, unknown>,
409
- > = Omit<BaseKey<kdf, kdfparams>, 'iv'> & {
540
+ > = Omit<BaseDeriveOpts<kdf, kdfparams>, 'iv'> & {
410
541
  iv?: Bytes.Bytes | Hex.Hex | undefined
411
542
  }
412
543
 
package/index.ts CHANGED
@@ -1563,9 +1563,9 @@ export * as Json from './core/Json.js'
1563
1563
  * Utilities & types for working with [Keystores](https://ethereum.org/en/developers/docs/data-structures-and-encoding/web3-secret-storage).
1564
1564
  *
1565
1565
  * @example
1566
- * ### Encrypting & Decrypting Private Keys
1566
+ * ### Encrypting Private Keys
1567
1567
  *
1568
- * Private keys can be encrypted into a JSON keystore using {@link ox#Keystore.(encrypt:function)} and decrypted using {@link ox#Keystore.(decrypt:function)}:
1568
+ * Private keys can be encrypted into a JSON keystore using {@link ox#Keystore.(encrypt:function)}:
1569
1569
  *
1570
1570
  * ```ts twoslash
1571
1571
  * import { Keystore, Secp256k1 } from 'ox'
@@ -1574,10 +1574,10 @@ export * as Json from './core/Json.js'
1574
1574
  * const privateKey = Secp256k1.randomPrivateKey()
1575
1575
  *
1576
1576
  * // Derive a key from a password.
1577
- * const key = Keystore.pbkdf2({ password: 'testpassword' })
1577
+ * const [key, opts] = Keystore.pbkdf2({ password: 'testpassword' })
1578
1578
  *
1579
1579
  * // Encrypt the private key.
1580
- * const encrypted = await Keystore.encrypt(privateKey, key)
1580
+ * const keystore = Keystore.encrypt(privateKey, key, opts)
1581
1581
  * // @log: {
1582
1582
  * // @log: "crypto": {
1583
1583
  * // @log: "cipher": "aes-128-ctr",
@@ -1597,10 +1597,26 @@ export * as Json from './core/Json.js'
1597
1597
  * // @log: "id": "...",
1598
1598
  * // @log: "version": 3,
1599
1599
  * // @log: }
1600
+ * ```
1601
+ *
1602
+ * @example
1603
+ * ### Decrypting Private Keys
1604
+ *
1605
+ * Private keys can be decrypted from a JSON keystore using {@link ox#Keystore.(decrypt:function)}:
1606
+ *
1607
+ * ```ts twoslash
1608
+ * // @noErrors
1609
+ * import { Keystore, Secp256k1 } from 'ox'
1610
+ *
1611
+ * const keystore = { crypto: { ... }, id: '...', version: 3 }
1612
+ *
1613
+ * // Derive the key.
1614
+ * const key = Keystore.toKey(keystore, { password: 'testpassword' })
1600
1615
  *
1601
1616
  * // Decrypt the private key.
1602
- * const decrypted = await Keystore.decrypt(encrypted, key)
1617
+ * const decrypted = Keystore.decrypt(keystore, key)
1603
1618
  * // @log: "0x..."
1619
+ *
1604
1620
  * ```
1605
1621
  *
1606
1622
  * @category Crypto
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ox",
3
3
  "description": "Ethereum Standard Library",
4
- "version": "0.7.2",
4
+ "version": "0.8.1",
5
5
  "main": "./_cjs/index.js",
6
6
  "module": "./_esm/index.js",
7
7
  "types": "./_types/index.d.ts",
package/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** @internal */
2
- export const version = '0.7.2'
2
+ export const version = '0.8.1'