@theqrl/dilithium5 1.1.1 → 1.1.5

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 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 `null` for random
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:
@@ -137,6 +144,7 @@ See [SECURITY.md](../../SECURITY.md) for important information about:
137
144
 
138
145
  - JavaScript memory security limitations
139
146
  - Constant-time verification
147
+ - **Signing timing variability** — signing is not constant-time due to the algorithm's rejection sampling loop; see SECURITY.md for measured impact and deployment mitigations
140
148
  - Secure key handling recommendations
141
149
 
142
150
  ## Requirements
@@ -0,0 +1,309 @@
1
+ /**
2
+ * TypeScript definitions for @theqrl/dilithium5
3
+ * Dilithium-5 post-quantum digital signature scheme
4
+ */
5
+
6
+ // Constants
7
+ export const Shake128Rate: number;
8
+ export const Shake256Rate: number;
9
+ export const Stream128BlockBytes: number;
10
+ export const Stream256BlockBytes: number;
11
+ export const SeedBytes: number;
12
+ export const CRHBytes: number;
13
+ export const TRBytes: number;
14
+ export const N: number;
15
+ export const Q: number;
16
+ export const QInv: number;
17
+ export const D: number;
18
+ export const K: number;
19
+ export const L: number;
20
+ export const ETA: number;
21
+ export const TAU: number;
22
+ export const BETA: number;
23
+ export const GAMMA1: number;
24
+ export const GAMMA2: number;
25
+ export const OMEGA: number;
26
+ export const PolyT1PackedBytes: number;
27
+ export const PolyT0PackedBytes: number;
28
+ export const PolyETAPackedBytes: number;
29
+ export const PolyZPackedBytes: number;
30
+ export const PolyVecHPackedBytes: number;
31
+ export const PolyW1PackedBytes: number;
32
+ export const CryptoPublicKeyBytes: number;
33
+ export const CryptoSecretKeyBytes: number;
34
+ export const CryptoBytes: number;
35
+ export const PolyUniformNBlocks: number;
36
+ export const PolyUniformETANBlocks: number;
37
+ export const PolyUniformGamma1NBlocks: number;
38
+ export const zetas: readonly number[];
39
+
40
+ // Core signing functions
41
+
42
+ /**
43
+ * Generate a Dilithium-5 key pair
44
+ * @param seed - Optional 32-byte seed for deterministic key generation (null for random)
45
+ * @param pk - Output buffer for public key (must be CryptoPublicKeyBytes length)
46
+ * @param sk - Output buffer for secret key (must be CryptoSecretKeyBytes length)
47
+ * @returns The seed used for key generation
48
+ * @throws Error if pk/sk buffers are wrong size or null
49
+ */
50
+ export function cryptoSignKeypair(
51
+ seed: Uint8Array | null,
52
+ pk: Uint8Array,
53
+ sk: Uint8Array
54
+ ): Uint8Array;
55
+
56
+ /**
57
+ * Create a signature for a message
58
+ * @param sig - Output buffer for signature (must be CryptoBytes length minimum)
59
+ * @param m - Message to sign (hex string or Uint8Array; strings are parsed as hex only)
60
+ * @param sk - Secret key
61
+ * @param randomizedSigning - If true, use random nonce; if false, deterministic
62
+ * @returns 0 on success
63
+ * @throws Error if sk is wrong size
64
+ */
65
+ export function cryptoSignSignature(
66
+ sig: Uint8Array,
67
+ m: Uint8Array | string,
68
+ sk: Uint8Array,
69
+ randomizedSigning: boolean
70
+ ): number;
71
+
72
+ /**
73
+ * Sign a message, returning signature concatenated with message
74
+ * @param msg - Message to sign
75
+ * @param sk - Secret key
76
+ * @param randomizedSigning - If true, use random nonce; if false, deterministic
77
+ * @returns Signed message (signature || message)
78
+ * @throws Error if signing fails
79
+ */
80
+ export function cryptoSign(
81
+ msg: Uint8Array | string,
82
+ sk: Uint8Array,
83
+ randomizedSigning: boolean
84
+ ): Uint8Array;
85
+
86
+ /**
87
+ * Verify a signature
88
+ * @param sig - Signature to verify
89
+ * @param m - Message that was signed (hex string or Uint8Array; strings are parsed as hex only)
90
+ * @param pk - Public key
91
+ * @returns true if signature is valid, false otherwise
92
+ */
93
+ export function cryptoSignVerify(
94
+ sig: Uint8Array,
95
+ m: Uint8Array | string,
96
+ pk: Uint8Array
97
+ ): boolean;
98
+
99
+ /**
100
+ * Open a signed message (verify and extract message)
101
+ * @param sm - Signed message (signature || message)
102
+ * @param pk - Public key
103
+ * @returns Message if valid, undefined if verification fails
104
+ */
105
+ export function cryptoSignOpen(
106
+ sm: Uint8Array,
107
+ pk: Uint8Array
108
+ ): Uint8Array | undefined;
109
+
110
+ // Utility functions
111
+
112
+ /**
113
+ * Zero out a buffer (best-effort, see SECURITY.md for limitations)
114
+ * @param buffer - Buffer to zero
115
+ * @throws TypeError if buffer is not Uint8Array
116
+ */
117
+ export function zeroize(buffer: Uint8Array): void;
118
+
119
+ /**
120
+ * Check if buffer is all zeros using constant-time comparison
121
+ * @param buffer - Buffer to check
122
+ * @returns true if all bytes are zero
123
+ * @throws TypeError if buffer is not Uint8Array
124
+ */
125
+ export function isZero(buffer: Uint8Array): boolean;
126
+
127
+ // Internal classes (exported but primarily for internal use)
128
+
129
+ export class Poly {
130
+ coeffs: Int32Array;
131
+ constructor();
132
+ copy(poly: Poly): void;
133
+ }
134
+
135
+ export class PolyVecK {
136
+ vec: Poly[];
137
+ constructor();
138
+ }
139
+
140
+ export class PolyVecL {
141
+ vec: Poly[];
142
+ constructor();
143
+ copy(polyVecL: PolyVecL): void;
144
+ }
145
+
146
+ export class KeccakState {
147
+ constructor();
148
+ }
149
+
150
+ // Internal functions (exported but primarily for internal use)
151
+ export function polyNTT(a: Poly): void;
152
+ export function polyInvNTTToMont(a: Poly): void;
153
+ export function polyChallenge(c: Poly, seed: Uint8Array): void;
154
+ export function ntt(a: Int32Array): void;
155
+ export function invNTTToMont(a: Int32Array): void;
156
+ export function montgomeryReduce(a: bigint): bigint;
157
+ export function reduce32(a: number): number;
158
+ export function cAddQ(a: number): number;
159
+ export function decompose(a0: Int32Array, i: number, a: number): number;
160
+ export function power2round(a0: Int32Array, i: number, a: number): number;
161
+ export function makeHint(a0: number, a1: number): number;
162
+ export function useHint(a: number, hint: number): number;
163
+ export function packPk(pk: Uint8Array, rho: Uint8Array, t1: PolyVecK): void;
164
+ export function packSk(
165
+ sk: Uint8Array,
166
+ rho: Uint8Array,
167
+ tr: Uint8Array,
168
+ key: Uint8Array,
169
+ t0: PolyVecK,
170
+ s1: PolyVecL,
171
+ s2: PolyVecK
172
+ ): void;
173
+ export function packSig(
174
+ sig: Uint8Array,
175
+ c: Uint8Array,
176
+ z: PolyVecL,
177
+ h: PolyVecK
178
+ ): void;
179
+ export function unpackPk(rho: Uint8Array, t1: PolyVecK, pk: Uint8Array): void;
180
+ export function unpackSk(
181
+ rho: Uint8Array,
182
+ tr: Uint8Array,
183
+ key: Uint8Array,
184
+ t0: PolyVecK,
185
+ s1: PolyVecL,
186
+ s2: PolyVecK,
187
+ sk: Uint8Array
188
+ ): void;
189
+ export function unpackSig(
190
+ c: Uint8Array,
191
+ z: PolyVecL,
192
+ h: PolyVecK,
193
+ sig: Uint8Array
194
+ ): number;
195
+
196
+ // FIPS 202 SHAKE primitives (low-level XOF interface, primarily internal)
197
+ export function shake128Init(state: KeccakState): void;
198
+ export function shake128Absorb(state: KeccakState, input: Uint8Array): void;
199
+ export function shake128Finalize(state: KeccakState): void;
200
+ export function shake128SqueezeBlocks(
201
+ out: Uint8Array,
202
+ outputOffset: number,
203
+ nBlocks: number,
204
+ state: KeccakState
205
+ ): void;
206
+ export function shake256Init(state: KeccakState): void;
207
+ export function shake256Absorb(state: KeccakState, input: Uint8Array): void;
208
+ export function shake256Finalize(state: KeccakState): void;
209
+ export function shake256SqueezeBlocks(
210
+ out: Uint8Array,
211
+ outputOffset: number,
212
+ nBlocks: number,
213
+ state: KeccakState
214
+ ): void;
215
+
216
+ // Dilithium-specific stream initializers
217
+ export function dilithiumShake128StreamInit(
218
+ state: KeccakState,
219
+ seed: Uint8Array,
220
+ nonce: number
221
+ ): void;
222
+ export function dilithiumShake256StreamInit(
223
+ state: KeccakState,
224
+ seed: Uint8Array,
225
+ nonce: number
226
+ ): void;
227
+
228
+ // Polynomial operations (internal)
229
+ export function polyReduce(a: Poly): void;
230
+ export function polyCAddQ(a: Poly): void;
231
+ export function polyAdd(c: Poly, a: Poly, b: Poly): void;
232
+ export function polySub(c: Poly, a: Poly, b: Poly): void;
233
+ export function polyShiftL(a: Poly): void;
234
+ export function polyPointWiseMontgomery(c: Poly, a: Poly, b: Poly): void;
235
+ export function polyPower2round(a1: Poly, a0: Poly, a: Poly): void;
236
+ export function polyDecompose(a1: Poly, a0: Poly, a: Poly): void;
237
+ export function polyMakeHint(h: Poly, a0: Poly, a1: Poly): number;
238
+ export function polyUseHint(b: Poly, a: Poly, h: Poly): void;
239
+ export function polyChkNorm(a: Poly, b: number): number;
240
+ export function rejUniform(
241
+ a: Int32Array,
242
+ aOffset: number,
243
+ len: number,
244
+ buf: Uint8Array,
245
+ bufLen: number
246
+ ): number;
247
+ export function polyUniform(a: Poly, seed: Uint8Array, nonce: number): void;
248
+ export function rejEta(
249
+ a: Int32Array,
250
+ aOffset: number,
251
+ len: number,
252
+ buf: Uint8Array,
253
+ bufLen: number
254
+ ): number;
255
+ export function polyUniformEta(a: Poly, seed: Uint8Array, nonce: number): void;
256
+ export function polyZUnpack(r: Poly, a: Uint8Array, aOffset: number): void;
257
+ export function polyUniformGamma1(a: Poly, seed: Uint8Array, nonce: number): void;
258
+ export function polyEtaPack(r: Uint8Array, rOffset: number, a: Poly): void;
259
+ export function polyEtaUnpack(r: Poly, a: Uint8Array, aOffset: number): void;
260
+ export function polyT1Pack(r: Uint8Array, rOffset: number, a: Poly): void;
261
+ export function polyT1Unpack(r: Poly, a: Uint8Array, aOffset: number): void;
262
+ export function polyT0Pack(r: Uint8Array, rOffset: number, a: Poly): void;
263
+ export function polyT0Unpack(r: Poly, a: Uint8Array, aOffset: number): void;
264
+ export function polyZPack(r: Uint8Array, rOffset: number, a: Poly): void;
265
+ export function polyW1Pack(r: Uint8Array, rOffset: number, a: Poly): void;
266
+
267
+ // Polynomial vector operations (internal)
268
+ export function polyVecMatrixExpand(mat: PolyVecL[], rho: Uint8Array): void;
269
+ export function polyVecMatrixPointWiseMontgomery(
270
+ t: PolyVecK,
271
+ mat: PolyVecL[],
272
+ v: PolyVecL
273
+ ): void;
274
+ export function polyVecLUniformEta(v: PolyVecL, seed: Uint8Array, nonce: number): void;
275
+ export function polyVecLUniformGamma1(v: PolyVecL, seed: Uint8Array, nonce: number): void;
276
+ export function polyVecLReduce(v: PolyVecL): void;
277
+ export function polyVecLAdd(w: PolyVecL, u: PolyVecL, v: PolyVecL): void;
278
+ export function polyVecLNTT(v: PolyVecL): void;
279
+ export function polyVecLInvNTTToMont(v: PolyVecL): void;
280
+ export function polyVecLPointWisePolyMontgomery(
281
+ r: PolyVecL,
282
+ a: Poly,
283
+ v: PolyVecL
284
+ ): void;
285
+ export function polyVecLPointWiseAccMontgomery(
286
+ w: Poly,
287
+ u: PolyVecL,
288
+ v: PolyVecL
289
+ ): void;
290
+ export function polyVecLChkNorm(v: PolyVecL, bound: number): number;
291
+ export function polyVecKUniformEta(v: PolyVecK, seed: Uint8Array, nonce: number): void;
292
+ export function polyVecKReduce(v: PolyVecK): void;
293
+ export function polyVecKCAddQ(v: PolyVecK): void;
294
+ export function polyVecKAdd(w: PolyVecK, u: PolyVecK, v: PolyVecK): void;
295
+ export function polyVecKSub(w: PolyVecK, u: PolyVecK, v: PolyVecK): void;
296
+ export function polyVecKShiftL(v: PolyVecK): void;
297
+ export function polyVecKNTT(v: PolyVecK): void;
298
+ export function polyVecKInvNTTToMont(v: PolyVecK): void;
299
+ export function polyVecKPointWisePolyMontgomery(
300
+ r: PolyVecK,
301
+ a: Poly,
302
+ v: PolyVecK
303
+ ): void;
304
+ export function polyVecKChkNorm(v: PolyVecK, bound: number): number;
305
+ export function polyVecKPower2round(v1: PolyVecK, v0: PolyVecK, v: PolyVecK): void;
306
+ export function polyVecKDecompose(v1: PolyVecK, v0: PolyVecK, v: PolyVecK): void;
307
+ export function polyVecKMakeHint(h: PolyVecK, v0: PolyVecK, v1: PolyVecK): number;
308
+ export function polyVecKUseHint(w: PolyVecK, u: PolyVecK, h: PolyVecK): void;
309
+ export function polyVecKPackW1(r: Uint8Array, w1: PolyVecK): void;
@@ -537,12 +537,16 @@ function montgomeryReduce(a) {
537
537
  return t;
538
538
  }
