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
@@ -0,0 +1,102 @@
1
+ import { p256 } from '@noble/curves/nist.js'
2
+ import { Hex } from 'ox'
3
+ import { describe, expect, test } from 'vp/test'
4
+ import * as P256 from '../P256.js'
5
+
6
+ const order = 'ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551'
7
+
8
+ describe('engine', () => {
9
+ test('behavior: exposes only public-key derivation', async () => {
10
+ const engine = await P256.engine()
11
+
12
+ expect(Object.keys(engine)).toMatchInlineSnapshot(`
13
+ [
14
+ "getPublicKey",
15
+ ]
16
+ `)
17
+ })
18
+
19
+ test('behavior: matches the SEC 2 generator vector', async () => {
20
+ const engine = await P256.engine()
21
+ const privateKey = new Uint8Array(32)
22
+ privateKey[31] = 1
23
+
24
+ expect(
25
+ Hex.fromBytes(engine.getPublicKey(privateKey)),
26
+ ).toMatchInlineSnapshot(
27
+ `"0x046b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5"`,
28
+ )
29
+ })
30
+
31
+ test('behavior: matches the default across valid scalars', async () => {
32
+ const engine = await P256.engine()
33
+ const privateKeys = [
34
+ `${'00'.repeat(31)}01`,
35
+ `${'00'.repeat(31)}02`,
36
+ '7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
37
+ 'dde57ae9b9ed6f76fa5358c24d5ca2057ebc1ece18b7273121450a29c96ec8e5',
38
+ 'ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632550',
39
+ ]
40
+
41
+ for (const privateKey of privateKeys) {
42
+ const bytes = fromHex(privateKey)
43
+ expect(engine.getPublicKey(bytes)).toEqual(
44
+ p256.getPublicKey(bytes, false),
45
+ )
46
+ }
47
+ })
48
+
49
+ test('behavior: respects typed-array offsets without mutating inputs', async () => {
50
+ const engine = await P256.engine()
51
+ const privateKey = offsetView(
52
+ fromHex(
53
+ 'dde57ae9b9ed6f76fa5358c24d5ca2057ebc1ece18b7273121450a29c96ec8e5',
54
+ ),
55
+ )
56
+ const before = privateKey.slice()
57
+
58
+ expect(engine.getPublicKey(privateKey)).toEqual(
59
+ p256.getPublicKey(privateKey, false),
60
+ )
61
+ expect(privateKey).toEqual(before)
62
+ })
63
+
64
+ test('behavior: rejects malformed and out-of-range private keys', async () => {
65
+ const engine = await P256.engine()
66
+
67
+ for (const size of [0, 1, 31, 33, 64])
68
+ expect(() => engine.getPublicKey(new Uint8Array(size))).toThrowError(
69
+ `P256 private key must be 32 bytes, got ${size}`,
70
+ )
71
+
72
+ expect(() => engine.getPublicKey(new Uint8Array(32))).toThrowError()
73
+ expect(() => engine.getPublicKey(fromHex(order))).toThrowError()
74
+ })
75
+
76
+ test('behavior: returns owned Uint8Array values', async () => {
77
+ const engine = await P256.engine()
78
+ const privateKey = fromHex(`${'00'.repeat(31)}01`)
79
+ const first = engine.getPublicKey(privateKey)
80
+ const second = engine.getPublicKey(privateKey)
81
+
82
+ expect(first.constructor === Uint8Array).toMatchInlineSnapshot('true')
83
+ expect(first === second).toMatchInlineSnapshot('false')
84
+ })
85
+
86
+ test('behavior: returns a fresh engine', async () => {
87
+ const first = await P256.engine()
88
+ const second = await P256.engine()
89
+
90
+ expect(first === second).toMatchInlineSnapshot('false')
91
+ })
92
+ })
93
+
94
+ function fromHex(value: string): Uint8Array {
95
+ return Hex.toBytes(`0x${value}`)
96
+ }
97
+
98
+ function offsetView(value: Uint8Array): Uint8Array {
99
+ const bytes = new Uint8Array(value.length + 4).fill(0xff)
100
+ bytes.set(value, 2)
101
+ return bytes.subarray(2, -2)
102
+ }
@@ -0,0 +1,28 @@
1
+ import { expectTypeOf, test } from 'vp/test'
2
+ import type * as Engine from '../../core/Engine.js'
3
+ import * as core_X25519 from '../../core/X25519.js'
4
+ import * as NodeX25519 from '../X25519.js'
5
+
6
+ type Slot = Awaited<ReturnType<typeof NodeX25519.engine>>
7
+
8
+ test('every implemented primitive is present', () => {
9
+ expectTypeOf<Slot['getPublicKey']>().toEqualTypeOf<
10
+ (privateKey: Uint8Array) => Uint8Array
11
+ >()
12
+ expectTypeOf<Slot['getSharedSecret']>().toEqualTypeOf<
13
+ (privateKey: Uint8Array, publicKey: Uint8Array) => Uint8Array
14
+ >()
15
+ })
16
+
17
+ test('unsupported primitives are absent', () => {
18
+ expectTypeOf<Slot>().not.toHaveProperty('randomSecretKey')
19
+ })
20
+
21
+ test('the result is the raw slot', () => {
22
+ expectTypeOf<{ X25519: Slot }>().toExtend<Engine.Engine>()
23
+ })
24
+
25
+ test('the Node namespace exposes the public X25519 API', () => {
26
+ expectTypeOf(NodeX25519.getPublicKey).toEqualTypeOf(core_X25519.getPublicKey)
27
+ expectTypeOf<typeof NodeX25519>().not.toHaveProperty('create')
28
+ })
@@ -0,0 +1,239 @@
1
+ import * as fs from 'node:fs'
2
+ import { x25519 } from '@noble/curves/ed25519.js'
3
+ import { Hex } from 'ox'
4
+ import { describe, expect, test } from 'vp/test'
5
+ import * as X25519 from '../X25519.js'
6
+
7
+ type LowOrderVector = {
8
+ name: string
9
+ publicKey: string
10
+ }
11
+
12
+ const alicePrivateKey =
13
+ '77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a'
14
+ const bobPrivateKey =
15
+ '5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb'
16
+
17
+ const lowOrderPublicKeys = JSON.parse(
18
+ fs.readFileSync(
19
+ new URL('../../../test/vectors/x25519/low-order.json', import.meta.url),
20
+ 'utf8',
21
+ ),
22
+ ) as readonly LowOrderVector[]
23
+
24
+ describe('engine', () => {
25
+ test('behavior: exposes only the supported primitives', async () => {
26
+ const engine = await X25519.engine()
27
+
28
+ expect(Object.keys(engine).sort()).toMatchInlineSnapshot(`
29
+ [
30
+ "getPublicKey",
31
+ "getSharedSecret",
32
+ ]
33
+ `)
34
+ })
35
+
36
+ test('behavior: matches RFC 7748 key-agreement vectors', async () => {
37
+ const engine = await X25519.engine()
38
+ const alicePublicKey = engine.getPublicKey(fromHex(alicePrivateKey))
39
+ const bobPublicKey = engine.getPublicKey(fromHex(bobPrivateKey))
40
+
41
+ expect({
42
+ alicePublicKey: Hex.fromBytes(alicePublicKey),
43
+ bobPublicKey: Hex.fromBytes(bobPublicKey),
44
+ sharedSecret: Hex.fromBytes(
45
+ engine.getSharedSecret(fromHex(alicePrivateKey), bobPublicKey),
46
+ ),
47
+ }).toMatchInlineSnapshot(`
48
+ {
49
+ "alicePublicKey": "0x8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a",
50
+ "bobPublicKey": "0xde9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f",
51
+ "sharedSecret": "0x4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742",
52
+ }
53
+ `)
54
+ })
55
+
56
+ test('behavior: matches RFC 7748 arbitrary-point vectors', async () => {
57
+ const engine = await X25519.engine()
58
+ const vectors = [
59
+ {
60
+ privateKey:
61
+ 'a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4',
62
+ publicKey:
63
+ 'e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c',
64
+ },
65
+ {
66
+ privateKey:
67
+ '4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d',
68
+ publicKey:
69
+ 'e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493',
70
+ },
71
+ ] as const
72
+
73
+ expect(
74
+ vectors.map(({ privateKey, publicKey }) =>
75
+ Hex.fromBytes(
76
+ engine.getSharedSecret(fromHex(privateKey), fromHex(publicKey)),
77
+ ),
78
+ ),
79
+ ).toMatchInlineSnapshot(`
80
+ [
81
+ "0xc3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552",
82
+ "0x95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957",
83
+ ]
84
+ `)
85
+ })
86
+
87
+ test('behavior: matches the default across private keys', async () => {
88
+ const engine = await X25519.engine()
89
+ const privateKeys = [
90
+ fromHex(alicePrivateKey),
91
+ fromHex(bobPrivateKey),
92
+ new Uint8Array(32),
93
+ new Uint8Array(32).fill(0xff),
94
+ Uint8Array.from({ length: 32 }, (_, index) => index),
95
+ ]
96
+
97
+ for (const [index, privateKey] of privateKeys.entries()) {
98
+ const peerPrivateKey = Uint8Array.from(
99
+ { length: 32 },
100
+ (_, offset) => (offset * 17 + index + 1) % 251,
101
+ )
102
+ const publicKey = x25519.getPublicKey(peerPrivateKey)
103
+ expect(engine.getPublicKey(privateKey)).toEqual(
104
+ x25519.getPublicKey(privateKey),
105
+ )
106
+ expect(engine.getSharedSecret(privateKey, publicKey)).toEqual(
107
+ x25519.getSharedSecret(privateKey, publicKey),
108
+ )
109
+ }
110
+ })
111
+
112
+ test('behavior: masks the public u-coordinate high bit', async () => {
113
+ const engine = await X25519.engine()
114
+ const privateKey = fromHex(alicePrivateKey)
115
+ const publicKey = x25519.getPublicKey(fromHex(bobPrivateKey))
116
+ const highBitPublicKey = publicKey.slice()
117
+ highBitPublicKey[31]! |= 0x80
118
+ const expected = x25519.getSharedSecret(privateKey, publicKey)
119
+
120
+ expect(x25519.getSharedSecret(privateKey, highBitPublicKey)).toEqual(
121
+ expected,
122
+ )
123
+ expect(engine.getSharedSecret(privateKey, highBitPublicKey)).toEqual(
124
+ expected,
125
+ )
126
+ })
127
+
128
+ test('behavior: reduces noncanonical public u-coordinates', async () => {
129
+ const engine = await X25519.engine()
130
+ const privateKey = fromHex(alicePrivateKey)
131
+ // 2^255 - 19 + 9 encodes the base point modulo the field prime.
132
+ const publicKey = fromHex(`f6${'ff'.repeat(30)}7f`)
133
+ const expected = x25519.getPublicKey(privateKey)
134
+
135
+ expect(x25519.getSharedSecret(privateKey, publicKey)).toEqual(expected)
136
+ expect(engine.getSharedSecret(privateKey, publicKey)).toEqual(expected)
137
+ })
138
+
139
+ test('behavior: rejects the complete published low-order set', async () => {
140
+ const engine = await X25519.engine()
141
+ const privateKey = fromHex(alicePrivateKey)
142
+
143
+ expect(lowOrderPublicKeys).toHaveLength(7)
144
+ for (const vector of lowOrderPublicKeys) {
145
+ const publicKey = fromHex(vector.publicKey)
146
+ const highBitPublicKey = publicKey.slice()
147
+ highBitPublicKey[31]! |= 0x80
148
+
149
+ for (const [name, value] of [
150
+ ['published', publicKey],
151
+ ['top-bit alias', highBitPublicKey],
152
+ ] as const) {
153
+ expect(
154
+ () => x25519.getSharedSecret(privateKey, value),
155
+ `${vector.name} (${name})`,
156
+ ).toThrowError()
157
+ expect(
158
+ () => engine.getSharedSecret(privateKey, value),
159
+ `${vector.name} (${name})`,
160
+ ).toThrowError()
161
+ }
162
+ }
163
+ })
164
+
165
+ test('behavior: respects typed-array offsets without mutating inputs', async () => {
166
+ const engine = await X25519.engine()
167
+ const privateKey = offsetView(fromHex(alicePrivateKey))
168
+ const publicKey = offsetView(x25519.getPublicKey(fromHex(bobPrivateKey)))
169
+ const privateKeyBefore = privateKey.slice()
170
+ const publicKeyBefore = publicKey.slice()
171
+
172
+ expect(engine.getPublicKey(privateKey)).toEqual(
173
+ x25519.getPublicKey(privateKey),
174
+ )
175
+ expect(engine.getSharedSecret(privateKey, publicKey)).toEqual(
176
+ x25519.getSharedSecret(privateKey, publicKey),
177
+ )
178
+ expect({ privateKey, publicKey }).toEqual({
179
+ privateKey: privateKeyBefore,
180
+ publicKey: publicKeyBefore,
181
+ })
182
+ })
183
+
184
+ test('behavior: rejects malformed key lengths', async () => {
185
+ const engine = await X25519.engine()
186
+ const privateKey = fromHex(alicePrivateKey)
187
+ const publicKey = x25519.getPublicKey(fromHex(bobPrivateKey))
188
+
189
+ for (const size of [0, 1, 31, 33, 64]) {
190
+ expect(() => engine.getPublicKey(new Uint8Array(size))).toThrowError(
191
+ `X25519 private key must be 32 bytes, got ${size}`,
192
+ )
193
+ expect(() =>
194
+ engine.getSharedSecret(new Uint8Array(size), publicKey),
195
+ ).toThrowError(`X25519 private key must be 32 bytes, got ${size}`)
196
+ expect(() =>
197
+ engine.getSharedSecret(privateKey, new Uint8Array(size)),
198
+ ).toThrowError(`X25519 public key must be 32 bytes, got ${size}`)
199
+ }
200
+ })
201
+
202
+ test('behavior: returns owned Uint8Array values', async () => {
203
+ const engine = await X25519.engine()
204
+ const privateKey = fromHex(alicePrivateKey)
205
+ const publicKey = x25519.getPublicKey(fromHex(bobPrivateKey))
206
+ const outputs = [
207
+ engine.getPublicKey(privateKey),
208
+ engine.getSharedSecret(privateKey, publicKey),
209
+ ]
210
+
211
+ expect(outputs.map((output) => output.constructor === Uint8Array))
212
+ .toMatchInlineSnapshot(`
213
+ [
214
+ true,
215
+ true,
216
+ ]
217
+ `)
218
+ expect(
219
+ engine.getPublicKey(privateKey) === engine.getPublicKey(privateKey),
220
+ ).toMatchInlineSnapshot('false')
221
+ })
222
+
223
+ test('behavior: returns a fresh engine', async () => {
224
+ const first = await X25519.engine()
225
+ const second = await X25519.engine()
226
+
227
+ expect(first === second).toMatchInlineSnapshot('false')
228
+ })
229
+ })
230
+
231
+ function fromHex(value: string): Uint8Array {
232
+ return Hex.toBytes(`0x${value}`)
233
+ }
234
+
235
+ function offsetView(value: Uint8Array): Uint8Array {
236
+ const bytes = new Uint8Array(value.length + 4).fill(0xff)
237
+ bytes.set(value, 2)
238
+ return bytes.subarray(2, -2)
239
+ }
package/src/node/index.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  /** @entrypointCategory Node */
2
2
 
