@theqrl/dilithium5 1.0.8 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -141,7 +141,8 @@ See [SECURITY.md](../../SECURITY.md) for important information about:
141
141
 
142
142
  ## Requirements
143
143
 
144
- - Node.js 18.20+ or modern browsers with ES2020 support
144
+ - **Node.js**: 20.19+, 22.x, or 24.x (requires `globalThis.crypto.getRandomValues`)
145
+ - **Browsers**: [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) and ES2020 (BigInt) -- Chrome 67+, Firefox 68+, Safari 14+, Edge 79+
145
146
  - Full TypeScript definitions included
146
147
 
147
148
  ## License
@@ -3,7 +3,6 @@
3
3
  var sha3_js = require('@noble/hashes/sha3.js');
4
4
  var utils_js = require('@noble/hashes/utils.js');
5
5
 
6
- var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
7
6
  const Shake128Rate = 168;
8
7
  const Shake256Rate = 136;
9
8
  const Stream128BlockBytes = Shake128Rate;
@@ -433,7 +432,7 @@ function polyZUnpack(rP, a, aOffset) {
433
432
  r.coeffs[2 * i + 1] = a[aOffset + 5 * i + 2] >> 4;
434
433
  r.coeffs[2 * i + 1] |= a[aOffset + 5 * i + 3] << 4;
435
434
  r.coeffs[2 * i + 1] |= a[aOffset + 5 * i + 4] << 12;
436
- r.coeffs[2 * i] &= 0xfffff;
435
+ r.coeffs[2 * i + 1] &= 0xfffff;
437
436
 
438
437
  r.coeffs[2 * i] = GAMMA1 - r.coeffs[2 * i];
439
438
  r.coeffs[2 * i + 1] = GAMMA1 - r.coeffs[2 * i + 1];
@@ -1026,43 +1025,8 @@ function unpackSig(cP, z, hP, sig) {
1026
1025
  const MAX_BYTES = 65536;
1027
1026
  const MAX_UINT32 = 0xffffffff;
1028
1027
 
1029
- function getGlobalScope() {
1030
- if (typeof globalThis === 'object') return globalThis;
1031
- if (typeof self === 'object') return self;
1032
- if (typeof window === 'object') return window;
1033
- if (typeof global === 'object') return global;
1034
- return {};
1035
- }
1036
-
1037
1028
  function getWebCrypto() {
1038
- const scope = getGlobalScope();
1039
- return scope.crypto || scope.msCrypto || null;
1040
- }
1041
-
1042
- function getNodeRandomBytes() {
1043
- /* c8 ignore next */
1044
- const isNode = typeof process === 'object' && process !== null && process.versions && process.versions.node;
1045
- if (!isNode) return null;
1046
-
1047
- const req =
1048
- typeof module !== 'undefined' && module && typeof module.require === 'function'
1049
- ? module.require.bind(module)
1050
- : typeof module !== 'undefined' && module && typeof module.createRequire === 'function'
1051
- ? module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('dilithium5.js', document.baseURI).href)))
1052
- : typeof require === 'function'
1053
- ? require
1054
- : null;
1055
- if (!req) return null;
1056
-
1057
- try {
1058
- const nodeCrypto = req('crypto');
1059
- if (nodeCrypto && typeof nodeCrypto.randomBytes === 'function') {
1060
- return nodeCrypto.randomBytes;
1061
- }
1062
- } catch {
1063
- return null;
1064
- }
1065
-
1029
+ if (typeof globalThis === 'object' && globalThis.crypto) return globalThis.crypto;
1066
1030
  return null;
1067
1031
  }
1068
1032
 
@@ -1081,15 +1045,69 @@ function randomBytes(size) {
1081
1045
  for (let i = 0; i < size; i += MAX_BYTES) {
1082
1046
  cryptoObj.getRandomValues(out.subarray(i, Math.min(size, i + MAX_BYTES)));
1083
1047
  }
1048
+ if (size >= 16) {
1049
+ let acc = 0;
1050
+ for (let i = 0; i < 16; i++) acc |= out[i];
1051
+ if (acc === 0) throw new Error('getRandomValues returned all zeros');
1052
+ }
1084
1053
  return out;
1085
1054
  }
1086
1055
 
1087
- const nodeRandomBytes = getNodeRandomBytes();
1088
- if (nodeRandomBytes) {
1089
- return nodeRandomBytes(size);
1056
+ throw new Error('Secure random number generation is not supported by this environment');
1057
+ }
1058
+
1059
+ /**
1060
+ * Security utilities for post-quantum signature schemes
1061
+ *
1062
+ * IMPORTANT: JavaScript cannot guarantee secure memory zeroization.
1063
+ * See SECURITY.md for details on limitations.
1064
+ */
1065
+
1066
+ /**
1067
+ * Attempts to zero out a Uint8Array buffer.
1068
+ *
1069
+ * WARNING: This is a BEST-EFFORT operation. Due to JavaScript/JIT limitations:
1070
+ * - The write may be optimized away if the buffer is unused afterward
1071
+ * - Copies may exist in garbage collector memory
1072
+ * - Data may have been swapped to disk
1073
+ *
1074
+ * For high-security applications, consider native implementations (go-qrllib)
1075
+ * or hardware security modules.
1076
+ *
1077
+ * @param {Uint8Array} buffer - The buffer to zero
1078
+ * @returns {void}
1079
+ */
1080
+ function zeroize(buffer) {
1081
+ if (!(buffer instanceof Uint8Array)) {
1082
+ throw new TypeError('zeroize requires a Uint8Array');
1083
+ }
1084
+ // Use fill(0) for zeroing - best effort
1085
+ buffer.fill(0);
1086
+ // Accumulator-OR over all bytes to discourage dead-store elimination
1087
+ // (Reading every byte makes it harder for JIT to prove fill is dead)
1088
+ let check = 0;
1089
+ for (let i = 0; i < buffer.length; i++) check |= buffer[i];
1090
+ if (check !== 0) {
1091
+ throw new Error('zeroize failed');
1090
1092
  }
1093
+ }
1091
1094
 
