@theqrl/dilithium5 1.0.4 → 1.0.6
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 +6 -4
- package/dist/cjs/dilithium5.js +140 -21
- package/dist/mjs/dilithium5.js +139 -21
- package/package.json +9 -5
- package/src/index.d.ts +5 -5
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ Generate a keypair from a seed.
|
|
|
64
64
|
|
|
65
65
|
Sign a message (combined mode: returns signature || message).
|
|
66
66
|
|
|
67
|
-
- `message`: `Uint8Array` - message
|
|
67
|
+
- `message`: `Uint8Array` or `string` - message bytes; if `string`, it must be hex only (optional `0x`, even length). Plain-text strings are not accepted.
|
|
68
68
|
- `sk`: `Uint8Array(4896)` - secret key
|
|
69
69
|
- `randomized`: `boolean` - `true` for hedged signing, `false` for deterministic
|
|
70
70
|
- Returns: `Uint8Array` containing signature + message
|
|
@@ -82,7 +82,7 @@ Verify and extract message from signed message.
|
|
|
82
82
|
Create a detached signature.
|
|
83
83
|
|
|
84
84
|
- `sig`: `Uint8Array(4595)` - output buffer for signature
|
|
85
|
-
- `message`: `Uint8Array` - message
|
|
85
|
+
- `message`: `Uint8Array` or `string` - message bytes; if `string`, it must be hex only (optional `0x`, even length). Plain-text strings are not accepted.
|
|
86
86
|
- `sk`: `Uint8Array(4896)` - secret key
|
|
87
87
|
- `randomized`: `boolean` - `true` for hedged, `false` for deterministic
|
|
88
88
|
- Returns: `0` on success
|
|
@@ -92,10 +92,12 @@ Create a detached signature.
|
|
|
92
92
|
Verify a detached signature.
|
|
93
93
|
|
|
94
94
|
- `sig`: `Uint8Array(4595)` - signature to verify
|
|
95
|
-
- `message`: `Uint8Array` - original message
|
|
95
|
+
- `message`: `Uint8Array` or `string` - original message bytes; if `string`, it must be hex only (optional `0x`, even length). Plain-text strings are not accepted.
|
|
96
96
|
- `pk`: `Uint8Array(2592)` - public key
|
|
97
97
|
- Returns: `true` if valid, `false` otherwise
|
|
98
98
|
|
|
99
|
+
**Note:** To sign or verify plain text, convert it to bytes (e.g., `new TextEncoder().encode('Hello')`). String inputs are interpreted as hex only.
|
|
100
|
+
|
|
99
101
|
#### `zeroize(buffer)`
|
|
100
102
|
|
|
101
103
|
Zero out sensitive data (best-effort, see security notes).
|
|
@@ -139,7 +141,7 @@ See [SECURITY.md](../../SECURITY.md) for important information about:
|
|
|
139
141
|
|
|
140
142
|
## Requirements
|
|
141
143
|
|
|
142
|
-
- Node.js 18+ or modern browsers with ES2020 support
|
|
144
|
+
- Node.js 18.20+ or modern browsers with ES2020 support
|
|
143
145
|
- Full TypeScript definitions included
|
|
144
146
|
|
|
145
147
|
## License
|
package/dist/cjs/dilithium5.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var sha3_js = require('@noble/hashes/sha3.js');
|
|
4
|
-
var
|
|
4
|
+
var utils_js = require('@noble/hashes/utils.js');
|
|
5
5
|
|
|
6
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
6
7
|
const Shake128Rate = 168;
|
|
7
8
|
const Shake256Rate = 136;
|
|
8
9
|
const Stream128BlockBytes = Shake128Rate;
|
|
@@ -367,6 +368,8 @@ function polyUniform(a, seed, nonce) {
|
|
|
367
368
|
|
|
368
369
|
let ctr = rejUniform(a.coeffs, 0, N, buf, bufLen);
|
|
369
370
|
|
|
371
|
+
// Note: With current parameters, needing extra blocks is vanishingly unlikely.
|
|
372
|
+
/* c8 ignore start */
|
|
370
373
|
while (ctr < N) {
|
|
371
374
|
off = bufLen % 3;
|
|
372
375
|
for (let i = 0; i < off; ++i) buf[i] = buf[bufLen - off + i];
|
|
@@ -375,6 +378,7 @@ function polyUniform(a, seed, nonce) {
|
|
|
375
378
|
bufLen = Stream128BlockBytes + off;
|
|
376
379
|
ctr += rejUniform(a.coeffs, ctr, N - ctr, buf, bufLen);
|
|
377
380
|
}
|
|
381
|
+
/* c8 ignore stop */
|
|
378
382
|
}
|
|
379
383
|
|
|
380
384
|
function rejEta(aP, aOffset, len, buf, bufLen) {
|
|
@@ -468,10 +472,13 @@ function polyChallenge(cP, seed) {
|
|
|
468
472
|
}
|
|
469
473
|
for (let i = N - TAU; i < N; ++i) {
|
|
470
474
|
do {
|
|
475
|
+
// Note: Re-squeezing here is extremely unlikely with TAU=60.
|
|
476
|
+
/* c8 ignore start */
|
|
471
477
|
if (pos >= Shake256Rate) {
|
|
472
478
|
shake256SqueezeBlocks(buf, 0, 1, state);
|
|
473
479
|
pos = 0;
|
|
474
480
|
}
|
|
481
|
+
/* c8 ignore stop */
|
|
475
482
|
|
|
476
483
|
b = buf[pos++];
|
|
477
484
|
} while (b > i);
|
|
@@ -1016,21 +1023,118 @@ function unpackSig(cP, z, hP, sig) {
|
|
|
1016
1023
|
return 0;
|
|
1017
1024
|
}
|
|
1018
1025
|
|
|
1019
|
-
const
|
|
1026
|
+
const MAX_BYTES = 65536;
|
|
1027
|
+
const MAX_UINT32 = 0xffffffff;
|
|
1028
|
+
|
|
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
|
+
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
|
+
|
|
1066
|
+
return null;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
function randomBytes(size) {
|
|
1070
|
+
if (!Number.isSafeInteger(size) || size < 0) {
|
|
1071
|
+
throw new RangeError('size must be a non-negative integer');
|
|
1072
|
+
}
|
|
1073
|
+
if (size > MAX_UINT32) {
|
|
1074
|
+
throw new RangeError('requested too many random bytes');
|
|
1075
|
+
}
|
|
1076
|
+
if (size === 0) return new Uint8Array(0);
|
|
1077
|
+
|
|
1078
|
+
const cryptoObj = getWebCrypto();
|
|
1079
|
+
if (cryptoObj && typeof cryptoObj.getRandomValues === 'function') {
|
|
1080
|
+
const out = new Uint8Array(size);
|
|
1081
|
+
for (let i = 0; i < size; i += MAX_BYTES) {
|
|
1082
|
+
cryptoObj.getRandomValues(out.subarray(i, Math.min(size, i + MAX_BYTES)));
|
|
1083
|
+
}
|
|
1084
|
+
return out;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
const nodeRandomBytes = getNodeRandomBytes();
|
|
1088
|
+
if (nodeRandomBytes) {
|
|
1089
|
+
return nodeRandomBytes(size);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
throw new Error('Secure random number generation is not supported by this environment');
|
|
1093
|
+
}
|
|
1020
1094
|
|
|
1021
1095
|
/**
|
|
1022
|
-
* Convert hex string to Uint8Array
|
|
1023
|
-
*
|
|
1024
|
-
*
|
|
1096
|
+
* Convert hex string to Uint8Array with strict validation.
|
|
1097
|
+
*
|
|
1098
|
+
* NOTE: This function accepts multiple hex formats (with/without 0x prefix,
|
|
1099
|
+
* leading/trailing whitespace). While user-friendly, this flexibility could
|
|
1100
|
+
* mask input errors. Applications requiring strict format validation should
|
|
1101
|
+
* validate hex format before calling cryptographic functions, e.g.:
|
|
1102
|
+
* - Reject strings with 0x prefix if raw hex is expected
|
|
1103
|
+
* - Reject strings with whitespace
|
|
1104
|
+
* - Enforce consistent casing (lowercase/uppercase)
|
|
1105
|
+
*
|
|
1106
|
+
* @param {string} hex - Hex string (optional 0x prefix, even length).
|
|
1107
|
+
* @returns {Uint8Array} Decoded bytes.
|
|
1025
1108
|
* @private
|
|
1026
1109
|
*/
|
|
1027
1110
|
function hexToBytes(hex) {
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1111
|
+
/* c8 ignore start */
|
|
1112
|
+
if (typeof hex !== 'string') {
|
|
1113
|
+
throw new Error('message must be a hex string');
|
|
1114
|
+
}
|
|
1115
|
+
/* c8 ignore stop */
|
|
1116
|
+
let clean = hex.trim();
|
|
1117
|
+
// Accepts both "0x..." and raw hex formats for convenience
|
|
1118
|
+
if (clean.startsWith('0x') || clean.startsWith('0X')) {
|
|
1119
|
+
clean = clean.slice(2);
|
|
1120
|
+
}
|
|
1121
|
+
if (clean.length % 2 !== 0) {
|
|
1122
|
+
throw new Error('hex string must have an even length');
|
|
1123
|
+
}
|
|
1124
|
+
if (!/^[0-9a-fA-F]*$/.test(clean)) {
|
|
1125
|
+
throw new Error('hex string contains non-hex characters');
|
|
1126
|
+
}
|
|
1127
|
+
return utils_js.hexToBytes(clean);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
function messageToBytes(message) {
|
|
1131
|
+
if (typeof message === 'string') {
|
|
1132
|
+
return hexToBytes(message);
|
|
1133
|
+
}
|
|
1134
|
+
if (message instanceof Uint8Array) {
|
|
1135
|
+
return message;
|
|
1032
1136
|
}
|
|
1033
|
-
|
|
1137
|
+
throw new Error('message must be Uint8Array or hex string');
|
|
1034
1138
|
}
|
|
1035
1139
|
|
|
1036
1140
|
/**
|
|
@@ -1122,7 +1226,7 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1122
1226
|
* Uses the Dilithium-5 (Round 3) signing algorithm with rejection sampling.
|
|
1123
1227
|
*
|
|
1124
1228
|
* @param {Uint8Array} sig - Output buffer for signature (must be at least CryptoBytes = 4595 bytes)
|
|
1125
|
-
* @param {string|Uint8Array} m - Message to sign (hex string or Uint8Array)
|
|
1229
|
+
* @param {string|Uint8Array} m - Message to sign (hex string, optional 0x prefix, or Uint8Array)
|
|
1126
1230
|
* @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1127
1231
|
* @param {boolean} randomizedSigning - If true, use random nonce for hedged signing.
|
|
1128
1232
|
* If false, use deterministic nonce derived from message and key.
|
|
@@ -1134,10 +1238,15 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1134
1238
|
* cryptoSignSignature(sig, message, sk, false);
|
|
1135
1239
|
*/
|
|
1136
1240
|
function cryptoSignSignature(sig, m, sk, randomizedSigning) {
|
|
1241
|
+
if (!sig || sig.length < CryptoBytes) {
|
|
1242
|
+
throw new Error(`sig must be at least ${CryptoBytes} bytes`);
|
|
1243
|
+
}
|
|
1137
1244
|
if (sk.length !== CryptoSecretKeyBytes) {
|
|
1138
1245
|
throw new Error(`invalid sk length ${sk.length} | Expected length ${CryptoSecretKeyBytes}`);
|
|
1139
1246
|
}
|
|
1140
1247
|
|
|
1248
|
+
const mBytes = messageToBytes(m);
|
|
1249
|
+
|
|
1141
1250
|
const rho = new Uint8Array(SeedBytes);
|
|
1142
1251
|
const tr = new Uint8Array(TRBytes);
|
|
1143
1252
|
const key = new Uint8Array(SeedBytes);
|
|
@@ -1158,8 +1267,6 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
|
|
|
1158
1267
|
|
|
1159
1268
|
unpackSk(rho, tr, key, t0, s1, s2, sk);
|
|
1160
1269
|
|
|
1161
|
-
// Convert hex message to bytes
|
|
1162
|
-
const mBytes = typeof m === 'string' ? hexToBytes(m) : m;
|
|
1163
1270
|
const mu = sha3_js.shake256.create({}).update(tr).update(mBytes).xof(CRHBytes);
|
|
1164
1271
|
|
|
1165
1272
|
if (randomizedSigning) {
|
|
@@ -1217,15 +1324,19 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
|
|
|
1217
1324
|
polyVecKPointWisePolyMontgomery(h, cp, t0);
|
|
1218
1325
|
polyVecKInvNTTToMont(h);
|
|
1219
1326
|
polyVecKReduce(h);
|
|
1327
|
+
/* c8 ignore start */
|
|
1220
1328
|
if (polyVecKChkNorm(h, GAMMA2) !== 0) {
|
|
1221
1329
|
continue;
|
|
1222
1330
|
}
|
|
1331
|
+
/* c8 ignore stop */
|
|
1223
1332
|
|
|
1224
1333
|
polyVecKAdd(w0, w0, h);
|
|
1225
1334
|
const n = polyVecKMakeHint(h, w0, w1);
|
|
1335
|
+
/* c8 ignore start */
|
|
1226
1336
|
if (n > OMEGA) {
|
|
1227
1337
|
continue;
|
|
1228
1338
|
}
|
|
1339
|
+
/* c8 ignore stop */
|
|
1229
1340
|
|
|
1230
1341
|
packSig(sig, sig, z, h);
|
|
1231
1342
|
return 0;
|
|
@@ -1238,7 +1349,7 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
|
|
|
1238
1349
|
* This is the combined sign operation that produces a "signed message" containing
|
|
1239
1350
|
* both the signature and the original message (signature || message).
|
|
1240
1351
|
*
|
|
1241
|
-
* @param {Uint8Array} msg - Message to sign
|
|
1352
|
+
* @param {string|Uint8Array} msg - Message to sign (hex string, optional 0x prefix, or Uint8Array)
|
|
1242
1353
|
* @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1243
1354
|
* @param {boolean} randomizedSigning - If true, use random nonce; if false, deterministic
|
|
1244
1355
|
* @returns {Uint8Array} Signed message (CryptoBytes + msg.length bytes)
|
|
@@ -1249,16 +1360,20 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
|
|
|
1249
1360
|
* // signedMsg contains: signature (4595 bytes) || message
|
|
1250
1361
|
*/
|
|
1251
1362
|
function cryptoSign(msg, sk, randomizedSigning) {
|
|
1252
|
-
const
|
|
1253
|
-
|
|
1363
|
+
const msgBytes = messageToBytes(msg);
|
|
1364
|
+
|
|
1365
|
+
const sm = new Uint8Array(CryptoBytes + msgBytes.length);
|
|
1366
|
+
const mLen = msgBytes.length;
|
|
1254
1367
|
for (let i = 0; i < mLen; ++i) {
|
|
1255
|
-
sm[CryptoBytes + mLen - 1 - i] =
|
|
1368
|
+
sm[CryptoBytes + mLen - 1 - i] = msgBytes[mLen - 1 - i];
|
|
1256
1369
|
}
|
|
1257
|
-
const result = cryptoSignSignature(sm,
|
|
1370
|
+
const result = cryptoSignSignature(sm, msgBytes, sk, randomizedSigning);
|
|
1258
1371
|
|
|
1372
|
+
/* c8 ignore start */
|
|
1259
1373
|
if (result !== 0) {
|
|
1260
1374
|
throw new Error('failed to sign');
|
|
1261
1375
|
}
|
|
1376
|
+
/* c8 ignore stop */
|
|
1262
1377
|
return sm;
|
|
1263
1378
|
}
|
|
1264
1379
|
|
|
@@ -1268,7 +1383,7 @@ function cryptoSign(msg, sk, randomizedSigning) {
|
|
|
1268
1383
|
* Performs constant-time verification to prevent timing side-channel attacks.
|
|
1269
1384
|
*
|
|
1270
1385
|
* @param {Uint8Array} sig - Signature to verify (must be CryptoBytes = 4595 bytes)
|
|
1271
|
-
* @param {string|Uint8Array} m - Message that was signed (hex string or Uint8Array)
|
|
1386
|
+
* @param {string|Uint8Array} m - Message that was signed (hex string, optional 0x prefix, or Uint8Array)
|
|
1272
1387
|
* @param {Uint8Array} pk - Public key (must be CryptoPublicKeyBytes = 2592 bytes)
|
|
1273
1388
|
* @returns {boolean} true if signature is valid, false otherwise
|
|
1274
1389
|
*
|
|
@@ -1311,8 +1426,12 @@ function cryptoSignVerify(sig, m, pk) {
|
|
|
1311
1426
|
const tr = sha3_js.shake256.create({}).update(pk).xof(TRBytes);
|
|
1312
1427
|
mu.set(tr);
|
|
1313
1428
|
|
|
1314
|
-
|
|
1315
|
-
|
|
1429
|
+
let mBytes;
|
|
1430
|
+
try {
|
|
1431
|
+
mBytes = messageToBytes(m);
|
|
1432
|
+
} catch {
|
|
1433
|
+
return false;
|
|
1434
|
+
}
|
|
1316
1435
|
const muFull = sha3_js.shake256.create({}).update(mu.slice(0, TRBytes)).update(mBytes).xof(CRHBytes);
|
|
1317
1436
|
mu.set(muFull);
|
|
1318
1437
|
|
package/dist/mjs/dilithium5.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { shake128, shake256 } from '@noble/hashes/sha3.js';
|
|
2
|
-
import
|
|
2
|
+
import { hexToBytes as hexToBytes$1 } from '@noble/hashes/utils.js';
|
|
3
3
|
|
|
4
4
|
const Shake128Rate = 168;
|
|
5
5
|
const Shake256Rate = 136;
|
|
@@ -365,6 +365,8 @@ function polyUniform(a, seed, nonce) {
|
|
|
365
365
|
|
|
366
366
|
let ctr = rejUniform(a.coeffs, 0, N, buf, bufLen);
|
|
367
367
|
|
|
368
|
+
// Note: With current parameters, needing extra blocks is vanishingly unlikely.
|
|
369
|
+
/* c8 ignore start */
|
|
368
370
|
while (ctr < N) {
|
|
369
371
|
off = bufLen % 3;
|
|
370
372
|
for (let i = 0; i < off; ++i) buf[i] = buf[bufLen - off + i];
|
|
@@ -373,6 +375,7 @@ function polyUniform(a, seed, nonce) {
|
|
|
373
375
|
bufLen = Stream128BlockBytes + off;
|
|
374
376
|
ctr += rejUniform(a.coeffs, ctr, N - ctr, buf, bufLen);
|
|
375
377
|
}
|
|
378
|
+
/* c8 ignore stop */
|
|
376
379
|
}
|
|
377
380
|
|
|
378
381
|
function rejEta(aP, aOffset, len, buf, bufLen) {
|
|
@@ -466,10 +469,13 @@ function polyChallenge(cP, seed) {
|
|
|
466
469
|
}
|
|
467
470
|
for (let i = N - TAU; i < N; ++i) {
|
|
468
471
|
do {
|
|
472
|
+
// Note: Re-squeezing here is extremely unlikely with TAU=60.
|
|
473
|
+
/* c8 ignore start */
|
|
469
474
|
if (pos >= Shake256Rate) {
|
|
470
475
|
shake256SqueezeBlocks(buf, 0, 1, state);
|
|
471
476
|
pos = 0;
|
|
472
477
|
}
|
|
478
|
+
/* c8 ignore stop */
|
|
473
479
|
|
|
474
480
|
b = buf[pos++];
|
|
475
481
|
} while (b > i);
|
|
@@ -1014,21 +1020,118 @@ function unpackSig(cP, z, hP, sig) {
|
|
|
1014
1020
|
return 0;
|
|
1015
1021
|
}
|
|
1016
1022
|
|
|
1017
|
-
const
|
|
1023
|
+
const MAX_BYTES = 65536;
|
|
1024
|
+
const MAX_UINT32 = 0xffffffff;
|
|
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
|
+
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
|
+
|
|
1063
|
+
return null;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
function randomBytes(size) {
|
|
1067
|
+
if (!Number.isSafeInteger(size) || size < 0) {
|
|
1068
|
+
throw new RangeError('size must be a non-negative integer');
|
|
1069
|
+
}
|
|
1070
|
+
if (size > MAX_UINT32) {
|
|
1071
|
+
throw new RangeError('requested too many random bytes');
|
|
1072
|
+
}
|
|
1073
|
+
if (size === 0) return new Uint8Array(0);
|
|
1074
|
+
|
|
1075
|
+
const cryptoObj = getWebCrypto();
|
|
1076
|
+
if (cryptoObj && typeof cryptoObj.getRandomValues === 'function') {
|
|
1077
|
+
const out = new Uint8Array(size);
|
|
1078
|
+
for (let i = 0; i < size; i += MAX_BYTES) {
|
|
1079
|
+
cryptoObj.getRandomValues(out.subarray(i, Math.min(size, i + MAX_BYTES)));
|
|
1080
|
+
}
|
|
1081
|
+
return out;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
const nodeRandomBytes = getNodeRandomBytes();
|
|
1085
|
+
if (nodeRandomBytes) {
|
|
1086
|
+
return nodeRandomBytes(size);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
throw new Error('Secure random number generation is not supported by this environment');
|
|
1090
|
+
}
|
|
1018
1091
|
|
|
1019
1092
|
/**
|
|
1020
|
-
* Convert hex string to Uint8Array
|
|
1021
|
-
*
|
|
1022
|
-
*
|
|
1093
|
+
* Convert hex string to Uint8Array with strict validation.
|
|
1094
|
+
*
|
|
1095
|
+
* NOTE: This function accepts multiple hex formats (with/without 0x prefix,
|
|
1096
|
+
* leading/trailing whitespace). While user-friendly, this flexibility could
|
|
1097
|
+
* mask input errors. Applications requiring strict format validation should
|
|
1098
|
+
* validate hex format before calling cryptographic functions, e.g.:
|
|
1099
|
+
* - Reject strings with 0x prefix if raw hex is expected
|
|
1100
|
+
* - Reject strings with whitespace
|
|
1101
|
+
* - Enforce consistent casing (lowercase/uppercase)
|
|
1102
|
+
*
|
|
1103
|
+
* @param {string} hex - Hex string (optional 0x prefix, even length).
|
|
1104
|
+
* @returns {Uint8Array} Decoded bytes.
|
|
1023
1105
|
* @private
|
|
1024
1106
|
*/
|
|
1025
1107
|
function hexToBytes(hex) {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1108
|
+
/* c8 ignore start */
|
|
1109
|
+
if (typeof hex !== 'string') {
|
|
1110
|
+
throw new Error('message must be a hex string');
|
|
1111
|
+
}
|
|
1112
|
+
/* c8 ignore stop */
|
|
1113
|
+
let clean = hex.trim();
|
|
1114
|
+
// Accepts both "0x..." and raw hex formats for convenience
|
|
1115
|
+
if (clean.startsWith('0x') || clean.startsWith('0X')) {
|
|
1116
|
+
clean = clean.slice(2);
|
|
1117
|
+
}
|
|
1118
|
+
if (clean.length % 2 !== 0) {
|
|
1119
|
+
throw new Error('hex string must have an even length');
|
|
1120
|
+
}
|
|
1121
|
+
if (!/^[0-9a-fA-F]*$/.test(clean)) {
|
|
1122
|
+
throw new Error('hex string contains non-hex characters');
|
|
1123
|
+
}
|
|
1124
|
+
return hexToBytes$1(clean);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function messageToBytes(message) {
|
|
1128
|
+
if (typeof message === 'string') {
|
|
1129
|
+
return hexToBytes(message);
|
|
1130
|
+
}
|
|
1131
|
+
if (message instanceof Uint8Array) {
|
|
1132
|
+
return message;
|
|
1030
1133
|
}
|
|
1031
|
-
|
|
1134
|
+
throw new Error('message must be Uint8Array or hex string');
|
|
1032
1135
|
}
|
|
1033
1136
|
|
|
1034
1137
|
/**
|
|
@@ -1120,7 +1223,7 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1120
1223
|
* Uses the Dilithium-5 (Round 3) signing algorithm with rejection sampling.
|
|
1121
1224
|
*
|
|
1122
1225
|
* @param {Uint8Array} sig - Output buffer for signature (must be at least CryptoBytes = 4595 bytes)
|
|
1123
|
-
* @param {string|Uint8Array} m - Message to sign (hex string or Uint8Array)
|
|
1226
|
+
* @param {string|Uint8Array} m - Message to sign (hex string, optional 0x prefix, or Uint8Array)
|
|
1124
1227
|
* @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1125
1228
|
* @param {boolean} randomizedSigning - If true, use random nonce for hedged signing.
|
|
1126
1229
|
* If false, use deterministic nonce derived from message and key.
|
|
@@ -1132,10 +1235,15 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1132
1235
|
* cryptoSignSignature(sig, message, sk, false);
|
|
1133
1236
|
*/
|
|
1134
1237
|
function cryptoSignSignature(sig, m, sk, randomizedSigning) {
|
|
1238
|
+
if (!sig || sig.length < CryptoBytes) {
|
|
1239
|
+
throw new Error(`sig must be at least ${CryptoBytes} bytes`);
|
|
1240
|
+
}
|
|
1135
1241
|
if (sk.length !== CryptoSecretKeyBytes) {
|
|
1136
1242
|
throw new Error(`invalid sk length ${sk.length} | Expected length ${CryptoSecretKeyBytes}`);
|
|
1137
1243
|
}
|
|
1138
1244
|
|
|
1245
|
+
const mBytes = messageToBytes(m);
|
|
1246
|
+
|
|
1139
1247
|
const rho = new Uint8Array(SeedBytes);
|
|
1140
1248
|
const tr = new Uint8Array(TRBytes);
|
|
1141
1249
|
const key = new Uint8Array(SeedBytes);
|
|
@@ -1156,8 +1264,6 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
|
|
|
1156
1264
|
|
|
1157
1265
|
unpackSk(rho, tr, key, t0, s1, s2, sk);
|
|
1158
1266
|
|
|
1159
|
-
// Convert hex message to bytes
|
|
1160
|
-
const mBytes = typeof m === 'string' ? hexToBytes(m) : m;
|
|
1161
1267
|
const mu = shake256.create({}).update(tr).update(mBytes).xof(CRHBytes);
|
|
1162
1268
|
|
|
1163
1269
|
if (randomizedSigning) {
|
|
@@ -1215,15 +1321,19 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
|
|
|
1215
1321
|
polyVecKPointWisePolyMontgomery(h, cp, t0);
|
|
1216
1322
|
polyVecKInvNTTToMont(h);
|
|
1217
1323
|
polyVecKReduce(h);
|
|
1324
|
+
/* c8 ignore start */
|
|
1218
1325
|
if (polyVecKChkNorm(h, GAMMA2) !== 0) {
|
|
1219
1326
|
continue;
|
|
1220
1327
|
}
|
|
1328
|
+
/* c8 ignore stop */
|
|
1221
1329
|
|
|
1222
1330
|
polyVecKAdd(w0, w0, h);
|
|
1223
1331
|
const n = polyVecKMakeHint(h, w0, w1);
|
|
1332
|
+
/* c8 ignore start */
|
|
1224
1333
|
if (n > OMEGA) {
|
|
1225
1334
|
continue;
|
|
1226
1335
|
}
|
|
1336
|
+
/* c8 ignore stop */
|
|
1227
1337
|
|
|
1228
1338
|
packSig(sig, sig, z, h);
|
|
1229
1339
|
return 0;
|
|
@@ -1236,7 +1346,7 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
|
|
|
1236
1346
|
* This is the combined sign operation that produces a "signed message" containing
|
|
1237
1347
|
* both the signature and the original message (signature || message).
|
|
1238
1348
|
*
|
|
1239
|
-
* @param {Uint8Array} msg - Message to sign
|
|
1349
|
+
* @param {string|Uint8Array} msg - Message to sign (hex string, optional 0x prefix, or Uint8Array)
|
|
1240
1350
|
* @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1241
1351
|
* @param {boolean} randomizedSigning - If true, use random nonce; if false, deterministic
|
|
1242
1352
|
* @returns {Uint8Array} Signed message (CryptoBytes + msg.length bytes)
|
|
@@ -1247,16 +1357,20 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
|
|
|
1247
1357
|
* // signedMsg contains: signature (4595 bytes) || message
|
|
1248
1358
|
*/
|
|
1249
1359
|
function cryptoSign(msg, sk, randomizedSigning) {
|
|
1250
|
-
const
|
|
1251
|
-
|
|
1360
|
+
const msgBytes = messageToBytes(msg);
|
|
1361
|
+
|
|
1362
|
+
const sm = new Uint8Array(CryptoBytes + msgBytes.length);
|
|
1363
|
+
const mLen = msgBytes.length;
|
|
1252
1364
|
for (let i = 0; i < mLen; ++i) {
|
|
1253
|
-
sm[CryptoBytes + mLen - 1 - i] =
|
|
1365
|
+
sm[CryptoBytes + mLen - 1 - i] = msgBytes[mLen - 1 - i];
|
|
1254
1366
|
}
|
|
1255
|
-
const result = cryptoSignSignature(sm,
|
|
1367
|
+
const result = cryptoSignSignature(sm, msgBytes, sk, randomizedSigning);
|
|
1256
1368
|
|
|
1369
|
+
/* c8 ignore start */
|
|
1257
1370
|
if (result !== 0) {
|
|
1258
1371
|
throw new Error('failed to sign');
|
|
1259
1372
|
}
|
|
1373
|
+
/* c8 ignore stop */
|
|
1260
1374
|
return sm;
|
|
1261
1375
|
}
|
|
1262
1376
|
|
|
@@ -1266,7 +1380,7 @@ function cryptoSign(msg, sk, randomizedSigning) {
|
|
|
1266
1380
|
* Performs constant-time verification to prevent timing side-channel attacks.
|
|
1267
1381
|
*
|
|
1268
1382
|
* @param {Uint8Array} sig - Signature to verify (must be CryptoBytes = 4595 bytes)
|
|
1269
|
-
* @param {string|Uint8Array} m - Message that was signed (hex string or Uint8Array)
|
|
1383
|
+
* @param {string|Uint8Array} m - Message that was signed (hex string, optional 0x prefix, or Uint8Array)
|
|
1270
1384
|
* @param {Uint8Array} pk - Public key (must be CryptoPublicKeyBytes = 2592 bytes)
|
|
1271
1385
|
* @returns {boolean} true if signature is valid, false otherwise
|
|
1272
1386
|
*
|
|
@@ -1309,8 +1423,12 @@ function cryptoSignVerify(sig, m, pk) {
|
|
|
1309
1423
|
const tr = shake256.create({}).update(pk).xof(TRBytes);
|
|
1310
1424
|
mu.set(tr);
|
|
1311
1425
|
|
|
1312
|
-
|
|
1313
|
-
|
|
1426
|
+
let mBytes;
|
|
1427
|
+
try {
|
|
1428
|
+
mBytes = messageToBytes(m);
|
|
1429
|
+
} catch {
|
|
1430
|
+
return false;
|
|
1431
|
+
}
|
|
1314
1432
|
const muFull = shake256.create({}).update(mu.slice(0, TRBytes)).update(mBytes).xof(CRHBytes);
|
|
1315
1433
|
mu.set(muFull);
|
|
1316
1434
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theqrl/dilithium5",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Dilithium-5 cryptography",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dilithium",
|
|
@@ -31,10 +31,12 @@
|
|
|
31
31
|
"url": "git+https://github.com/theQRL/qrypto.js.git"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
|
-
"test": "../../node_modules/mocha/bin/mocha.js --timeout 10000",
|
|
34
|
+
"test": "../../node_modules/mocha/bin/mocha.js --require ../../scripts/node-test-setup.cjs --timeout 10000",
|
|
35
|
+
"test:browser": "playwright test",
|
|
35
36
|
"build": "rollup src/index.js --file ./dist/cjs/dilithium5.js --format cjs && rollup src/index.js --file ./dist/mjs/dilithium5.js --format esm && ./fixup",
|
|
36
37
|
"lint-check": "eslint 'src/**/*.js' 'test/**/*.js'",
|
|
37
38
|
"lint": "eslint --fix 'src/**/*.js' 'test/**/*.js'",
|
|
39
|
+
"coverage": "c8 npm run test",
|
|
38
40
|
"report-coverage": "c8 --reporter=text-lcov npm run test > coverage.lcov"
|
|
39
41
|
},
|
|
40
42
|
"bugs": {
|
|
@@ -47,6 +49,9 @@
|
|
|
47
49
|
}
|
|
48
50
|
},
|
|
49
51
|
"type": "module",
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=18.20.0"
|
|
54
|
+
},
|
|
50
55
|
"devDependencies": {
|
|
51
56
|
"@eslint/js": "^9.39.0",
|
|
52
57
|
"c8": "^10.1.3",
|
|
@@ -55,13 +60,12 @@
|
|
|
55
60
|
"eslint-config-prettier": "^10.1.8",
|
|
56
61
|
"eslint-plugin-import-x": "^4.15.0",
|
|
57
62
|
"eslint-plugin-prettier": "^5.5.4",
|
|
58
|
-
"globals": "^
|
|
63
|
+
"globals": "^17.0.0",
|
|
59
64
|
"mocha": "^11.7.5",
|
|
60
65
|
"prettier": "^3.7.4",
|
|
61
66
|
"rollup": "^4.55.1"
|
|
62
67
|
},
|
|
63
68
|
"dependencies": {
|
|
64
|
-
"@noble/hashes": "^2.0.1"
|
|
65
|
-
"randombytes": "^2.1.0"
|
|
69
|
+
"@noble/hashes": "^2.0.1"
|
|
66
70
|
}
|
|
67
71
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -56,7 +56,7 @@ export function cryptoSignKeypair(
|
|
|
56
56
|
/**
|
|
57
57
|
* Create a signature for a message
|
|
58
58
|
* @param sig - Output buffer for signature (must be CryptoBytes length minimum)
|
|
59
|
-
* @param m - Message to sign (hex
|
|
59
|
+
* @param m - Message to sign (hex string or Uint8Array; strings are parsed as hex only)
|
|
60
60
|
* @param sk - Secret key
|
|
61
61
|
* @param randomizedSigning - If true, use random nonce; if false, deterministic
|
|
62
62
|
* @returns 0 on success
|
|
@@ -64,7 +64,7 @@ export function cryptoSignKeypair(
|
|
|
64
64
|
*/
|
|
65
65
|
export function cryptoSignSignature(
|
|
66
66
|
sig: Uint8Array,
|
|
67
|
-
m: string,
|
|
67
|
+
m: Uint8Array | string,
|
|
68
68
|
sk: Uint8Array,
|
|
69
69
|
randomizedSigning: boolean
|
|
70
70
|
): number;
|
|
@@ -78,7 +78,7 @@ export function cryptoSignSignature(
|
|
|
78
78
|
* @throws Error if signing fails
|
|
79
79
|
*/
|
|
80
80
|
export function cryptoSign(
|
|
81
|
-
msg: Uint8Array,
|
|
81
|
+
msg: Uint8Array | string,
|
|
82
82
|
sk: Uint8Array,
|
|
83
83
|
randomizedSigning: boolean
|
|
84
84
|
): Uint8Array;
|
|
@@ -86,13 +86,13 @@ export function cryptoSign(
|
|
|
86
86
|
/**
|
|
87
87
|
* Verify a signature
|
|
88
88
|
* @param sig - Signature to verify
|
|
89
|
-
* @param m - Message that was signed (hex
|
|
89
|
+
* @param m - Message that was signed (hex string or Uint8Array; strings are parsed as hex only)
|
|
90
90
|
* @param pk - Public key
|
|
91
91
|
* @returns true if signature is valid, false otherwise
|
|
92
92
|
*/
|
|
93
93
|
export function cryptoSignVerify(
|
|
94
94
|
sig: Uint8Array,
|
|
95
|
-
m: string,
|
|
95
|
+
m: Uint8Array | string,
|
|
96
96
|
pk: Uint8Array
|
|
97
97
|
): boolean;
|
|
98
98
|
|