3
3
  /**
4
- * Node.js implementations of Ox's hash primitives, backed by `node:crypto`.
4
+ * Node.js implementations of Ox's supported cryptographic primitives, backed
5
+ * by `node:crypto`.
5
6
  *
6
7
  * Import this entrypoint only in Node.js. Its static `node:crypto` dependency
7
8
  * is intentionally kept outside Ox's runtime-neutral entrypoint.
@@ -9,10 +10,9 @@
9
10
  * @example
10
11
  * ```ts twoslash
11
12
  * // @noErrors
12
- * import { Hash } from 'ox'
13
- * import { Engine } from 'ox/node'
13
+ * import { Engine, Hash } from 'ox/node'
14
14
  *
15
- * await Engine.load()
15
+ * await Engine.install()
16
16
  *
17
17
  * Hash.sha256('0xdeadbeef')
18
18
  * ```
@@ -22,15 +22,33 @@
22
22
  export * as Engine from './Engine.js'
23
23
 
24
24
  /**
25
- * Node.js implementations of Ox's supported hash primitives.
25
+ * Ox's Ed25519 API and its supported Node.js engine primitives.
26
26
  *
27
27
  * @example
28
28
  * ```ts twoslash
29
29
  * // @noErrors
30
- * import { Engine, Hash } from 'ox'
31
- * import * as NodeHash from 'ox/node/Hash'
30
+ * import { Engine } from 'ox'
31
+ * import { Ed25519 } from 'ox/node'
32
32
  *
33
- * Engine.set(await NodeHash.create())
33
+ * await Engine.install({ Ed25519: Ed25519.engine() })
34
+ *
35
+ * Ed25519.getPublicKey({ privateKey: '0x...' })
36
+ * ```
37
+ *
38
+ * @category Crypto
39
+ */
40
+ export * as Ed25519 from './Ed25519.js'
41
+
42
+ /**
43
+ * Ox's hash API and its supported Node.js engine primitives.
44
+ *
45
+ * @example
46
+ * ```ts twoslash
47
+ * // @noErrors
48
+ * import { Engine } from 'ox'
49
+ * import { Hash } from 'ox/node'
50
+ *
51
+ * await Engine.install({ Hash: Hash.engine() })
34
52
  *
35
53
  * Hash.sha256('0xdeadbeef')
36
54
  * ```
@@ -38,3 +56,77 @@ export * as Engine from './Engine.js'
38
56
  * @category Crypto
39
57
  */