1092
- throw new Error('Secure random number generation is not supported by this environment');
1095
+ /**
1096
+ * Checks if a buffer is all zeros.
1097
+ * Uses constant-time comparison to avoid timing leaks.
1098
+ *
1099
+ * @param {Uint8Array} buffer - The buffer to check
1100
+ * @returns {boolean} True if all bytes are zero
1101
+ */
1102
+ function isZero(buffer) {
1103
+ if (!(buffer instanceof Uint8Array)) {
1104
+ throw new TypeError('isZero requires a Uint8Array');
1105
+ }
1106
+ let acc = 0;
1107
+ for (let i = 0; i < buffer.length; i++) {
1108
+ acc |= buffer[i];
1109
+ }
1110
+ return acc === 0;
1093
1111
  }
1094
1112
 
1095
1113
  /**
@@ -1190,34 +1208,45 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
1190
1208
  const rhoPrime = seedBuf.slice(SeedBytes, SeedBytes + CRHBytes);
1191
1209
  const key = seedBuf.slice(SeedBytes + CRHBytes);
1192
1210
 
1193
- // Expand matrix
1194
- polyVecMatrixExpand(mat, rho);
1211
+ let s1hat;
1212
+ try {
1213
+ // Expand matrix
1214
+ polyVecMatrixExpand(mat, rho);
1195
1215
 
1196
- // Sample short vectors s1 and s2
1197
- polyVecLUniformEta(s1, rhoPrime, 0);
1198
- polyVecKUniformEta(s2, rhoPrime, L);
1216
+ // Sample short vectors s1 and s2
1217
+ polyVecLUniformEta(s1, rhoPrime, 0);
1218
+ polyVecKUniformEta(s2, rhoPrime, L);
1199
1219
 
1200
- // Matrix-vector multiplication
1201
- const s1hat = new PolyVecL();
1202
- s1hat.copy(s1);
1203
- polyVecLNTT(s1hat);
1204
- polyVecMatrixPointWiseMontgomery(t1, mat, s1hat);
1205
- polyVecKReduce(t1);
1206
- polyVecKInvNTTToMont(t1);
1220
+ // Matrix-vector multiplication
1221
+ s1hat = new PolyVecL();
1222
+ s1hat.copy(s1);
1223
+ polyVecLNTT(s1hat);
1224
+ polyVecMatrixPointWiseMontgomery(t1, mat, s1hat);
1225
+ polyVecKReduce(t1);
1226
+ polyVecKInvNTTToMont(t1);
1207
1227
 
1208
- // Add error vector s2
1209
- polyVecKAdd(t1, t1, s2);
1228
+ // Add error vector s2
1229
+ polyVecKAdd(t1, t1, s2);
1210
1230
 
1211
- // Extract t1 and write public key
1212
- polyVecKCAddQ(t1);
1213
- polyVecKPower2round(t1, t0, t1);
1214
- packPk(pk, rho, t1);
1231
+ // Extract t1 and write public key
1232
+ polyVecKCAddQ(t1);
1233
+ polyVecKPower2round(t1, t0, t1);
1234
+ packPk(pk, rho, t1);
1215
1235
 
1216
- // Compute H(rho, t1) and write secret key
1217
- const tr = sha3_js.shake256.create({}).update(pk).xof(TRBytes);
1218
- packSk(sk, rho, tr, key, t0, s1, s2);
1236
+ // Compute H(rho, t1) and write secret key
1237
+ const tr = sha3_js.shake256.create({}).update(pk).xof(TRBytes);
1238
+ packSk(sk, rho, tr, key, t0, s1, s2);
1219
1239
 
1220
- return seed;
1240
+ return seed;
1241
+ } finally {
1242
+ zeroize(seedBuf);
1243
+ zeroize(rhoPrime);
1244
+ zeroize(key);
1245
+ for (let i = 0; i < L; i++) s1.vec[i].coeffs.fill(0);
1246
+ for (let i = 0; i < K; i++) s2.vec[i].coeffs.fill(0);
1247
+ if (s1hat) for (let i = 0; i < L; i++) s1hat.vec[i].coeffs.fill(0);
1248
+ for (let i = 0; i < K; i++) t0.vec[i].coeffs.fill(0);
1249
+ }
1221
1250
  }
1222
1251
 
1223
1252
  /**
@@ -1265,81 +1294,90 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
1265
1294
  const h = new PolyVecK();
1266
1295
  const cp = new Poly();
1267
1296
 
1268
- unpackSk(rho, tr, key, t0, s1, s2, sk);
1297
+ try {
1298
+ unpackSk(rho, tr, key, t0, s1, s2, sk);
1269
1299
 
1270
- const mu = sha3_js.shake256.create({}).update(tr).update(mBytes).xof(CRHBytes);
1300
+ const mu = sha3_js.shake256.create({}).update(tr).update(mBytes).xof(CRHBytes);
1271
1301
 
1272
- if (randomizedSigning) {
1273
- rhoPrime = new Uint8Array(randomBytes(CRHBytes));
1274
- } else {
1275
- rhoPrime = sha3_js.shake256.create({}).update(key).update(mu).xof(CRHBytes);
1276
- }
1302
+ if (randomizedSigning) {
1303
+ rhoPrime = new Uint8Array(randomBytes(CRHBytes));
1304
+ } else {
1305
+ rhoPrime = sha3_js.shake256.create({}).update(key).update(mu).xof(CRHBytes);
1306
+ }
1277
1307
 
1278
- polyVecMatrixExpand(mat, rho);
1279
- polyVecLNTT(s1);
1280
- polyVecKNTT(s2);
1281
- polyVecKNTT(t0);
1308
+ polyVecMatrixExpand(mat, rho);
1309
+ polyVecLNTT(s1);
1310
+ polyVecKNTT(s2);
1311
+ polyVecKNTT(t0);
1312
+
1313
+ while (true) {
1314
+ polyVecLUniformGamma1(y, rhoPrime, nonce++);
1315
+ // Matrix-vector multiplication
1316
+ z.copy(y);
1317
+ polyVecLNTT(z);
1318
+ polyVecMatrixPointWiseMontgomery(w1, mat, z);
1319
+ polyVecKReduce(w1);
1320
+ polyVecKInvNTTToMont(w1);
1321
+
1322
+ // Decompose w and call the random oracle
1323
+ polyVecKCAddQ(w1);
1324
+ polyVecKDecompose(w1, w0, w1);
1325
+ polyVecKPackW1(sig, w1);
1326
+
1327
+ const cHash = sha3_js.shake256
1328
+ .create({})
1329
+ .update(mu)
1330
+ .update(sig.slice(0, K * PolyW1PackedBytes))
1331
+ .xof(SeedBytes);
1332
+ sig.set(cHash);
1333
+
1334
+ polyChallenge(cp, sig);
1335
+ polyNTT(cp);
1336
+
1337
+ // Compute z, reject if it reveals secret
1338
+ polyVecLPointWisePolyMontgomery(z, cp, s1);
1339
+ polyVecLInvNTTToMont(z);
1340
+ polyVecLAdd(z, z, y);
1341
+ polyVecLReduce(z);
1342
+ if (polyVecLChkNorm(z, GAMMA1 - BETA) !== 0) {
1343
+ continue;
1344
+ }
1282
1345
 
1283
- while (true) {
1284
- polyVecLUniformGamma1(y, rhoPrime, nonce++);
1285
- // Matrix-vector multiplication
1286
- z.copy(y);
1287
- polyVecLNTT(z);
1288
- polyVecMatrixPointWiseMontgomery(w1, mat, z);
1289
- polyVecKReduce(w1);
1290
- polyVecKInvNTTToMont(w1);
1291
-
1292
- // Decompose w and call the random oracle
1293
- polyVecKCAddQ(w1);
1294
- polyVecKDecompose(w1, w0, w1);
1295
- polyVecKPackW1(sig, w1);
1296
-
1297
- const cHash = sha3_js.shake256
1298
- .create({})
1299
- .update(mu)
1300
- .update(sig.slice(0, K * PolyW1PackedBytes))
1301
- .xof(SeedBytes);
1302
- sig.set(cHash);
1303
-
1304
- polyChallenge(cp, sig);
1305
- polyNTT(cp);
1306
-
1307
- // Compute z, reject if it reveals secret
1308
- polyVecLPointWisePolyMontgomery(z, cp, s1);
1309
- polyVecLInvNTTToMont(z);
1310
- polyVecLAdd(z, z, y);
1311
- polyVecLReduce(z);
1312
- if (polyVecLChkNorm(z, GAMMA1 - BETA) !== 0) {
1313
- continue;
1314
- }
1346
+ polyVecKPointWisePolyMontgomery(h, cp, s2);
1347
+ polyVecKInvNTTToMont(h);
1348
+ polyVecKSub(w0, w0, h);
1349
+ polyVecKReduce(w0);
1350
+ if (polyVecKChkNorm(w0, GAMMA2 - BETA) !== 0) {
1351
+ continue;
1352
+ }
1315
1353
 
1316
- polyVecKPointWisePolyMontgomery(h, cp, s2);
1317
- polyVecKInvNTTToMont(h);
1318
- polyVecKSub(w0, w0, h);
1319
- polyVecKReduce(w0);
1320
- if (polyVecKChkNorm(w0, GAMMA2 - BETA) !== 0) {
1321
- continue;
1322
- }
1354
+ polyVecKPointWisePolyMontgomery(h, cp, t0);
1355
+ polyVecKInvNTTToMont(h);
1356
+ polyVecKReduce(h);
1357
+ /* c8 ignore start */
1358
+ if (polyVecKChkNorm(h, GAMMA2) !== 0) {
1359
+ continue;
1360
+ }
1361
+ /* c8 ignore stop */
1323
1362
 