539
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.
540
542
  function reduce32(a) {
541
543
  let t = (a + (1 << 22)) >> 23;
542
544
  t = a - t * Q;
543
545
  return t;
544
546
  }
545
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.
546
550
  function cAddQ(a) {
547
551
  let ar = a;
548
552
  ar += (ar >> 31) & Q;
@@ -551,7 +555,7 @@ function cAddQ(a) {
551
555
 
552
556
  function ntt(a) {
553
557
  let k = 0;
554
- let j = 0;
558
+ let j;
555
559
 
556
560
  for (let len = 128; len > 0; len >>= 1) {
557
561
  for (let start = 0; start < N; start = j + len) {
@@ -567,7 +571,7 @@ function ntt(a) {
567
571
 
568
572
  function invNTTToMont(a) {
569
573
  const f = 41978n; // mont^2/256
570
- let j = 0;
574
+ let j;
571
575
  let k = 256;
572
576
 
573
577
  for (let len = 1; len < N; len <<= 1) {
@@ -704,10 +708,7 @@ function polyChkNorm(a, b) {
704
708
  }
705
709
 
706
710
  for (let i = 0; i < N; i++) {
707
- let t = a.coeffs[i] >> 31;
708
- t = a.coeffs[i] - (t & (2 * a.coeffs[i]));
709
-
710
- if (t >= b) {
711
+ if (Math.abs(a.coeffs[i]) >= b) {
711
712
  return 1;
712
713
  }
713
714
  }
@@ -826,6 +827,8 @@ function polyUniformGamma1(a, seed, nonce) {
826
827
  }
827
828
 
828
829
  function polyChallenge(cP, seed) {
830
+ if (seed.length !== SeedBytes) throw new Error('invalid seed length');
831
+
829
832
  let b;
830
833
  let pos;
831
834
  const c = cP;
@@ -833,7 +836,7 @@ function polyChallenge(cP, seed) {
833
836
 
834
837
  const state = new KeccakState();
835
838
  shake256Init(state);
836
- shake256Absorb(state, seed.slice(0, SeedBytes));
839
+ shake256Absorb(state, seed);
837
840
  shake256Finalize(state);
838
841
  shake256SqueezeBlocks(buf, 0, 1, state);
839
842
 
@@ -1246,6 +1249,9 @@ function packPk(pkp, rho, t1) {
1246
1249
  }
1247
1250
 
1248
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
+ }
1249
1255
  const rho = rhop;
1250
1256
  for (let i = 0; i < SeedBytes; ++i) {
1251
1257
  rho[i] = pk[i];
@@ -1290,6 +1296,9 @@ function packSk(skp, rho, tr, key, t0, s1, s2) {
1290
1296
  }
1291
1297
 
1292
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
+ }
1293
1302
  let skOffset = 0;
1294
1303
  const rho = rhoP;
1295
1304
  const tr = trP;
@@ -1345,6 +1354,12 @@ function packSig(sigP, c, z, h) {
1345
1354
  for (let i = 0; i < K; ++i) {
1346
1355
  for (let j = 0; j < N; ++j) {
1347
1356
  if (h.vec[i].coeffs[j] !== 0) {
1357
+ if (h.vec[i].coeffs[j] !== 1) {
1358
+ throw new Error('hint coefficients must be binary (0 or 1)');
1359
+ }
1360
+ if (k >= OMEGA) {
1361
+ throw new Error(`hint count exceeds OMEGA (${OMEGA})`);
1362
+ }
1348
1363
  sig[sigOffset + k++] = j;
1349
1364
  }
1350
1365
  }
@@ -1353,7 +1368,12 @@ function packSig(sigP, c, z, h) {
1353
1368
  }
1354
1369
  }
1355
1370
 
1371
+ // Returns 0 on success, 1 on failure. On failure, output buffers (c, z, h)
1372
+ // may contain partial data and must not be used.
1356
1373
  function unpackSig(cP, z, hP, sig) {
1374
+ if (!(sig instanceof Uint8Array) || sig.length !== CryptoBytes) {
1375
+ throw new Error(`sig must be a Uint8Array of ${CryptoBytes} bytes`);
1376
+ }
1357
1377
  let sigOffset = 0;
1358
1378
  const c = cP;
1359
1379
  const h = hP;
@@ -1453,6 +1473,8 @@ function randomBytes(size) {
1453
1473
  *
1454
1474
  * @param {Uint8Array} buffer - The buffer to zero
1455
1475
  * @returns {void}
1476
+ * @throws {TypeError} If buffer is not a Uint8Array
1477
+ * @throws {Error} If zeroization verification fails
1456
1478
  */
1457
1479
  function zeroize(buffer) {
1458
1480
  if (!(buffer instanceof Uint8Array)) {
@@ -1475,6 +1497,7 @@ function zeroize(buffer) {
1475
1497
  *
1476
1498
  * @param {Uint8Array} buffer - The buffer to check
1477
1499
  * @returns {boolean} True if all bytes are zero
1500
+ * @throws {TypeError} If buffer is not a Uint8Array
1478
1501
  */
1479
1502
  function isZero(buffer) {
1480
1503
  if (!(buffer instanceof Uint8Array)) {
@@ -1490,16 +1513,12 @@ function isZero(buffer) {
1490
1513
  /**
1491
1514
  * Convert hex string to Uint8Array with strict validation.
1492
1515
  *
1493
- * NOTE: This function accepts multiple hex formats (with/without 0x prefix,
1494
- * leading/trailing whitespace). While user-friendly, this flexibility could
1495
- * mask input errors. Applications requiring strict format validation should
1496
- * validate hex format before calling cryptographic functions, e.g.:
1497
- * - Reject strings with 0x prefix if raw hex is expected
1498
- * - Reject strings with whitespace
1499
- * - Enforce consistent casing (lowercase/uppercase)
1516
+ * Accepts an optional 0x/0X prefix. Leading/trailing whitespace is rejected.
1517
+ * Empty strings and whitespace-only strings are rejected.
1500
1518
  *
1501
- * @param {string} hex - Hex string (optional 0x prefix, even length).
1519
+ * @param {string} hex - Hex string (optional 0x prefix, even length, no whitespace).
1502
1520
  * @returns {Uint8Array} Decoded bytes.
1521
+ * @throws {Error} If input is not a valid hex string
1503
1522
  * @private
1504
1523
  */
1505
1524
  function hexToBytes(hex) {
@@ -1508,11 +1527,16 @@ function hexToBytes(hex) {
1508
1527
  throw new Error('message must be a hex string');
1509
1528
  }
1510
1529
  /* c8 ignore stop */
1511
- let clean = hex.trim();
1512
- // Accepts both "0x..." and raw hex formats for convenience
1530
+ if (hex !== hex.trim()) {
1531
+ throw new Error('hex string must not have leading or trailing whitespace');
1532
+ }
1533
+ let clean = hex;
1513
1534
  if (clean.startsWith('0x') || clean.startsWith('0X')) {
1514
1535
  clean = clean.slice(2);
1515
1536
  }
1537
+ if (clean.length === 0) {
1538
+ throw new Error('hex string must not be empty');
1539
+ }
1516
1540
  if (clean.length % 2 !== 0) {
1517
1541
  throw new Error('hex string must have an even length');
1518
1542
  }
@@ -1522,6 +1546,14 @@ function hexToBytes(hex) {
1522
1546
  return hexToBytes$1(clean);
1523
1547
  }
1524
1548
 
1549
+ /**
1550
+ * Convert a message to Uint8Array.
1551
+ *
1552
+ * @param {string|Uint8Array} message - Message as hex string (optional 0x prefix) or Uint8Array.
1553
+ * @returns {Uint8Array} Message bytes.
1554
+ * @throws {Error} If message is not a Uint8Array or valid hex string
1555
+ * @private
1556
+ */
1525
1557
  function messageToBytes(message) {
1526
1558
  if (typeof message === 'string') {
1527
1559
  return hexToBytes(message);
@@ -1535,8 +1567,8 @@ function messageToBytes(message) {
1535
1567
  /**
1536
1568
  * Generate a Dilithium-5 key pair.
1537
1569
  *
1538
- * @param {Uint8Array|null} passedSeed - Optional 32-byte seed for deterministic key generation.
1539
- * Pass null for random key generation.
1570
+ * @param {Uint8Array|null} [passedSeed=null] - Optional 32-byte seed for deterministic key generation.
1571
+ * Pass null or undefined for random key generation.
1540
1572
  * @param {Uint8Array} pk - Output buffer for public key (must be CryptoPublicKeyBytes = 2592 bytes)
1541
1573
  * @param {Uint8Array} sk - Output buffer for secret key (must be CryptoSecretKeyBytes = 4896 bytes)
1542
1574
  * @returns {Uint8Array} The seed used for key generation (useful when passedSeed is null)
@@ -1557,9 +1589,9 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
1557
1589
  }
1558
1590
  } catch (e) {
1559
1591
  if (e instanceof TypeError) {
1560
- throw new Error(`pk/sk cannot be null`);
1592
+ throw new Error(`pk/sk cannot be null`, { cause: e });
1561
1593
  } else {
1562
- throw new Error(`${e.message}`);
1594
+ throw new Error(`${e.message}`, { cause: e });
1563
1595
  }
1564
1596
  }
1565
1597
 
@@ -1637,15 +1669,25 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
1637
1669
  * @param {boolean} randomizedSigning - If true, use random nonce for hedged signing.
1638
1670
  * If false, use deterministic nonce derived from message and key.
1639
1671
  * @returns {number} 0 on success
1640
- * @throws {Error} If sk is wrong size
1672
+ * @throws {TypeError} If sig is not a Uint8Array or is smaller than CryptoBytes
1673
+ * @throws {TypeError} If sk is not a Uint8Array
1674
+ * @throws {TypeError} If randomizedSigning is not a boolean
1675
+ * @throws {Error} If sk length does not equal CryptoSecretKeyBytes
1676
+ * @throws {Error} If message is not a Uint8Array or valid hex string
1641
1677
  *
1642
1678
  * @example
1643
1679
  * const sig = new Uint8Array(CryptoBytes);
1644
1680
  * cryptoSignSignature(sig, message, sk, false);
1645
1681
  */
1646
1682
  function cryptoSignSignature(sig, m, sk, randomizedSigning) {
1647
- if (!sig || sig.length < CryptoBytes) {
1648
- throw new Error(`sig must be at least ${CryptoBytes} bytes`);
1683
+ if (!(sig instanceof Uint8Array) || sig.length < CryptoBytes) {
1684
+ throw new TypeError(`sig must be at least ${CryptoBytes} bytes and a Uint8Array`);
1685
+ }
1686
+ if (!(sk instanceof Uint8Array)) {
1687
+ throw new TypeError('sk must be a Uint8Array');
1688
+ }
1689
+ if (typeof randomizedSigning !== 'boolean') {
1690
+ throw new TypeError('randomizedSigning must be a boolean');
1649
1691
  }
1650
1692
  if (sk.length !== CryptoSecretKeyBytes) {
1651
1693
  throw new Error(`invalid sk length ${sk.length} | Expected length ${CryptoSecretKeyBytes}`);
@@ -1708,7 +1750,7 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
1708
1750
  .xof(SeedBytes);
1709
1751
  sig.set(cHash);
1710
1752
 
1711
- polyChallenge(cp, sig);
1753
+ polyChallenge(cp, sig.slice(0, SeedBytes));
1712
1754
  polyNTT(cp);
1713
1755
 
1714
1756
  // Compute z, reject if it reveals secret
@@ -1768,7 +1810,8 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
1768
1810
  * @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
1769
1811
  * @param {boolean} randomizedSigning - If true, use random nonce; if false, deterministic
1770
1812
  * @returns {Uint8Array} Signed message (CryptoBytes + msg.length bytes)
1771
- * @throws {Error} If signing fails
1813
+ * @throws {TypeError} If sk or randomizedSigning fail type validation (see cryptoSignSignature)
1814
+ * @throws {Error} If signing fails or message/sk are invalid
1772
1815
  *
1773
1816
  * @example
1774
1817
  * const signedMsg = cryptoSign(message, sk, false);
@@ -1822,10 +1865,10 @@ function cryptoSignVerify(sig, m, pk) {
1822
1865
  const w1 = new PolyVecK();
1823
1866
  const h = new PolyVecK();
1824
1867
 
1825
- if (sig.length !== CryptoBytes) {
1868
+ if (!(sig instanceof Uint8Array) || sig.length !== CryptoBytes) {
1826
1869
  return false;
1827
1870
  }
1828
- if (pk.length !== CryptoPublicKeyBytes) {
1871
+ if (!(pk instanceof Uint8Array) || pk.length !== CryptoPublicKeyBytes) {
1829
1872
  return false;
1830
1873
  }
1831
1874
 
@@ -0,0 +1,309 @@
1
+ /**
2
+ * TypeScript definitions for @theqrl/dilithium5
3
+ * Dilithium-5 post-quantum digital signature scheme
4
+ */
5
+
6
+ // Constants
7
+ export const Shake128Rate: number;
8
+ export const Shake256Rate: number;
9
+ export const Stream128BlockBytes: number;
10
+ export const Stream256BlockBytes: number;
11
+ export const SeedBytes: number;
12
+ export const CRHBytes: number;
13
+ export const TRBytes: number;
14
+ export const N: number;
15
+ export const Q: number;
16
+ export const QInv: number;
17
+ export const D: number;
18
+ export const K: number;
19
+ export const L: number;
20
+ export const ETA: number;
21
+ export const TAU: number;
22
+ export const BETA: number;
23
+ export const GAMMA1: number;
24
+ export const GAMMA2: number;
25
+ export const OMEGA: number;
26
+ export const PolyT1PackedBytes: number;
27
+ export const PolyT0PackedBytes: number;
28
+ export const PolyETAPackedBytes: number;
29
+ export const PolyZPackedBytes: number;
30
+ export const PolyVecHPackedBytes: number;
31
+ export const PolyW1PackedBytes: number;
32
+ export const CryptoPublicKeyBytes: number;
33
+ export const CryptoSecretKeyBytes: number;
34
+ export const CryptoBytes: number;
35
+ export const PolyUniformNBlocks: number;
36
+ export const PolyUniformETANBlocks: number;
37
+ export const PolyUniformGamma1NBlocks: number;
38
+ export const zetas: readonly number[];
39
+
40
+ // Core signing functions
41
+
42
+ /**
43
+ * Generate a Dilithium-5 key pair
44
+ * @param seed - Optional 32-byte seed for deterministic key generation (null for random)
45
+ * @param pk - Output buffer for public key (must be CryptoPublicKeyBytes length)
46
+ * @param sk - Output buffer for secret key (must be CryptoSecretKeyBytes length)
47
+ * @returns The seed used for key generation
48
+ * @throws Error if pk/sk buffers are wrong size or null
49
+ */
50
+ export function cryptoSignKeypair(
51
+ seed: Uint8Array | null,
52
+ pk: Uint8Array,
53
+ sk: Uint8Array
54
+ ): Uint8Array;
55
+
56
+ /**
57
+ * Create a signature for a message
58
+ * @param sig - Output buffer for signature (must be CryptoBytes length minimum)
59
+ * @param m - Message to sign (hex string or Uint8Array; strings are parsed as hex only)
60
+ * @param sk - Secret key
61
+ * @param randomizedSigning - If true, use random nonce; if false, deterministic
62
+ * @returns 0 on success
63
+ * @throws Error if sk is wrong size
64
+ */
65
+ export function cryptoSignSignature(
66
+ sig: Uint8Array,
67
+ m: Uint8Array | string,
68
+ sk: Uint8Array,
69
+ randomizedSigning: boolean
70
+ ): number;
71
+
72
+ /**
73
+ * Sign a message, returning signature concatenated with message
74
+ * @param msg - Message to sign
75
+ * @param sk - Secret key
76
+ * @param randomizedSigning - If true, use random nonce; if false, deterministic
77
+ * @returns Signed message (signature || message)
78
+ * @throws Error if signing fails
79
+ */
80
+ export function cryptoSign(
81
+ msg: Uint8Array | string,
82
+ sk: Uint8Array,
83
+ randomizedSigning: boolean
84
+ ): Uint8Array;
85
+
86
+ /**
87
+ * Verify a signature
88
+ * @param sig - Signature to verify
89
+ * @param m - Message that was signed (hex string or Uint8Array; strings are parsed as hex only)
90
+ * @param pk - Public key
91
+ * @returns true if signature is valid, false otherwise
92
+ */
93
+ export function cryptoSignVerify(
94
+ sig: Uint8Array,
95
+ m: Uint8Array | string,
96
+ pk: Uint8Array
97
+ ): boolean;
98
+
99
+ /**
100
+ * Open a signed message (verify and extract message)
101
+ * @param sm - Signed message (signature || message)
102
+ * @param pk - Public key
103
+ * @returns Message if valid, undefined if verification fails
104
+ */
105
+ export function cryptoSignOpen(
106
+ sm: Uint8Array,
107
+ pk: Uint8Array
108
+ ): Uint8Array | undefined;
109
+
110
+ // Utility functions
111
+
112
+ /**
113
+ * Zero out a buffer (best-effort, see SECURITY.md for limitations)
114
+ * @param buffer - Buffer to zero
115
+ * @throws TypeError if buffer is not Uint8Array
116
+ */
117
+ export function zeroize(buffer: Uint8Array): void;
118
+
119
+ /**
120
+ * Check if buffer is all zeros using constant-time comparison
121
+ * @param buffer - Buffer to check
122
+ * @returns true if all bytes are zero
123
+ * @throws TypeError if buffer is not Uint8Array
124
+ */
125
+ export function isZero(buffer: Uint8Array): boolean;
126
+
127
+ // Internal classes (exported but primarily for internal use)
128
+
129
+ export class Poly {
130
+ coeffs: Int32Array;
131
+ constructor();
132
+ copy(poly: Poly): void;
133
+ }
134
+
135
+ export class PolyVecK {
136
+ vec: Poly[];
137
+ constructor();
138
+ }
139
+
140
+ export class PolyVecL {
141
+ vec: Poly[];
142
+ constructor();
143
+ copy(polyVecL: PolyVecL): void;
144
+ }
145
+
146
+ export class KeccakState {
147
+ constructor();
148
+ }
149
+
150
+ // Internal functions (exported but primarily for internal use)
151
+ export function polyNTT(a: Poly): void;
152
+ export function polyInvNTTToMont(a: Poly): void;
153
+ export function polyChallenge(c: Poly, seed: Uint8Array): void;
154
+ export function ntt(a: Int32Array): void;
155
+ export function invNTTToMont(a: Int32Array): void;
156
+ export function montgomeryReduce(a: bigint): bigint;
157
+ export function reduce32(a: number): number;
158
+ export function cAddQ(a: number): number;
159
+ export function decompose(a0: Int32Array, i: number, a: number): number;
160
+ export function power2round(a0: Int32Array, i: number, a: number): number;
161
+ export function makeHint(a0: number, a1: number): number;
162
+ export function useHint(a: number, hint: number): number;
163
+ export function packPk(pk: Uint8Array, rho: Uint8Array, t1: PolyVecK): void;
164
+ export function packSk(
165
+ sk: Uint8Array,
166
+ rho: Uint8Array,
167
+ tr: Uint8Array,
168
+ key: Uint8Array,
169
+ t0: PolyVecK,
170
+ s1: PolyVecL,
171
+ s2: PolyVecK
172
+ ): void;
173
+ export function packSig(
174
+ sig: Uint8Array,
175
+ c: Uint8Array,
176
+ z: PolyVecL,
177
+ h: PolyVecK
178
+ ): void;
179
+ export function unpackPk(rho: Uint8Array, t1: PolyVecK, pk: Uint8Array): void;
180
+ export function unpackSk(
181
+ rho: Uint8Array,
182
+ tr: Uint8Array,
183
+ key: Uint8Array,
184
+ t0: PolyVecK,
185
+ s1: PolyVecL,
186
+ s2: PolyVecK,
187
+ sk: Uint8Array
188
+ ): void;
189
+ export function unpackSig(
190
+ c: Uint8Array,
191
+ z: PolyVecL,
192
+ h: PolyVecK,
193
+ sig: Uint8Array
194
+ ): number;
195
+
196
+ // FIPS 202 SHAKE primitives (low-level XOF interface, primarily internal)
197
+ export function shake128Init(state: KeccakState): void;
198
+ export function shake128Absorb(state: KeccakState, input: Uint8Array): void;
199
+ export function shake128Finalize(state: KeccakState): void;
200
+ export function shake128SqueezeBlocks(
201
+ out: Uint8Array,
202
+ outputOffset: number,
203
+ nBlocks: number,
204
+ state: KeccakState
205
+ ): void;
206
+ export function shake256Init(state: KeccakState): void;
207
+ export function shake256Absorb(state: KeccakState, input: Uint8Array): void;
208
+ export function shake256Finalize(state: KeccakState): void;
209
+ export function shake256SqueezeBlocks(
210
+ out: Uint8Array,
211
+ outputOffset: number,
212
+ nBlocks: number,
213
+ state: KeccakState
214
+ ): void;
215
+
216
+ // Dilithium-specific stream initializers
217
+ export function dilithiumShake128StreamInit(
218
+ state: KeccakState,
219
+ seed: Uint8Array,
220
+ nonce: number
221
+ ): void;
222
+ export function dilithiumShake256StreamInit(
223
+ state: KeccakState,
224
+ seed: Uint8Array,
225
+ nonce: number
226
+ ): void;
227
+
228
+ // Polynomial operations (internal)
229
+ export function polyReduce(a: Poly): void;
230
+ export function polyCAddQ(a: Poly): void;
231
+ export function polyAdd(c: Poly, a: Poly, b: Poly): void;
232
+ export function polySub(c: Poly, a: Poly, b: Poly): void;
233
+ export function polyShiftL(a: Poly): void;
234
+ export function polyPointWiseMontgomery(c: Poly, a: Poly, b: Poly): void;
235
+ export function polyPower2round(a1: Poly, a0: Poly, a: Poly): void;
236
+ export function polyDecompose(a1: Poly, a0: Poly, a: Poly): void;
237
+ export function polyMakeHint(h: Poly, a0: Poly, a1: Poly): number;
238
+ export function polyUseHint(b: Poly, a: Poly, h: Poly): void;
239
+ export function polyChkNorm(a: Poly, b: number): number;
240
+ export function rejUniform(
241
+ a: Int32Array,
242
+ aOffset: number,
243
+ len: number,
244
+ buf: Uint8Array,
245
+ bufLen: number
246
+ ): number;
247
+ export function polyUniform(a: Poly, seed: Uint8Array, nonce: number): void;
248
+ export function rejEta(
249
+ a: Int32Array,
250
+ aOffset: number,
251
+ len: number,
252
+ buf: Uint8Array,
253
+ bufLen: number
254
+ ): number;
255
+ export function polyUniformEta(a: Poly, seed: Uint8Array, nonce: number): void;
256
+ export function polyZUnpack(r: Poly, a: Uint8Array, aOffset: number): void;
257
+ export function polyUniformGamma1(a: Poly, seed: Uint8Array, nonce: number): void;
258
+ export function polyEtaPack(r: Uint8Array, rOffset: number, a: Poly): void;
259
+ export function polyEtaUnpack(r: Poly, a: Uint8Array, aOffset: number): void;
260
+ export function polyT1Pack(r: Uint8Array, rOffset: number, a: Poly): void;
261
+ export function polyT1Unpack(r: Poly, a: Uint8Array, aOffset: number): void;
262
+ export function polyT0Pack(r: Uint8Array, rOffset: number, a: Poly): void;
263
+ export function polyT0Unpack(r: Poly, a: Uint8Array, aOffset: number): void;
264
+ export function polyZPack(r: Uint8Array, rOffset: number, a: Poly): void;
265
+ export function polyW1Pack(r: Uint8Array, rOffset: number, a: Poly): void;
266
+
267
+ // Polynomial vector operations (internal)
268
+ export function polyVecMatrixExpand(mat: PolyVecL[], rho: Uint8Array): void;
269
+ export function polyVecMatrixPointWiseMontgomery(
270
+ t: PolyVecK,
271
+ mat: PolyVecL[],
272
+ v: PolyVecL
273
+ ): void;
274
+ export function polyVecLUniformEta(v: PolyVecL, seed: Uint8Array, nonce: number): void;
275
+ export function polyVecLUniformGamma1(v: PolyVecL, seed: Uint8Array, nonce: number): void;
276
+ export function polyVecLReduce(v: PolyVecL): void;
277
+ export function polyVecLAdd(w: PolyVecL, u: PolyVecL, v: PolyVecL): void;
278
+ export function polyVecLNTT(v: PolyVecL): void;
279
+ export function polyVecLInvNTTToMont(v: PolyVecL): void;
280
+ export function polyVecLPointWisePolyMontgomery(
281
+ r: PolyVecL,
282
+ a: Poly,
283
+ v: PolyVecL
284
+ ): void;
285
+ export function polyVecLPointWiseAccMontgomery(
286
+ w: Poly,
287
+ u: PolyVecL,
288
+ v: PolyVecL
289
+ ): void;
290
+ export function polyVecLChkNorm(v: PolyVecL, bound: number): number;
291
+ export function polyVecKUniformEta(v: PolyVecK, seed: Uint8Array, nonce: number): void;
292
+ export function polyVecKReduce(v: PolyVecK): void;
293
+ export function polyVecKCAddQ(v: PolyVecK): void;
294
+ export function polyVecKAdd(w: PolyVecK, u: PolyVecK, v: PolyVecK): void;
295
+ export function polyVecKSub(w: PolyVecK, u: PolyVecK, v: PolyVecK): void;
296
+ export function polyVecKShiftL(v: PolyVecK): void;
297
+ export function polyVecKNTT(v: PolyVecK): void;
298
+ export function polyVecKInvNTTToMont(v: PolyVecK): void;
299
+ export function polyVecKPointWisePolyMontgomery(
300
+ r: PolyVecK,
301
+ a: Poly,
302
+ v: PolyVecK
303
+ ): void;
304
+ export function polyVecKChkNorm(v: PolyVecK, bound: number): number;
305
+ export function polyVecKPower2round(v1: PolyVecK, v0: PolyVecK, v: PolyVecK): void;
306
+ export function polyVecKDecompose(v1: PolyVecK, v0: PolyVecK, v: PolyVecK): void;
307
+ export function polyVecKMakeHint(h: PolyVecK, v0: PolyVecK, v1: PolyVecK): number;
308
+ export function polyVecKUseHint(w: PolyVecK, u: PolyVecK, h: PolyVecK): void;
309
+ export function polyVecKPackW1(r: Uint8Array, w1: PolyVecK): void;
@@ -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 = 0;
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 = 0;
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
- let t = a.coeffs[i] >> 31;
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.slice(0, SeedBytes));
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;
@@ -966,6 +975,12 @@ function packSig(sigP, c, z, h) {
966
975
  for (let i = 0; i < K; ++i) {
967
976
  for (let j = 0; j < N; ++j) {
968
977
  if (h.vec[i].coeffs[j] !== 0) {
978
+ if (h.vec[i].coeffs[j] !== 1) {
979
+ throw new Error('hint coefficients must be binary (0 or 1)');
980
+ }
981
+ if (k >= OMEGA) {
982
+ throw new Error(`hint count exceeds OMEGA (${OMEGA})`);
983
+ }
969
984
  sig[sigOffset + k++] = j;
970
985
  }
971
986
  }
@@ -974,7 +989,12 @@ function packSig(sigP, c, z, h) {
974
989
  }
975
990
  }
976
991
 
992
+ // Returns 0 on success, 1 on failure. On failure, output buffers (c, z, h)
993
+ // may contain partial data and must not be used.
977
994
  function unpackSig(cP, z, hP, sig) {
995
+ if (!(sig instanceof Uint8Array) || sig.length !== CryptoBytes) {
996
+ throw new Error(`sig must be a Uint8Array of ${CryptoBytes} bytes`);
997
+ }
978
998
  let sigOffset = 0;
979
999
  const c = cP;
980
1000
  const h = hP;
@@ -1074,6 +1094,8 @@ function randomBytes(size) {
1074
1094
  *
1075
1095
  * @param {Uint8Array} buffer - The buffer to zero
1076
1096
  * @returns {void}
1097
+ * @throws {TypeError} If buffer is not a Uint8Array
1098
+ * @throws {Error} If zeroization verification fails
1077
1099
  */
1078
1100
  function zeroize(buffer) {
1079
1101
  if (!(buffer instanceof Uint8Array)) {
@@ -1096,6 +1118,7 @@ function zeroize(buffer) {
1096
1118
  *
1097
1119
  * @param {Uint8Array} buffer - The buffer to check
1098
1120
  * @returns {boolean} True if all bytes are zero
1121
+ * @throws {TypeError} If buffer is not a Uint8Array
1099
1122
  */
1100
1123
  function isZero(buffer) {
1101
1124
  if (!(buffer instanceof Uint8Array)) {
@@ -1111,16 +1134,12 @@ function isZero(buffer) {
1111
1134
  /**
1112
1135
  * Convert hex string to Uint8Array with strict validation.
1113
1136
  *
1114
- * NOTE: This function accepts multiple hex formats (with/without 0x prefix,
1115
- * leading/trailing whitespace). While user-friendly, this flexibility could
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)
1137
+ * Accepts an optional 0x/0X prefix. Leading/trailing whitespace is rejected.
1138
+ * Empty strings and whitespace-only strings are rejected.
1121
1139
  *
1122
- * @param {string} hex - Hex string (optional 0x prefix, even length).
1140
+ * @param {string} hex - Hex string (optional 0x prefix, even length, no whitespace).
1123
1141
  * @returns {Uint8Array} Decoded bytes.
1142
+ * @throws {Error} If input is not a valid hex string
1124
1143
  * @private
1125
1144
  */
1126
1145
  function hexToBytes(hex) {
@@ -1129,11 +1148,16 @@ function hexToBytes(hex) {
1129
1148
  throw new Error('message must be a hex string');
1130
1149
  }
1131
1150
  /* c8 ignore stop */
1132
- let clean = hex.trim();
1133
- // Accepts both "0x..." and raw hex formats for convenience
1151
+ if (hex !== hex.trim()) {
1152
+ throw new Error('hex string must not have leading or trailing whitespace');
1153
+ }
1154
+ let clean = hex;
1134
1155
  if (clean.startsWith('0x') || clean.startsWith('0X')) {
1135
1156
  clean = clean.slice(2);
1136
1157
  }
1158
+ if (clean.length === 0) {
1159
+ throw new Error('hex string must not be empty');
1160
+ }
1137
1161
  if (clean.length % 2 !== 0) {
1138
1162
  throw new Error('hex string must have an even length');
1139
1163
  }
@@ -1143,6 +1167,14 @@ function hexToBytes(hex) {
1143
1167
  return hexToBytes$1(clean);
1144
1168
  }
1145
1169
 
1170
+ /**
1171
+ * Convert a message to Uint8Array.
1172
+ *
1173
+ * @param {string|Uint8Array} message - Message as hex string (optional 0x prefix) or Uint8Array.
1174
+ * @returns {Uint8Array} Message bytes.
1175
+ * @throws {Error} If message is not a Uint8Array or valid hex string
1176
+ * @private
1177
+ */
1146
1178
  function messageToBytes(message) {
1147
1179
  if (typeof message === 'string') {
1148
1180
  return hexToBytes(message);
@@ -1156,8 +1188,8 @@ function messageToBytes(message) {
1156
1188
  /**
1157
1189
  * Generate a Dilithium-5 key pair.
1158
1190
  *
1159
- * @param {Uint8Array|null} passedSeed - Optional 32-byte seed for deterministic key generation.
1160
- * Pass null for random key generation.
1191
+ * @param {Uint8Array|null} [passedSeed=null] - Optional 32-byte seed for deterministic key generation.
1192
+ * Pass null or undefined for random key generation.
1161
1193
  * @param {Uint8Array} pk - Output buffer for public key (must be CryptoPublicKeyBytes = 2592 bytes)
1162
1194
  * @param {Uint8Array} sk - Output buffer for secret key (must be CryptoSecretKeyBytes = 4896 bytes)
1163
1195
  * @returns {Uint8Array} The seed used for key generation (useful when passedSeed is null)
@@ -1178,9 +1210,9 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
1178
1210
  }
1179
1211
  } catch (e) {
1180
1212
  if (e instanceof TypeError) {
1181
- throw new Error(`pk/sk cannot be null`);
1213
+ throw new Error(`pk/sk cannot be null`, { cause: e });
1182
1214
  } else {
1183
- throw new Error(`${e.message}`);
1215
+ throw new Error(`${e.message}`, { cause: e });
1184
1216
  }
1185
1217
  }
1186
1218
 
@@ -1258,15 +1290,25 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
1258
1290
  * @param {boolean} randomizedSigning - If true, use random nonce for hedged signing.
1259
1291
  * If false, use deterministic nonce derived from message and key.
1260
1292
  * @returns {number} 0 on success
1261
- * @throws {Error} If sk is wrong size
1293
+ * @throws {TypeError} If sig is not a Uint8Array or is smaller than CryptoBytes
1294
+ * @throws {TypeError} If sk is not a Uint8Array
1295
+ * @throws {TypeError} If randomizedSigning is not a boolean
1296
+ * @throws {Error} If sk length does not equal CryptoSecretKeyBytes
1297
+ * @throws {Error} If message is not a Uint8Array or valid hex string
1262
1298
  *
1263
1299
  * @example
1264
1300
  * const sig = new Uint8Array(CryptoBytes);
1265
1301
  * cryptoSignSignature(sig, message, sk, false);
1266
1302
  */
1267
1303
  function cryptoSignSignature(sig, m, sk, randomizedSigning) {
1268
- if (!sig || sig.length < CryptoBytes) {
1269
- throw new Error(`sig must be at least ${CryptoBytes} bytes`);
1304
+ if (!(sig instanceof Uint8Array) || sig.length < CryptoBytes) {
1305
+ throw new TypeError(`sig must be at least ${CryptoBytes} bytes and a Uint8Array`);
1306
+ }
1307
+ if (!(sk instanceof Uint8Array)) {
1308
+ throw new TypeError('sk must be a Uint8Array');
1309
+ }
1310
+ if (typeof randomizedSigning !== 'boolean') {
1311
+ throw new TypeError('randomizedSigning must be a boolean');
1270
1312
  }
1271
1313
  if (sk.length !== CryptoSecretKeyBytes) {
1272
1314
  throw new Error(`invalid sk length ${sk.length} | Expected length ${CryptoSecretKeyBytes}`);
@@ -1329,7 +1371,7 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
1329
1371
  .xof(SeedBytes);
1330
1372
  sig.set(cHash);
1331
1373
 
1332
- polyChallenge(cp, sig);
1374
+ polyChallenge(cp, sig.slice(0, SeedBytes));
1333
1375
  polyNTT(cp);
1334
1376
 
1335
1377
  // Compute z, reject if it reveals secret
@@ -1389,7 +1431,8 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
1389
1431
  * @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
1390
1432
  * @param {boolean} randomizedSigning - If true, use random nonce; if false, deterministic
1391
1433
  * @returns {Uint8Array} Signed message (CryptoBytes + msg.length bytes)
1392
- * @throws {Error} If signing fails
1434
+ * @throws {TypeError} If sk or randomizedSigning fail type validation (see cryptoSignSignature)
1435
+ * @throws {Error} If signing fails or message/sk are invalid
1393
1436
  *
1394
1437
  * @example
1395
1438
  * const signedMsg = cryptoSign(message, sk, false);
@@ -1443,10 +1486,10 @@ function cryptoSignVerify(sig, m, pk) {
1443
1486
  const w1 = new PolyVecK();
1444
1487
  const h = new PolyVecK();
1445
1488
 
1446
- if (sig.length !== CryptoBytes) {
1489
+ if (!(sig instanceof Uint8Array) || sig.length !== CryptoBytes) {
1447
1490
  return false;
1448
1491
  }
1449
- if (pk.length !== CryptoPublicKeyBytes) {
1492
+ if (!(pk instanceof Uint8Array) || pk.length !== CryptoPublicKeyBytes) {
1450
1493
  return false;
1451
1494
  }
1452
1495
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theqrl/dilithium5",
3
- "version": "1.1.1",
3
+ "version": "1.1.5",
4
4
  "description": "Dilithium-5 cryptography",
5
5
  "keywords": [
6
6
  "dilithium",
@@ -44,8 +44,14 @@
44
44
  },
45
45
  "exports": {
46
46
  ".": {
47
- "import": "./dist/mjs/dilithium5.js",
48
- "require": "./dist/cjs/dilithium5.js"
47
+ "import": {
48
+ "types": "./dist/mjs/dilithium5.d.mts",
49
+ "default": "./dist/mjs/dilithium5.js"
50
+ },
51
+ "require": {
52
+ "types": "./dist/cjs/dilithium5.d.cts",
53
+ "default": "./dist/cjs/dilithium5.js"
54
+ }
49
55
  }
50
56
  },
51
57
  "type": "module",
@@ -53,20 +59,39 @@
53
59
  "node": ">=20.19.0"
54
60
  },
55
61
  "devDependencies": {
56
- "@eslint/js": "^10.0.1",
57
- "@rollup/plugin-node-resolve": "^16.0.3",
58
- "c8": "^10.1.3",
59
- "chai": "^6.2.2",
60
- "eslint": "^10.0.0",
61
- "eslint-config-prettier": "^10.1.8",
62
- "eslint-plugin-import-x": "^4.15.0",
63
- "eslint-plugin-prettier": "^5.5.4",
64
- "globals": "^17.3.0",
65
- "mocha": "^11.7.5",
66
- "prettier": "^3.8.1",
67
- "rollup": "^4.57.1"
62
+ "@eslint/js": "10.0.1",
63
+ "@rollup/plugin-node-resolve": "16.0.3",
64
+ "c8": "11.0.0",
65
+ "chai": "6.2.2",
66
+ "eslint": "10.0.3",
67
+ "eslint-config-prettier": "10.1.8",
68
+ "eslint-plugin-import-x": "4.16.2",
69
+ "eslint-plugin-prettier": "5.5.5",
70
+ "globals": "17.4.0",
71
+ "minimatch": "10.2.4",
72
+ "mocha": "11.7.5",
73
+ "prettier": "3.8.1",
74
+ "rollup": "4.59.0",
75
+ "serialize-javascript": "7.0.4",
76
+ "tar": "7.5.11"
68
77
  },
69
78
  "dependencies": {
70
- "@noble/hashes": "^2.0.1"
79
+ "@noble/hashes": "2.0.1"
80
+ },
81
+ "overrides": {
82
+ "diff": "8.0.3",
83
+ "minimatch": "10.2.4"
84
+ },
85
+ "c8": {
86
+ "include": [
87
+ "src/**"
88
+ ],
89
+ "exclude": [
90
+ "**/dist/**",
91
+ "**/test/**",
92
+ "**/browser-tests/**",
93
+ "**/*.d.ts"
94
+ ],
95
+ "all": true
71
96
  }
72
97
  }
package/src/index.d.ts CHANGED
@@ -192,3 +192,118 @@ export function unpackSig(
192
192
  h: PolyVecK,
193
193
  sig: Uint8Array
194
194
  ): number;
195
+
196
+ // FIPS 202 SHAKE primitives (low-level XOF interface, primarily internal)
197
+ export function shake128Init(state: KeccakState): void;
198
+ export function shake128Absorb(state: KeccakState, input: Uint8Array): void;
199
+ export function shake128Finalize(state: KeccakState): void;
200
+ export function shake128SqueezeBlocks(
201
+ out: Uint8Array,
202
+ outputOffset: number,
203
+ nBlocks: number,
204
+ state: KeccakState
205
+ ): void;
206
+ export function shake256Init(state: KeccakState): void;
207
+ export function shake256Absorb(state: KeccakState, input: Uint8Array): void;
208
+ export function shake256Finalize(state: KeccakState): void;
209
+ export function shake256SqueezeBlocks(
210
+ out: Uint8Array,
211
+ outputOffset: number,
212
+ nBlocks: number,
213
+ state: KeccakState
214
+ ): void;
215
+
216
+ // Dilithium-specific stream initializers
217
+ export function dilithiumShake128StreamInit(
218
+ state: KeccakState,
219
+ seed: Uint8Array,
220
+ nonce: number
221
+ ): void;
222
+ export function dilithiumShake256StreamInit(
223
+ state: KeccakState,
224
+ seed: Uint8Array,
225
+ nonce: number
226
+ ): void;
227
+
228
+ // Polynomial operations (internal)
229
+ export function polyReduce(a: Poly): void;
230
+ export function polyCAddQ(a: Poly): void;
231
+ export function polyAdd(c: Poly, a: Poly, b: Poly): void;
232
+ export function polySub(c: Poly, a: Poly, b: Poly): void;
233
+ export function polyShiftL(a: Poly): void;
234
+ export function polyPointWiseMontgomery(c: Poly, a: Poly, b: Poly): void;
235
+ export function polyPower2round(a1: Poly, a0: Poly, a: Poly): void;
236
+ export function polyDecompose(a1: Poly, a0: Poly, a: Poly): void;
237
+ export function polyMakeHint(h: Poly, a0: Poly, a1: Poly): number;
238
+ export function polyUseHint(b: Poly, a: Poly, h: Poly): void;
239
+ export function polyChkNorm(a: Poly, b: number): number;
240
+ export function rejUniform(
241
+ a: Int32Array,
242
+ aOffset: number,
243
+ len: number,
244
+ buf: Uint8Array,
245
+ bufLen: number
246
+ ): number;
247
+ export function polyUniform(a: Poly, seed: Uint8Array, nonce: number): void;
248
+ export function rejEta(
249
+ a: Int32Array,
250
+ aOffset: number,
251
+ len: number,
252
+ buf: Uint8Array,
253
+ bufLen: number
254
+ ): number;
255
+ export function polyUniformEta(a: Poly, seed: Uint8Array, nonce: number): void;
256
+ export function polyZUnpack(r: Poly, a: Uint8Array, aOffset: number): void;
257
+ export function polyUniformGamma1(a: Poly, seed: Uint8Array, nonce: number): void;
258
+ export function polyEtaPack(r: Uint8Array, rOffset: number, a: Poly): void;
259
+ export function polyEtaUnpack(r: Poly, a: Uint8Array, aOffset: number): void;
260
+ export function polyT1Pack(r: Uint8Array, rOffset: number, a: Poly): void;
261
+ export function polyT1Unpack(r: Poly, a: Uint8Array, aOffset: number): void;
262
+ export function polyT0Pack(r: Uint8Array, rOffset: number, a: Poly): void;
263
+ export function polyT0Unpack(r: Poly, a: Uint8Array, aOffset: number): void;
264
+ export function polyZPack(r: Uint8Array, rOffset: number, a: Poly): void;
265
+ export function polyW1Pack(r: Uint8Array, rOffset: number, a: Poly): void;
266
+
267
+ // Polynomial vector operations (internal)
268
+ export function polyVecMatrixExpand(mat: PolyVecL[], rho: Uint8Array): void;
269
+ export function polyVecMatrixPointWiseMontgomery(
270
+ t: PolyVecK,
271
+ mat: PolyVecL[],
272
+ v: PolyVecL
273
+ ): void;
274
+ export function polyVecLUniformEta(v: PolyVecL, seed: Uint8Array, nonce: number): void;
275
+ export function polyVecLUniformGamma1(v: PolyVecL, seed: Uint8Array, nonce: number): void;
276
+ export function polyVecLReduce(v: PolyVecL): void;
277
+ export function polyVecLAdd(w: PolyVecL, u: PolyVecL, v: PolyVecL): void;
278
+ export function polyVecLNTT(v: PolyVecL): void;
279
+ export function polyVecLInvNTTToMont(v: PolyVecL): void;
280
+ export function polyVecLPointWisePolyMontgomery(
281
+ r: PolyVecL,
282
+ a: Poly,
283
+ v: PolyVecL
284
+ ): void;
285
+ export function polyVecLPointWiseAccMontgomery(
286
+ w: Poly,
287
+ u: PolyVecL,
288
+ v: PolyVecL
289
+ ): void;
290
+ export function polyVecLChkNorm(v: PolyVecL, bound: number): number;
291
+ export function polyVecKUniformEta(v: PolyVecK, seed: Uint8Array, nonce: number): void;
292
+ export function polyVecKReduce(v: PolyVecK): void;
293
+ export function polyVecKCAddQ(v: PolyVecK): void;
294
+ export function polyVecKAdd(w: PolyVecK, u: PolyVecK, v: PolyVecK): void;
295
+ export function polyVecKSub(w: PolyVecK, u: PolyVecK, v: PolyVecK): void;
296
+ export function polyVecKShiftL(v: PolyVecK): void;
297
+ export function polyVecKNTT(v: PolyVecK): void;
298
+ export function polyVecKInvNTTToMont(v: PolyVecK): void;
299
+ export function polyVecKPointWisePolyMontgomery(
300
+ r: PolyVecK,
301
+ a: Poly,
302
+ v: PolyVecK
303
+ ): void;
304
+ export function polyVecKChkNorm(v: PolyVecK, bound: number): number;
305
+ export function polyVecKPower2round(v1: PolyVecK, v0: PolyVecK, v: PolyVecK): void;
306
+ export function polyVecKDecompose(v1: PolyVecK, v0: PolyVecK, v: PolyVecK): void;
307
+ export function polyVecKMakeHint(h: PolyVecK, v0: PolyVecK, v1: PolyVecK): number;
308
+ export function polyVecKUseHint(w: PolyVecK, u: PolyVecK, h: PolyVecK): void;
309
+ export function polyVecKPackW1(r: Uint8Array, w1: PolyVecK): void;