ox 1.1.0 → 1.1.2

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.
Files changed (184) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/core/Engine.d.ts +43 -11
  3. package/dist/core/Engine.d.ts.map +1 -1
  4. package/dist/core/Engine.js +40 -10
  5. package/dist/core/Engine.js.map +1 -1
  6. package/dist/core/internal/engine.d.ts +8 -8
  7. package/dist/core/internal/engine.d.ts.map +1 -1
  8. package/dist/index.d.ts +5 -2
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +5 -2
  11. package/dist/index.js.map +1 -1
  12. package/dist/node/Ed25519.d.ts +33 -0
  13. package/dist/node/Ed25519.d.ts.map +1 -0
  14. package/dist/node/Ed25519.js +67 -0
  15. package/dist/node/Ed25519.js.map +1 -0
  16. package/dist/node/Engine.d.ts +29 -16
  17. package/dist/node/Engine.d.ts.map +1 -1
  18. package/dist/node/Engine.js +38 -14
  19. package/dist/node/Engine.js.map +1 -1
  20. package/dist/node/Hash.d.ts +14 -16
  21. package/dist/node/Hash.d.ts.map +1 -1
  22. package/dist/node/Hash.js +14 -16
  23. package/dist/node/Hash.js.map +1 -1
  24. package/dist/node/Keystore.d.ts +36 -0
  25. package/dist/node/Keystore.d.ts.map +1 -0
  26. package/dist/node/Keystore.js +72 -0
  27. package/dist/node/Keystore.js.map +1 -0
  28. package/dist/node/Mnemonic.d.ts +35 -0
  29. package/dist/node/Mnemonic.d.ts.map +1 -0
  30. package/dist/node/Mnemonic.js +46 -0
  31. package/dist/node/Mnemonic.js.map +1 -0
  32. package/dist/node/P256.d.ts +32 -0
  33. package/dist/node/P256.d.ts.map +1 -0
  34. package/dist/node/P256.js +34 -0
  35. package/dist/node/P256.js.map +1 -0
  36. package/dist/node/X25519.d.ts +31 -0
  37. package/dist/node/X25519.d.ts.map +1 -0
  38. package/dist/node/X25519.js +71 -0
  39. package/dist/node/X25519.js.map +1 -0
  40. package/dist/node/index.d.ts +95 -8
  41. package/dist/node/index.d.ts.map +1 -1
  42. package/dist/node/index.js +95 -8
  43. package/dist/node/index.js.map +1 -1
  44. package/dist/wasm/Ed25519.d.ts +35 -0
  45. package/dist/wasm/Ed25519.d.ts.map +1 -0
  46. package/dist/wasm/Ed25519.js +88 -0
  47. package/dist/wasm/Ed25519.js.map +1 -0
  48. package/dist/wasm/Engine.d.ts +35 -26
  49. package/dist/wasm/Engine.d.ts.map +1 -1
  50. package/dist/wasm/Engine.js +44 -25
  51. package/dist/wasm/Engine.js.map +1 -1
  52. package/dist/wasm/Hash.d.ts +13 -19
  53. package/dist/wasm/Hash.d.ts.map +1 -1
  54. package/dist/wasm/Hash.js +21 -27
  55. package/dist/wasm/Hash.js.map +1 -1
  56. package/dist/wasm/Keystore.d.ts +37 -0
  57. package/dist/wasm/Keystore.d.ts.map +1 -0
  58. package/dist/wasm/Keystore.js +34 -0
  59. package/dist/wasm/Keystore.js.map +1 -0
  60. package/dist/wasm/Mnemonic.d.ts +33 -0
  61. package/dist/wasm/Mnemonic.d.ts.map +1 -0
  62. package/dist/wasm/Mnemonic.js +30 -0
  63. package/dist/wasm/Mnemonic.js.map +1 -0
  64. package/dist/wasm/X25519.d.ts +33 -0
  65. package/dist/wasm/X25519.d.ts.map +1 -0
  66. package/dist/wasm/X25519.js +53 -0
  67. package/dist/wasm/X25519.js.map +1 -0
  68. package/dist/wasm/index.d.ts +81 -12
  69. package/dist/wasm/index.d.ts.map +1 -1
  70. package/dist/wasm/index.js +81 -12
  71. package/dist/wasm/index.js.map +1 -1
  72. package/dist/wasm/internal/blake3.d.ts +11 -0
  73. package/dist/wasm/internal/blake3.d.ts.map +1 -0
  74. package/dist/wasm/internal/blake3.js +31 -0
  75. package/dist/wasm/internal/blake3.js.map +1 -0
  76. package/dist/wasm/internal/blake3.wasm.d.ts +3 -0
  77. package/dist/wasm/internal/blake3.wasm.d.ts.map +1 -0
  78. package/dist/wasm/internal/blake3.wasm.js +10 -0
  79. package/dist/wasm/internal/blake3.wasm.js.map +1 -0
  80. package/dist/wasm/internal/crypto25519.d.ts +16 -0
  81. package/dist/wasm/internal/crypto25519.d.ts.map +1 -0
  82. package/dist/wasm/internal/crypto25519.js +5 -0
  83. package/dist/wasm/internal/crypto25519.js.map +1 -0
  84. package/dist/wasm/internal/crypto25519.wasm.d.ts +3 -0
  85. package/dist/wasm/internal/crypto25519.wasm.d.ts.map +1 -0
  86. package/dist/wasm/internal/crypto25519.wasm.js +10 -0
  87. package/dist/wasm/internal/crypto25519.wasm.js.map +1 -0
  88. package/dist/wasm/internal/ed25519.d.ts +4 -0
  89. package/dist/wasm/internal/ed25519.d.ts.map +1 -0
  90. package/dist/wasm/internal/ed25519.js +21 -0
  91. package/dist/wasm/internal/ed25519.js.map +1 -0
  92. package/dist/wasm/internal/hashes.d.ts +12 -0
  93. package/dist/wasm/internal/hashes.d.ts.map +1 -0
  94. package/dist/wasm/internal/hashes.js +8 -0
  95. package/dist/wasm/internal/hashes.js.map +1 -0
  96. package/dist/wasm/internal/hashes.wasm.d.ts +4 -2
  97. package/dist/wasm/internal/hashes.wasm.d.ts.map +1 -1
  98. package/dist/wasm/internal/hashes.wasm.js +4 -2
  99. package/dist/wasm/internal/hashes.wasm.js.map +1 -1
  100. package/dist/wasm/internal/mnemonic.d.ts +4 -0
  101. package/dist/wasm/internal/mnemonic.d.ts.map +1 -0
  102. package/dist/wasm/internal/mnemonic.js +45 -0
  103. package/dist/wasm/internal/mnemonic.js.map +1 -0
  104. package/dist/wasm/internal/pbkdf2.d.ts +12 -0
  105. package/dist/wasm/internal/pbkdf2.d.ts.map +1 -0
  106. package/dist/wasm/internal/pbkdf2.js +51 -0
  107. package/dist/wasm/internal/pbkdf2.js.map +1 -0
  108. package/dist/wasm/internal/x25519.d.ts +4 -0
  109. package/dist/wasm/internal/x25519.d.ts.map +1 -0
  110. package/dist/wasm/internal/x25519.js +21 -0
  111. package/dist/wasm/internal/x25519.js.map +1 -0
  112. package/package.json +46 -1
  113. package/src/core/Engine.ts +84 -11
  114. package/src/core/_test/Engine.test-d.ts +56 -0
  115. package/src/core/_test/Engine.test.ts +95 -0
  116. package/src/core/_test/Hash.vectors.test.ts +8 -0
  117. package/src/core/internal/engine.ts +8 -8
  118. package/src/index.ts +5 -2
  119. package/src/node/Ed25519.ts +87 -0
  120. package/src/node/Engine.ts +79 -23
  121. package/src/node/Hash.ts +22 -27
  122. package/src/node/Keystore.ts +103 -0
  123. package/src/node/Mnemonic.ts +68 -0
  124. package/src/node/P256.ts +48 -0
  125. package/src/node/X25519.ts +93 -0
  126. package/src/node/_test/Curves.fuzz.ts +76 -0
  127. package/src/node/_test/Ed25519.test-d.ts +35 -0
  128. package/src/node/_test/Ed25519.test.ts +220 -0
  129. package/src/node/_test/Engine.test-d.ts +28 -0
  130. package/src/node/_test/Engine.test.ts +62 -22
  131. package/src/node/_test/Hash.test-d.ts +15 -9
  132. package/src/node/_test/Hash.test.ts +10 -11
  133. package/src/node/_test/Keystore.fuzz.ts +58 -0
  134. package/src/node/_test/Keystore.test-d.ts +43 -0
  135. package/src/node/_test/Keystore.test.ts +198 -0
  136. package/src/node/_test/Mnemonic.fuzz.ts +31 -0
  137. package/src/node/_test/Mnemonic.test-d.ts +21 -0
  138. package/src/node/_test/Mnemonic.test.ts +98 -0
  139. package/src/node/_test/P256.test-d.ts +29 -0
  140. package/src/node/_test/P256.test.ts +102 -0
  141. package/src/node/_test/X25519.test-d.ts +28 -0
  142. package/src/node/_test/X25519.test.ts +239 -0
  143. package/src/node/index.ts +100 -8
  144. package/src/version.ts +1 -1
  145. package/src/wasm/Ed25519.ts +114 -0
  146. package/src/wasm/Engine.ts +76 -31
  147. package/src/wasm/Hash.bench.ts +2 -2
  148. package/src/wasm/Hash.ts +29 -50
  149. package/src/wasm/Keystore.ts +48 -0
  150. package/src/wasm/Mnemonic.ts +44 -0
  151. package/src/wasm/X25519.ts +70 -0
  152. package/src/wasm/_test/Crypto25519.conformance.ts +57 -0
  153. package/src/wasm/_test/Crypto25519.fuzz.ts +187 -0
  154. package/src/wasm/_test/Ed25519.test-d.ts +28 -0
  155. package/src/wasm/_test/Ed25519.test.ts +168 -0
  156. package/src/wasm/_test/Ed25519.vectors.test.ts +73 -0
  157. package/src/wasm/_test/Engine.test-d.ts +28 -0
  158. package/src/wasm/_test/Engine.test.ts +59 -25
  159. package/src/wasm/_test/Hash.browser.test.ts +11 -7
  160. package/src/wasm/_test/Hash.fuzz.ts +58 -23
  161. package/src/wasm/_test/Hash.test-d.ts +15 -10
  162. package/src/wasm/_test/Hash.test.ts +148 -48
  163. package/src/wasm/_test/Hash.vectors.test.ts +15 -8
  164. package/src/wasm/_test/Keystore.browser.test.ts +42 -0
  165. package/src/wasm/_test/Keystore.fuzz.ts +113 -0
  166. package/src/wasm/_test/Keystore.test-d.ts +33 -0
  167. package/src/wasm/_test/Keystore.test.ts +192 -0
  168. package/src/wasm/_test/Keystore.vectors.test.ts +21 -0
  169. package/src/wasm/_test/Mnemonic.test-d.ts +17 -0
  170. package/src/wasm/_test/Mnemonic.test.ts +110 -0
  171. package/src/wasm/_test/X25519.test-d.ts +21 -0
  172. package/src/wasm/_test/X25519.test.ts +194 -0
  173. package/src/wasm/_test/fixtures.ts +4 -0
  174. package/src/wasm/index.ts +85 -12
  175. package/src/wasm/internal/blake3.ts +45 -0
  176. package/src/wasm/internal/blake3.wasm.ts +11 -0
  177. package/src/wasm/internal/crypto25519.ts +43 -0
  178. package/src/wasm/internal/crypto25519.wasm.ts +11 -0
  179. package/src/wasm/internal/ed25519.ts +31 -0
  180. package/src/wasm/internal/hashes.ts +29 -0
  181. package/src/wasm/internal/hashes.wasm.ts +5 -2
  182. package/src/wasm/internal/mnemonic.ts +56 -0
  183. package/src/wasm/internal/pbkdf2.ts +86 -0
  184. package/src/wasm/internal/x25519.ts +29 -0