1324
- polyVecKPointWisePolyMontgomery(h, cp, t0);
1325
- polyVecKInvNTTToMont(h);
1326
- polyVecKReduce(h);
1327
- /* c8 ignore start */
1328
- if (polyVecKChkNorm(h, GAMMA2) !== 0) {
1329
- continue;
1330
- }
1331
- /* c8 ignore stop */
1363
+ polyVecKAdd(w0, w0, h);
1364
+ const n = polyVecKMakeHint(h, w0, w1);
1365
+ /* c8 ignore start */
1366
+ if (n > OMEGA) {
1367
+ continue;
1368
+ }
1369
+ /* c8 ignore stop */
1332
1370
 
1333
- polyVecKAdd(w0, w0, h);
1334
- const n = polyVecKMakeHint(h, w0, w1);
1335
- /* c8 ignore start */
1336
- if (n > OMEGA) {
1337
- continue;
1371
+ packSig(sig, sig, z, h);
1372
+ return 0;
1338
1373
  }
1339
- /* c8 ignore stop */
1340
-
1341
- packSig(sig, sig, z, h);
1342
- return 0;
1374
+ } finally {
1375
+ zeroize(key);
1376
+ zeroize(rhoPrime);
1377
+ for (let i = 0; i < L; i++) s1.vec[i].coeffs.fill(0);
1378
+ for (let i = 0; i < K; i++) s2.vec[i].coeffs.fill(0);
1379
+ for (let i = 0; i < K; i++) t0.vec[i].coeffs.fill(0);
1380
+ for (let i = 0; i < L; i++) y.vec[i].coeffs.fill(0);
1343
1381
  }
