@theqrl/mldsa87 1.0.5 → 1.0.7
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/LICENSE +1 -0
- package/README.md +6 -4
- package/dist/cjs/mldsa87.js +89 -12
- package/dist/mjs/mldsa87.js +88 -12
- package/package.json +8 -4
- package/src/index.d.ts +2 -2
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ Generate a keypair from a seed.
|
|
|
81
81
|
|
|
82
82
|
Sign a message (combined mode: returns signature || message).
|
|
83
83
|
|
|
84
|
-
- `message`: `Uint8Array` - message
|
|
84
|
+
- `message`: `Uint8Array` or `string` - message bytes; if `string`, it must be hex only (optional `0x`, even length). Plain-text strings are not accepted.
|
|
85
85
|
- `sk`: `Uint8Array(4896)` - secret key
|
|
86
86
|
- `randomized`: `boolean` - `true` for hedged signing, `false` for deterministic
|
|
87
87
|
- `context`: `Uint8Array` (optional) - context string, 0-255 bytes. Default: `"ZOND"`
|
|
@@ -101,7 +101,7 @@ Verify and extract message from signed message.
|
|
|
101
101
|
Create a detached signature.
|
|
102
102
|
|
|
103
103
|
- `sig`: `Uint8Array(4627)` - output buffer for signature
|
|
104
|
-
- `message`: `Uint8Array` - message
|
|
104
|
+
- `message`: `Uint8Array` or `string` - message bytes; if `string`, it must be hex only (optional `0x`, even length). Plain-text strings are not accepted.
|
|
105
105
|
- `sk`: `Uint8Array(4896)` - secret key
|
|
106
106
|
- `randomized`: `boolean` - `true` for hedged, `false` for deterministic
|
|
107
107
|
- `context`: `Uint8Array` (optional) - context string, 0-255 bytes
|
|
@@ -112,11 +112,13 @@ Create a detached signature.
|
|
|
112
112
|
Verify a detached signature.
|
|
113
113
|
|
|
114
114
|
- `sig`: `Uint8Array(4627)` - signature to verify
|
|
115
|
-
- `message`: `Uint8Array` - original message
|
|
115
|
+
- `message`: `Uint8Array` or `string` - original message bytes; if `string`, it must be hex only (optional `0x`, even length). Plain-text strings are not accepted.
|
|
116
116
|
- `pk`: `Uint8Array(2592)` - public key
|
|
117
117
|
- `context`: `Uint8Array` (optional) - must match signing context
|
|
118
118
|
- Returns: `true` if valid, `false` otherwise
|
|
119
119
|
|
|
120
|
+
**Note:** To sign or verify plain text, convert it to bytes (e.g., `new TextEncoder().encode('Hello')`). String inputs are interpreted as hex only.
|
|
121
|
+
|
|
120
122
|
#### `zeroize(buffer)`
|
|
121
123
|
|
|
122
124
|
Zero out sensitive data (best-effort, see security notes).
|
|
@@ -157,7 +159,7 @@ See [SECURITY.md](../../SECURITY.md) for important information about:
|
|
|
157
159
|
|
|
158
160
|
## Requirements
|
|
159
161
|
|
|
160
|
-
- Node.js 18+ or modern browsers with ES2020 support
|
|
162
|
+
- Node.js 18.20+ or modern browsers with ES2020 support
|
|
161
163
|
- Full TypeScript definitions included
|
|
162
164
|
|
|
163
165
|
## License
|
package/dist/cjs/mldsa87.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var sha3_js = require('@noble/hashes/sha3.js');
|
|
4
|
-
var pkg = require('randombytes');
|
|
5
4
|
var utils_js = require('@noble/hashes/utils.js');
|
|
6
5
|
|
|
6
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
7
7
|
const Shake128Rate = 168;
|
|
8
8
|
const Shake256Rate = 136;
|
|
9
9
|
const Stream128BlockBytes = Shake128Rate;
|
|
@@ -370,6 +370,8 @@ function polyUniform(a, seed, nonce) {
|
|
|
370
370
|
|
|
371
371
|
let ctr = rejUniform(a.coeffs, 0, N, buf, bufLen);
|
|
372
372
|
|
|
373
|
+
// Note: With current parameters, needing extra blocks is vanishingly unlikely.
|
|
374
|
+
/* c8 ignore start */
|
|
373
375
|
while (ctr < N) {
|
|
374
376
|
off = bufLen % 3;
|
|
375
377
|
for (let i = 0; i < off; ++i) buf[i] = buf[bufLen - off + i];
|
|
@@ -378,6 +380,7 @@ function polyUniform(a, seed, nonce) {
|
|
|
378
380
|
bufLen = Stream128BlockBytes + off;
|
|
379
381
|
ctr += rejUniform(a.coeffs, ctr, N - ctr, buf, bufLen);
|
|
380
382
|
}
|
|
383
|
+
/* c8 ignore stop */
|
|
381
384
|
}
|
|
382
385
|
|
|
383
386
|
function rejEta(aP, aOffset, len, buf, bufLen) {
|
|
@@ -473,10 +476,13 @@ function polyChallenge(cP, seed) {
|
|
|
473
476
|
}
|
|
474
477
|
for (let i = N - TAU; i < N; ++i) {
|
|
475
478
|
do {
|
|
479
|
+
// Note: Re-squeezing here is extremely unlikely with TAU=60.
|
|
480
|
+
/* c8 ignore start */
|
|
476
481
|
if (pos >= Shake256Rate) {
|
|
477
482
|
shake256SqueezeBlocks(buf, 0, 1, state);
|
|
478
483
|
pos = 0;
|
|
479
484
|
}
|
|
485
|
+
/* c8 ignore stop */
|
|
480
486
|
|
|
481
487
|
b = buf[pos++];
|
|
482
488
|
} while (b > i);
|
|
@@ -1021,7 +1027,69 @@ function unpackSig(cP, z, hP, sig) {
|
|
|
1021
1027
|
return 0;
|
|
1022
1028
|
}
|
|
1023
1029
|
|
|
1024
|
-
const
|
|
1030
|
+
const MAX_BYTES = 65536;
|
|
1031
|
+
|
|
1032
|
+
function getGlobalScope() {
|
|
1033
|
+
if (typeof globalThis === 'object') return globalThis;
|
|
1034
|
+
if (typeof self === 'object') return self;
|
|
1035
|
+
if (typeof window === 'object') return window;
|
|
1036
|
+
if (typeof global === 'object') return global;
|
|
1037
|
+
return {};
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function getWebCrypto() {
|
|
1041
|
+
const scope = getGlobalScope();
|
|
1042
|
+
return scope.crypto || scope.msCrypto || null;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function getNodeRandomBytes() {
|
|
1046
|
+
/* c8 ignore next */
|
|
1047
|
+
const isNode = typeof process === 'object' && process !== null && process.versions && process.versions.node;
|
|
1048
|
+
if (!isNode) return null;
|
|
1049
|
+
|
|
1050
|
+
const req =
|
|
1051
|
+
typeof module !== 'undefined' && module && typeof module.require === 'function'
|
|
1052
|
+
? module.require.bind(module)
|
|
1053
|
+
: typeof module !== 'undefined' && module && typeof module.createRequire === 'function'
|
|
1054
|
+
? module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('mldsa87.js', document.baseURI).href)))
|
|
1055
|
+
: typeof require === 'function'
|
|
1056
|
+
? require
|
|
1057
|
+
: null;
|
|
1058
|
+
if (!req) return null;
|
|
1059
|
+
|
|
1060
|
+
try {
|
|
1061
|
+
const nodeCrypto = req('crypto');
|
|
1062
|
+
if (nodeCrypto && typeof nodeCrypto.randomBytes === 'function') {
|
|
1063
|
+
return nodeCrypto.randomBytes;
|
|
1064
|
+
}
|
|
1065
|
+
} catch {
|
|
1066
|
+
return null;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
return null;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
function randomBytes(size) {
|
|
1073
|
+
if (!Number.isSafeInteger(size) || size < 0) {
|
|
1074
|
+
throw new RangeError('size must be a non-negative integer');
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
const cryptoObj = getWebCrypto();
|
|
1078
|
+
if (cryptoObj && typeof cryptoObj.getRandomValues === 'function') {
|
|
1079
|
+
const out = new Uint8Array(size);
|
|
1080
|
+
for (let i = 0; i < size; i += MAX_BYTES) {
|
|
1081
|
+
cryptoObj.getRandomValues(out.subarray(i, Math.min(size, i + MAX_BYTES)));
|
|
1082
|
+
}
|
|
1083
|
+
return out;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
const nodeRandomBytes = getNodeRandomBytes();
|
|
1087
|
+
if (nodeRandomBytes) {
|
|
1088
|
+
return nodeRandomBytes(size);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
throw new Error('Secure random number generation is not supported by this environment');
|
|
1092
|
+
}
|
|
1025
1093
|
|
|
1026
1094
|
/**
|
|
1027
1095
|
* Default signing context ("ZOND" in ASCII).
|
|
@@ -1032,15 +1100,27 @@ const DEFAULT_CTX = new Uint8Array([0x5a, 0x4f, 0x4e, 0x44]); // "ZOND"
|
|
|
1032
1100
|
|
|
1033
1101
|
/**
|
|
1034
1102
|
* Convert hex string to Uint8Array with strict validation.
|
|
1103
|
+
*
|
|
1104
|
+
* NOTE: This function accepts multiple hex formats (with/without 0x prefix,
|
|
1105
|
+
* leading/trailing whitespace). While user-friendly, this flexibility could
|
|
1106
|
+
* mask input errors. Applications requiring strict format validation should
|
|
1107
|
+
* validate hex format before calling cryptographic functions, e.g.:
|
|
1108
|
+
* - Reject strings with 0x prefix if raw hex is expected
|
|
1109
|
+
* - Reject strings with whitespace
|
|
1110
|
+
* - Enforce consistent casing (lowercase/uppercase)
|
|
1111
|
+
*
|
|
1035
1112
|
* @param {string} hex - Hex string (optional 0x prefix, even length).
|
|
1036
1113
|
* @returns {Uint8Array} Decoded bytes.
|
|
1037
1114
|
* @private
|
|
1038
1115
|
*/
|
|
1039
1116
|
function hexToBytes(hex) {
|
|
1117
|
+
/* c8 ignore start */
|
|
1040
1118
|
if (typeof hex !== 'string') {
|
|
1041
1119
|
throw new Error('message must be a hex string');
|
|
1042
1120
|
}
|
|
1121
|
+
/* c8 ignore stop */
|
|
1043
1122
|
let clean = hex.trim();
|
|
1123
|
+
// Accepts both "0x..." and raw hex formats for convenience
|
|
1044
1124
|
if (clean.startsWith('0x') || clean.startsWith('0X')) {
|
|
1045
1125
|
clean = clean.slice(2);
|
|
1046
1126
|
}
|
|
@@ -1060,7 +1140,7 @@ function messageToBytes(message) {
|
|
|
1060
1140
|
if (message instanceof Uint8Array) {
|
|
1061
1141
|
return message;
|
|
1062
1142
|
}
|
|
1063
|
-
|
|
1143
|
+
throw new Error('message must be Uint8Array or hex string');
|
|
1064
1144
|
}
|
|
1065
1145
|
|
|
1066
1146
|
/**
|
|
@@ -1208,9 +1288,6 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1208
1288
|
pre.set(ctx, 2);
|
|
1209
1289
|
|
|
1210
1290
|
const mBytes = messageToBytes(m);
|
|
1211
|
-
if (!mBytes) {
|
|
1212
|
-
throw new Error('message must be Uint8Array or hex string');
|
|
1213
|
-
}
|
|
1214
1291
|
|
|
1215
1292
|
// mu = SHAKE256(tr || pre || m)
|
|
1216
1293
|
const mu = sha3_js.shake256.create({}).update(tr).update(pre).update(mBytes).xof(CRHBytes);
|
|
@@ -1268,15 +1345,19 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1268
1345
|
polyVecKPointWisePolyMontgomery(h, cp, t0);
|
|
1269
1346
|
polyVecKInvNTTToMont(h);
|
|
1270
1347
|
polyVecKReduce(h);
|
|
1348
|
+
/* c8 ignore start */
|
|
1271
1349
|
if (polyVecKChkNorm(h, GAMMA2) !== 0) {
|
|
1272
1350
|
continue;
|
|
1273
1351
|
}
|
|
1352
|
+
/* c8 ignore stop */
|
|
1274
1353
|
|
|
1275
1354
|
polyVecKAdd(w0, w0, h);
|
|
1276
1355
|
const n = polyVecKMakeHint(h, w0, w1);
|
|
1356
|
+
/* c8 ignore start */
|
|
1277
1357
|
if (n > OMEGA) {
|
|
1278
1358
|
continue;
|
|
1279
1359
|
}
|
|
1360
|
+
/* c8 ignore stop */
|
|
1280
1361
|
|
|
1281
1362
|
packSig(sig, ctilde, z, h);
|
|
1282
1363
|
return 0;
|
|
@@ -1303,9 +1384,6 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1303
1384
|
*/
|
|
1304
1385
|
function cryptoSign(msg, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
1305
1386
|
const msgBytes = messageToBytes(msg);
|
|
1306
|
-
if (!msgBytes) {
|
|
1307
|
-
throw new Error('message must be Uint8Array or hex string');
|
|
1308
|
-
}
|
|
1309
1387
|
|
|
1310
1388
|
const sm = new Uint8Array(CryptoBytes + msgBytes.length);
|
|
1311
1389
|
const mLen = msgBytes.length;
|
|
@@ -1314,9 +1392,11 @@ function cryptoSign(msg, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1314
1392
|
}
|
|
1315
1393
|
const result = cryptoSignSignature(sm, msgBytes, sk, randomizedSigning, ctx);
|
|
1316
1394
|
|
|
1395
|
+
/* c8 ignore start */
|
|
1317
1396
|
if (result !== 0) {
|
|
1318
1397
|
throw new Error('failed to sign');
|
|
1319
1398
|
}
|
|
1399
|
+
/* c8 ignore stop */
|
|
1320
1400
|
return sm;
|
|
1321
1401
|
}
|
|
1322
1402
|
|
|
@@ -1383,9 +1463,6 @@ function cryptoSignVerify(sig, m, pk, ctx = DEFAULT_CTX) {
|
|
|
1383
1463
|
} catch {
|
|
1384
1464
|
return false;
|
|
1385
1465
|
}
|
|
1386
|
-
if (!mBytes) {
|
|
1387
|
-
return false;
|
|
1388
|
-
}
|
|
1389
1466
|
const muFull = sha3_js.shake256.create({}).update(tr).update(pre).update(mBytes).xof(CRHBytes);
|
|
1390
1467
|
mu.set(muFull);
|
|
1391
1468
|
|
package/dist/mjs/mldsa87.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { shake128, shake256 } from '@noble/hashes/sha3.js';
|
|
2
|
-
import pkg from 'randombytes';
|
|
3
2
|
import { hexToBytes as hexToBytes$1 } from '@noble/hashes/utils.js';
|
|
4
3
|
|
|
5
4
|
const Shake128Rate = 168;
|
|
@@ -368,6 +367,8 @@ function polyUniform(a, seed, nonce) {
|
|
|
368
367
|
|
|
369
368
|
let ctr = rejUniform(a.coeffs, 0, N, buf, bufLen);
|
|
370
369
|
|
|
370
|
+
// Note: With current parameters, needing extra blocks is vanishingly unlikely.
|
|
371
|
+
/* c8 ignore start */
|
|
371
372
|
while (ctr < N) {
|
|
372
373
|
off = bufLen % 3;
|
|
373
374
|
for (let i = 0; i < off; ++i) buf[i] = buf[bufLen - off + i];
|
|
@@ -376,6 +377,7 @@ function polyUniform(a, seed, nonce) {
|
|
|
376
377
|
bufLen = Stream128BlockBytes + off;
|
|
377
378
|
ctr += rejUniform(a.coeffs, ctr, N - ctr, buf, bufLen);
|
|
378
379
|
}
|
|
380
|
+
/* c8 ignore stop */
|
|
379
381
|
}
|
|
380
382
|
|
|
381
383
|
function rejEta(aP, aOffset, len, buf, bufLen) {
|
|
@@ -471,10 +473,13 @@ function polyChallenge(cP, seed) {
|
|
|
471
473
|
}
|
|
472
474
|
for (let i = N - TAU; i < N; ++i) {
|
|
473
475
|
do {
|
|
476
|
+
// Note: Re-squeezing here is extremely unlikely with TAU=60.
|
|
477
|
+
/* c8 ignore start */
|
|
474
478
|
if (pos >= Shake256Rate) {
|
|
475
479
|
shake256SqueezeBlocks(buf, 0, 1, state);
|
|
476
480
|
pos = 0;
|
|
477
481
|
}
|
|
482
|
+
/* c8 ignore stop */
|
|
478
483
|
|
|
479
484
|
b = buf[pos++];
|
|
480
485
|
} while (b > i);
|
|
@@ -1019,7 +1024,69 @@ function unpackSig(cP, z, hP, sig) {
|
|
|
1019
1024
|
return 0;
|
|
1020
1025
|
}
|
|
1021
1026
|
|
|
1022
|
-
const
|
|
1027
|
+
const MAX_BYTES = 65536;
|
|
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(import.meta.url)
|
|
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
|
+
|
|
1074
|
+
const cryptoObj = getWebCrypto();
|
|
1075
|
+
if (cryptoObj && typeof cryptoObj.getRandomValues === 'function') {
|
|
1076
|
+
const out = new Uint8Array(size);
|
|
1077
|
+
for (let i = 0; i < size; i += MAX_BYTES) {
|
|
1078
|
+
cryptoObj.getRandomValues(out.subarray(i, Math.min(size, i + MAX_BYTES)));
|
|
1079
|
+
}
|
|
1080
|
+
return out;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
const nodeRandomBytes = getNodeRandomBytes();
|
|
1084
|
+
if (nodeRandomBytes) {
|
|
1085
|
+
return nodeRandomBytes(size);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
throw new Error('Secure random number generation is not supported by this environment');
|
|
1089
|
+
}
|
|
1023
1090
|
|
|
1024
1091
|
/**
|
|
1025
1092
|
* Default signing context ("ZOND" in ASCII).
|
|
@@ -1030,15 +1097,27 @@ const DEFAULT_CTX = new Uint8Array([0x5a, 0x4f, 0x4e, 0x44]); // "ZOND"
|
|
|
1030
1097
|
|
|
1031
1098
|
/**
|
|
1032
1099
|
* Convert hex string to Uint8Array with strict validation.
|
|
1100
|
+
*
|
|
1101
|
+
* NOTE: This function accepts multiple hex formats (with/without 0x prefix,
|
|
1102
|
+
* leading/trailing whitespace). While user-friendly, this flexibility could
|
|
1103
|
+
* mask input errors. Applications requiring strict format validation should
|
|
1104
|
+
* validate hex format before calling cryptographic functions, e.g.:
|
|
1105
|
+
* - Reject strings with 0x prefix if raw hex is expected
|
|
1106
|
+
* - Reject strings with whitespace
|
|
1107
|
+
* - Enforce consistent casing (lowercase/uppercase)
|
|
1108
|
+
*
|
|
1033
1109
|
* @param {string} hex - Hex string (optional 0x prefix, even length).
|
|
1034
1110
|
* @returns {Uint8Array} Decoded bytes.
|
|
1035
1111
|
* @private
|
|
1036
1112
|
*/
|
|
1037
1113
|
function hexToBytes(hex) {
|
|
1114
|
+
/* c8 ignore start */
|
|
1038
1115
|
if (typeof hex !== 'string') {
|
|
1039
1116
|
throw new Error('message must be a hex string');
|
|
1040
1117
|
}
|
|
1118
|
+
/* c8 ignore stop */
|
|
1041
1119
|
let clean = hex.trim();
|
|
1120
|
+
// Accepts both "0x..." and raw hex formats for convenience
|
|
1042
1121
|
if (clean.startsWith('0x') || clean.startsWith('0X')) {
|
|
1043
1122
|
clean = clean.slice(2);
|
|
1044
1123
|
}
|
|
@@ -1058,7 +1137,7 @@ function messageToBytes(message) {
|
|
|
1058
1137
|
if (message instanceof Uint8Array) {
|
|
1059
1138
|
return message;
|
|
1060
1139
|
}
|
|
1061
|
-
|
|
1140
|
+
throw new Error('message must be Uint8Array or hex string');
|
|
1062
1141
|
}
|
|
1063
1142
|
|
|
1064
1143
|
/**
|
|
@@ -1206,9 +1285,6 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1206
1285
|
pre.set(ctx, 2);
|
|
1207
1286
|
|
|
1208
1287
|
const mBytes = messageToBytes(m);
|
|
1209
|
-
if (!mBytes) {
|
|
1210
|
-
throw new Error('message must be Uint8Array or hex string');
|
|
1211
|
-
}
|
|
1212
1288
|
|
|
1213
1289
|
// mu = SHAKE256(tr || pre || m)
|
|
1214
1290
|
const mu = shake256.create({}).update(tr).update(pre).update(mBytes).xof(CRHBytes);
|
|
@@ -1266,15 +1342,19 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1266
1342
|
polyVecKPointWisePolyMontgomery(h, cp, t0);
|
|
1267
1343
|
polyVecKInvNTTToMont(h);
|
|
1268
1344
|
polyVecKReduce(h);
|
|
1345
|
+
/* c8 ignore start */
|
|
1269
1346
|
if (polyVecKChkNorm(h, GAMMA2) !== 0) {
|
|
1270
1347
|
continue;
|
|
1271
1348
|
}
|
|
1349
|
+
/* c8 ignore stop */
|
|
1272
1350
|
|
|
1273
1351
|
polyVecKAdd(w0, w0, h);
|
|
1274
1352
|
const n = polyVecKMakeHint(h, w0, w1);
|
|
1353
|
+
/* c8 ignore start */
|
|
1275
1354
|
if (n > OMEGA) {
|
|
1276
1355
|
continue;
|
|
1277
1356
|
}
|
|
1357
|
+
/* c8 ignore stop */
|
|
1278
1358
|
|
|
1279
1359
|
packSig(sig, ctilde, z, h);
|
|
1280
1360
|
return 0;
|
|
@@ -1301,9 +1381,6 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1301
1381
|
*/
|
|
1302
1382
|
function cryptoSign(msg, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
1303
1383
|
const msgBytes = messageToBytes(msg);
|
|
1304
|
-
if (!msgBytes) {
|
|
1305
|
-
throw new Error('message must be Uint8Array or hex string');
|
|
1306
|
-
}
|
|
1307
1384
|
|
|
1308
1385
|
const sm = new Uint8Array(CryptoBytes + msgBytes.length);
|
|
1309
1386
|
const mLen = msgBytes.length;
|
|
@@ -1312,9 +1389,11 @@ function cryptoSign(msg, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1312
1389
|
}
|
|
1313
1390
|
const result = cryptoSignSignature(sm, msgBytes, sk, randomizedSigning, ctx);
|
|
1314
1391
|
|
|
1392
|
+
/* c8 ignore start */
|
|
1315
1393
|
if (result !== 0) {
|
|
1316
1394
|
throw new Error('failed to sign');
|
|
1317
1395
|
}
|
|
1396
|
+
/* c8 ignore stop */
|
|
1318
1397
|
return sm;
|
|
1319
1398
|
}
|
|
1320
1399
|
|
|
@@ -1381,9 +1460,6 @@ function cryptoSignVerify(sig, m, pk, ctx = DEFAULT_CTX) {
|
|
|
1381
1460
|
} catch {
|
|
1382
1461
|
return false;
|
|
1383
1462
|
}
|
|
1384
|
-
if (!mBytes) {
|
|
1385
|
-
return false;
|
|
1386
|
-
}
|
|
1387
1463
|
const muFull = shake256.create({}).update(tr).update(pre).update(mBytes).xof(CRHBytes);
|
|
1388
1464
|
mu.set(muFull);
|
|
1389
1465
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theqrl/mldsa87",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "ML-DSA-87 cryptography",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ml-dsa",
|
|
@@ -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/mldsa87.js --format cjs && rollup src/index.js --file ./dist/mjs/mldsa87.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",
|
|
@@ -61,7 +66,6 @@
|
|
|
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
|
@@ -58,7 +58,7 @@ export function cryptoSignKeypair(
|
|
|
58
58
|
/**
|
|
59
59
|
* Create a signature for a message with optional context
|
|
60
60
|
* @param sig - Output buffer for signature (must be CryptoBytes length minimum)
|
|
61
|
-
* @param m - Message to sign (hex string or Uint8Array)
|
|
61
|
+
* @param m - Message to sign (hex string or Uint8Array; strings are parsed as hex only)
|
|
62
62
|
* @param sk - Secret key
|
|
63
63
|
* @param randomizedSigning - If true, use random nonce; if false, deterministic
|
|
64
64
|
* @param ctx - Optional context string (max 255 bytes, defaults to "ZOND")
|
|
@@ -92,7 +92,7 @@ export function cryptoSign(
|
|
|
92
92
|
/**
|
|
93
93
|
* Verify a signature with optional context
|
|
94
94
|
* @param sig - Signature to verify
|
|
95
|
-
* @param m - Message that was signed (hex string or Uint8Array)
|
|
95
|
+
* @param m - Message that was signed (hex string or Uint8Array; strings are parsed as hex only)
|
|
96
96
|
* @param pk - Public key
|
|
97
97
|
* @param ctx - Optional context string (max 255 bytes, defaults to "ZOND")
|
|
98
98
|
* @returns true if signature is valid, false otherwise
|