40
58
  export * as Hash from './Hash.js'
59
+
60
+ /**
61
+ * Ox's keystore API and its supported Node.js engine primitives.
62
+ *
63
+ * @example
64
+ * ```ts twoslash
65
+ * // @noErrors
66
+ * import { Engine } from 'ox'
67
+ * import { Keystore } from 'ox/node'
68
+ *
69
+ * await Engine.install({ Keystore: Keystore.engine() })
70
+ *
71
+ * Keystore.pbkdf2({ password: 'testpassword' })
72
+ * ```
73
+ *
74
+ * @category Crypto
75
+ */
76
+ export * as Keystore from './Keystore.js'
77
+
78
+ /**
79
+ * Ox's mnemonic API and its Node.js BIP-39 seed engine primitive.
80
+ *
81
+ * @example
82
+ * ```ts twoslash
83
+ * // @noErrors
84
+ * import { Engine } from 'ox'
85
+ * import { Mnemonic } from 'ox/node'
86
+ *
87
+ * await Engine.install({ Mnemonic: Mnemonic.engine() })
88
+ *
89
+ * Mnemonic.toSeed(
90
+ * 'test test test test test test test test test test test junk'
91
+ * )
92
+ * ```
93
+ *
94
+ * @category Crypto
95
+ */
96
+ export * as Mnemonic from './Mnemonic.js'
97
+
98
+ /**
99
+ * Ox's P256 API and its Node.js public-key derivation engine primitive.
100
+ *
101
+ * @example
102
+ * ```ts twoslash
103
+ * // @noErrors
104
+ * import { Engine } from 'ox'
105
+ * import { P256 } from 'ox/node'
106
+ *
107
+ * await Engine.install({ P256: P256.engine() })
108
+ *
109
+ * P256.getPublicKey({ privateKey: '0x...' })
110
+ * ```
111
+ *
112
+ * @category Crypto
113
+ */
114
+ export * as P256 from './P256.js'
115
+
116
+ /**
117
+ * Ox's X25519 API and its supported Node.js engine primitives.
118
+ *
119
+ * @example
120
+ * ```ts twoslash
121
+ * // @noErrors
122
+ * import { Engine } from 'ox'
123
+ * import { X25519 } from 'ox/node'
124
+ *
125
+ * await Engine.install({ X25519: X25519.engine() })
126
+ *
127
+ * X25519.getPublicKey({ privateKey: '0x...' })
128
+ * ```
129
+ *
130
+ * @category Crypto
131
+ */
132
+ export * as X25519 from './X25519.js'
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** @internal */
2
- export const version = '1.1.0'
2
+ export const version = '1.1.2'
@@ -0,0 +1,114 @@
1
+ import type * as Engine from '../core/Engine.js'
2
+ import type * as Errors from '../core/Errors.js'
3
+ import * as crypto25519 from './internal/crypto25519.js'
4
+ import * as ed25519 from './internal/ed25519.js'
5
+ import * as internal from './internal/instantiate.js'
6
+
7
+ export * from '../core/Ed25519.js'
8
+ export { MemoryError } from './internal/instantiate.js'
9
+
10
+ const keySize = 32
11
+ const signatureSize = 64
12
+
13
+ /**
14
+ * Compiles WASM implementations of selected [`Ed25519`](/api/Ed25519)
15
+ * primitives, without installing them.
16
+ *
17
+ * Public-key conversion and random key generation remain on Ox's default
18
+ * implementation. The public conversion is omitted because Monocypher does not
19
+ * reproduce Ox's invalid-point validation.
20
+ *
21
+ * @example
22
+ * ```ts twoslash
23
+ * // @noErrors
24
+ * import { Engine } from 'ox'
25
+ * import { Ed25519 } from 'ox/wasm'
26
+ *
27
+ * await Engine.install({ Ed25519: Ed25519.engine() })
28
+ *
29
+ * Ed25519.getPublicKey({ privateKey: '0x...' })
30
+ * ```
31
+ *
32
+ * @returns The WASM implementation of part of the `Ed25519` slot.
33
+ */
34
+ export async function engine(): Promise<engine.ReturnType> {
35
+ const module = await crypto25519.load()
36
+
37
+ return {
38
+ getPublicKey(privateKey) {
39
+ assertLength(privateKey, keySize, 'private key')
40
+ module.reserve(keySize * 2)
41
+ const privateKeyPtr = module.heapBase
42
+ const publicKeyPtr = privateKeyPtr + keySize
43
+ try {
44
+ module.view().set(privateKey, privateKeyPtr)
45
+ module.exports.ed25519_get_public_key(privateKeyPtr, publicKeyPtr)
46
+ return module.view().slice(publicKeyPtr, publicKeyPtr + keySize)
47
+ } finally {
48
+ module.exports.zero(privateKeyPtr, keySize * 2)
49
+ }
50
+ },
51
+ sign(payload, privateKey) {
52
+ assertLength(privateKey, keySize, 'private key')
53
+ return ed25519.sign(module, payload, privateKey)
54
+ },
55
+ toMontgomerySecret(privateKey) {
56
+ assertLength(privateKey, keySize, 'private key')
57
+ module.reserve(keySize * 2)
58
+ const privateKeyPtr = module.heapBase
59
+ const secretKeyPtr = privateKeyPtr + keySize
60
+ try {
61
+ module.view().set(privateKey, privateKeyPtr)
62
+ module.exports.ed25519_to_montgomery_secret(privateKeyPtr, secretKeyPtr)
63
+ return module.view().slice(secretKeyPtr, secretKeyPtr + keySize)
64
+ } finally {
65
+ module.exports.zero(privateKeyPtr, keySize * 2)
66
+ }
67
+ },
68
+ verify(signature, payload, publicKey) {
69
+ assertLength(signature, signatureSize, 'signature')
70
+ assertLength(publicKey, keySize, 'public key')
71
+ const size = signatureSize + payload.length + keySize
72
+ module.reserve(size)
73
+ const signaturePtr = module.heapBase
74
+ const payloadPtr = signaturePtr + signatureSize
75
+ const publicKeyPtr = payloadPtr + payload.length
76
+ try {
77
+ const memory = module.view()
78
+ memory.set(signature, signaturePtr)
79
+ memory.set(payload, payloadPtr)
80
+ memory.set(publicKey, publicKeyPtr)
81
+ return (
82
+ module.exports.ed25519_verify(
83
+ signaturePtr,
84
+ payloadPtr,
85
+ payload.length,
86
+ publicKeyPtr,
87
+ ) === 1
88
+ )
89
+ } finally {
90
+ module.exports.zero(signaturePtr, size)
91
+ }
92
+ },
93
+ }
94
+ }
95
+
96
+ export declare namespace engine {
97
+ /** Every `Ed25519` primitive this module implements. */
98
+ type ReturnType = {
99
+ [key in
100
+ | 'getPublicKey'
101
+ | 'sign'
102
+ | 'toMontgomerySecret'
103
+ | 'verify']-?: NonNullable<Engine.Eddsa[key]>
104
+ }
105
+
106
+ type ErrorType = internal.MemoryError | Errors.GlobalErrorType
107
+ }
108
+
109
+ function assertLength(value: Uint8Array, length: number, name: string): void {
110
+ if (value.length !== length)
111
+ throw new RangeError(
112
+ `Ed25519 ${name} must be ${length} bytes, got ${value.length}`,
113
+ )
114
+ }