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.
- package/CHANGELOG.md +27 -0
- package/dist/core/Engine.d.ts +43 -11
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +40 -10
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/internal/engine.d.ts +8 -8
- package/dist/core/internal/engine.d.ts.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/node/Ed25519.d.ts +33 -0
- package/dist/node/Ed25519.d.ts.map +1 -0
- package/dist/node/Ed25519.js +67 -0
- package/dist/node/Ed25519.js.map +1 -0
- package/dist/node/Engine.d.ts +29 -16
- package/dist/node/Engine.d.ts.map +1 -1
- package/dist/node/Engine.js +38 -14
- package/dist/node/Engine.js.map +1 -1
- package/dist/node/Hash.d.ts +14 -16
- package/dist/node/Hash.d.ts.map +1 -1
- package/dist/node/Hash.js +14 -16
- package/dist/node/Hash.js.map +1 -1
- package/dist/node/Keystore.d.ts +36 -0
- package/dist/node/Keystore.d.ts.map +1 -0
- package/dist/node/Keystore.js +72 -0
- package/dist/node/Keystore.js.map +1 -0
- package/dist/node/Mnemonic.d.ts +35 -0
- package/dist/node/Mnemonic.d.ts.map +1 -0
- package/dist/node/Mnemonic.js +46 -0
- package/dist/node/Mnemonic.js.map +1 -0
- package/dist/node/P256.d.ts +32 -0
- package/dist/node/P256.d.ts.map +1 -0
- package/dist/node/P256.js +34 -0
- package/dist/node/P256.js.map +1 -0
- package/dist/node/X25519.d.ts +31 -0
- package/dist/node/X25519.d.ts.map +1 -0
- package/dist/node/X25519.js +71 -0
- package/dist/node/X25519.js.map +1 -0
- package/dist/node/index.d.ts +95 -8
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +95 -8
- package/dist/node/index.js.map +1 -1
- package/dist/wasm/Ed25519.d.ts +35 -0
- package/dist/wasm/Ed25519.d.ts.map +1 -0
- package/dist/wasm/Ed25519.js +88 -0
- package/dist/wasm/Ed25519.js.map +1 -0
- package/dist/wasm/Engine.d.ts +35 -26
- package/dist/wasm/Engine.d.ts.map +1 -1
- package/dist/wasm/Engine.js +44 -25
- package/dist/wasm/Engine.js.map +1 -1
- package/dist/wasm/Hash.d.ts +13 -19
- package/dist/wasm/Hash.d.ts.map +1 -1
- package/dist/wasm/Hash.js +21 -27
- package/dist/wasm/Hash.js.map +1 -1
- package/dist/wasm/Keystore.d.ts +37 -0
- package/dist/wasm/Keystore.d.ts.map +1 -0
- package/dist/wasm/Keystore.js +34 -0
- package/dist/wasm/Keystore.js.map +1 -0
- package/dist/wasm/Mnemonic.d.ts +33 -0
- package/dist/wasm/Mnemonic.d.ts.map +1 -0
- package/dist/wasm/Mnemonic.js +30 -0
- package/dist/wasm/Mnemonic.js.map +1 -0
- package/dist/wasm/X25519.d.ts +33 -0
- package/dist/wasm/X25519.d.ts.map +1 -0
- package/dist/wasm/X25519.js +53 -0
- package/dist/wasm/X25519.js.map +1 -0
- package/dist/wasm/index.d.ts +81 -12
- package/dist/wasm/index.d.ts.map +1 -1
- package/dist/wasm/index.js +81 -12
- package/dist/wasm/index.js.map +1 -1
- package/dist/wasm/internal/blake3.d.ts +11 -0
- package/dist/wasm/internal/blake3.d.ts.map +1 -0
- package/dist/wasm/internal/blake3.js +31 -0
- package/dist/wasm/internal/blake3.js.map +1 -0
- package/dist/wasm/internal/blake3.wasm.d.ts +3 -0
- package/dist/wasm/internal/blake3.wasm.d.ts.map +1 -0
- package/dist/wasm/internal/blake3.wasm.js +10 -0
- package/dist/wasm/internal/blake3.wasm.js.map +1 -0
- package/dist/wasm/internal/crypto25519.d.ts +16 -0
- package/dist/wasm/internal/crypto25519.d.ts.map +1 -0
- package/dist/wasm/internal/crypto25519.js +5 -0
- package/dist/wasm/internal/crypto25519.js.map +1 -0
- package/dist/wasm/internal/crypto25519.wasm.d.ts +3 -0
- package/dist/wasm/internal/crypto25519.wasm.d.ts.map +1 -0
- package/dist/wasm/internal/crypto25519.wasm.js +10 -0
- package/dist/wasm/internal/crypto25519.wasm.js.map +1 -0
- package/dist/wasm/internal/ed25519.d.ts +4 -0
- package/dist/wasm/internal/ed25519.d.ts.map +1 -0
- package/dist/wasm/internal/ed25519.js +21 -0
- package/dist/wasm/internal/ed25519.js.map +1 -0
- package/dist/wasm/internal/hashes.d.ts +12 -0
- package/dist/wasm/internal/hashes.d.ts.map +1 -0
- package/dist/wasm/internal/hashes.js +8 -0
- package/dist/wasm/internal/hashes.js.map +1 -0
- package/dist/wasm/internal/hashes.wasm.d.ts +4 -2
- package/dist/wasm/internal/hashes.wasm.d.ts.map +1 -1
- package/dist/wasm/internal/hashes.wasm.js +4 -2
- package/dist/wasm/internal/hashes.wasm.js.map +1 -1
- package/dist/wasm/internal/mnemonic.d.ts +4 -0
- package/dist/wasm/internal/mnemonic.d.ts.map +1 -0
- package/dist/wasm/internal/mnemonic.js +45 -0
- package/dist/wasm/internal/mnemonic.js.map +1 -0
- package/dist/wasm/internal/pbkdf2.d.ts +12 -0
- package/dist/wasm/internal/pbkdf2.d.ts.map +1 -0
- package/dist/wasm/internal/pbkdf2.js +51 -0
- package/dist/wasm/internal/pbkdf2.js.map +1 -0
- package/dist/wasm/internal/x25519.d.ts +4 -0
- package/dist/wasm/internal/x25519.d.ts.map +1 -0
- package/dist/wasm/internal/x25519.js +21 -0
- package/dist/wasm/internal/x25519.js.map +1 -0
- package/package.json +46 -1
- package/src/core/Engine.ts +84 -11
- package/src/core/_test/Engine.test-d.ts +56 -0
- package/src/core/_test/Engine.test.ts +95 -0
- package/src/core/_test/Hash.vectors.test.ts +8 -0
- package/src/core/internal/engine.ts +8 -8
- package/src/index.ts +5 -2
- package/src/node/Ed25519.ts +87 -0
- package/src/node/Engine.ts +79 -23
- package/src/node/Hash.ts +22 -27
- package/src/node/Keystore.ts +103 -0
- package/src/node/Mnemonic.ts +68 -0
- package/src/node/P256.ts +48 -0
- package/src/node/X25519.ts +93 -0
- package/src/node/_test/Curves.fuzz.ts +76 -0
- package/src/node/_test/Ed25519.test-d.ts +35 -0
- package/src/node/_test/Ed25519.test.ts +220 -0
- package/src/node/_test/Engine.test-d.ts +28 -0
- package/src/node/_test/Engine.test.ts +62 -22
- package/src/node/_test/Hash.test-d.ts +15 -9
- package/src/node/_test/Hash.test.ts +10 -11
- package/src/node/_test/Keystore.fuzz.ts +58 -0
- package/src/node/_test/Keystore.test-d.ts +43 -0
- package/src/node/_test/Keystore.test.ts +198 -0
- package/src/node/_test/Mnemonic.fuzz.ts +31 -0
- package/src/node/_test/Mnemonic.test-d.ts +21 -0
- package/src/node/_test/Mnemonic.test.ts +98 -0
- package/src/node/_test/P256.test-d.ts +29 -0
- package/src/node/_test/P256.test.ts +102 -0
- package/src/node/_test/X25519.test-d.ts +28 -0
- package/src/node/_test/X25519.test.ts +239 -0
- package/src/node/index.ts +100 -8
- package/src/version.ts +1 -1
- package/src/wasm/Ed25519.ts +114 -0
- package/src/wasm/Engine.ts +76 -31
- package/src/wasm/Hash.bench.ts +2 -2
- package/src/wasm/Hash.ts +29 -50
- package/src/wasm/Keystore.ts +48 -0
- package/src/wasm/Mnemonic.ts +44 -0
- package/src/wasm/X25519.ts +70 -0
- package/src/wasm/_test/Crypto25519.conformance.ts +57 -0
- package/src/wasm/_test/Crypto25519.fuzz.ts +187 -0
- package/src/wasm/_test/Ed25519.test-d.ts +28 -0
- package/src/wasm/_test/Ed25519.test.ts +168 -0
- package/src/wasm/_test/Ed25519.vectors.test.ts +73 -0
- package/src/wasm/_test/Engine.test-d.ts +28 -0
- package/src/wasm/_test/Engine.test.ts +59 -25
- package/src/wasm/_test/Hash.browser.test.ts +11 -7
- package/src/wasm/_test/Hash.fuzz.ts +58 -23
- package/src/wasm/_test/Hash.test-d.ts +15 -10
- package/src/wasm/_test/Hash.test.ts +148 -48
- package/src/wasm/_test/Hash.vectors.test.ts +15 -8
- package/src/wasm/_test/Keystore.browser.test.ts +42 -0
- package/src/wasm/_test/Keystore.fuzz.ts +113 -0
- package/src/wasm/_test/Keystore.test-d.ts +33 -0
- package/src/wasm/_test/Keystore.test.ts +192 -0
- package/src/wasm/_test/Keystore.vectors.test.ts +21 -0
- package/src/wasm/_test/Mnemonic.test-d.ts +17 -0
- package/src/wasm/_test/Mnemonic.test.ts +110 -0
- package/src/wasm/_test/X25519.test-d.ts +21 -0
- package/src/wasm/_test/X25519.test.ts +194 -0
- package/src/wasm/_test/fixtures.ts +4 -0
- package/src/wasm/index.ts +85 -12
- package/src/wasm/internal/blake3.ts +45 -0
- package/src/wasm/internal/blake3.wasm.ts +11 -0
- package/src/wasm/internal/crypto25519.ts +43 -0
- package/src/wasm/internal/crypto25519.wasm.ts +11 -0
- package/src/wasm/internal/ed25519.ts +31 -0
- package/src/wasm/internal/hashes.ts +29 -0
- package/src/wasm/internal/hashes.wasm.ts +5 -2
- package/src/wasm/internal/mnemonic.ts +56 -0
- package/src/wasm/internal/pbkdf2.ts +86 -0
- 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
|
|
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.
|
|
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
|
-
*
|
|
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
|
|
31
|
-
* import
|
|
30
|
+
* import { Engine } from 'ox'
|
|
31
|
+
* import { Ed25519 } from 'ox/node'
|
|
32
32
|
*
|
|
33
|
-
* Engine.
|
|
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.
|
|
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
|
+
}
|