1344
1382
  }
1345
1383
 
@@ -1498,58 +1536,6 @@ function cryptoSignOpen(sm, pk) {
1498
1536
  return msg;
1499
1537
  }
1500
1538
 
1501
- /**
1502
- * Security utilities for Dilithium5
1503
- *
1504
- * IMPORTANT: JavaScript cannot guarantee secure memory zeroization.
1505
- * See SECURITY.md for details on limitations.
1506
- */
1507
-
1508
- /**
1509
- * Attempts to zero out a Uint8Array buffer.
1510
- *
1511
- * WARNING: This is a BEST-EFFORT operation. Due to JavaScript/JIT limitations:
1512
- * - The write may be optimized away if the buffer is unused afterward
1513
- * - Copies may exist in garbage collector memory
1514
- * - Data may have been swapped to disk
1515
- *
1516
- * For high-security applications, consider native implementations (go-qrllib)
1517
- * or hardware security modules.
1518
- *
1519
- * @param {Uint8Array} buffer - The buffer to zero
1520
- * @returns {void}
1521
- */
1522
- function zeroize(buffer) {
1523
- if (!(buffer instanceof Uint8Array)) {
1524
- throw new TypeError('zeroize requires a Uint8Array');
1525
- }
1526
- // Use fill(0) for zeroing - best effort
1527
- buffer.fill(0);
1528
- // Additional volatile-like access to discourage optimization
1529
- // (This is a hint to the JIT, not a guarantee)
1530
- if (buffer.length > 0 && buffer[0] !== 0) {
1531
- throw new Error('zeroize failed'); // Should never happen
1532
- }
1533
- }
1534
-
1535
- /**
1536
- * Checks if a buffer is all zeros.
1537
- * Uses constant-time comparison to avoid timing leaks.
1538
- *
1539
- * @param {Uint8Array} buffer - The buffer to check
1540
- * @returns {boolean} True if all bytes are zero
1541
- */
1542
- function isZero(buffer) {
1543
- if (!(buffer instanceof Uint8Array)) {
1544
- throw new TypeError('isZero requires a Uint8Array');
1545
- }
1546
- let acc = 0;
1547
- for (let i = 0; i < buffer.length; i++) {
1548
- acc |= buffer[i];
1549
- }
1550
- return acc === 0;
1551
- }
1552
-
1553
1539
  exports.BETA = BETA;
1554
1540
  exports.CRHBytes = CRHBytes;
1555
1541
  exports.CryptoBytes = CryptoBytes;
