@theqrl/dilithium5 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/README.md +8 -1
- package/dist/cjs/dilithium5.js +456 -42
- package/dist/mjs/dilithium5.js +65 -28
- package/package.json +34 -14
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@ console.log(new TextDecoder().decode(extracted)); // "Hello, quantum world!"
|
|
|
55
55
|
|
|
56
56
|
Generate a keypair from a seed.
|
|
57
57
|
|
|
58
|
-
- `seed`: `Uint8Array(32)` or `
|
|
58
|
+
- `seed`: `Uint8Array(32)`, `null`, or `undefined` for random
|
|
59
59
|
- `pk`: `Uint8Array(2592)` - output buffer for public key
|
|
60
60
|
- `sk`: `Uint8Array(4896)` - output buffer for secret key
|
|
61
61
|
- Returns: The seed used (useful when `seed` is `null`)
|
|
@@ -102,10 +102,17 @@ Verify a detached signature.
|
|
|
102
102
|
|
|
103
103
|
Zero out sensitive data (best-effort, see security notes).
|
|
104
104
|
|
|
105
|
+
- `buffer`: `Uint8Array` - the buffer to zero
|
|
106
|
+
- Throws: `TypeError` if buffer is not a `Uint8Array`
|
|
107
|
+
|
|
105
108
|
#### `isZero(buffer)`
|
|
106
109
|
|
|
107
110
|
Check if buffer is all zeros (constant-time).
|
|
108
111
|
|
|
112
|
+
- `buffer`: `Uint8Array` - the buffer to check
|
|
113
|
+
- Returns: `true` if all bytes are zero
|
|
114
|
+
- Throws: `TypeError` if buffer is not a `Uint8Array`
|
|
115
|
+
|
|
109
116
|
## Interoperability with go-qrllib
|
|
110
117
|
|
|
111
118
|
go-qrllib pre-hashes seeds with SHAKE256 before key generation. To generate matching keys:
|
package/dist/cjs/dilithium5.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var sha3_js = require('@noble/hashes/sha3.js');
|
|
4
|
-
var utils_js = require('@noble/hashes/utils.js');
|
|
5
|
-
|
|
6
3
|
const Shake128Rate = 168;
|
|
7
4
|
const Shake256Rate = 136;
|
|
8
5
|
const Stream128BlockBytes = Shake128Rate;
|
|
@@ -65,6 +62,386 @@ const zetas = [
|
|
|
65
62
|
-1362209, 3937738, 1400424, -846154, 1976782,
|
|
66
63
|
];
|
|
67
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Internal helpers for u64. BigUint64Array is too slow as per 2025, so we implement it using Uint32Array.
|
|
67
|
+
* @todo re-check https://issues.chromium.org/issues/42212588
|
|
68
|
+
* @module
|
|
69
|
+
*/
|
|
70
|
+
const U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1);
|
|
71
|
+
const _32n = /* @__PURE__ */ BigInt(32);
|
|
72
|
+
function fromBig(n, le = false) {
|
|
73
|
+
if (le)
|
|
74
|
+
return { h: Number(n & U32_MASK64), l: Number((n >> _32n) & U32_MASK64) };
|
|
75
|
+
return { h: Number((n >> _32n) & U32_MASK64) | 0, l: Number(n & U32_MASK64) | 0 };
|
|
76
|
+
}
|
|
77
|
+
function split(lst, le = false) {
|
|
78
|
+
const len = lst.length;
|
|
79
|
+
let Ah = new Uint32Array(len);
|
|
80
|
+
let Al = new Uint32Array(len);
|
|
81
|
+
for (let i = 0; i < len; i++) {
|
|
82
|
+
const { h, l } = fromBig(lst[i], le);
|
|
83
|
+
[Ah[i], Al[i]] = [h, l];
|
|
84
|
+
}
|
|
85
|
+
return [Ah, Al];
|
|
86
|
+
}
|
|
87
|
+
// Left rotate for Shift in [1, 32)
|
|
88
|
+
const rotlSH = (h, l, s) => (h << s) | (l >>> (32 - s));
|
|
89
|
+
const rotlSL = (h, l, s) => (l << s) | (h >>> (32 - s));
|
|
90
|
+
// Left rotate for Shift in (32, 64), NOTE: 32 is special case.
|
|
91
|
+
const rotlBH = (h, l, s) => (l << (s - 32)) | (h >>> (64 - s));
|
|
92
|
+
const rotlBL = (h, l, s) => (h << (s - 32)) | (l >>> (64 - s));
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Utilities for hex, bytes, CSPRNG.
|
|
96
|
+
* @module
|
|
97
|
+
*/
|
|
98
|
+
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
99
|
+
/** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */
|
|
100
|
+
function isBytes(a) {
|
|
101
|
+
return a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array');
|
|
102
|
+
}
|
|
103
|
+
/** Asserts something is positive integer. */
|
|
104
|
+
function anumber(n, title = '') {
|
|
105
|
+
if (!Number.isSafeInteger(n) || n < 0) {
|
|
106
|
+
const prefix = title && `"${title}" `;
|
|
107
|
+
throw new Error(`${prefix}expected integer >= 0, got ${n}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Asserts something is Uint8Array. */
|
|
111
|
+
function abytes(value, length, title = '') {
|
|
112
|
+
const bytes = isBytes(value);
|
|
113
|
+
const len = value?.length;
|
|
114
|
+
const needsLen = length !== undefined;
|
|
115
|
+
if (!bytes || (needsLen)) {
|
|
116
|
+
const prefix = title && `"${title}" `;
|
|
117
|
+
const ofLen = '';
|
|
118
|
+
const got = bytes ? `length=${len}` : `type=${typeof value}`;
|
|
119
|
+
throw new Error(prefix + 'expected Uint8Array' + ofLen + ', got ' + got);
|
|
120
|
+
}
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
/** Asserts a hash instance has not been destroyed / finished */
|
|
124
|
+
function aexists(instance, checkFinished = true) {
|
|
125
|
+
if (instance.destroyed)
|
|
126
|
+
throw new Error('Hash instance has been destroyed');
|
|
127
|
+
if (checkFinished && instance.finished)
|
|
128
|
+
throw new Error('Hash#digest() has already been called');
|
|
129
|
+
}
|
|
130
|
+
/** Asserts output is properly-sized byte array */
|
|
131
|
+
function aoutput(out, instance) {
|
|
132
|
+
abytes(out, undefined, 'digestInto() output');
|
|
133
|
+
const min = instance.outputLen;
|
|
134
|
+
if (out.length < min) {
|
|
135
|
+
throw new Error('"digestInto() output" expected to be of length >=' + min);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** Cast u8 / u16 / u32 to u32. */
|
|
139
|
+
function u32(arr) {
|
|
140
|
+
return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
|
141
|
+
}
|
|
142
|
+
/** Zeroize a byte array. Warning: JS provides no guarantees. */
|
|
143
|
+
function clean(...arrays) {
|
|
144
|
+
for (let i = 0; i < arrays.length; i++) {
|
|
145
|
+
arrays[i].fill(0);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/** Is current platform little-endian? Most are. Big-Endian platform: IBM */
|
|
149
|
+
const isLE = /* @__PURE__ */ (() => new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44)();
|
|
150
|
+
/** The byte swap operation for uint32 */
|
|
151
|
+
function byteSwap(word) {
|
|
152
|
+
return (((word << 24) & 0xff000000) |
|
|
153
|
+
((word << 8) & 0xff0000) |
|
|
154
|
+
((word >>> 8) & 0xff00) |
|
|
155
|
+
((word >>> 24) & 0xff));
|
|
156
|
+
}
|
|
157
|
+
/** In place byte swap for Uint32Array */
|
|
158
|
+
function byteSwap32(arr) {
|
|
159
|
+
for (let i = 0; i < arr.length; i++) {
|
|
160
|
+
arr[i] = byteSwap(arr[i]);
|
|
161
|
+
}
|
|
162
|
+
return arr;
|
|
163
|
+
}
|
|
164
|
+
const swap32IfBE = isLE
|
|
165
|
+
? (u) => u
|
|
166
|
+
: byteSwap32;
|
|
167
|
+
// Built-in hex conversion https://caniuse.com/mdn-javascript_builtins_uint8array_fromhex
|
|
168
|
+
const hasHexBuiltin = /* @__PURE__ */ (() =>
|
|
169
|
+
// @ts-ignore
|
|
170
|
+
typeof Uint8Array.from([]).toHex === 'function' && typeof Uint8Array.fromHex === 'function')();
|
|
171
|
+
// We use optimized technique to convert hex string to byte array
|
|
172
|
+
const asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
|
|
173
|
+
function asciiToBase16(ch) {
|
|
174
|
+
if (ch >= asciis._0 && ch <= asciis._9)
|
|
175
|
+
return ch - asciis._0; // '2' => 50-48
|
|
176
|
+
if (ch >= asciis.A && ch <= asciis.F)
|
|
177
|
+
return ch - (asciis.A - 10); // 'B' => 66-(65-10)
|
|
178
|
+
if (ch >= asciis.a && ch <= asciis.f)
|
|
179
|
+
return ch - (asciis.a - 10); // 'b' => 98-(97-10)
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Convert hex string to byte array. Uses built-in function, when available.
|
|
184
|
+
* @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
|
|
185
|
+
*/
|
|
186
|
+
function hexToBytes$1(hex) {
|
|
187
|
+
if (typeof hex !== 'string')
|
|
188
|
+
throw new Error('hex string expected, got ' + typeof hex);
|
|
189
|
+
// @ts-ignore
|
|
190
|
+
if (hasHexBuiltin)
|
|
191
|
+
return Uint8Array.fromHex(hex);
|
|
192
|
+
const hl = hex.length;
|
|
193
|
+
const al = hl / 2;
|
|
194
|
+
if (hl % 2)
|
|
195
|
+
throw new Error('hex string expected, got unpadded hex of length ' + hl);
|
|
196
|
+
const array = new Uint8Array(al);
|
|
197
|
+
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
|
198
|
+
const n1 = asciiToBase16(hex.charCodeAt(hi));
|
|
199
|
+
const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
|
|
200
|
+
if (n1 === undefined || n2 === undefined) {
|
|
201
|
+
const char = hex[hi] + hex[hi + 1];
|
|
202
|
+
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
|
|
203
|
+
}
|
|
204
|
+
array[ai] = n1 * 16 + n2; // multiply first octet, e.g. 'a3' => 10*16+3 => 160 + 3 => 163
|
|
205
|
+
}
|
|
206
|
+
return array;
|
|
207
|
+
}
|
|
208
|
+
/** Creates function with outputLen, blockLen, create properties from a class constructor. */
|
|
209
|
+
function createHasher(hashCons, info = {}) {
|
|
210
|
+
const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
|
|
211
|
+
const tmp = hashCons(undefined);
|
|
212
|
+
hashC.outputLen = tmp.outputLen;
|
|
213
|
+
hashC.blockLen = tmp.blockLen;
|
|
214
|
+
hashC.create = (opts) => hashCons(opts);
|
|
215
|
+
Object.assign(hashC, info);
|
|
216
|
+
return Object.freeze(hashC);
|
|
217
|
+
}
|
|
218
|
+
/** Creates OID opts for NIST hashes, with prefix 06 09 60 86 48 01 65 03 04 02. */
|
|
219
|
+
const oidNist = (suffix) => ({
|
|
220
|
+
oid: Uint8Array.from([0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, suffix]),
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* SHA3 (keccak) hash function, based on a new "Sponge function" design.
|
|
225
|
+
* Different from older hashes, the internal state is bigger than output size.
|
|
226
|
+
*
|
|
227
|
+
* Check out [FIPS-202](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf),
|
|
228
|
+
* [Website](https://keccak.team/keccak.html),
|
|
229
|
+
* [the differences between SHA-3 and Keccak](https://crypto.stackexchange.com/questions/15727/what-are-the-key-differences-between-the-draft-sha-3-standard-and-the-keccak-sub).
|
|
230
|
+
*
|
|
231
|
+
* Check out `sha3-addons` module for cSHAKE, k12, and others.
|
|
232
|
+
* @module
|
|
233
|
+
*/
|
|
234
|
+
// No __PURE__ annotations in sha3 header:
|
|
235
|
+
// EVERYTHING is in fact used on every export.
|
|
236
|
+
// Various per round constants calculations
|
|
237
|
+
const _0n = BigInt(0);
|
|
238
|
+
const _1n = BigInt(1);
|
|
239
|
+
const _2n = BigInt(2);
|
|
240
|
+
const _7n = BigInt(7);
|
|
241
|
+
const _256n = BigInt(256);
|
|
242
|
+
const _0x71n = BigInt(0x71);
|
|
243
|
+
const SHA3_PI = [];
|
|
244
|
+
const SHA3_ROTL = [];
|
|
245
|
+
const _SHA3_IOTA = []; // no pure annotation: var is always used
|
|
246
|
+
for (let round = 0, R = _1n, x = 1, y = 0; round < 24; round++) {
|
|
247
|
+
// Pi
|
|
248
|
+
[x, y] = [y, (2 * x + 3 * y) % 5];
|
|
249
|
+
SHA3_PI.push(2 * (5 * y + x));
|
|
250
|
+
// Rotational
|
|
251
|
+
SHA3_ROTL.push((((round + 1) * (round + 2)) / 2) % 64);
|
|
252
|
+
// Iota
|
|
253
|
+
let t = _0n;
|
|
254
|
+
for (let j = 0; j < 7; j++) {
|
|
255
|
+
R = ((R << _1n) ^ ((R >> _7n) * _0x71n)) % _256n;
|
|
256
|
+
if (R & _2n)
|
|
257
|
+
t ^= _1n << ((_1n << BigInt(j)) - _1n);
|
|
258
|
+
}
|
|
259
|
+
_SHA3_IOTA.push(t);
|
|
260
|
+
}
|
|
261
|
+
const IOTAS = split(_SHA3_IOTA, true);
|
|
262
|
+
const SHA3_IOTA_H = IOTAS[0];
|
|
263
|
+
const SHA3_IOTA_L = IOTAS[1];
|
|
264
|
+
// Left rotation (without 0, 32, 64)
|
|
265
|
+
const rotlH = (h, l, s) => (s > 32 ? rotlBH(h, l, s) : rotlSH(h, l, s));
|
|
266
|
+
const rotlL = (h, l, s) => (s > 32 ? rotlBL(h, l, s) : rotlSL(h, l, s));
|
|
267
|
+
/** `keccakf1600` internal function, additionally allows to adjust round count. */
|
|
268
|
+
function keccakP(s, rounds = 24) {
|
|
269
|
+
const B = new Uint32Array(5 * 2);
|
|
270
|
+
// NOTE: all indices are x2 since we store state as u32 instead of u64 (bigints to slow in js)
|
|
271
|
+
for (let round = 24 - rounds; round < 24; round++) {
|
|
272
|
+
// Theta θ
|
|
273
|
+
for (let x = 0; x < 10; x++)
|
|
274
|
+
B[x] = s[x] ^ s[x + 10] ^ s[x + 20] ^ s[x + 30] ^ s[x + 40];
|
|
275
|
+
for (let x = 0; x < 10; x += 2) {
|
|
276
|
+
const idx1 = (x + 8) % 10;
|
|
277
|
+
const idx0 = (x + 2) % 10;
|
|
278
|
+
const B0 = B[idx0];
|
|
279
|
+
const B1 = B[idx0 + 1];
|
|
280
|
+
const Th = rotlH(B0, B1, 1) ^ B[idx1];
|
|
281
|
+
const Tl = rotlL(B0, B1, 1) ^ B[idx1 + 1];
|
|
282
|
+
for (let y = 0; y < 50; y += 10) {
|
|
283
|
+
s[x + y] ^= Th;
|
|
284
|
+
s[x + y + 1] ^= Tl;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// Rho (ρ) and Pi (π)
|
|
288
|
+
let curH = s[2];
|
|
289
|
+
let curL = s[3];
|
|
290
|
+
for (let t = 0; t < 24; t++) {
|
|
291
|
+
const shift = SHA3_ROTL[t];
|
|
292
|
+
const Th = rotlH(curH, curL, shift);
|
|
293
|
+
const Tl = rotlL(curH, curL, shift);
|
|
294
|
+
const PI = SHA3_PI[t];
|
|
295
|
+
curH = s[PI];
|
|
296
|
+
curL = s[PI + 1];
|
|
297
|
+
s[PI] = Th;
|
|
298
|
+
s[PI + 1] = Tl;
|
|
299
|
+
}
|
|
300
|
+
// Chi (χ)
|
|
301
|
+
for (let y = 0; y < 50; y += 10) {
|
|
302
|
+
for (let x = 0; x < 10; x++)
|
|
303
|
+
B[x] = s[y + x];
|
|
304
|
+
for (let x = 0; x < 10; x++)
|
|
305
|
+
s[y + x] ^= ~B[(x + 2) % 10] & B[(x + 4) % 10];
|
|
306
|
+
}
|
|
307
|
+
// Iota (ι)
|
|
308
|
+
s[0] ^= SHA3_IOTA_H[round];
|
|
309
|
+
s[1] ^= SHA3_IOTA_L[round];
|
|
310
|
+
}
|
|
311
|
+
clean(B);
|
|
312
|
+
}
|
|
313
|
+
/** Keccak sponge function. */
|
|
314
|
+
class Keccak {
|
|
315
|
+
state;
|
|
316
|
+
pos = 0;
|
|
317
|
+
posOut = 0;
|
|
318
|
+
finished = false;
|
|
319
|
+
state32;
|
|
320
|
+
destroyed = false;
|
|
321
|
+
blockLen;
|
|
322
|
+
suffix;
|
|
323
|
+
outputLen;
|
|
324
|
+
enableXOF = false;
|
|
325
|
+
rounds;
|
|
326
|
+
// NOTE: we accept arguments in bytes instead of bits here.
|
|
327
|
+
constructor(blockLen, suffix, outputLen, enableXOF = false, rounds = 24) {
|
|
328
|
+
this.blockLen = blockLen;
|
|
329
|
+
this.suffix = suffix;
|
|
330
|
+
this.outputLen = outputLen;
|
|
331
|
+
this.enableXOF = enableXOF;
|
|
332
|
+
this.rounds = rounds;
|
|
333
|
+
// Can be passed from user as dkLen
|
|
334
|
+
anumber(outputLen, 'outputLen');
|
|
335
|
+
// 1600 = 5x5 matrix of 64bit. 1600 bits === 200 bytes
|
|
336
|
+
// 0 < blockLen < 200
|
|
337
|
+
if (!(0 < blockLen && blockLen < 200))
|
|
338
|
+
throw new Error('only keccak-f1600 function is supported');
|
|
339
|
+
this.state = new Uint8Array(200);
|
|
340
|
+
this.state32 = u32(this.state);
|
|
341
|
+
}
|
|
342
|
+
clone() {
|
|
343
|
+
return this._cloneInto();
|
|
344
|
+
}
|
|
345
|
+
keccak() {
|
|
346
|
+
swap32IfBE(this.state32);
|
|
347
|
+
keccakP(this.state32, this.rounds);
|
|
348
|
+
swap32IfBE(this.state32);
|
|
349
|
+
this.posOut = 0;
|
|
350
|
+
this.pos = 0;
|
|
351
|
+
}
|
|
352
|
+
update(data) {
|
|
353
|
+
aexists(this);
|
|
354
|
+
abytes(data);
|
|
355
|
+
const { blockLen, state } = this;
|
|
356
|
+
const len = data.length;
|
|
357
|
+
for (let pos = 0; pos < len;) {
|
|
358
|
+
const take = Math.min(blockLen - this.pos, len - pos);
|
|
359
|
+
for (let i = 0; i < take; i++)
|
|
360
|
+
state[this.pos++] ^= data[pos++];
|
|
361
|
+
if (this.pos === blockLen)
|
|
362
|
+
this.keccak();
|
|
363
|
+
}
|
|
364
|
+
return this;
|
|
365
|
+
}
|
|
366
|
+
finish() {
|
|
367
|
+
if (this.finished)
|
|
368
|
+
return;
|
|
369
|
+
this.finished = true;
|
|
370
|
+
const { state, suffix, pos, blockLen } = this;
|
|
371
|
+
// Do the padding
|
|
372
|
+
state[pos] ^= suffix;
|
|
373
|
+
if ((suffix & 0x80) !== 0 && pos === blockLen - 1)
|
|
374
|
+
this.keccak();
|
|
375
|
+
state[blockLen - 1] ^= 0x80;
|
|
376
|
+
this.keccak();
|
|
377
|
+
}
|
|
378
|
+
writeInto(out) {
|
|
379
|
+
aexists(this, false);
|
|
380
|
+
abytes(out);
|
|
381
|
+
this.finish();
|
|
382
|
+
const bufferOut = this.state;
|
|
383
|
+
const { blockLen } = this;
|
|
384
|
+
for (let pos = 0, len = out.length; pos < len;) {
|
|
385
|
+
if (this.posOut >= blockLen)
|
|
386
|
+
this.keccak();
|
|
387
|
+
const take = Math.min(blockLen - this.posOut, len - pos);
|
|
388
|
+
out.set(bufferOut.subarray(this.posOut, this.posOut + take), pos);
|
|
389
|
+
this.posOut += take;
|
|
390
|
+
pos += take;
|
|
391
|
+
}
|
|
392
|
+
return out;
|
|
393
|
+
}
|
|
394
|
+
xofInto(out) {
|
|
395
|
+
// Sha3/Keccak usage with XOF is probably mistake, only SHAKE instances can do XOF
|
|
396
|
+
if (!this.enableXOF)
|
|
397
|
+
throw new Error('XOF is not possible for this instance');
|
|
398
|
+
return this.writeInto(out);
|
|
399
|
+
}
|
|
400
|
+
xof(bytes) {
|
|
401
|
+
anumber(bytes);
|
|
402
|
+
return this.xofInto(new Uint8Array(bytes));
|
|
403
|
+
}
|
|
404
|
+
digestInto(out) {
|
|
405
|
+
aoutput(out, this);
|
|
406
|
+
if (this.finished)
|
|
407
|
+
throw new Error('digest() was already called');
|
|
408
|
+
this.writeInto(out);
|
|
409
|
+
this.destroy();
|
|
410
|
+
return out;
|
|
411
|
+
}
|
|
412
|
+
digest() {
|
|
413
|
+
return this.digestInto(new Uint8Array(this.outputLen));
|
|
414
|
+
}
|
|
415
|
+
destroy() {
|
|
416
|
+
this.destroyed = true;
|
|
417
|
+
clean(this.state);
|
|
418
|
+
}
|
|
419
|
+
_cloneInto(to) {
|
|
420
|
+
const { blockLen, suffix, outputLen, rounds, enableXOF } = this;
|
|
421
|
+
to ||= new Keccak(blockLen, suffix, outputLen, enableXOF, rounds);
|
|
422
|
+
to.state32.set(this.state32);
|
|
423
|
+
to.pos = this.pos;
|
|
424
|
+
to.posOut = this.posOut;
|
|
425
|
+
to.finished = this.finished;
|
|
426
|
+
to.rounds = rounds;
|
|
427
|
+
// Suffix can change in cSHAKE
|
|
428
|
+
to.suffix = suffix;
|
|
429
|
+
to.outputLen = outputLen;
|
|
430
|
+
to.enableXOF = enableXOF;
|
|
431
|
+
to.destroyed = this.destroyed;
|
|
432
|
+
return to;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
const genShake = (suffix, blockLen, outputLen, info = {}) => createHasher((opts = {}) => new Keccak(blockLen, suffix, opts.dkLen === undefined ? outputLen : opts.dkLen, true), info);
|
|
436
|
+
/** SHAKE128 XOF with 128-bit security. */
|
|
437
|
+
const shake128 =
|
|
438
|
+
/* @__PURE__ */
|
|
439
|
+
genShake(0x1f, 168, 16, /* @__PURE__ */ oidNist(0x0b));
|
|
440
|
+
/** SHAKE256 XOF with 256-bit security. */
|
|
441
|
+
const shake256 =
|
|
442
|
+
/* @__PURE__ */
|
|
443
|
+
genShake(0x1f, 136, 32, /* @__PURE__ */ oidNist(0x0c));
|
|
444
|
+
|
|
68
445
|
/**
|
|
69
446
|
* FIPS 202 SHAKE functions using @noble/hashes
|
|
70
447
|
* Provides streaming XOF (extendable output function) interface
|
|
@@ -85,7 +462,7 @@ class KeccakState {
|
|
|
85
462
|
// SHAKE-128 functions
|
|
86
463
|
|
|
87
464
|
function shake128Init(state) {
|
|
88
|
-
state.hasher =
|
|
465
|
+
state.hasher = shake128.create({});
|
|
89
466
|
state.finalized = false;
|
|
90
467
|
}
|
|
91
468
|
|
|
@@ -107,7 +484,7 @@ function shake128SqueezeBlocks(out, outputOffset, nBlocks, state) {
|
|
|
107
484
|
// SHAKE-256 functions
|
|
108
485
|
|
|
109
486
|
function shake256Init(state) {
|
|
110
|
-
state.hasher =
|
|
487
|
+
state.hasher = shake256.create({});
|
|
111
488
|
state.finalized = false;
|
|
112
489
|
}
|
|
113
490
|
|
|
@@ -160,12 +537,16 @@ function montgomeryReduce(a) {
|
|
|
160
537
|
return t;
|
|
161
538
|
}
|
|
162
539
|
|
|
540
|
+
// Partial reduction modulo Q. Input must satisfy |a| < 2^31 - 2^22.
|
|
541
|
+
// Output is in (-Q, Q). Mirrors the reference C implementation.
|
|
163
542
|
function reduce32(a) {
|
|
164
543
|
let t = (a + (1 << 22)) >> 23;
|
|
165
544
|
t = a - t * Q;
|
|
166
545
|
return t;
|
|
167
546
|
}
|
|
168
547
|
|
|
548
|
+
// Conditional add Q: if a is negative, add Q. Input must satisfy -Q < a < 2^31.
|
|
549
|
+
// Output is in [0, Q). Mirrors the reference C implementation.
|
|
169
550
|
function cAddQ(a) {
|
|
170
551
|
let ar = a;
|
|
171
552
|
ar += (ar >> 31) & Q;
|
|
@@ -174,7 +555,7 @@ function cAddQ(a) {
|
|
|
174
555
|
|
|
175
556
|
function ntt(a) {
|
|
176
557
|
let k = 0;
|
|
177
|
-
let j
|
|
558
|
+
let j;
|
|
178
559
|
|
|
179
560
|
for (let len = 128; len > 0; len >>= 1) {
|
|
180
561
|
for (let start = 0; start < N; start = j + len) {
|
|
@@ -190,7 +571,7 @@ function ntt(a) {
|
|
|
190
571
|
|
|
191
572
|
function invNTTToMont(a) {
|
|
192
573
|
const f = 41978n; // mont^2/256
|
|
193
|
-
let j
|
|
574
|
+
let j;
|
|
194
575
|
let k = 256;
|
|
195
576
|
|
|
196
577
|
for (let len = 1; len < N; len <<= 1) {
|
|
@@ -327,10 +708,7 @@ function polyChkNorm(a, b) {
|
|
|
327
708
|
}
|
|
328
709
|
|
|
329
710
|
for (let i = 0; i < N; i++) {
|
|
330
|
-
|
|
331
|
-
t = a.coeffs[i] - (t & (2 * a.coeffs[i]));
|
|
332
|
-
|
|
333
|
-
if (t >= b) {
|
|
711
|
+
if (Math.abs(a.coeffs[i]) >= b) {
|
|
334
712
|
return 1;
|
|
335
713
|
}
|
|
336
714
|
}
|
|
@@ -449,6 +827,8 @@ function polyUniformGamma1(a, seed, nonce) {
|
|
|
449
827
|
}
|
|
450
828
|
|
|
451
829
|
function polyChallenge(cP, seed) {
|
|
830
|
+
if (seed.length !== SeedBytes) throw new Error('invalid seed length');
|
|
831
|
+
|
|
452
832
|
let b;
|
|
453
833
|
let pos;
|
|
454
834
|
const c = cP;
|
|
@@ -456,7 +836,7 @@ function polyChallenge(cP, seed) {
|
|
|
456
836
|
|
|
457
837
|
const state = new KeccakState();
|
|
458
838
|
shake256Init(state);
|
|
459
|
-
shake256Absorb(state, seed
|
|
839
|
+
shake256Absorb(state, seed);
|
|
460
840
|
shake256Finalize(state);
|
|
461
841
|
shake256SqueezeBlocks(buf, 0, 1, state);
|
|
462
842
|
|
|
@@ -869,6 +1249,9 @@ function packPk(pkp, rho, t1) {
|
|
|
869
1249
|
}
|
|
870
1250
|
|
|
871
1251
|
function unpackPk(rhop, t1, pk) {
|
|
1252
|
+
if (!(pk instanceof Uint8Array) || pk.length !== CryptoPublicKeyBytes) {
|
|
1253
|
+
throw new Error(`pk must be a Uint8Array of ${CryptoPublicKeyBytes} bytes`);
|
|
1254
|
+
}
|
|
872
1255
|
const rho = rhop;
|
|
873
1256
|
for (let i = 0; i < SeedBytes; ++i) {
|
|
874
1257
|
rho[i] = pk[i];
|
|
@@ -913,6 +1296,9 @@ function packSk(skp, rho, tr, key, t0, s1, s2) {
|
|
|
913
1296
|
}
|
|
914
1297
|
|
|
915
1298
|
function unpackSk(rhoP, trP, keyP, t0, s1, s2, sk) {
|
|
1299
|
+
if (!(sk instanceof Uint8Array) || sk.length !== CryptoSecretKeyBytes) {
|
|
1300
|
+
throw new Error(`sk must be a Uint8Array of ${CryptoSecretKeyBytes} bytes`);
|
|
1301
|
+
}
|
|
916
1302
|
let skOffset = 0;
|
|
917
1303
|
const rho = rhoP;
|
|
918
1304
|
const tr = trP;
|
|
@@ -976,7 +1362,12 @@ function packSig(sigP, c, z, h) {
|
|
|
976
1362
|
}
|
|
977
1363
|
}
|
|
978
1364
|
|
|
1365
|
+
// Returns 0 on success, 1 on failure. On failure, output buffers (c, z, h)
|
|
1366
|
+
// may contain partial data and must not be used.
|
|
979
1367
|
function unpackSig(cP, z, hP, sig) {
|
|
1368
|
+
if (!(sig instanceof Uint8Array) || sig.length !== CryptoBytes) {
|
|
1369
|
+
throw new Error(`sig must be a Uint8Array of ${CryptoBytes} bytes`);
|
|
1370
|
+
}
|
|
980
1371
|
let sigOffset = 0;
|
|
981
1372
|
const c = cP;
|
|
982
1373
|
const h = hP;
|
|
@@ -1076,6 +1467,8 @@ function randomBytes(size) {
|
|
|
1076
1467
|
*
|
|
1077
1468
|
* @param {Uint8Array} buffer - The buffer to zero
|
|
1078
1469
|
* @returns {void}
|
|
1470
|
+
* @throws {TypeError} If buffer is not a Uint8Array
|
|
1471
|
+
* @throws {Error} If zeroization verification fails
|
|
1079
1472
|
*/
|
|
1080
1473
|
function zeroize(buffer) {
|
|
1081
1474
|
if (!(buffer instanceof Uint8Array)) {
|
|
@@ -1098,6 +1491,7 @@ function zeroize(buffer) {
|
|
|
1098
1491
|
*
|
|
1099
1492
|
* @param {Uint8Array} buffer - The buffer to check
|
|
1100
1493
|
* @returns {boolean} True if all bytes are zero
|
|
1494
|
+
* @throws {TypeError} If buffer is not a Uint8Array
|
|
1101
1495
|
*/
|
|
1102
1496
|
function isZero(buffer) {
|
|
1103
1497
|
if (!(buffer instanceof Uint8Array)) {
|
|
@@ -1113,16 +1507,12 @@ function isZero(buffer) {
|
|
|
1113
1507
|
/**
|
|
1114
1508
|
* Convert hex string to Uint8Array with strict validation.
|
|
1115
1509
|
*
|
|
1116
|
-
*
|
|
1117
|
-
*
|
|
1118
|
-
* mask input errors. Applications requiring strict format validation should
|
|
1119
|
-
* validate hex format before calling cryptographic functions, e.g.:
|
|
1120
|
-
* - Reject strings with 0x prefix if raw hex is expected
|
|
1121
|
-
* - Reject strings with whitespace
|
|
1122
|
-
* - Enforce consistent casing (lowercase/uppercase)
|
|
1510
|
+
* Accepts an optional 0x/0X prefix. Leading/trailing whitespace is rejected.
|
|
1511
|
+
* Empty strings and whitespace-only strings are rejected.
|
|
1123
1512
|
*
|
|
1124
|
-
* @param {string} hex - Hex string (optional 0x prefix, even length).
|
|
1513
|
+
* @param {string} hex - Hex string (optional 0x prefix, even length, no whitespace).
|
|
1125
1514
|
* @returns {Uint8Array} Decoded bytes.
|
|
1515
|
+
* @throws {Error} If input is not a valid hex string
|
|
1126
1516
|
* @private
|
|
1127
1517
|
*/
|
|
1128
1518
|
function hexToBytes(hex) {
|
|
@@ -1131,20 +1521,33 @@ function hexToBytes(hex) {
|
|
|
1131
1521
|
throw new Error('message must be a hex string');
|
|
1132
1522
|
}
|
|
1133
1523
|
/* c8 ignore stop */
|
|
1134
|
-
|
|
1135
|
-
|
|
1524
|
+
if (hex !== hex.trim()) {
|
|
1525
|
+
throw new Error('hex string must not have leading or trailing whitespace');
|
|
1526
|
+
}
|
|
1527
|
+
let clean = hex;
|
|
1136
1528
|
if (clean.startsWith('0x') || clean.startsWith('0X')) {
|
|
1137
1529
|
clean = clean.slice(2);
|
|
1138
1530
|
}
|
|
1531
|
+
if (clean.length === 0) {
|
|
1532
|
+
throw new Error('hex string must not be empty');
|
|
1533
|
+
}
|
|
1139
1534
|
if (clean.length % 2 !== 0) {
|
|
1140
1535
|
throw new Error('hex string must have an even length');
|
|
1141
1536
|
}
|
|
1142
1537
|
if (!/^[0-9a-fA-F]*$/.test(clean)) {
|
|
1143
1538
|
throw new Error('hex string contains non-hex characters');
|
|
1144
1539
|
}
|
|
1145
|
-
return
|
|
1540
|
+
return hexToBytes$1(clean);
|
|
1146
1541
|
}
|
|
1147
1542
|
|
|
1543
|
+
/**
|
|
1544
|
+
* Convert a message to Uint8Array.
|
|
1545
|
+
*
|
|
1546
|
+
* @param {string|Uint8Array} message - Message as hex string (optional 0x prefix) or Uint8Array.
|
|
1547
|
+
* @returns {Uint8Array} Message bytes.
|
|
1548
|
+
* @throws {Error} If message is not a Uint8Array or valid hex string
|
|
1549
|
+
* @private
|
|
1550
|
+
*/
|
|
1148
1551
|
function messageToBytes(message) {
|
|
1149
1552
|
if (typeof message === 'string') {
|
|
1150
1553
|
return hexToBytes(message);
|
|
@@ -1158,8 +1561,8 @@ function messageToBytes(message) {
|
|
|
1158
1561
|
/**
|
|
1159
1562
|
* Generate a Dilithium-5 key pair.
|
|
1160
1563
|
*
|
|
1161
|
-
* @param {Uint8Array|null} passedSeed - Optional 32-byte seed for deterministic key generation.
|
|
1162
|
-
* Pass null for random key generation.
|
|
1564
|
+
* @param {Uint8Array|null} [passedSeed=null] - Optional 32-byte seed for deterministic key generation.
|
|
1565
|
+
* Pass null or undefined for random key generation.
|
|
1163
1566
|
* @param {Uint8Array} pk - Output buffer for public key (must be CryptoPublicKeyBytes = 2592 bytes)
|
|
1164
1567
|
* @param {Uint8Array} sk - Output buffer for secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1165
1568
|
* @returns {Uint8Array} The seed used for key generation (useful when passedSeed is null)
|
|
@@ -1180,9 +1583,9 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1180
1583
|
}
|
|
1181
1584
|
} catch (e) {
|
|
1182
1585
|
if (e instanceof TypeError) {
|
|
1183
|
-
throw new Error(`pk/sk cannot be null
|
|
1586
|
+
throw new Error(`pk/sk cannot be null`, { cause: e });
|
|
1184
1587
|
} else {
|
|
1185
|
-
throw new Error(`${e.message}
|
|
1588
|
+
throw new Error(`${e.message}`, { cause: e });
|
|
1186
1589
|
}
|
|
1187
1590
|
}
|
|
1188
1591
|
|
|
@@ -1203,7 +1606,7 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1203
1606
|
const seed = passedSeed || randomBytes(SeedBytes);
|
|
1204
1607
|
|
|
1205
1608
|
const outputLength = 2 * SeedBytes + CRHBytes;
|
|
1206
|
-
const seedBuf =
|
|
1609
|
+
const seedBuf = shake256.create({}).update(seed).xof(outputLength);
|
|
1207
1610
|
const rho = seedBuf.slice(0, SeedBytes);
|
|
1208
1611
|
const rhoPrime = seedBuf.slice(SeedBytes, SeedBytes + CRHBytes);
|
|
1209
1612
|
const key = seedBuf.slice(SeedBytes + CRHBytes);
|
|
@@ -1234,7 +1637,7 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1234
1637
|
packPk(pk, rho, t1);
|
|
1235
1638
|
|
|
1236
1639
|
// Compute H(rho, t1) and write secret key
|
|
1237
|
-
const tr =
|
|
1640
|
+
const tr = shake256.create({}).update(pk).xof(TRBytes);
|
|
1238
1641
|
packSk(sk, rho, tr, key, t0, s1, s2);
|
|
1239
1642
|
|
|
1240
1643
|
return seed;
|
|
@@ -1260,15 +1663,25 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1260
1663
|
* @param {boolean} randomizedSigning - If true, use random nonce for hedged signing.
|
|
1261
1664
|
* If false, use deterministic nonce derived from message and key.
|
|
1262
1665
|
* @returns {number} 0 on success
|
|
1263
|
-
* @throws {
|
|
1666
|
+
* @throws {TypeError} If sig is not a Uint8Array or is smaller than CryptoBytes
|
|
1667
|
+
* @throws {TypeError} If sk is not a Uint8Array
|
|
1668
|
+
* @throws {TypeError} If randomizedSigning is not a boolean
|
|
1669
|
+
* @throws {Error} If sk length does not equal CryptoSecretKeyBytes
|
|
1670
|
+
* @throws {Error} If message is not a Uint8Array or valid hex string
|
|
1264
1671
|
*
|
|
1265
1672
|
* @example
|
|
1266
1673
|
* const sig = new Uint8Array(CryptoBytes);
|
|
1267
1674
|
* cryptoSignSignature(sig, message, sk, false);
|
|
1268
1675
|
*/
|
|
1269
1676
|
function cryptoSignSignature(sig, m, sk, randomizedSigning) {
|
|
1270
|
-
if (!sig || sig.length < CryptoBytes) {
|
|
1271
|
-
throw new
|
|
1677
|
+
if (!(sig instanceof Uint8Array) || sig.length < CryptoBytes) {
|
|
1678
|
+
throw new TypeError(`sig must be at least ${CryptoBytes} bytes and a Uint8Array`);
|
|
1679
|
+
}
|
|
1680
|
+
if (!(sk instanceof Uint8Array)) {
|
|
1681
|
+
throw new TypeError('sk must be a Uint8Array');
|
|
1682
|
+
}
|
|
1683
|
+
if (typeof randomizedSigning !== 'boolean') {
|
|
1684
|
+
throw new TypeError('randomizedSigning must be a boolean');
|
|
1272
1685
|
}
|
|
1273
1686
|
if (sk.length !== CryptoSecretKeyBytes) {
|
|
1274
1687
|
throw new Error(`invalid sk length ${sk.length} | Expected length ${CryptoSecretKeyBytes}`);
|
|
@@ -1297,12 +1710,12 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
|
|
|
1297
1710
|
try {
|
|
1298
1711
|
unpackSk(rho, tr, key, t0, s1, s2, sk);
|
|
1299
1712
|
|
|
1300
|
-
const mu =
|
|
1713
|
+
const mu = shake256.create({}).update(tr).update(mBytes).xof(CRHBytes);
|
|
1301
1714
|
|
|
1302
1715
|
if (randomizedSigning) {
|
|
1303
1716
|
rhoPrime = new Uint8Array(randomBytes(CRHBytes));
|
|
1304
1717
|
} else {
|
|
1305
|
-
rhoPrime =
|
|
1718
|
+
rhoPrime = shake256.create({}).update(key).update(mu).xof(CRHBytes);
|
|
1306
1719
|
}
|
|
1307
1720
|
|
|
1308
1721
|
polyVecMatrixExpand(mat, rho);
|
|
@@ -1324,14 +1737,14 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
|
|
|
1324
1737
|
polyVecKDecompose(w1, w0, w1);
|
|
1325
1738
|
polyVecKPackW1(sig, w1);
|
|
1326
1739
|
|
|
1327
|
-
const cHash =
|
|
1740
|
+
const cHash = shake256
|
|
1328
1741
|
.create({})
|
|
1329
1742
|
.update(mu)
|
|
1330
1743
|
.update(sig.slice(0, K * PolyW1PackedBytes))
|
|
1331
1744
|
.xof(SeedBytes);
|
|
1332
1745
|
sig.set(cHash);
|
|
1333
1746
|
|
|
1334
|
-
polyChallenge(cp, sig);
|
|
1747
|
+
polyChallenge(cp, sig.slice(0, SeedBytes));
|
|
1335
1748
|
polyNTT(cp);
|
|
1336
1749
|
|
|
1337
1750
|
// Compute z, reject if it reveals secret
|
|
@@ -1391,7 +1804,8 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
|
|
|
1391
1804
|
* @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1392
1805
|
* @param {boolean} randomizedSigning - If true, use random nonce; if false, deterministic
|
|
1393
1806
|
* @returns {Uint8Array} Signed message (CryptoBytes + msg.length bytes)
|
|
1394
|
-
* @throws {
|
|
1807
|
+
* @throws {TypeError} If sk or randomizedSigning fail type validation (see cryptoSignSignature)
|
|
1808
|
+
* @throws {Error} If signing fails or message/sk are invalid
|
|
1395
1809
|
*
|
|
1396
1810
|
* @example
|
|
1397
1811
|
* const signedMsg = cryptoSign(message, sk, false);
|
|
@@ -1445,10 +1859,10 @@ function cryptoSignVerify(sig, m, pk) {
|
|
|
1445
1859
|
const w1 = new PolyVecK();
|
|
1446
1860
|
const h = new PolyVecK();
|
|
1447
1861
|
|
|
1448
|
-
if (sig.length !== CryptoBytes) {
|
|
1862
|
+
if (!(sig instanceof Uint8Array) || sig.length !== CryptoBytes) {
|
|
1449
1863
|
return false;
|
|
1450
1864
|
}
|
|
1451
|
-
if (pk.length !== CryptoPublicKeyBytes) {
|
|
1865
|
+
if (!(pk instanceof Uint8Array) || pk.length !== CryptoPublicKeyBytes) {
|
|
1452
1866
|
return false;
|
|
1453
1867
|
}
|
|
1454
1868
|
|
|
@@ -1461,7 +1875,7 @@ function cryptoSignVerify(sig, m, pk) {
|
|
|
1461
1875
|
}
|
|
1462
1876
|
|
|
1463
1877
|
/* Compute CRH(H(rho, t1), msg) */
|
|
1464
|
-
const tr =
|
|
1878
|
+
const tr = shake256.create({}).update(pk).xof(TRBytes);
|
|
1465
1879
|
mu.set(tr);
|
|
1466
1880
|
|
|
1467
1881
|
let mBytes;
|
|
@@ -1470,7 +1884,7 @@ function cryptoSignVerify(sig, m, pk) {
|
|
|
1470
1884
|
} catch {
|
|
1471
1885
|
return false;
|
|
1472
1886
|
}
|
|
1473
|
-
const muFull =
|
|
1887
|
+
const muFull = shake256.create({}).update(mu.slice(0, TRBytes)).update(mBytes).xof(CRHBytes);
|
|
1474
1888
|
mu.set(muFull);
|
|
1475
1889
|
|
|
1476
1890
|
/* Matrix-vector multiplication; compute Az - c2^dt1 */
|
|
@@ -1495,7 +1909,7 @@ function cryptoSignVerify(sig, m, pk) {
|
|
|
1495
1909
|
polyVecKPackW1(buf, w1);
|
|
1496
1910
|
|
|
1497
1911
|
/* Call random oracle and verify challenge */
|
|
1498
|
-
const c2Hash =
|
|
1912
|
+
const c2Hash = shake256.create({}).update(mu).update(buf).xof(SeedBytes);
|
|
1499
1913
|
c2.set(c2Hash);
|
|
1500
1914
|
|
|
1501
1915
|
// Constant-time comparison to prevent timing attacks
|
package/dist/mjs/dilithium5.js
CHANGED
|
@@ -158,12 +158,16 @@ function montgomeryReduce(a) {
|
|
|
158
158
|
return t;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
// Partial reduction modulo Q. Input must satisfy |a| < 2^31 - 2^22.
|
|
162
|
+
// Output is in (-Q, Q). Mirrors the reference C implementation.
|
|
161
163
|
function reduce32(a) {
|
|
162
164
|
let t = (a + (1 << 22)) >> 23;
|
|
163
165
|
t = a - t * Q;
|
|
164
166
|
return t;
|
|
165
167
|
}
|
|
166
168
|
|
|
169
|
+
// Conditional add Q: if a is negative, add Q. Input must satisfy -Q < a < 2^31.
|
|
170
|
+
// Output is in [0, Q). Mirrors the reference C implementation.
|
|
167
171
|
function cAddQ(a) {
|
|
168
172
|
let ar = a;
|
|
169
173
|
ar += (ar >> 31) & Q;
|
|
@@ -172,7 +176,7 @@ function cAddQ(a) {
|
|
|
172
176
|
|
|
173
177
|
function ntt(a) {
|
|
174
178
|
let k = 0;
|
|
175
|
-
let j
|
|
179
|
+
let j;
|
|
176
180
|
|
|
177
181
|
for (let len = 128; len > 0; len >>= 1) {
|
|
178
182
|
for (let start = 0; start < N; start = j + len) {
|
|
@@ -188,7 +192,7 @@ function ntt(a) {
|
|
|
188
192
|
|
|
189
193
|
function invNTTToMont(a) {
|
|
190
194
|
const f = 41978n; // mont^2/256
|
|
191
|
-
let j
|
|
195
|
+
let j;
|
|
192
196
|
let k = 256;
|
|
193
197
|
|
|
194
198
|
for (let len = 1; len < N; len <<= 1) {
|
|
@@ -325,10 +329,7 @@ function polyChkNorm(a, b) {
|
|
|
325
329
|
}
|
|
326
330
|
|
|
327
331
|
for (let i = 0; i < N; i++) {
|
|
328
|
-
|
|
329
|
-
t = a.coeffs[i] - (t & (2 * a.coeffs[i]));
|
|
330
|
-
|
|
331
|
-
if (t >= b) {
|
|
332
|
+
if (Math.abs(a.coeffs[i]) >= b) {
|
|
332
333
|
return 1;
|
|
333
334
|
}
|
|
334
335
|
}
|
|
@@ -447,6 +448,8 @@ function polyUniformGamma1(a, seed, nonce) {
|
|
|
447
448
|
}
|
|
448
449
|
|
|
449
450
|
function polyChallenge(cP, seed) {
|
|
451
|
+
if (seed.length !== SeedBytes) throw new Error('invalid seed length');
|
|
452
|
+
|
|
450
453
|
let b;
|
|
451
454
|
let pos;
|
|
452
455
|
const c = cP;
|
|
@@ -454,7 +457,7 @@ function polyChallenge(cP, seed) {
|
|
|
454
457
|
|
|
455
458
|
const state = new KeccakState();
|
|
456
459
|
shake256Init(state);
|
|
457
|
-
shake256Absorb(state, seed
|
|
460
|
+
shake256Absorb(state, seed);
|
|
458
461
|
shake256Finalize(state);
|
|
459
462
|
shake256SqueezeBlocks(buf, 0, 1, state);
|
|
460
463
|
|
|
@@ -867,6 +870,9 @@ function packPk(pkp, rho, t1) {
|
|
|
867
870
|
}
|
|
868
871
|
|
|
869
872
|
function unpackPk(rhop, t1, pk) {
|
|
873
|
+
if (!(pk instanceof Uint8Array) || pk.length !== CryptoPublicKeyBytes) {
|
|
874
|
+
throw new Error(`pk must be a Uint8Array of ${CryptoPublicKeyBytes} bytes`);
|
|
875
|
+
}
|
|
870
876
|
const rho = rhop;
|
|
871
877
|
for (let i = 0; i < SeedBytes; ++i) {
|
|
872
878
|
rho[i] = pk[i];
|
|
@@ -911,6 +917,9 @@ function packSk(skp, rho, tr, key, t0, s1, s2) {
|
|
|
911
917
|
}
|
|
912
918
|
|
|
913
919
|
function unpackSk(rhoP, trP, keyP, t0, s1, s2, sk) {
|
|
920
|
+
if (!(sk instanceof Uint8Array) || sk.length !== CryptoSecretKeyBytes) {
|
|
921
|
+
throw new Error(`sk must be a Uint8Array of ${CryptoSecretKeyBytes} bytes`);
|
|
922
|
+
}
|
|
914
923
|
let skOffset = 0;
|
|
915
924
|
const rho = rhoP;
|
|
916
925
|
const tr = trP;
|
|
@@ -974,7 +983,12 @@ function packSig(sigP, c, z, h) {
|
|
|
974
983
|
}
|
|
975
984
|
}
|
|
976
985
|
|
|
986
|
+
// Returns 0 on success, 1 on failure. On failure, output buffers (c, z, h)
|
|
987
|
+
// may contain partial data and must not be used.
|
|
977
988
|
function unpackSig(cP, z, hP, sig) {
|
|
989
|
+
if (!(sig instanceof Uint8Array) || sig.length !== CryptoBytes) {
|
|
990
|
+
throw new Error(`sig must be a Uint8Array of ${CryptoBytes} bytes`);
|
|
991
|
+
}
|
|
978
992
|
let sigOffset = 0;
|
|
979
993
|
const c = cP;
|
|
980
994
|
const h = hP;
|
|
@@ -1074,6 +1088,8 @@ function randomBytes(size) {
|
|
|
1074
1088
|
*
|
|
1075
1089
|
* @param {Uint8Array} buffer - The buffer to zero
|
|
1076
1090
|
* @returns {void}
|
|
1091
|
+
* @throws {TypeError} If buffer is not a Uint8Array
|
|
1092
|
+
* @throws {Error} If zeroization verification fails
|
|
1077
1093
|
*/
|
|
1078
1094
|
function zeroize(buffer) {
|
|
1079
1095
|
if (!(buffer instanceof Uint8Array)) {
|
|
@@ -1096,6 +1112,7 @@ function zeroize(buffer) {
|
|
|
1096
1112
|
*
|
|
1097
1113
|
* @param {Uint8Array} buffer - The buffer to check
|
|
1098
1114
|
* @returns {boolean} True if all bytes are zero
|
|
1115
|
+
* @throws {TypeError} If buffer is not a Uint8Array
|
|
1099
1116
|
*/
|
|
1100
1117
|
function isZero(buffer) {
|
|
1101
1118
|
if (!(buffer instanceof Uint8Array)) {
|
|
@@ -1111,16 +1128,12 @@ function isZero(buffer) {
|
|
|
1111
1128
|
/**
|
|
1112
1129
|
* Convert hex string to Uint8Array with strict validation.
|
|
1113
1130
|
*
|
|
1114
|
-
*
|
|
1115
|
-
*
|
|
1116
|
-
* mask input errors. Applications requiring strict format validation should
|
|
1117
|
-
* validate hex format before calling cryptographic functions, e.g.:
|
|
1118
|
-
* - Reject strings with 0x prefix if raw hex is expected
|
|
1119
|
-
* - Reject strings with whitespace
|
|
1120
|
-
* - Enforce consistent casing (lowercase/uppercase)
|
|
1131
|
+
* Accepts an optional 0x/0X prefix. Leading/trailing whitespace is rejected.
|
|
1132
|
+
* Empty strings and whitespace-only strings are rejected.
|
|
1121
1133
|
*
|
|
1122
|
-
* @param {string} hex - Hex string (optional 0x prefix, even length).
|
|
1134
|
+
* @param {string} hex - Hex string (optional 0x prefix, even length, no whitespace).
|
|
1123
1135
|
* @returns {Uint8Array} Decoded bytes.
|
|
1136
|
+
* @throws {Error} If input is not a valid hex string
|
|
1124
1137
|
* @private
|
|
1125
1138
|
*/
|
|
1126
1139
|
function hexToBytes(hex) {
|
|
@@ -1129,11 +1142,16 @@ function hexToBytes(hex) {
|
|
|
1129
1142
|
throw new Error('message must be a hex string');
|
|
1130
1143
|
}
|
|
1131
1144
|
/* c8 ignore stop */
|
|
1132
|
-
|
|
1133
|
-
|
|
1145
|
+
if (hex !== hex.trim()) {
|
|
1146
|
+
throw new Error('hex string must not have leading or trailing whitespace');
|
|
1147
|
+
}
|
|
1148
|
+
let clean = hex;
|
|
1134
1149
|
if (clean.startsWith('0x') || clean.startsWith('0X')) {
|
|
1135
1150
|
clean = clean.slice(2);
|
|
1136
1151
|
}
|
|
1152
|
+
if (clean.length === 0) {
|
|
1153
|
+
throw new Error('hex string must not be empty');
|
|
1154
|
+
}
|
|
1137
1155
|
if (clean.length % 2 !== 0) {
|
|
1138
1156
|
throw new Error('hex string must have an even length');
|
|
1139
1157
|
}
|
|
@@ -1143,6 +1161,14 @@ function hexToBytes(hex) {
|
|
|
1143
1161
|
return hexToBytes$1(clean);
|
|
1144
1162
|
}
|
|
1145
1163
|
|
|
1164
|
+
/**
|
|
1165
|
+
* Convert a message to Uint8Array.
|
|
1166
|
+
*
|
|
1167
|
+
* @param {string|Uint8Array} message - Message as hex string (optional 0x prefix) or Uint8Array.
|
|
1168
|
+
* @returns {Uint8Array} Message bytes.
|
|
1169
|
+
* @throws {Error} If message is not a Uint8Array or valid hex string
|
|
1170
|
+
* @private
|
|
1171
|
+
*/
|
|
1146
1172
|
function messageToBytes(message) {
|
|
1147
1173
|
if (typeof message === 'string') {
|
|
1148
1174
|
return hexToBytes(message);
|
|
@@ -1156,8 +1182,8 @@ function messageToBytes(message) {
|
|
|
1156
1182
|
/**
|
|
1157
1183
|
* Generate a Dilithium-5 key pair.
|
|
1158
1184
|
*
|
|
1159
|
-
* @param {Uint8Array|null} passedSeed - Optional 32-byte seed for deterministic key generation.
|
|
1160
|
-
* Pass null for random key generation.
|
|
1185
|
+
* @param {Uint8Array|null} [passedSeed=null] - Optional 32-byte seed for deterministic key generation.
|
|
1186
|
+
* Pass null or undefined for random key generation.
|
|
1161
1187
|
* @param {Uint8Array} pk - Output buffer for public key (must be CryptoPublicKeyBytes = 2592 bytes)
|
|
1162
1188
|
* @param {Uint8Array} sk - Output buffer for secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1163
1189
|
* @returns {Uint8Array} The seed used for key generation (useful when passedSeed is null)
|
|
@@ -1178,9 +1204,9 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1178
1204
|
}
|
|
1179
1205
|
} catch (e) {
|
|
1180
1206
|
if (e instanceof TypeError) {
|
|
1181
|
-
throw new Error(`pk/sk cannot be null
|
|
1207
|
+
throw new Error(`pk/sk cannot be null`, { cause: e });
|
|
1182
1208
|
} else {
|
|
1183
|
-
throw new Error(`${e.message}
|
|
1209
|
+
throw new Error(`${e.message}`, { cause: e });
|
|
1184
1210
|
}
|
|
1185
1211
|
}
|
|
1186
1212
|
|
|
@@ -1258,15 +1284,25 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1258
1284
|
* @param {boolean} randomizedSigning - If true, use random nonce for hedged signing.
|
|
1259
1285
|
* If false, use deterministic nonce derived from message and key.
|
|
1260
1286
|
* @returns {number} 0 on success
|
|
1261
|
-
* @throws {
|
|
1287
|
+
* @throws {TypeError} If sig is not a Uint8Array or is smaller than CryptoBytes
|
|
1288
|
+
* @throws {TypeError} If sk is not a Uint8Array
|
|
1289
|
+
* @throws {TypeError} If randomizedSigning is not a boolean
|
|
1290
|
+
* @throws {Error} If sk length does not equal CryptoSecretKeyBytes
|
|
1291
|
+
* @throws {Error} If message is not a Uint8Array or valid hex string
|
|
1262
1292
|
*
|
|
1263
1293
|
* @example
|
|
1264
1294
|
* const sig = new Uint8Array(CryptoBytes);
|
|
1265
1295
|
* cryptoSignSignature(sig, message, sk, false);
|
|
1266
1296
|
*/
|
|
1267
1297
|
function cryptoSignSignature(sig, m, sk, randomizedSigning) {
|
|
1268
|
-
if (!sig || sig.length < CryptoBytes) {
|
|
1269
|
-
throw new
|
|
1298
|
+
if (!(sig instanceof Uint8Array) || sig.length < CryptoBytes) {
|
|
1299
|
+
throw new TypeError(`sig must be at least ${CryptoBytes} bytes and a Uint8Array`);
|
|
1300
|
+
}
|
|
1301
|
+
if (!(sk instanceof Uint8Array)) {
|
|
1302
|
+
throw new TypeError('sk must be a Uint8Array');
|
|
1303
|
+
}
|
|
1304
|
+
if (typeof randomizedSigning !== 'boolean') {
|
|
1305
|
+
throw new TypeError('randomizedSigning must be a boolean');
|
|
1270
1306
|
}
|
|
1271
1307
|
if (sk.length !== CryptoSecretKeyBytes) {
|
|
1272
1308
|
throw new Error(`invalid sk length ${sk.length} | Expected length ${CryptoSecretKeyBytes}`);
|
|
@@ -1329,7 +1365,7 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
|
|
|
1329
1365
|
.xof(SeedBytes);
|
|
1330
1366
|
sig.set(cHash);
|
|
1331
1367
|
|
|
1332
|
-
polyChallenge(cp, sig);
|
|
1368
|
+
polyChallenge(cp, sig.slice(0, SeedBytes));
|
|
1333
1369
|
polyNTT(cp);
|
|
1334
1370
|
|
|
1335
1371
|
// Compute z, reject if it reveals secret
|
|
@@ -1389,7 +1425,8 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
|
|
|
1389
1425
|
* @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1390
1426
|
* @param {boolean} randomizedSigning - If true, use random nonce; if false, deterministic
|
|
1391
1427
|
* @returns {Uint8Array} Signed message (CryptoBytes + msg.length bytes)
|
|
1392
|
-
* @throws {
|
|
1428
|
+
* @throws {TypeError} If sk or randomizedSigning fail type validation (see cryptoSignSignature)
|
|
1429
|
+
* @throws {Error} If signing fails or message/sk are invalid
|
|
1393
1430
|
*
|
|
1394
1431
|
* @example
|
|
1395
1432
|
* const signedMsg = cryptoSign(message, sk, false);
|
|
@@ -1443,10 +1480,10 @@ function cryptoSignVerify(sig, m, pk) {
|
|
|
1443
1480
|
const w1 = new PolyVecK();
|
|
1444
1481
|
const h = new PolyVecK();
|
|
1445
1482
|
|
|
1446
|
-
if (sig.length !== CryptoBytes) {
|
|
1483
|
+
if (!(sig instanceof Uint8Array) || sig.length !== CryptoBytes) {
|
|
1447
1484
|
return false;
|
|
1448
1485
|
}
|
|
1449
|
-
if (pk.length !== CryptoPublicKeyBytes) {
|
|
1486
|
+
if (!(pk instanceof Uint8Array) || pk.length !== CryptoPublicKeyBytes) {
|
|
1450
1487
|
return false;
|
|
1451
1488
|
}
|
|
1452
1489
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theqrl/dilithium5",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Dilithium-5 cryptography",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dilithium",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"scripts": {
|
|
34
34
|
"test": "../../node_modules/mocha/bin/mocha.js --require ../../scripts/node-test-setup.cjs --timeout 10000",
|
|
35
35
|
"test:browser": "playwright test",
|
|
36
|
-
"build": "rollup
|
|
36
|
+
"build": "rollup -c && ./fixup",
|
|
37
37
|
"lint-check": "eslint 'src/**/*.js' 'test/**/*.js'",
|
|
38
38
|
"lint": "eslint --fix 'src/**/*.js' 'test/**/*.js'",
|
|
39
39
|
"coverage": "c8 npm run test",
|
|
@@ -53,19 +53,39 @@
|
|
|
53
53
|
"node": ">=20.19.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@eslint/js": "
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"eslint
|
|
61
|
-
"eslint-
|
|
62
|
-
"eslint-plugin-
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
56
|
+
"@eslint/js": "10.0.1",
|
|
57
|
+
"@rollup/plugin-node-resolve": "16.0.3",
|
|
58
|
+
"c8": "11.0.0",
|
|
59
|
+
"chai": "6.2.2",
|
|
60
|
+
"eslint": "10.0.3",
|
|
61
|
+
"eslint-config-prettier": "10.1.8",
|
|
62
|
+
"eslint-plugin-import-x": "4.16.2",
|
|
63
|
+
"eslint-plugin-prettier": "5.5.5",
|
|
64
|
+
"globals": "17.4.0",
|
|
65
|
+
"minimatch": "10.2.4",
|
|
66
|
+
"mocha": "11.7.5",
|
|
67
|
+
"prettier": "3.8.1",
|
|
68
|
+
"rollup": "4.59.0",
|
|
69
|
+
"serialize-javascript": "7.0.4",
|
|
70
|
+
"tar": "7.5.11"
|
|
67
71
|
},
|
|
68
72
|
"dependencies": {
|
|
69
|
-
"@noble/hashes": "
|
|
73
|
+
"@noble/hashes": "2.0.1"
|
|
74
|
+
},
|
|
75
|
+
"overrides": {
|
|
76
|
+
"diff": "8.0.3",
|
|
77
|
+
"minimatch": "10.2.4"
|
|
78
|
+
},
|
|
79
|
+
"c8": {
|
|
80
|
+
"include": [
|
|
81
|
+
"src/**"
|
|
82
|
+
],
|
|
83
|
+
"exclude": [
|
|
84
|
+
"**/dist/**",
|
|
85
|
+
"**/test/**",
|
|
86
|
+
"**/browser-tests/**",
|
|
87
|
+
"**/*.d.ts"
|
|
88
|
+
],
|
|
89
|
+
"all": true
|
|
70
90
|
}
|
|
71
91
|
}
|