ox 1.4.0 → 1.6.0
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 +12 -0
- package/dist/core/Engine.d.ts +1 -1
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/MlDsa44.d.ts +389 -0
- package/dist/core/MlDsa44.d.ts.map +1 -0
- package/dist/core/MlDsa44.js +206 -0
- package/dist/core/MlDsa44.js.map +1 -0
- package/dist/core/internal/engine.d.ts +35 -1
- package/dist/core/internal/engine.d.ts.map +1 -1
- package/dist/core/internal/engine.js +2 -0
- package/dist/core/internal/engine.js.map +1 -1
- package/dist/core/internal/mlDsa44.d.ts +10 -0
- package/dist/core/internal/mlDsa44.d.ts.map +1 -0
- package/dist/core/internal/mlDsa44.js +41 -0
- package/dist/core/internal/mlDsa44.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -1
- package/dist/tempo/WithdrawalSenderTag.d.ts +36 -0
- package/dist/tempo/WithdrawalSenderTag.d.ts.map +1 -0
- package/dist/tempo/WithdrawalSenderTag.js +31 -0
- package/dist/tempo/WithdrawalSenderTag.js.map +1 -0
- package/dist/tempo/index.d.ts +23 -0
- package/dist/tempo/index.d.ts.map +1 -1
- package/dist/tempo/index.js +23 -0
- package/dist/tempo/index.js.map +1 -1
- package/dist/wasm/Engine.d.ts +5 -1
- package/dist/wasm/Engine.d.ts.map +1 -1
- package/dist/wasm/Engine.js +9 -1
- package/dist/wasm/Engine.js.map +1 -1
- package/dist/wasm/MlDsa44.d.ts +55 -0
- package/dist/wasm/MlDsa44.d.ts.map +1 -0
- package/dist/wasm/MlDsa44.js +143 -0
- package/dist/wasm/MlDsa44.js.map +1 -0
- package/dist/wasm/index.d.ts +23 -2
- package/dist/wasm/index.d.ts.map +1 -1
- package/dist/wasm/index.js +23 -2
- package/dist/wasm/index.js.map +1 -1
- package/dist/wasm/internal/mldsa44.d.ts +12 -0
- package/dist/wasm/internal/mldsa44.d.ts.map +1 -0
- package/dist/wasm/internal/mldsa44.js +5 -0
- package/dist/wasm/internal/mldsa44.js.map +1 -0
- package/dist/wasm/internal/mldsa44.wasm.d.ts +3 -0
- package/dist/wasm/internal/mldsa44.wasm.d.ts.map +1 -0
- package/dist/wasm/internal/mldsa44.wasm.js +10 -0
- package/dist/wasm/internal/mldsa44.wasm.js.map +1 -0
- package/dist/zod/TypedData.d.ts +2 -2
- package/package.json +19 -2
- package/src/core/Engine.ts +1 -0
- package/src/core/MlDsa44.ts +415 -0
- package/src/core/_test/Engine.test.ts +2 -2
- package/src/core/_test/MlDsa44.test-d.ts +56 -0
- package/src/core/_test/MlDsa44.test.ts +393 -0
- package/src/core/_test/index.test.ts +1 -0
- package/src/core/internal/engine.ts +49 -0
- package/src/core/internal/mlDsa44.ts +74 -0
- package/src/index.ts +41 -0
- package/src/tempo/WithdrawalSenderTag.test-d.ts +13 -0
- package/src/tempo/WithdrawalSenderTag.test.ts +22 -0
- package/src/tempo/WithdrawalSenderTag.ts +47 -0
- package/src/tempo/index.ts +24 -0
- package/src/version.ts +1 -1
- package/src/wasm/Engine.ts +19 -7
- package/src/wasm/MlDsa44.ts +190 -0
- package/src/wasm/_test/Engine.test-d.ts +2 -0
- package/src/wasm/_test/Engine.test.ts +16 -0
- package/src/wasm/_test/MlDsa44.test-d.ts +28 -0
- package/src/wasm/_test/MlDsa44.test.ts +192 -0
- package/src/wasm/index.ts +24 -2
- package/src/wasm/internal/mldsa44.ts +34 -0
- package/src/wasm/internal/mldsa44.wasm.ts +11 -0
- package/wasm/vendor/mldsa-native/LICENSE +286 -0
package/src/tempo/index.ts
CHANGED
|
@@ -529,6 +529,30 @@ export * as VirtualAddress from './VirtualAddress.js'
|
|
|
529
529
|
*/
|
|
530
530
|
export * as VirtualMaster from './VirtualMaster.js'
|
|
531
531
|
|
|
532
|
+
/**
|
|
533
|
+
* Utilities for deriving the sender tag that correlates a Zone withdrawal with
|
|
534
|
+
* its indexed parent-chain `WithdrawalProcessed` event.
|
|
535
|
+
*
|
|
536
|
+
* The sender tag commits to the withdrawal sender and the Zone transaction hash
|
|
537
|
+
* containing the `ZoneOutbox.requestWithdrawal` call.
|
|
538
|
+
*
|
|
539
|
+
* [Authenticated Withdrawals Specification](https://github.com/tempoxyz/zones/blob/main/specs/spec.md#authenticated-withdrawals)
|
|
540
|
+
*
|
|
541
|
+
* @example
|
|
542
|
+
* ```ts twoslash
|
|
543
|
+
* import { WithdrawalSenderTag } from 'ox/tempo'
|
|
544
|
+
*
|
|
545
|
+
* const senderTag = WithdrawalSenderTag.from({
|
|
546
|
+
* sender: '0x1234567890abcdef1234567890abcdef12345678',
|
|
547
|
+
* transactionHash:
|
|
548
|
+
* '0xabababababababababababababababababababababababababababababababab'
|
|
549
|
+
* })
|
|
550
|
+
* ```
|
|
551
|
+
*
|
|
552
|
+
* @category Reference
|
|
553
|
+
*/
|
|
554
|
+
export * as WithdrawalSenderTag from './WithdrawalSenderTag.js'
|
|
555
|
+
|
|
532
556
|
/**
|
|
533
557
|
* Zone ID utilities for converting between zone IDs and zone chain IDs.
|
|
534
558
|
*
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** @internal */
|
|
2
|
-
export const version = '1.
|
|
2
|
+
export const version = '1.6.0'
|
package/src/wasm/Engine.ts
CHANGED
|
@@ -3,7 +3,9 @@ import type * as Errors from '../core/Errors.js'
|
|
|
3
3
|
import * as Ed25519 from './Ed25519.js'
|
|
4
4
|
import * as Hash from './Hash.js'
|
|
5
5
|
import * as keystore from './internal/keystore.js'
|
|
6
|
+
import * as MlDsa44 from './MlDsa44.js'
|
|
6
7
|
import * as Mnemonic from './Mnemonic.js'
|
|
8
|
+
import * as Secp256k1 from './Secp256k1.js'
|
|
7
9
|
import * as X25519 from './X25519.js'
|
|
8
10
|
import type * as internal from './internal/instantiate.js'
|
|
9
11
|
|
|
@@ -26,6 +28,7 @@ export type {
|
|
|
26
28
|
Hash,
|
|
27
29
|
HashState,
|
|
28
30
|
Keystore,
|
|
31
|
+
MlDsa,
|
|
29
32
|
Mnemonic,
|
|
30
33
|
} from '../core/Engine.js'
|
|
31
34
|
|
|
@@ -56,7 +59,9 @@ export async function install(): Promise<install.ReturnType> {
|
|
|
56
59
|
Ed25519: Ed25519.engine(),
|
|
57
60
|
Hash: Hash.engine(),
|
|
58
61
|
Keystore: keystore.engine(),
|
|
62
|
+
MlDsa44: MlDsa44.engine(),
|
|
59
63
|
Mnemonic: Mnemonic.engine(),
|
|
64
|
+
Secp256k1: Secp256k1.engine(),
|
|
60
65
|
X25519: X25519.engine(),
|
|
61
66
|
})
|
|
62
67
|
}
|
|
@@ -92,18 +97,23 @@ export declare namespace install {
|
|
|
92
97
|
* @returns An engine, ready to install.
|
|
93
98
|
*/
|
|
94
99
|
export async function engine(): Promise<engine.ReturnType> {
|
|
95
|
-
const [ed25519, hash, keystoreEngine, mnemonic, x25519] =
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
const [ed25519, hash, keystoreEngine, mlDsa44, mnemonic, secp256k1, x25519] =
|
|
101
|
+
await Promise.all([
|
|
102
|
+
Ed25519.engine(),
|
|
103
|
+
Hash.engine(),
|
|
104
|
+
keystore.engine(),
|
|
105
|
+
MlDsa44.engine(),
|
|
106
|
+
Mnemonic.engine(),
|
|
107
|
+
Secp256k1.engine(),
|
|
108
|
+
X25519.engine(),
|
|
109
|
+
])
|
|
102
110
|
return {
|
|
103
111
|
Ed25519: ed25519,
|
|
104
112
|
Hash: hash,
|
|
105
113
|
Keystore: keystoreEngine,
|
|
114
|
+
MlDsa44: mlDsa44,
|
|
106
115
|
Mnemonic: mnemonic,
|
|
116
|
+
Secp256k1: secp256k1,
|
|
107
117
|
X25519: x25519,
|
|
108
118
|
}
|
|
109
119
|
}
|
|
@@ -116,7 +126,9 @@ export declare namespace engine {
|
|
|
116
126
|
Keystore: {
|
|
117
127
|
pbkdf2Sha256: NonNullable<CoreEngine.Keystore['pbkdf2Sha256']>
|
|
118
128
|
}
|
|
129
|
+
MlDsa44: MlDsa44.engine.ReturnType
|
|
119
130
|
Mnemonic: Mnemonic.engine.ReturnType
|
|
131
|
+
Secp256k1: Secp256k1.engine.ReturnType
|
|
120
132
|
X25519: X25519.engine.ReturnType
|
|
121
133
|
}
|
|
122
134
|
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type * as Engine from '../core/Engine.js'
|
|
2
|
+
import * as Errors from '../core/Errors.js'
|
|
3
|
+
import * as internal from './internal/instantiate.js'
|
|
4
|
+
import * as mldsa44 from './internal/mldsa44.js'
|
|
5
|
+
|
|
6
|
+
export * from '../core/MlDsa44.js'
|
|
7
|
+
export { MemoryError } from './internal/instantiate.js'
|
|
8
|
+
|
|
9
|
+
const privateKeySize = 32
|
|
10
|
+
const publicKeySize = 1312
|
|
11
|
+
const signatureSize = 2420
|
|
12
|
+
const randomSize = 32
|
|
13
|
+
const maxContextSize = 255
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Compiles WASM implementations of selected [`MlDsa44`](/api/MlDsa44)
|
|
17
|
+
* primitives, without installing them.
|
|
18
|
+
*
|
|
19
|
+
* Random key generation remains on Ox's host-backed default implementation.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts twoslash
|
|
23
|
+
* // @noErrors
|
|
24
|
+
* import { Engine } from 'ox'
|
|
25
|
+
* import { MlDsa44 } from 'ox/wasm'
|
|
26
|
+
*
|
|
27
|
+
* await Engine.install({ MlDsa44: MlDsa44.engine() })
|
|
28
|
+
*
|
|
29
|
+
* MlDsa44.getPublicKey({ privateKey: '0x...' })
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @returns The WASM implementation of part of the `MlDsa44` slot.
|
|
33
|
+
*/
|
|
34
|
+
export async function engine(): Promise<engine.ReturnType> {
|
|
35
|
+
const module = await mldsa44.load()
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
getPublicKey(privateKey) {
|
|
39
|
+
assertLength(privateKey, privateKeySize, 'private key')
|
|
40
|
+
const size = privateKeySize + publicKeySize
|
|
41
|
+
module.reserve(size)
|
|
42
|
+
const privateKeyPtr = module.heapBase
|
|
43
|
+
const publicKeyPtr = privateKeyPtr + privateKeySize
|
|
44
|
+
try {
|
|
45
|
+
module.view().set(privateKey, privateKeyPtr)
|
|
46
|
+
if (
|
|
47
|
+
module.exports.mldsa44_get_public_key(privateKeyPtr, publicKeyPtr) !==
|
|
48
|
+
1
|
|
49
|
+
)
|
|
50
|
+
throw new InvalidInputError({ operation: 'getPublicKey' })
|
|
51
|
+
return module.view().slice(publicKeyPtr, publicKeyPtr + publicKeySize)
|
|
52
|
+
} finally {
|
|
53
|
+
module.exports.zero(privateKeyPtr, size)
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
sign(payload, privateKey, options) {
|
|
57
|
+
assertLength(privateKey, privateKeySize, 'private key')
|
|
58
|
+
const context = options.context ?? new Uint8Array(0)
|
|
59
|
+
assertContextLength(context)
|
|
60
|
+
// FIPS 204 hedged signing draws 32 bytes of randomness; the
|
|
61
|
+
// deterministic variant pins them to zero.
|
|
62
|
+
const random =
|
|
63
|
+
options.extraEntropy === true
|
|
64
|
+
? crypto.getRandomValues(new Uint8Array(randomSize))
|
|
65
|
+
: options.extraEntropy === false
|
|
66
|
+
? new Uint8Array(randomSize)
|
|
67
|
+
: options.extraEntropy
|
|
68
|
+
assertLength(random, randomSize, 'extra entropy')
|
|
69
|
+
const size =
|
|
70
|
+
privateKeySize +
|
|
71
|
+
randomSize +
|
|
72
|
+
context.length +
|
|
73
|
+
payload.length +
|
|
74
|
+
signatureSize
|
|
75
|
+
module.reserve(size)
|
|
76
|
+
const privateKeyPtr = module.heapBase
|
|
77
|
+
const randomPtr = privateKeyPtr + privateKeySize
|
|
78
|
+
const contextPtr = randomPtr + randomSize
|
|
79
|
+
const payloadPtr = contextPtr + context.length
|
|
80
|
+
const signaturePtr = payloadPtr + payload.length
|
|
81
|
+
try {
|
|
82
|
+
const memory = module.view()
|
|
83
|
+
memory.set(privateKey, privateKeyPtr)
|
|
84
|
+
memory.set(random, randomPtr)
|
|
85
|
+
memory.set(context, contextPtr)
|
|
86
|
+
memory.set(payload, payloadPtr)
|
|
87
|
+
if (
|
|
88
|
+
module.exports.mldsa44_sign(
|
|
89
|
+
privateKeyPtr,
|
|
90
|
+
payloadPtr,
|
|
91
|
+
payload.length,
|
|
92
|
+
contextPtr,
|
|
93
|
+
context.length,
|
|
94
|
+
randomPtr,
|
|
95
|
+
signaturePtr,
|
|
96
|
+
) !== 1
|
|
97
|
+
)
|
|
98
|
+
throw new InvalidInputError({ operation: 'sign' })
|
|
99
|
+
return module.view().slice(signaturePtr, signaturePtr + signatureSize)
|
|
100
|
+
} finally {
|
|
101
|
+
module.exports.zero(privateKeyPtr, size)
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
verify(signature, payload, publicKey, options) {
|
|
105
|
+
assertLength(publicKey, publicKeySize, 'public key')
|
|
106
|
+
const context = options.context ?? new Uint8Array(0)
|
|
107
|
+
assertContextLength(context)
|
|
108
|
+
// A malformed signature is a verification failure, not an error.
|
|
109
|
+
if (signature.length !== signatureSize) return false
|
|
110
|
+
const size =
|
|
111
|
+
signatureSize + context.length + payload.length + publicKeySize
|
|
112
|
+
module.reserve(size)
|
|
113
|
+
const signaturePtr = module.heapBase
|
|
114
|
+
const contextPtr = signaturePtr + signatureSize
|
|
115
|
+
const payloadPtr = contextPtr + context.length
|
|
116
|
+
const publicKeyPtr = payloadPtr + payload.length
|
|
117
|
+
try {
|
|
118
|
+
const memory = module.view()
|
|
119
|
+
memory.set(signature, signaturePtr)
|
|
120
|
+
memory.set(context, contextPtr)
|
|
121
|
+
memory.set(payload, payloadPtr)
|
|
122
|
+
memory.set(publicKey, publicKeyPtr)
|
|
123
|
+
return (
|
|
124
|
+
module.exports.mldsa44_verify(
|
|
125
|
+
signaturePtr,
|
|
126
|
+
payloadPtr,
|
|
127
|
+
payload.length,
|
|
128
|
+
contextPtr,
|
|
129
|
+
context.length,
|
|
130
|
+
publicKeyPtr,
|
|
131
|
+
) === 1
|
|
132
|
+
)
|
|
133
|
+
} finally {
|
|
134
|
+
module.exports.zero(signaturePtr, size)
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export declare namespace engine {
|
|
141
|
+
/** Every `MlDsa44` primitive this module implements. */
|
|
142
|
+
type ReturnType = {
|
|
143
|
+
[key in 'getPublicKey' | 'sign' | 'verify']-?: NonNullable<
|
|
144
|
+
Engine.MlDsa[key]
|
|
145
|
+
>
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
type ErrorType =
|
|
149
|
+
| InvalidInputError
|
|
150
|
+
| internal.MemoryError
|
|
151
|
+
| Errors.GlobalErrorType
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function assertLength(value: Uint8Array, length: number, name: string): void {
|
|
155
|
+
if (value.length !== length)
|
|
156
|
+
throw new RangeError(
|
|
157
|
+
`ML-DSA-44 ${name} must be ${length} bytes, got ${value.length}`,
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function assertContextLength(context: Uint8Array): void {
|
|
162
|
+
if (context.length > maxContextSize)
|
|
163
|
+
throw new RangeError(
|
|
164
|
+
`ML-DSA-44 context must be at most ${maxContextSize} bytes, got ${context.length}`,
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Thrown when mldsa-native rejects an input or fails to produce a signature.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```ts twoslash
|
|
173
|
+
* import { MlDsa44 } from 'ox/wasm'
|
|
174
|
+
*
|
|
175
|
+
* try {
|
|
176
|
+
* const engine = await MlDsa44.engine()
|
|
177
|
+
* engine.getPublicKey(new Uint8Array(32))
|
|
178
|
+
* } catch (error) {
|
|
179
|
+
* if (error instanceof MlDsa44.InvalidInputError)
|
|
180
|
+
* console.error(error.message)
|
|
181
|
+
* }
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
export class InvalidInputError extends Errors.BaseError {
|
|
185
|
+
override readonly name = 'MlDsa44.InvalidInputError'
|
|
186
|
+
|
|
187
|
+
constructor({ operation }: { operation: string }) {
|
|
188
|
+
super(`WASM ML-DSA-44 ${operation} received invalid input.`)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -11,6 +11,8 @@ test('install and engine expose the same precise engine', () => {
|
|
|
11
11
|
(input: Uint8Array) => Uint8Array
|
|
12
12
|
>()
|
|
13
13
|
expectTypeOf<Installed['Keystore']>().not.toHaveProperty('scrypt')
|
|
14
|
+
expectTypeOf<Installed['MlDsa44']>().not.toHaveProperty('randomSecretKey')
|
|
15
|
+
expectTypeOf<Installed['Secp256k1']>().not.toHaveProperty('randomSecretKey')
|
|
14
16
|
})
|
|
15
17
|
|
|
16
18
|
test('the WASM Engine namespace exposes synchronous core operations', () => {
|
|
@@ -10,7 +10,9 @@ describe('engine', () => {
|
|
|
10
10
|
"Ed25519",
|
|
11
11
|
"Hash",
|
|
12
12
|
"Keystore",
|
|
13
|
+
"MlDsa44",
|
|
13
14
|
"Mnemonic",
|
|
15
|
+
"Secp256k1",
|
|
14
16
|
"X25519",
|
|
15
17
|
]
|
|
16
18
|
`)
|
|
@@ -39,7 +41,9 @@ describe('install', () => {
|
|
|
39
41
|
"Ed25519",
|
|
40
42
|
"Hash",
|
|
41
43
|
"Keystore",
|
|
44
|
+
"MlDsa44",
|
|
42
45
|
"Mnemonic",
|
|
46
|
+
"Secp256k1",
|
|
43
47
|
"X25519",
|
|
44
48
|
]
|
|
45
49
|
`)
|
|
@@ -88,9 +92,21 @@ describe('install', () => {
|
|
|
88
92
|
"Keystore": [
|
|
89
93
|
"pbkdf2Sha256",
|
|
90
94
|
],
|
|
95
|
+
"MlDsa44": [
|
|
96
|
+
"getPublicKey",
|
|
97
|
+
"sign",
|
|
98
|
+
"verify",
|
|
99
|
+
],
|
|
91
100
|
"Mnemonic": [
|
|
92
101
|
"toSeed",
|
|
93
102
|
],
|
|
103
|
+
"Secp256k1": [
|
|
104
|
+
"getPublicKey",
|
|
105
|
+
"getSharedSecret",
|
|
106
|
+
"recoverPublicKey",
|
|
107
|
+
"sign",
|
|
108
|
+
"verify",
|
|
109
|
+
],
|
|
94
110
|
"X25519": [
|
|
95
111
|
"getPublicKey",
|
|
96
112
|
"getSharedSecret",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { expectTypeOf, test } from 'vp/test'
|
|
2
|
+
import type * as Engine from '../../core/Engine.js'
|
|
3
|
+
import * as core_MlDsa44 from '../../core/MlDsa44.js'
|
|
4
|
+
import * as MlDsa44 from '../MlDsa44.js'
|
|
5
|
+
|
|
6
|
+
type Slot = Awaited<ReturnType<typeof MlDsa44.engine>>
|
|
7
|
+
|
|
8
|
+
test('return type exposes only deterministic MlDsa44 primitives', () => {
|
|
9
|
+
expectTypeOf<Slot['getPublicKey']>().toEqualTypeOf<
|
|
10
|
+
NonNullable<Engine.MlDsa['getPublicKey']>
|
|
11
|
+
>()
|
|
12
|
+
expectTypeOf<Slot['sign']>().toEqualTypeOf<
|
|
13
|
+
NonNullable<Engine.MlDsa['sign']>
|
|
14
|
+
>()
|
|
15
|
+
expectTypeOf<Slot['verify']>().toEqualTypeOf<
|
|
16
|
+
NonNullable<Engine.MlDsa['verify']>
|
|
17
|
+
>()
|
|
18
|
+
expectTypeOf<Slot>().not.toHaveProperty('randomSecretKey')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('the WASM namespace exposes the public MlDsa44 API', () => {
|
|
22
|
+
expectTypeOf(MlDsa44.getPublicKey).toEqualTypeOf(core_MlDsa44.getPublicKey)
|
|
23
|
+
expectTypeOf(MlDsa44.randomPrivateKey).toEqualTypeOf(
|
|
24
|
+
core_MlDsa44.randomPrivateKey,
|
|
25
|
+
)
|
|
26
|
+
expectTypeOf(MlDsa44.sign).toEqualTypeOf(core_MlDsa44.sign)
|
|
27
|
+
expectTypeOf(MlDsa44.verify).toEqualTypeOf(core_MlDsa44.verify)
|
|
28
|
+
})
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { ml_dsa44 } from '@noble/post-quantum/ml-dsa.js'
|
|
2
|
+
import { Engine } from 'ox'
|
|
3
|
+
import { describe, expect, test } from 'vp/test'
|
|
4
|
+
import * as MlDsa44 from '../MlDsa44.js'
|
|
5
|
+
|
|
6
|
+
const seed = new Uint8Array(32).fill(7)
|
|
7
|
+
const keys = ml_dsa44.keygen(seed)
|
|
8
|
+
const payload = Uint8Array.of(0xde, 0xad, 0xbe, 0xef)
|
|
9
|
+
|
|
10
|
+
function nobleSign(
|
|
11
|
+
payload: Uint8Array,
|
|
12
|
+
options: { context?: Uint8Array; extraEntropy?: Uint8Array | false } = {},
|
|
13
|
+
) {
|
|
14
|
+
return ml_dsa44.sign(payload, keys.secretKey, {
|
|
15
|
+
extraEntropy: false,
|
|
16
|
+
...options,
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('engine', () => {
|
|
21
|
+
test('behavior: exposes only deterministic primitives', async () => {
|
|
22
|
+
const engine = await MlDsa44.engine()
|
|
23
|
+
|
|
24
|
+
expect(Object.keys(engine).sort()).toMatchInlineSnapshot(`
|
|
25
|
+
[
|
|
26
|
+
"getPublicKey",
|
|
27
|
+
"sign",
|
|
28
|
+
"verify",
|
|
29
|
+
]
|
|
30
|
+
`)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('behavior: matches noble public keys', async () => {
|
|
34
|
+
const engine = await MlDsa44.engine()
|
|
35
|
+
|
|
36
|
+
expect(engine.getPublicKey(seed)).toEqual(keys.publicKey)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('behavior: matches noble signing across payload boundaries', async () => {
|
|
40
|
+
const engine = await MlDsa44.engine()
|
|
41
|
+
|
|
42
|
+
for (const size of [0, 1, 31, 32, 33, 64, 1024, 100_000]) {
|
|
43
|
+
const payload = new Uint8Array(size).fill(size % 256)
|
|
44
|
+
const signature = engine.sign(payload, seed, { extraEntropy: false })
|
|
45
|
+
|
|
46
|
+
expect(signature).toEqual(nobleSign(payload))
|
|
47
|
+
expect(engine.verify(signature, payload, keys.publicKey, {})).toBe(true)
|
|
48
|
+
expect(ml_dsa44.verify(signature, payload, keys.publicKey)).toBe(true)
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('behavior: matches noble signing with context and entropy', async () => {
|
|
53
|
+
const engine = await MlDsa44.engine()
|
|
54
|
+
const context = Uint8Array.of(1, 2, 3)
|
|
55
|
+
const entropy = new Uint8Array(32).fill(9)
|
|
56
|
+
|
|
57
|
+
for (const options of [
|
|
58
|
+
{ context },
|
|
59
|
+
{ extraEntropy: entropy },
|
|
60
|
+
{ context, extraEntropy: entropy },
|
|
61
|
+
{ context: new Uint8Array(255).fill(5) },
|
|
62
|
+
] as const) {
|
|
63
|
+
const signature = engine.sign(payload, seed, {
|
|
64
|
+
extraEntropy: false,
|
|
65
|
+
...options,
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
expect(signature).toEqual(nobleSign(payload, options))
|
|
69
|
+
expect(
|
|
70
|
+
engine.verify(signature, payload, keys.publicKey, {
|
|
71
|
+
context: options.context,
|
|
72
|
+
}),
|
|
73
|
+
).toBe(true)
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('behavior: context binds verification', async () => {
|
|
78
|
+
const engine = await MlDsa44.engine()
|
|
79
|
+
const context = Uint8Array.of(1)
|
|
80
|
+
const signature = engine.sign(payload, seed, {
|
|
81
|
+
context,
|
|
82
|
+
extraEntropy: false,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
expect(engine.verify(signature, payload, keys.publicKey, { context })).toBe(
|
|
86
|
+
true,
|
|
87
|
+
)
|
|
88
|
+
expect(engine.verify(signature, payload, keys.publicKey, {})).toBe(false)
|
|
89
|
+
expect(
|
|
90
|
+
engine.verify(signature, payload, keys.publicKey, {
|
|
91
|
+
context: Uint8Array.of(2),
|
|
92
|
+
}),
|
|
93
|
+
).toBe(false)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test('behavior: generates host entropy for hedged signing', async () => {
|
|
97
|
+
const engine = await MlDsa44.engine()
|
|
98
|
+
const signatureA = engine.sign(payload, seed, { extraEntropy: true })
|
|
99
|
+
const signatureB = engine.sign(payload, seed, { extraEntropy: true })
|
|
100
|
+
|
|
101
|
+
expect(signatureA).not.toEqual(signatureB)
|
|
102
|
+
expect(engine.verify(signatureA, payload, keys.publicKey, {})).toBe(true)
|
|
103
|
+
expect(engine.verify(signatureB, payload, keys.publicKey, {})).toBe(true)
|
|
104
|
+
expect(ml_dsa44.verify(signatureA, payload, keys.publicKey)).toBe(true)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('behavior: rejects corrupted and truncated signatures', async () => {
|
|
108
|
+
const engine = await MlDsa44.engine()
|
|
109
|
+
const signature = engine.sign(payload, seed, { extraEntropy: false })
|
|
110
|
+
|
|
111
|
+
for (const corrupt of [0, 1, 100, 2419]) {
|
|
112
|
+
const bad = signature.slice()
|
|
113
|
+
bad[corrupt]! ^= 1
|
|
114
|
+
expect(engine.verify(bad, payload, keys.publicKey, {})).toBe(false)
|
|
115
|
+
}
|
|
116
|
+
expect(engine.verify(signature.slice(1), payload, keys.publicKey, {})).toBe(
|
|
117
|
+
false,
|
|
118
|
+
)
|
|
119
|
+
expect(engine.verify(new Uint8Array(0), payload, keys.publicKey, {})).toBe(
|
|
120
|
+
false,
|
|
121
|
+
)
|
|
122
|
+
expect(engine.verify(signature, payload.slice(1), keys.publicKey, {})).toBe(
|
|
123
|
+
false,
|
|
124
|
+
)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('behavior: rejects noble signatures made with other keys', async () => {
|
|
128
|
+
const engine = await MlDsa44.engine()
|
|
129
|
+
const other = ml_dsa44.keygen(new Uint8Array(32).fill(8))
|
|
130
|
+
const signature = engine.sign(payload, seed, { extraEntropy: false })
|
|
131
|
+
|
|
132
|
+
expect(engine.verify(signature, payload, other.publicKey, {})).toBe(false)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('behavior: installs into the Engine registry', async () => {
|
|
136
|
+
await Engine.install({ MlDsa44: MlDsa44.engine() })
|
|
137
|
+
try {
|
|
138
|
+
expect(Engine.get().MlDsa44).toBeDefined()
|
|
139
|
+
|
|
140
|
+
const signature = MlDsa44.sign({
|
|
141
|
+
payload: '0xdeadbeef',
|
|
142
|
+
privateKey: seed,
|
|
143
|
+
as: 'Bytes',
|
|
144
|
+
})
|
|
145
|
+
expect(signature).toEqual(nobleSign(payload))
|
|
146
|
+
expect(
|
|
147
|
+
MlDsa44.verify({
|
|
148
|
+
payload: '0xdeadbeef',
|
|
149
|
+
publicKey: keys.publicKey,
|
|
150
|
+
signature,
|
|
151
|
+
}),
|
|
152
|
+
).toBe(true)
|
|
153
|
+
} finally {
|
|
154
|
+
Engine.reset('MlDsa44')
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('error: rejects malformed inputs', async () => {
|
|
159
|
+
const engine = await MlDsa44.engine()
|
|
160
|
+
|
|
161
|
+
for (const size of [0, 31, 33, 2560]) {
|
|
162
|
+
expect(() => engine.getPublicKey(new Uint8Array(size))).toThrowError(
|
|
163
|
+
`ML-DSA-44 private key must be 32 bytes, got ${size}`,
|
|
164
|
+
)
|
|
165
|
+
expect(() =>
|
|
166
|
+
engine.sign(payload, new Uint8Array(size), { extraEntropy: false }),
|
|
167
|
+
).toThrowError(`ML-DSA-44 private key must be 32 bytes, got ${size}`)
|
|
168
|
+
}
|
|
169
|
+
expect(() =>
|
|
170
|
+
engine.sign(payload, seed, { extraEntropy: new Uint8Array(31) }),
|
|
171
|
+
).toThrowError('ML-DSA-44 extra entropy must be 32 bytes, got 31')
|
|
172
|
+
expect(() =>
|
|
173
|
+
engine.sign(payload, seed, {
|
|
174
|
+
context: new Uint8Array(256),
|
|
175
|
+
extraEntropy: false,
|
|
176
|
+
}),
|
|
177
|
+
).toThrowError('ML-DSA-44 context must be at most 255 bytes, got 256')
|
|
178
|
+
expect(() =>
|
|
179
|
+
engine.verify(
|
|
180
|
+
new Uint8Array(2420),
|
|
181
|
+
payload,
|
|
182
|
+
keys.publicKey.slice(0, 100),
|
|
183
|
+
{},
|
|
184
|
+
),
|
|
185
|
+
).toThrowError('ML-DSA-44 public key must be 1312 bytes, got 100')
|
|
186
|
+
expect(() =>
|
|
187
|
+
engine.verify(new Uint8Array(2420), payload, keys.publicKey, {
|
|
188
|
+
context: new Uint8Array(256),
|
|
189
|
+
}),
|
|
190
|
+
).toThrowError('ML-DSA-44 context must be at most 255 bytes, got 256')
|
|
191
|
+
})
|
|
192
|
+
})
|
package/src/wasm/index.ts
CHANGED
|
@@ -105,6 +105,28 @@ export * as Keystore from './Keystore.js'
|
|
|
105
105
|
*/
|
|
106
106
|
export * as Kzg from './Kzg.js'
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* WASM implementations of Ox's supported ML-DSA-44 primitives, backed by
|
|
110
|
+
* [mldsa-native](https://github.com/pq-code-package/mldsa-native).
|
|
111
|
+
*
|
|
112
|
+
* This provider is installed by [`Engine.install`](/api/Engine/install), or
|
|
113
|
+
* can be installed individually.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts twoslash
|
|
117
|
+
* // @noErrors
|
|
118
|
+
* import { Engine } from 'ox'
|
|
119
|
+
* import { MlDsa44 } from 'ox/wasm'
|
|
120
|
+
*
|
|
121
|
+
* await Engine.install({ MlDsa44: MlDsa44.engine() })
|
|
122
|
+
*
|
|
123
|
+
* MlDsa44.getPublicKey({ privateKey: '0x...' })
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* @category Post Quantum
|
|
127
|
+
*/
|
|
128
|
+
export * as MlDsa44 from './MlDsa44.js'
|
|
129
|
+
|
|
108
130
|
/**
|
|
109
131
|
* WASM implementation of Ox's BIP-39 seed derivation.
|
|
110
132
|
*
|
|
@@ -128,8 +150,8 @@ export * as Mnemonic from './Mnemonic.js'
|
|
|
128
150
|
/**
|
|
129
151
|
* WASM implementations of deterministic Secp256k1 primitives.
|
|
130
152
|
*
|
|
131
|
-
* This provider is
|
|
132
|
-
*
|
|
153
|
+
* This provider is installed by [`Engine.install`](/api/Engine/install), or
|
|
154
|
+
* can be installed individually.
|
|
133
155
|
*
|
|
134
156
|
* @example
|
|
135
157
|
* ```ts twoslash
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
instantiate,
|
|
3
|
+
memoize,
|
|
4
|
+
type Module as WasmModule,
|
|
5
|
+
} from './instantiate.js'
|
|
6
|
+
import { wasmBase64 } from './mldsa44.wasm.js'
|
|
7
|
+
|
|
8
|
+
export type Exports = {
|
|
9
|
+
mldsa44_get_public_key(seed: number, publicKey: number): number
|
|
10
|
+
mldsa44_sign(
|
|
11
|
+
seed: number,
|
|
12
|
+
message: number,
|
|
13
|
+
messageSize: number,
|
|
14
|
+
context: number,
|
|
15
|
+
contextSize: number,
|
|
16
|
+
random: number,
|
|
17
|
+
signature: number,
|
|
18
|
+
): number
|
|
19
|
+
mldsa44_verify(
|
|
20
|
+
signature: number,
|
|
21
|
+
message: number,
|
|
22
|
+
messageSize: number,
|
|
23
|
+
context: number,
|
|
24
|
+
contextSize: number,
|
|
25
|
+
publicKey: number,
|
|
26
|
+
): number
|
|
27
|
+
zero(pointer: number, length: number): void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Instantiated ML-DSA-44 module. */
|
|
31
|
+
export type Module = WasmModule<Exports>
|
|
32
|
+
|
|
33
|
+
/** Instantiates the ML-DSA-44 module once. */
|
|
34
|
+
export const load = memoize(() => instantiate<Exports>(wasmBase64))
|