@@ -430,7 +430,7 @@ function polyZUnpack(rP, a, aOffset) {
430
430
  r.coeffs[2 * i + 1] = a[aOffset + 5 * i + 2] >> 4;
431
431
  r.coeffs[2 * i + 1] |= a[aOffset + 5 * i + 3] << 4;
432
432
  r.coeffs[2 * i + 1] |= a[aOffset + 5 * i + 4] << 12;
433
- r.coeffs[2 * i] &= 0xfffff;
433
+ r.coeffs[2 * i + 1] &= 0xfffff;
434
434
 
435
435
  r.coeffs[2 * i] = GAMMA1 - r.coeffs[2 * i];
436
436
  r.coeffs[2 * i + 1] = GAMMA1 - r.coeffs[2 * i + 1];
@@ -1023,43 +1023,8 @@ function unpackSig(cP, z, hP, sig) {
1023
1023
  const MAX_BYTES = 65536;
1024
1024
  const MAX_UINT32 = 0xffffffff;
1025
1025
 
1026
- function getGlobalScope() {
1027
- if (typeof globalThis === 'object') return globalThis;
1028
- if (typeof self === 'object') return self;
1029
- if (typeof window === 'object') return window;
1030
- if (typeof global === 'object') return global;
1031
- return {};
1032
- }
1033
-
1034
1026
  function getWebCrypto() {
1035
- const scope = getGlobalScope();
1036
- return scope.crypto || scope.msCrypto || null;
1037
- }
1038
-
1039
- function getNodeRandomBytes() {
1040
- /* c8 ignore next */
1041
- const isNode = typeof process === 'object' && process !== null && process.versions && process.versions.node;
1042
- if (!isNode) return null;
1043
-
1044
- const req =
1045
- typeof module !== 'undefined' && module && typeof module.require === 'function'
1046
- ? module.require.bind(module)
1047
- : typeof module !== 'undefined' && module && typeof module.createRequire === 'function'
1048
- ? module.createRequire(import.meta.url)
1049
- : typeof require === 'function'
1050
- ? require
1051
- : null;
1052
- if (!req) return null;
1053
-
1054
- try {
1055
- const nodeCrypto = req('crypto');
1056
- if (nodeCrypto && typeof nodeCrypto.randomBytes === 'function') {
1057
- return nodeCrypto.randomBytes;
1058
- }
1059
- } catch {
1060
- return null;
1061
- }
1062
-
1027
+ if (typeof globalThis === 'object' && globalThis.crypto) return globalThis.crypto;
1063
1028
  return null;
1064
1029
  }
1065
1030
 
@@ -1078,15 +1043,69 @@ function randomBytes(size) {
1078
1043
  for (let i = 0; i < size; i += MAX_BYTES) {
1079
1044
  cryptoObj.getRandomValues(out.subarray(i, Math.min(size, i + MAX_BYTES)));
1080
1045
  }
1046
+ if (size >= 16) {
1047
+ let acc = 0;
1048
+ for (let i = 0; i < 16; i++) acc |= out[i];
1049
+ if (acc === 0) throw new Error('getRandomValues returned all zeros');
1050
+ }
1081
1051
  return out;
1082
1052
  }
1083
1053
 
1084
- const nodeRandomBytes = getNodeRandomBytes();
1085
- if (nodeRandomBytes) {
1086
- return nodeRandomBytes(size);
1054
+ throw new Error('Secure random number generation is not supported by this environment');
1055
+ }
1056
+
1057
+ /**
1058
+ * Security utilities for post-quantum signature schemes
1059
+ *
1060
+ * IMPORTANT: JavaScript cannot guarantee secure memory zeroization.
1061
+ * See SECURITY.md for details on limitations.
1062
+ */
1063
+
1064
+ /**
1065
+ * Attempts to zero out a Uint8Array buffer.
1066
+ *
1067
+ * WARNING: This is a BEST-EFFORT operation. Due to JavaScript/JIT limitations:
1068
+ * - The write may be optimized away if the buffer is unused afterward
1069
+ * - Copies may exist in garbage collector memory
1070
+ * - Data may have been swapped to disk
1071
+ *
1072
+ * For high-security applications, consider native implementations (go-qrllib)
1073
+ * or hardware security modules.
1074
+ *
1075
+ * @param {Uint8Array} buffer - The buffer to zero
1076
+ * @returns {void}
1077
+ */
1078
+ function zeroize(buffer) {
1079
+ if (!(buffer instanceof Uint8Array)) {
1080
+ throw new TypeError('zeroize requires a Uint8Array');
1081
+ }
1082
+ // Use fill(0) for zeroing - best effort
1083
+ buffer.fill(0);
1084
+ // Accumulator-OR over all bytes to discourage dead-store elimination
1085
+ // (Reading every byte makes it harder for JIT to prove fill is dead)
1086
+ let check = 0;
1087
+ for (let i = 0; i < buffer.length; i++) check |= buffer[i];
1088
+ if (check !== 0) {
1089
+ throw new Error('zeroize failed');
1087
1090
  }
1091
+ }
1088
1092
 
1089
- throw new Error('Secure random number generation is not supported by this environment');
1093
+ /**
1094
+ * Checks if a buffer is all zeros.
1095
+ * Uses constant-time comparison to avoid timing leaks.
1096
+ *
1097
+ * @param {Uint8Array} buffer - The buffer to check
1098
+ * @returns {boolean} True if all bytes are zero
1099
+ */
1100
+ function isZero(buffer) {
1101
+ if (!(buffer instanceof Uint8Array)) {
1102
+ throw new TypeError('isZero requires a Uint8Array');
1103
+ }
1104
+ let acc = 0;
1105
+ for (let i = 0; i < buffer.length; i++) {
1106
+ acc |= buffer[i];
1107
+ }
1108
+ return acc === 0;
1090
1109
  }
1091
1110
 
1092
1111
  /**
@@ -1187,34 +1206,45 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
1187
1206
  const rhoPrime = seedBuf.slice(SeedBytes, SeedBytes + CRHBytes);
1188
1207
  const key = seedBuf.slice(SeedBytes + CRHBytes);
1189
1208
 
1190
- // Expand matrix
1191
- polyVecMatrixExpand(mat, rho);
1209
+ let s1hat;
1210
+ try {
1211
+ // Expand matrix
1212
+ polyVecMatrixExpand(mat, rho);
1192
1213
 
1193
- // Sample short vectors s1 and s2
1194
- polyVecLUniformEta(s1, rhoPrime, 0);
1195
- polyVecKUniformEta(s2, rhoPrime, L);
1214
+ // Sample short vectors s1 and s2
1215
+ polyVecLUniformEta(s1, rhoPrime, 0);
1216
+ polyVecKUniformEta(s2, rhoPrime, L);
1196
1217
 
1197
- // Matrix-vector multiplication
1198
- const s1hat = new PolyVecL();
1199
- s1hat.copy(s1);
1200
- polyVecLNTT(s1hat);
1201
- polyVecMatrixPointWiseMontgomery(t1, mat, s1hat);
1202
- polyVecKReduce(t1);
1203
- polyVecKInvNTTToMont(t1);
1218
+ // Matrix-vector multiplication
1219
+ s1hat = new PolyVecL();
1220
+ s1hat.copy(s1);
1221
+ polyVecLNTT(s1hat);
1222
+ polyVecMatrixPointWiseMontgomery(t1, mat, s1hat);
1223
+ polyVecKReduce(t1);
1224
+ polyVecKInvNTTToMont(t1);
1204
1225
 
1205
- // Add error vector s2
1206
- polyVecKAdd(t1, t1, s2);
1226
+ // Add error vector s2
1227
+ polyVecKAdd(t1, t1, s2);
1207
1228
 
1208
- // Extract t1 and write public key
1209
- polyVecKCAddQ(t1);
1210
- polyVecKPower2round(t1, t0, t1);
1211
- packPk(pk, rho, t1);
1229
+ // Extract t1 and write public key
1230
+ polyVecKCAddQ(t1);
1231
+ polyVecKPower2round(t1, t0, t1);
1232
+ packPk(pk, rho, t1);
1212
1233
 
1213
- // Compute H(rho, t1) and write secret key
1214
- const tr = shake256.create({}).update(pk).xof(TRBytes);
1215
- packSk(sk, rho, tr, key, t0, s1, s2);
1234
+ // Compute H(rho, t1) and write secret key
1235
+ const tr = shake256.create({}).update(pk).xof(TRBytes);
1236
+ packSk(sk, rho, tr, key, t0, s1, s2);
1216
1237
 
1217
- return seed;
1238
+ return seed;
1239
+ } finally {
1240
+ zeroize(seedBuf);
1241
+ zeroize(rhoPrime);
1242
+ zeroize(key);
1243
+ for (let i = 0; i < L; i++) s1.vec[i].coeffs.fill(0);
1244
+ for (let i = 0; i < K; i++) s2.vec[i].coeffs.fill(0);
1245
+ if (s1hat) for (let i = 0; i < L; i++) s1hat.vec[i].coeffs.fill(0);
1246
+ for (let i = 0; i < K; i++) t0.vec[i].coeffs.fill(0);
1247
+ }
1218
1248
  }
1219
1249
 
1220
1250
  /**
@@ -1262,81 +1292,90 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
1262
1292
  const h = new PolyVecK();
1263
1293
  const cp = new Poly();
1264
1294
 
1265
- unpackSk(rho, tr, key, t0, s1, s2, sk);
1295
+ try {
1296
+ unpackSk(rho, tr, key, t0, s1, s2, sk);
1266
1297
 
1267
- const mu = shake256.create({}).update(tr).update(mBytes).xof(CRHBytes);
1298
+ const mu = shake256.create({}).update(tr).update(mBytes).xof(CRHBytes);
1268
1299
 
1269
- if (randomizedSigning) {
1270
- rhoPrime = new Uint8Array(randomBytes(CRHBytes));
1271
- } else {
1272
- rhoPrime = shake256.create({}).update(key).update(mu).xof(CRHBytes);
1273
- }
1300
+ if (randomizedSigning) {
1301
+ rhoPrime = new Uint8Array(randomBytes(CRHBytes));
1302
+ } else {
1303
+ rhoPrime = shake256.create({}).update(key).update(mu).xof(CRHBytes);
1304
+ }
1274
1305
 
1275
- polyVecMatrixExpand(mat, rho);
1276
- polyVecLNTT(s1);
1277
- polyVecKNTT(s2);
1278
- polyVecKNTT(t0);
1306
+ polyVecMatrixExpand(mat, rho);
1307
+ polyVecLNTT(s1);
1308
+ polyVecKNTT(s2);
1309
+ polyVecKNTT(t0);
1310
+
1311
+ while (true) {
1312
+ polyVecLUniformGamma1(y, rhoPrime, nonce++);
1313
+ // Matrix-vector multiplication
1314
+ z.copy(y);
1315
+ polyVecLNTT(z);
1316
+ polyVecMatrixPointWiseMontgomery(w1, mat, z);
1317
+ polyVecKReduce(w1);
1318
+ polyVecKInvNTTToMont(w1);
1319
+
1320
+ // Decompose w and call the random oracle
1321
+ polyVecKCAddQ(w1);
1322
+ polyVecKDecompose(w1, w0, w1);
1323
+ polyVecKPackW1(sig, w1);
1324
+
1325
+ const cHash = shake256
1326
+ .create({})
1327
+ .update(mu)
1328
+ .update(sig.slice(0, K * PolyW1PackedBytes))
1329
+ .xof(SeedBytes);
1330
+ sig.set(cHash);
1331
+
1332
+ polyChallenge(cp, sig);
1333
+ polyNTT(cp);
1334
+
1335
+ // Compute z, reject if it reveals secret
1336
+ polyVecLPointWisePolyMontgomery(z, cp, s1);
1337
+ polyVecLInvNTTToMont(z);
1338
+ polyVecLAdd(z, z, y);
1339
+ polyVecLReduce(z);
1340
+ if (polyVecLChkNorm(z, GAMMA1 - BETA) !== 0) {
1341
+ continue;
1342
+ }
1279
1343
 
1280
- while (true) {
1281
- polyVecLUniformGamma1(y, rhoPrime, nonce++);
1282
- // Matrix-vector multiplication
1283
- z.copy(y);
1284
- polyVecLNTT(z);
1285
- polyVecMatrixPointWiseMontgomery(w1, mat, z);
1286
- polyVecKReduce(w1);
1287
- polyVecKInvNTTToMont(w1);
1288
-
1289
- // Decompose w and call the random oracle
1290
- polyVecKCAddQ(w1);
1291
- polyVecKDecompose(w1, w0, w1);
1292
- polyVecKPackW1(sig, w1);
1293
-
1294
- const cHash = shake256
1295
- .create({})
1296
- .update(mu)
1297
- .update(sig.slice(0, K * PolyW1PackedBytes))
1298
- .xof(SeedBytes);
1299
- sig.set(cHash);
1300
-
1301
- polyChallenge(cp, sig);
1302
- polyNTT(cp);
1303
-
1304
- // Compute z, reject if it reveals secret
1305
- polyVecLPointWisePolyMontgomery(z, cp, s1);
1306
- polyVecLInvNTTToMont(z);
1307
- polyVecLAdd(z, z, y);
1308
- polyVecLReduce(z);
1309
- if (polyVecLChkNorm(z, GAMMA1 - BETA) !== 0) {
1310
- continue;
1311
- }
1344
+ polyVecKPointWisePolyMontgomery(h, cp, s2);
1345
+ polyVecKInvNTTToMont(h);
1346
+ polyVecKSub(w0, w0, h);
1347
+ polyVecKReduce(w0);
1348
+ if (polyVecKChkNorm(w0, GAMMA2 - BETA) !== 0) {
1349
+ continue;
1350
+ }
1312
1351
 
1313
- polyVecKPointWisePolyMontgomery(h, cp, s2);
1314
- polyVecKInvNTTToMont(h);
1315
- polyVecKSub(w0, w0, h);
1316
- polyVecKReduce(w0);
1317
- if (polyVecKChkNorm(w0, GAMMA2 - BETA) !== 0) {
1318
- continue;
1319
- }
1352
+ polyVecKPointWisePolyMontgomery(h, cp, t0);
1353
+ polyVecKInvNTTToMont(h);
1354
+ polyVecKReduce(h);
1355
+ /* c8 ignore start */
1356
+ if (polyVecKChkNorm(h, GAMMA2) !== 0) {
1357
+ continue;
1358
+ }
1359
+ /* c8 ignore stop */
1320
1360
 
1321
- polyVecKPointWisePolyMontgomery(h, cp, t0);
1322
- polyVecKInvNTTToMont(h);
1323
- polyVecKReduce(h);
1324
- /* c8 ignore start */
1325
- if (polyVecKChkNorm(h, GAMMA2) !== 0) {
1326
- continue;
1327
- }
1328
- /* c8 ignore stop */
1361
+ polyVecKAdd(w0, w0, h);
1362
+ const n = polyVecKMakeHint(h, w0, w1);
1363
+ /* c8 ignore start */
1364
+ if (n > OMEGA) {
1365
+ continue;
1366
+ }
1367
+ /* c8 ignore stop */
1329
1368
 
1330
- polyVecKAdd(w0, w0, h);
1331
- const n = polyVecKMakeHint(h, w0, w1);
1332
- /* c8 ignore start */
1333
- if (n > OMEGA) {
1334
- continue;
1369
+ packSig(sig, sig, z, h);
1370
+ return 0;
1335
1371
  }
1336
- /* c8 ignore stop */
1337
-
1338
- packSig(sig, sig, z, h);
1339
- return 0;
1372
+ } finally {
1373
+ zeroize(key);
1374
+ zeroize(rhoPrime);
1375
+ for (let i = 0; i < L; i++) s1.vec[i].coeffs.fill(0);
1376
+ for (let i = 0; i < K; i++) s2.vec[i].coeffs.fill(0);
1377
+ for (let i = 0; i < K; i++) t0.vec[i].coeffs.fill(0);
1378
+ for (let i = 0; i < L; i++) y.vec[i].coeffs.fill(0);
1340
1379
  }
1341
1380
  }