@@ -17,6 +17,62 @@ type Unlisted = {
17
17
  >
18
18
  }[keyof Engine.Engine]
19
19
 
20
+ describe('install', () => {
21
+ test('accepts raw and promised slots and preserves their resolved shape', () => {
22
+ const keccak256 = (input: Uint8Array) => input
23
+ const toSeed = (_mnemonic: string) => new Uint8Array(64)
24
+
25
+ expectTypeOf(
26
+ Engine.install({
27
+ Hash: Promise.resolve({ keccak256 }),
28
+ Mnemonic: { toSeed },
29
+ }),
30
+ ).toEqualTypeOf<
31
+ Promise<{
32
+ Hash: { keccak256: typeof keccak256 }
33
+ Mnemonic: { toSeed: typeof toSeed }
34
+ }>
35
+ >()
36
+ })
37
+
38
+ test('rejects an unknown slot', () => {
39
+ // @ts-expect-error
40
+ void Engine.install({ Keccak: Promise.resolve({}) })
41
+ })
42
+
43
+ test('rejects an unknown slot alongside a valid slot', () => {
44
+ void Engine.install({
45
+ Hash: { keccak256: (input) => input },
46
+ // @ts-expect-error
47
+ Keccak: {},
48
+ })
49
+ })
50
+
51
+ test('rejects a misspelled function', () => {
52
+ void Engine.install({
53
+ // @ts-expect-error
54
+ Hash: Promise.resolve({ keccak_256: (input: Uint8Array) => input }),
55
+ })
56
+ })
57
+
58
+ test('rejects a misspelled function alongside a valid function', () => {
59
+ void Engine.install({
60
+ // @ts-expect-error
61
+ Hash: {
62
+ keccak256: (input: Uint8Array) => input,
63
+ keccak_256: (input: Uint8Array) => input,
64
+ },
65
+ })
66
+ })
67
+
68
+ test('rejects a mistyped promised function', () => {
69
+ void Engine.install({
70
+ // @ts-expect-error
71
+ Hash: Promise.resolve({ keccak256: (input: string) => input }),
72
+ })
73
+ })
74
+ })
75
+
20
76
  describe('set', () => {
21
77
  test('accepts a partial engine', () => {
22
78
  expectTypeOf(Engine.set).parameter(0).toEqualTypeOf<Engine.Engine>()
@@ -38,6 +38,101 @@ function merkelize() {
38
38
  return BinaryStateTree.merkelize(tree)
39
39
  }
40
40
 
41
+ describe('install', () => {
42
+ test('behavior: resolves slots in parallel and installs them atomically', async () => {
43
+ const keccak256 = () => new Uint8Array(32).fill(1)
44
+ const sign = () => new Uint8Array(65).fill(3)
45
+ const toSeed = () => new Uint8Array(64).fill(2)
46
+ let releaseHash: (() => void) | undefined
47
+
48
+ Engine.set({ Secp256k1: { sign } })
49
+
50
+ const hash = {
51
+ // oxlint-disable-next-line unicorn/no-thenable -- Distinguishes parallel resolution from sequential awaits.
52
+ then(
53
+ resolve: (value: Engine.Hash) => void,
54
+ reject: (reason: unknown) => void,
55
+ ) {
56
+ const timeout = setTimeout(
57
+ () => reject(new Error('slots resolved serially')),
58
+ 0,
59
+ )
60
+ releaseHash = () => {
61
+ clearTimeout(timeout)
62
+ resolve({ keccak256 })
63
+ }
64
+ },
65
+ } as unknown as PromiseLike<Engine.Hash>
66
+ const mnemonic = {
67
+ // oxlint-disable-next-line unicorn/no-thenable -- Distinguishes parallel resolution from sequential awaits.
68
+ then(resolve: (value: Engine.Mnemonic) => void) {
69
+ resolve({ toSeed })
70
+ releaseHash?.()
71
+ },
72
+ } as unknown as PromiseLike<Engine.Mnemonic>
73
+
74
+ const installed = await Engine.install({ Hash: hash, Mnemonic: mnemonic })
75
+
76
+ expect(installed).toEqual({
77
+ Hash: { keccak256 },
78
+ Mnemonic: { toSeed },
79
+ })
80
+ expect(Engine.get()).toEqual({
81
+ ...installed,
82
+ Secp256k1: { sign },
83
+ })
84
+ })
85
+
86
+ test('behavior: a rejected slot leaves the previous engine installed', async () => {
87
+ const keccak256 = () => new Uint8Array(32).fill(1)
88
+ Engine.set({ Hash: { keccak256 } })
89
+
90
+ await expect(
91
+ Engine.install({
92
+ Hash: Promise.resolve({
93
+ sha256: () => new Uint8Array(32).fill(2),
94
+ }),
95
+ Mnemonic: Promise.reject(new Error('could not load engine')),
96
+ }),
97
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
98
+ `[Error: could not load engine]`,
99
+ )
100
+ expect(Engine.get()).toEqual({ Hash: { keccak256 } })
101
+ })
102
+
103
+ test('behavior: an invalid resolved engine leaves the previous engine installed', async () => {
104
+ const keccak256 = () => new Uint8Array(32).fill(1)
105
+ Engine.set({ Hash: { keccak256 } })
106
+
107
+ await expect(
108
+ Engine.install({
109
+ Hash: Promise.resolve({
110
+ sha256: () => new Uint8Array(32).fill(2),
111
+ }),
112
+ Mnemonic: Promise.resolve({
113
+ to_seed: () => new Uint8Array(64),
114
+ }),
115
+ } as never),
116
+ ).rejects.toThrowError(Engine.UnknownPrimitiveError)
117
+ expect(Engine.get()).toEqual({ Hash: { keccak256 } })
118
+ })
119
+
120
+ test('behavior: a null resolved slot leaves the previous engine installed', async () => {
121
+ const keccak256 = () => new Uint8Array(32).fill(1)
122
+ Engine.set({ Hash: { keccak256 } })
123
+
124
+ await expect(
125
+ Engine.install({
126
+ Hash: Promise.resolve({
127
+ sha256: () => new Uint8Array(32).fill(2),
128
+ }),
129
+ Mnemonic: Promise.resolve(null),
130
+ } as never),
131
+ ).rejects.toThrowError(Engine.InvalidSlotValueError)
132
+ expect(Engine.get()).toEqual({ Hash: { keccak256 } })
133
+ })
134
+ })
135
+
41
136
  describe('set', () => {
42
137
  test('behavior: empty engine is a no-op', () => {
43
138
  const before = Hash.keccak256(payload)
@@ -1,6 +1,7 @@
1
1
  import { Hash } from 'ox'
2
2
  import { describe, expect, test } from 'vp/test'
3
3
  import * as vectors from '../../../test/vectors/hashes/index.js'
4
+ import * as hash from '../internal/hash.js'
4
5
 
5
6
  /**
6
7
  * Published vectors against ox's default (`@noble/hashes`) implementations.
@@ -11,6 +12,13 @@ import * as vectors from '../../../test/vectors/hashes/index.js'
11
12
  * See `test/vectors/hashes/README.md`.
12
13
  */
13
14
 
15
+ describe('blake3', () => {
16
+ test(`matches ${vectors.blake3.length} official BLAKE3 vectors`, () => {
17
+ for (const { digest, message } of vectors.blake3)
18
+ expect(hash.blake3(message)).toEqual(digest)
19
+ })
20
+ })
21
+
14
22
  describe('sha256', () => {
15
23
  test(`matches ${vectors.sha256.length} NIST CAVP vectors`, () => {
16
24
  for (const { digest, message } of vectors.sha256)
@@ -263,21 +263,21 @@ export type Bls = {
263
263
  * @internal
264
264
  */
265
265
  export type Engine = {
266
- /** Implementation for {@link ox#Bls}. */
266
+ /** Implementation for [`Bls`](/api/Bls). */
267
267
  Bls?: Bls | undefined
268
- /** Implementation for {@link ox#Ed25519}. */
268
+ /** Implementation for [`Ed25519`](/api/Ed25519). */
269
269
  Ed25519?: Eddsa | undefined
270
- /** Implementation for {@link ox#Hash}. */
270
+ /** Implementation for [`Hash`](/api/Hash). */
271
271
  Hash?: Hash | undefined
272
- /** Implementation for {@link ox#Keystore}. */
272
+ /** Implementation for [`Keystore`](/api/Keystore). */
273
273
  Keystore?: Keystore | undefined
274
- /** Implementation for {@link ox#Mnemonic}. */
274
+ /** Implementation for [`Mnemonic`](/api/Mnemonic). */
275
275
  Mnemonic?: Mnemonic | undefined
276
- /** Implementation for {@link ox#P256}. */
276
+ /** Implementation for [`P256`](/api/P256). */
277
277
  P256?: Ecdsa | undefined
278
- /** Implementation for {@link ox#Secp256k1}. */
278
+ /** Implementation for [`Secp256k1`](/api/Secp256k1). */
279
279
  Secp256k1?: Ecdsa | undefined
280
- /** Implementation for {@link ox#X25519}. */
280
+ /** Implementation for [`X25519`](/api/X25519). */
281
281
  X25519?: Ecdh | undefined
282
282
  }
283
283
 
package/src/index.ts CHANGED
@@ -1536,9 +1536,12 @@ export * as Ed25519 from './core/Ed25519.js'
1536
1536
  *
1537
1537
  * ```ts twoslash
1538
1538
  * // @noErrors
1539
- * import { Engine } from 'ox/wasm'
1539
+ * import { Engine } from 'ox'
1540
+ * import { Hash } from 'ox/wasm'
1540
1541
  *
1541
- * await Engine.load()
1542
+ * await Engine.install({
1543
+ * Hash: Hash.engine()
1544
+ * })
1542
1545
  * ```
1543
1546
  *
1544
1547
  * @example
@@ -0,0 +1,87 @@
1
+ import * as crypto from 'node:crypto'
2
+ import type * as Engine from '../core/Engine.js'
3
+ import type * as Errors from '../core/Errors.js'
4
+
5
+ export * from '../core/Ed25519.js'
6
+
7
+ const privateKeyPrefix = Buffer.from('302e020100300506032b657004220420', 'hex')
8
+
9
+ /**
10
+ * Creates a Node.js implementation of the [`Ed25519`](/api/Ed25519) engine
11
+ * slot, without installing it.
12
+ *
13
+ * Node's verifier does not implement Ox's ZIP-215 verification semantics, so
14
+ * this engine deliberately omits `verify`. Public-key conversion and random
15
+ * key generation also remain on Ox's default implementation.
16
+ *
17
+ * @example
18
+ * ```ts twoslash
19
+ * // @noErrors
20
+ * import { Engine } from 'ox'
21
+ * import { Ed25519 } from 'ox/node'
22
+ *
23
+ * await Engine.install({ Ed25519: Ed25519.engine() })
24
+ *
25
+ * Ed25519.getPublicKey({ privateKey: '0x...' })
26
+ * ```
27
+ *
28
+ * @returns The raw `Ed25519` engine slot.
29
+ */
30
+ export function engine(): Promise<engine.ReturnType> {
31
+ return Promise.resolve({
32
+ getPublicKey: (privateKey) =>
33
+ rawPublicKey(crypto.createPublicKey(toPrivateKey(privateKey))),
34
+ sign: (payload, privateKey) =>
35
+ new Uint8Array(crypto.sign(null, payload, toPrivateKey(privateKey))),
36
+ toMontgomerySecret: (privateKey) => {
37
+ assertLength(privateKey)
38
+ const digest = crypto.hash('sha512', privateKey, 'buffer')
39
+ try {
40
+ const secretKey = Uint8Array.from(digest.subarray(0, 32))
41
+ secretKey[0]! &= 248
42
+ secretKey[31]! &= 127
43
+ secretKey[31]! |= 64
44
+ return secretKey
45
+ } finally {
46
+ digest.fill(0)
47
+ }
48
+ },
49
+ })
50
+ }
51
+
52
+ export declare namespace engine {
53
+ /** Every `Ed25519` primitive this module implements. */
54
+ type ReturnType = {
55
+ [key in 'getPublicKey' | 'sign' | 'toMontgomerySecret']-?: NonNullable<
56
+ Engine.Eddsa[key]
57
+ >
58
+ }
59
+
60
+ type ErrorType = Errors.GlobalErrorType
61
+ }
62
+
63
+ function assertLength(key: Uint8Array): void {
64
+ if (key.length !== 32)
65
+ throw new RangeError(
66
+ `Ed25519 private key must be 32 bytes, got ${key.length}`,
67
+ )
68
+ }
69
+
70
+ function rawPublicKey(publicKey: crypto.KeyObject): Uint8Array {
71
+ const der = publicKey.export({ format: 'der', type: 'spki' })
72
+ return Uint8Array.from(der.subarray(-32))
73
+ }
74
+
75
+ function toPrivateKey(privateKey: Uint8Array): crypto.KeyObject {
76
+ assertLength(privateKey)
77
+ const der = Buffer.concat([privateKeyPrefix, privateKey])
78
+ try {
79
+ return crypto.createPrivateKey({
80
+ format: 'der',
81
+ key: der,
82
+ type: 'pkcs8',
83
+ })
84
+ } finally {
85
+ der.fill(0)
86
+ }
87
+ }
@@ -1,40 +1,67 @@
1
1
  import * as CoreEngine from '../core/Engine.js'
2
2
  import type * as Errors from '../core/Errors.js'
3
+ import * as Ed25519 from './Ed25519.js'
3
4
  import * as Hash from './Hash.js'
5
+ import * as Keystore from './Keystore.js'
6
+ import * as Mnemonic from './Mnemonic.js'
7
+ import * as P256 from './P256.js'
8
+ import * as X25519 from './X25519.js'
9
+
10
+ export {
11
+ AsyncScopeError,
12
+ get,
13
+ InvalidSlotValueError,
14
+ reset,
15
+ set,
16
+ UnknownPrimitiveError,
17
+ UnknownSlotError,
18
+ with,
19
+ } from '../core/Engine.js'
20
+ export type {
21
+ Bls,
22
+ Ecdh,
23
+ Ecdsa,
24
+ Eddsa,
25
+ Engine,
26
+ Hash,
27
+ Keystore,
28
+ Mnemonic,
29
+ } from '../core/Engine.js'
4
30
 
5
31
  /**
6
- * Creates and installs every Node.js implementation this entrypoint provides.
32
+ * Installs every Node.js implementation this entrypoint provides.
7
33
  *
8
- * The function is asynchronous to match the `ox/wasm` entrypoint, even though
9
- * Node's built-in cryptography needs no compilation. Call it once during
10
- * startup, before any cryptographic operation.
34
+ * Slots initialize in parallel and are installed atomically. Call this once
35
+ * during startup, before any cryptographic operation.
11
36
  *
12
37
  * @example
13
38
  * ```ts twoslash
14
39
  * // @noErrors
15
- * import { Hash } from 'ox'
16
- * import { Engine } from 'ox/node'
40
+ * import { Engine, Hash } from 'ox/node'
17
41
  *
18
- * await Engine.load()
42
+ * await Engine.install()
19
43
  *
20
44
  * Hash.sha256('0xdeadbeef')
21
45
  * ```
22
46
  *
23
47
  * @returns The engine that was installed.
24
48
  */
25
- export async function load(): Promise<load.ReturnType> {
26
- const engine = await create()
27
- CoreEngine.set(engine)
28
- return engine
49
+ export function install(): Promise<install.ReturnType> {
50
+ return CoreEngine.install({
51
+ Ed25519: Ed25519.engine(),
52
+ Hash: Hash.engine(),
53
+ Keystore: Keystore.engine(),
54
+ Mnemonic: Mnemonic.engine(),
55
+ P256: P256.engine(),
56
+ X25519: X25519.engine(),
57
+ })
29
58
  }
30
59
 
31
- export declare namespace load {
32
- type ReturnType = create.ReturnType
60
+ export declare namespace install {
61
+ /** Every slot this entrypoint installs, each with its primitives present. */
62
+ type ReturnType = engine.ReturnType
33
63
 
34
- type ErrorType =
35
- | create.ErrorType
36
- | CoreEngine.set.ErrorType
37
- | Errors.GlobalErrorType
64
+ type ErrorType = engine.ErrorType | CoreEngine.install.ErrorType
38
65
  }
39
66
 
40
67
  /**
@@ -50,20 +77,49 @@ export declare namespace load {
50
77
  * import { Engine, Hash } from 'ox'
51
78
  * import { Engine as NodeEngine } from 'ox/node'
52
79
  *
53
- * const node = await NodeEngine.create()
80
+ * const node = await NodeEngine.engine()
54
81
  *
55
82
  * Engine.with(node, () => Hash.sha256('0xdeadbeef'))
56
83
  * ```
57
84
  *
58
85
  * @returns An engine, ready to install.
59
86
  */
60
- export async function create(): Promise<create.ReturnType> {
61
- return { ...(await Hash.create()) }
87
+ export async function engine(): Promise<engine.ReturnType> {
88
+ const [ed25519, hash, keystore, mnemonic, p256, x25519] = await Promise.all([
89
+ Ed25519.engine(),
90
+ Hash.engine(),
91
+ Keystore.engine(),
92
+ Mnemonic.engine(),
93
+ P256.engine(),
94
+ X25519.engine(),
95
+ ])
96
+ return {
97
+ Ed25519: ed25519,
98
+ Hash: hash,
99
+ Keystore: keystore,
100
+ Mnemonic: mnemonic,
101
+ P256: p256,
102
+ X25519: x25519,
103
+ }
62
104
  }
63
105
 
64
- export declare namespace create {
106
+ export declare namespace engine {
65
107
  /** Every slot this entrypoint supplies, each with its primitives present. */
66
- type ReturnType = Hash.create.ReturnType
108
+ type ReturnType = {
109
+ Ed25519: Ed25519.engine.ReturnType
110
+ Hash: Hash.engine.ReturnType
111
+ Keystore: Keystore.engine.ReturnType
112
+ Mnemonic: Mnemonic.engine.ReturnType
113
+ P256: P256.engine.ReturnType
114
+ X25519: X25519.engine.ReturnType
115
+ }
67
116
 
68
- type ErrorType = Hash.create.ErrorType | Errors.GlobalErrorType
117
+ type ErrorType =
118
+ | Ed25519.engine.ErrorType
119
+ | Hash.engine.ErrorType
120
+ | Keystore.engine.ErrorType
121
+ | Mnemonic.engine.ErrorType
122
+ | P256.engine.ErrorType
123
+ | X25519.engine.ErrorType
124
+ | Errors.GlobalErrorType
69
125
  }
package/src/node/Hash.ts CHANGED
@@ -2,14 +2,15 @@ import * as crypto from 'node:crypto'
2
2
  import type * as Engine from '../core/Engine.js'
3
3
  import type * as Errors from '../core/Errors.js'
4
4
 
5
+ export * from '../core/Hash.js'
6
+
5
7
  /**
6
- * Creates Node.js implementations of the [`Hash`](/api/Hash) primitives, without
7
- * installing them.
8
+ * Creates a Node.js implementation of the [`Hash`](/api/Hash) engine slot,
9
+ * without installing it.
8
10
  *
9
- * Most callers want {@link ox#Engine.load} instead, which installs every
10
- * implementation this entrypoint provides. Reach for this to take the `Hash`
11
- * slot on its own, or to hold the implementation without touching the
12
- * installed engine.
11
+ * Most callers want [`Engine.install`](/node/crypto/Engine/install) instead,
12
+ * which installs every implementation this entrypoint provides. Reach for this
13
+ * to install or hold the `Hash` slot on its own.
13
14
  *
14
15
  * Node's `sha3-256` is not Ethereum Keccak256, so this engine deliberately
15
16
  * omits `keccak256`. Any earlier override remains installed; otherwise Ox uses
@@ -18,38 +19,32 @@ import type * as Errors from '../core/Errors.js'
18
19
  * @example
19
20
  * ```ts twoslash
20
21
  * // @noErrors
21
- * import { Engine, Hash } from 'ox'
22
- * import * as NodeHash from 'ox/node/Hash'
22
+ * import { Engine } from 'ox'
23
+ * import { Hash } from 'ox/node'
23
24
  *
24
- * Engine.set(await NodeHash.create())
25
+ * await Engine.install({ Hash: Hash.engine() })
25
26
  *
26
27
  * Hash.sha256('0xdeadbeef')
27
28
  * ```
28
29
  *
29
- * @returns An engine supplying the `Hash` slot.
30
+ * @returns The raw `Hash` engine slot.
30
31
  */
31
- export function create(): Promise<create.ReturnType> {
32
+ export function engine(): Promise<engine.ReturnType> {
32
33
  return Promise.resolve({
33
- Hash: {
34
- hmacSha256: (key, message) =>
35
- new Uint8Array(
36
- crypto.createHmac('sha256', key).update(message).digest(),
37
- ),
38
- ripemd160: (input) =>
39
- new Uint8Array(crypto.hash('ripemd160', input, 'buffer')),
40
- sha256: (input) => new Uint8Array(crypto.hash('sha256', input, 'buffer')),
41
- },
34
+ hmacSha256: (key, message) =>
35
+ new Uint8Array(crypto.createHmac('sha256', key).update(message).digest()),
36
+ ripemd160: (input) =>
37
+ new Uint8Array(crypto.hash('ripemd160', input, 'buffer')),
38
+ sha256: (input) => new Uint8Array(crypto.hash('sha256', input, 'buffer')),
42
39
  })
43
40
  }
44
41
 
45
- export declare namespace create {
46
- /** The `Hash` slot, carrying every primitive this module implements. */
42
+ export declare namespace engine {
43
+ /** Every `Hash` primitive this module implements. */
47
44
  type ReturnType = {
48
- Hash: {
49
- [key in 'hmacSha256' | 'ripemd160' | 'sha256']-?: NonNullable<
50
- Engine.Hash[key]
51
- >
52
- }
45
+ [key in 'hmacSha256' | 'ripemd160' | 'sha256']-?: NonNullable<
46
+ Engine.Hash[key]
47
+ >
53
48
  }
54
49
 
55
50
  type ErrorType = Errors.GlobalErrorType
@@ -0,0 +1,103 @@
1
+ import * as crypto from 'node:crypto'
2
+ import type * as Engine from '../core/Engine.js'
3
+ import type * as Errors from '../core/Errors.js'
4
+
5
+ export * from '../core/Keystore.js'
6
+
7
+ /**
8
+ * Creates a Node.js implementation of the [`Keystore`](/api/Keystore) engine
9
+ * slot, without installing it.
10
+ *
11
+ * Most callers want [`Engine.install`](/node/crypto/Engine/install) instead,
12
+ * which installs every implementation this entrypoint provides. Reach for this
13
+ * to install or hold the `Keystore` slot on its own.
14
+ *
15
+ * This engine deliberately omits scrypt. OpenSSL rejects Ox's default scrypt
16
+ * parameter shape, so installing it would make valid existing calls fail.
17
+ *
18
+ * @example
19
+ * ```ts twoslash
20
+ * // @noErrors
21
+ * import { Engine } from 'ox'
22
+ * import { Keystore } from 'ox/node'
23
+ *
24
+ * await Engine.install({ Keystore: Keystore.engine() })
25
+ *
26
+ * Keystore.pbkdf2({ password: 'testpassword' })
27
+ * ```
28
+ *
29
+ * @returns The raw `Keystore` engine slot.
30
+ */
31
+ export function engine(): Promise<engine.ReturnType> {
32
+ return Promise.resolve({
33
+ aesCtrDecrypt: (key, iv, data) => {
34
+ const cipher = crypto.createDecipheriv(
35
+ `aes-${key.length * 8}-ctr`,
36
+ key,
37
+ iv,
38
+ )
39
+ return cipherData(cipher, data)
40
+ },
41
+ aesCtrEncrypt: (key, iv, data) => {
42
+ const cipher = crypto.createCipheriv(`aes-${key.length * 8}-ctr`, key, iv)
43
+ return cipherData(cipher, data)
44
+ },
45
+ pbkdf2Sha256: (password, salt, options) =>
46
+ copyAndClear(
47
+ crypto.pbkdf2Sync(password, salt, options.c, options.dkLen, 'sha256'),
48
+ ),
49
+ pbkdf2Sha256Async: (password, salt, options) =>
50
+ new Promise((resolve, reject) => {
51
+ crypto.pbkdf2(
52
+ password,
53
+ salt,
54
+ options.c,
55
+ options.dkLen,
56
+ 'sha256',
57
+ (error, key) => {
58
+ if (error) return reject(error)
59
+ try {
60
+ resolve(copyAndClear(key))
61
+ } catch (error) {
62
+ reject(error)
63
+ }
64
+ },
65
+ )
66
+ }),
67
+ })
68
+ }
69
+
70
+ export declare namespace engine {
71
+ /** Every `Keystore` primitive this module implements. */
72
+ type ReturnType = {
73
+ [key in
74
+ | 'aesCtrDecrypt'
75
+ | 'aesCtrEncrypt'
76
+ | 'pbkdf2Sha256'
77
+ | 'pbkdf2Sha256Async']-?: NonNullable<Engine.Keystore[key]>
78
+ }
79
+
80
+ type ErrorType = Errors.GlobalErrorType
81
+ }
82
+
83
+ function cipherData(
84
+ cipher: crypto.Cipheriv | crypto.Decipheriv,
85
+ data: Uint8Array,
86
+ ): Uint8Array {
87
+ const chunks: Buffer[] = []
88
+ try {
89
+ chunks.push(cipher.update(data))
90
+ chunks.push(cipher.final())
91
+ return copyAndClear(Buffer.concat(chunks))
92
+ } finally {
93
+ for (const chunk of chunks) chunk.fill(0)
94
+ }
95
+ }
96
+
97
+ function copyAndClear(value: Buffer): Uint8Array {
98
+ try {
99
+ return Uint8Array.from(value)
100
+ } finally {
101
+ value.fill(0)
102
+ }
103
+ }