1342
1381
 
@@ -1495,56 +1534,4 @@ function cryptoSignOpen(sm, pk) {
1495
1534
  return msg;
1496
1535
  }
1497
1536
 
1498
- /**
1499
- * Security utilities for Dilithium5
1500
- *
1501
- * IMPORTANT: JavaScript cannot guarantee secure memory zeroization.
1502
- * See SECURITY.md for details on limitations.
1503
- */
1504
-
1505
- /**
1506
- * Attempts to zero out a Uint8Array buffer.
1507
- *
1508
- * WARNING: This is a BEST-EFFORT operation. Due to JavaScript/JIT limitations:
1509
- * - The write may be optimized away if the buffer is unused afterward
1510
- * - Copies may exist in garbage collector memory
1511
- * - Data may have been swapped to disk
1512
- *
1513
- * For high-security applications, consider native implementations (go-qrllib)
1514
- * or hardware security modules.
1515
- *
1516
- * @param {Uint8Array} buffer - The buffer to zero
1517
- * @returns {void}
1518
- */
1519
- function zeroize(buffer) {
1520
- if (!(buffer instanceof Uint8Array)) {
1521
- throw new TypeError('zeroize requires a Uint8Array');
1522
- }
1523
- // Use fill(0) for zeroing - best effort
1524
- buffer.fill(0);
1525
- // Additional volatile-like access to discourage optimization
1526
- // (This is a hint to the JIT, not a guarantee)
1527
- if (buffer.length > 0 && buffer[0] !== 0) {
1528
- throw new Error('zeroize failed'); // Should never happen
1529
- }
1530
- }
1531
-
1532
- /**
1533
- * Checks if a buffer is all zeros.
1534
- * Uses constant-time comparison to avoid timing leaks.
1535
- *
1536
- * @param {Uint8Array} buffer - The buffer to check
1537
- * @returns {boolean} True if all bytes are zero
1538
- */
1539
- function isZero(buffer) {
1540
- if (!(buffer instanceof Uint8Array)) {
1541
- throw new TypeError('isZero requires a Uint8Array');
1542
- }
1543
- let acc = 0;
1544
- for (let i = 0; i < buffer.length; i++) {
1545
- acc |= buffer[i];
1546
- }
1547
- return acc === 0;
1548
- }
1549
-
1550
1537
  export { BETA, CRHBytes, CryptoBytes, CryptoPublicKeyBytes, CryptoSecretKeyBytes, D, ETA, GAMMA1, GAMMA2, K, KeccakState, L, N, OMEGA, Poly, PolyETAPackedBytes, PolyT0PackedBytes, PolyT1PackedBytes, PolyUniformETANBlocks, PolyUniformGamma1NBlocks, PolyUniformNBlocks, PolyVecHPackedBytes, PolyVecK, PolyVecL, PolyW1PackedBytes, PolyZPackedBytes, Q, QInv, SeedBytes, Shake128Rate, Shake256Rate, Stream128BlockBytes, Stream256BlockBytes, TAU, TRBytes, cAddQ, cryptoSign, cryptoSignKeypair, cryptoSignOpen, cryptoSignSignature, cryptoSignVerify, decompose, dilithiumShake128StreamInit, dilithiumShake256StreamInit, invNTTToMont, isZero, makeHint, montgomeryReduce, ntt, packPk, packSig, packSk, polyAdd, polyCAddQ, polyChallenge, polyChkNorm, polyDecompose, polyEtaPack, polyEtaUnpack, polyInvNTTToMont, polyMakeHint, polyNTT, polyPointWiseMontgomery, polyPower2round, polyReduce, polyShiftL, polySub, polyT0Pack, polyT0Unpack, polyT1Pack, polyT1Unpack, polyUniform, polyUniformEta, polyUniformGamma1, polyUseHint, polyVecKAdd, polyVecKCAddQ, polyVecKChkNorm, polyVecKDecompose, polyVecKInvNTTToMont, polyVecKMakeHint, polyVecKNTT, polyVecKPackW1, polyVecKPointWisePolyMontgomery, polyVecKPower2round, polyVecKReduce, polyVecKShiftL, polyVecKSub, polyVecKUniformEta, polyVecKUseHint, polyVecLAdd, polyVecLChkNorm, polyVecLInvNTTToMont, polyVecLNTT, polyVecLPointWiseAccMontgomery, polyVecLPointWisePolyMontgomery, polyVecLReduce, polyVecLUniformEta, polyVecLUniformGamma1, polyVecMatrixExpand, polyVecMatrixPointWiseMontgomery, polyW1Pack, polyZPack, polyZUnpack, power2round, reduce32, rejEta, rejUniform, shake128Absorb, shake128Finalize, shake128Init, shake128SqueezeBlocks, shake256Absorb, shake256Finalize, shake256Init, shake256SqueezeBlocks, unpackPk, unpackSig, unpackSk, useHint, zeroize, zetas };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theqrl/dilithium5",
3
- "version": "1.0.8",
3
+ "version": "1.1.0",
4
4
  "description": "Dilithium-5 cryptography",
5
5
  "keywords": [
6
6
  "dilithium",
@@ -50,20 +50,20 @@
50
50
  },
51
51
  "type": "module",
52
52
  "engines": {
53
- "node": ">=18.20.0"
53
+ "node": ">=20.19.0"
54
54
  },
55
55
  "devDependencies": {
56
- "@eslint/js": "^9.39.0",
56
+ "@eslint/js": "^10.0.1",
57
57
  "c8": "^10.1.3",
58
58
  "chai": "^6.2.2",
59
- "eslint": "^9.39.2",
59
+ "eslint": "^10.0.0",
60
60
  "eslint-config-prettier": "^10.1.8",
61
61
  "eslint-plugin-import-x": "^4.15.0",
62
62
  "eslint-plugin-prettier": "^5.5.4",
63
- "globals": "^17.0.0",
63
+ "globals": "^17.3.0",
64
64
  "mocha": "^11.7.5",
65
- "prettier": "^3.7.4",
66
- "rollup": "^4.55.1"
65
+ "prettier": "^3.8.1",
66
+ "rollup": "^4.57.1"
67
67
  },
68
68
  "dependencies": {
69
69
  "@noble/hashes": "^2.0.1"