@theqrl/mldsa87 1.0.4 → 1.0.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/dist/cjs/mldsa87.js +55 -19
- package/dist/mjs/mldsa87.js +55 -19
- package/package.json +2 -2
- package/src/index.d.ts +5 -5
package/dist/cjs/mldsa87.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var sha3_js = require('@noble/hashes/sha3.js');
|
|
4
4
|
var pkg = require('randombytes');
|
|
5
|
+
var utils_js = require('@noble/hashes/utils.js');
|
|
5
6
|
|
|
6
7
|
const Shake128Rate = 168;
|
|
7
8
|
const Shake256Rate = 136;
|
|
@@ -1030,18 +1031,36 @@ const randomBytes = pkg;
|
|
|
1030
1031
|
const DEFAULT_CTX = new Uint8Array([0x5a, 0x4f, 0x4e, 0x44]); // "ZOND"
|
|
1031
1032
|
|
|
1032
1033
|
/**
|
|
1033
|
-
* Convert hex string to Uint8Array
|
|
1034
|
-
* @param {string} hex - Hex
|
|
1035
|
-
* @returns {Uint8Array} Decoded bytes
|
|
1034
|
+
* Convert hex string to Uint8Array with strict validation.
|
|
1035
|
+
* @param {string} hex - Hex string (optional 0x prefix, even length).
|
|
1036
|
+
* @returns {Uint8Array} Decoded bytes.
|
|
1036
1037
|
* @private
|
|
1037
1038
|
*/
|
|
1038
1039
|
function hexToBytes(hex) {
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
for (let i = 0; i < len; i++) {
|
|
1042
|
-
result[i] = parseInt(hex.substr(i * 2, 2), 16);
|
|
1040
|
+
if (typeof hex !== 'string') {
|
|
1041
|
+
throw new Error('message must be a hex string');
|
|
1043
1042
|
}
|
|
1044
|
-
|
|
1043
|
+
let clean = hex.trim();
|
|
1044
|
+
if (clean.startsWith('0x') || clean.startsWith('0X')) {
|
|
1045
|
+
clean = clean.slice(2);
|
|
1046
|
+
}
|
|
1047
|
+
if (clean.length % 2 !== 0) {
|
|
1048
|
+
throw new Error('hex string must have an even length');
|
|
1049
|
+
}
|
|
1050
|
+
if (!/^[0-9a-fA-F]*$/.test(clean)) {
|
|
1051
|
+
throw new Error('hex string contains non-hex characters');
|
|
1052
|
+
}
|
|
1053
|
+
return utils_js.hexToBytes(clean);
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
function messageToBytes(message) {
|
|
1057
|
+
if (typeof message === 'string') {
|
|
1058
|
+
return hexToBytes(message);
|
|
1059
|
+
}
|
|
1060
|
+
if (message instanceof Uint8Array) {
|
|
1061
|
+
return message;
|
|
1062
|
+
}
|
|
1063
|
+
return null;
|
|
1045
1064
|
}
|
|
1046
1065
|
|
|
1047
1066
|
/**
|
|
@@ -1138,7 +1157,7 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1138
1157
|
* The context parameter provides domain separation as required by FIPS 204.
|
|
1139
1158
|
*
|
|
1140
1159
|
* @param {Uint8Array} sig - Output buffer for signature (must be at least CryptoBytes = 4627 bytes)
|
|
1141
|
-
* @param {string|Uint8Array} m - Message to sign (hex string or Uint8Array)
|
|
1160
|
+
* @param {string|Uint8Array} m - Message to sign (hex string, optional 0x prefix, or Uint8Array)
|
|
1142
1161
|
* @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1143
1162
|
* @param {boolean} randomizedSigning - If true, use random nonce for hedged signing.
|
|
1144
1163
|
* If false, use deterministic nonce derived from message and key.
|
|
@@ -1154,6 +1173,9 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1154
1173
|
* cryptoSignSignature(sig, message, sk, false, new Uint8Array([0x01, 0x02]));
|
|
1155
1174
|
*/
|
|
1156
1175
|
function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
1176
|
+
if (!sig || sig.length < CryptoBytes) {
|
|
1177
|
+
throw new Error(`sig must be at least ${CryptoBytes} bytes`);
|
|
1178
|
+
}
|
|
1157
1179
|
if (ctx.length > 255) throw new Error(`invalid context length: ${ctx.length} (max 255)`);
|
|
1158
1180
|
if (sk.length !== CryptoSecretKeyBytes) {
|
|
1159
1181
|
throw new Error(`invalid sk length ${sk.length} | Expected length ${CryptoSecretKeyBytes}`);
|
|
@@ -1185,8 +1207,10 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1185
1207
|
pre[1] = ctx.length;
|
|
1186
1208
|
pre.set(ctx, 2);
|
|
1187
1209
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1210
|
+
const mBytes = messageToBytes(m);
|
|
1211
|
+
if (!mBytes) {
|
|
1212
|
+
throw new Error('message must be Uint8Array or hex string');
|
|
1213
|
+
}
|
|
1190
1214
|
|
|
1191
1215
|
// mu = SHAKE256(tr || pre || m)
|
|
1192
1216
|
const mu = sha3_js.shake256.create({}).update(tr).update(pre).update(mBytes).xof(CRHBytes);
|
|
@@ -1265,7 +1289,7 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1265
1289
|
* This is the combined sign operation that produces a "signed message" containing
|
|
1266
1290
|
* both the signature and the original message (signature || message).
|
|
1267
1291
|
*
|
|
1268
|
-
* @param {Uint8Array} msg - Message to sign
|
|
1292
|
+
* @param {string|Uint8Array} msg - Message to sign (hex string, optional 0x prefix, or Uint8Array)
|
|
1269
1293
|
* @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1270
1294
|
* @param {boolean} randomizedSigning - If true, use random nonce; if false, deterministic
|
|
1271
1295
|
* @param {Uint8Array} [ctx=DEFAULT_CTX] - Context string for domain separation (max 255 bytes).
|
|
@@ -1278,12 +1302,17 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1278
1302
|
* // signedMsg contains: signature (4627 bytes) || message
|
|
1279
1303
|
*/
|
|
1280
1304
|
function cryptoSign(msg, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
1281
|
-
const
|
|
1282
|
-
|
|
1305
|
+
const msgBytes = messageToBytes(msg);
|
|
1306
|
+
if (!msgBytes) {
|
|
1307
|
+
throw new Error('message must be Uint8Array or hex string');
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
const sm = new Uint8Array(CryptoBytes + msgBytes.length);
|
|
1311
|
+
const mLen = msgBytes.length;
|
|
1283
1312
|
for (let i = 0; i < mLen; ++i) {
|
|
1284
|
-
sm[CryptoBytes + mLen - 1 - i] =
|
|
1313
|
+
sm[CryptoBytes + mLen - 1 - i] = msgBytes[mLen - 1 - i];
|
|
1285
1314
|
}
|
|
1286
|
-
const result = cryptoSignSignature(sm,
|
|
1315
|
+
const result = cryptoSignSignature(sm, msgBytes, sk, randomizedSigning, ctx);
|
|
1287
1316
|
|
|
1288
1317
|
if (result !== 0) {
|
|
1289
1318
|
throw new Error('failed to sign');
|
|
@@ -1298,7 +1327,7 @@ function cryptoSign(msg, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1298
1327
|
* The context must match the one used during signing.
|
|
1299
1328
|
*
|
|
1300
1329
|
* @param {Uint8Array} sig - Signature to verify (must be CryptoBytes = 4627 bytes)
|
|
1301
|
-
* @param {string|Uint8Array} m - Message that was signed (hex string or Uint8Array)
|
|
1330
|
+
* @param {string|Uint8Array} m - Message that was signed (hex string, optional 0x prefix, or Uint8Array)
|
|
1302
1331
|
* @param {Uint8Array} pk - Public key (must be CryptoPublicKeyBytes = 2592 bytes)
|
|
1303
1332
|
* @param {Uint8Array} [ctx=DEFAULT_CTX] - Context string used during signing (max 255 bytes).
|
|
1304
1333
|
* Defaults to "ZOND" for QRL compatibility.
|
|
@@ -1348,8 +1377,15 @@ function cryptoSignVerify(sig, m, pk, ctx = DEFAULT_CTX) {
|
|
|
1348
1377
|
pre[1] = ctx.length;
|
|
1349
1378
|
pre.set(ctx, 2);
|
|
1350
1379
|
|
|
1351
|
-
|
|
1352
|
-
|
|
1380
|
+
let mBytes;
|
|
1381
|
+
try {
|
|
1382
|
+
mBytes = messageToBytes(m);
|
|
1383
|
+
} catch {
|
|
1384
|
+
return false;
|
|
1385
|
+
}
|
|
1386
|
+
if (!mBytes) {
|
|
1387
|
+
return false;
|
|
1388
|
+
}
|
|
1353
1389
|
const muFull = sha3_js.shake256.create({}).update(tr).update(pre).update(mBytes).xof(CRHBytes);
|
|
1354
1390
|
mu.set(muFull);
|
|
1355
1391
|
|
package/dist/mjs/mldsa87.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { shake128, shake256 } from '@noble/hashes/sha3.js';
|
|
2
2
|
import pkg from 'randombytes';
|
|
3
|
+
import { hexToBytes as hexToBytes$1 } from '@noble/hashes/utils.js';
|
|
3
4
|
|
|
4
5
|
const Shake128Rate = 168;
|
|
5
6
|
const Shake256Rate = 136;
|
|
@@ -1028,18 +1029,36 @@ const randomBytes = pkg;
|
|
|
1028
1029
|
const DEFAULT_CTX = new Uint8Array([0x5a, 0x4f, 0x4e, 0x44]); // "ZOND"
|
|
1029
1030
|
|
|
1030
1031
|
/**
|
|
1031
|
-
* Convert hex string to Uint8Array
|
|
1032
|
-
* @param {string} hex - Hex
|
|
1033
|
-
* @returns {Uint8Array} Decoded bytes
|
|
1032
|
+
* Convert hex string to Uint8Array with strict validation.
|
|
1033
|
+
* @param {string} hex - Hex string (optional 0x prefix, even length).
|
|
1034
|
+
* @returns {Uint8Array} Decoded bytes.
|
|
1034
1035
|
* @private
|
|
1035
1036
|
*/
|
|
1036
1037
|
function hexToBytes(hex) {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
for (let i = 0; i < len; i++) {
|
|
1040
|
-
result[i] = parseInt(hex.substr(i * 2, 2), 16);
|
|
1038
|
+
if (typeof hex !== 'string') {
|
|
1039
|
+
throw new Error('message must be a hex string');
|
|
1041
1040
|
}
|
|
1042
|
-
|
|
1041
|
+
let clean = hex.trim();
|
|
1042
|
+
if (clean.startsWith('0x') || clean.startsWith('0X')) {
|
|
1043
|
+
clean = clean.slice(2);
|
|
1044
|
+
}
|
|
1045
|
+
if (clean.length % 2 !== 0) {
|
|
1046
|
+
throw new Error('hex string must have an even length');
|
|
1047
|
+
}
|
|
1048
|
+
if (!/^[0-9a-fA-F]*$/.test(clean)) {
|
|
1049
|
+
throw new Error('hex string contains non-hex characters');
|
|
1050
|
+
}
|
|
1051
|
+
return hexToBytes$1(clean);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
function messageToBytes(message) {
|
|
1055
|
+
if (typeof message === 'string') {
|
|
1056
|
+
return hexToBytes(message);
|
|
1057
|
+
}
|
|
1058
|
+
if (message instanceof Uint8Array) {
|
|
1059
|
+
return message;
|
|
1060
|
+
}
|
|
1061
|
+
return null;
|
|
1043
1062
|
}
|
|
1044
1063
|
|
|
1045
1064
|
/**
|
|
@@ -1136,7 +1155,7 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1136
1155
|
* The context parameter provides domain separation as required by FIPS 204.
|
|
1137
1156
|
*
|
|
1138
1157
|
* @param {Uint8Array} sig - Output buffer for signature (must be at least CryptoBytes = 4627 bytes)
|
|
1139
|
-
* @param {string|Uint8Array} m - Message to sign (hex string or Uint8Array)
|
|
1158
|
+
* @param {string|Uint8Array} m - Message to sign (hex string, optional 0x prefix, or Uint8Array)
|
|
1140
1159
|
* @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1141
1160
|
* @param {boolean} randomizedSigning - If true, use random nonce for hedged signing.
|
|
1142
1161
|
* If false, use deterministic nonce derived from message and key.
|
|
@@ -1152,6 +1171,9 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1152
1171
|
* cryptoSignSignature(sig, message, sk, false, new Uint8Array([0x01, 0x02]));
|
|
1153
1172
|
*/
|
|
1154
1173
|
function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
1174
|
+
if (!sig || sig.length < CryptoBytes) {
|
|
1175
|
+
throw new Error(`sig must be at least ${CryptoBytes} bytes`);
|
|
1176
|
+
}
|
|
1155
1177
|
if (ctx.length > 255) throw new Error(`invalid context length: ${ctx.length} (max 255)`);
|
|
1156
1178
|
if (sk.length !== CryptoSecretKeyBytes) {
|
|
1157
1179
|
throw new Error(`invalid sk length ${sk.length} | Expected length ${CryptoSecretKeyBytes}`);
|
|
@@ -1183,8 +1205,10 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1183
1205
|
pre[1] = ctx.length;
|
|
1184
1206
|
pre.set(ctx, 2);
|
|
1185
1207
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1208
|
+
const mBytes = messageToBytes(m);
|
|
1209
|
+
if (!mBytes) {
|
|
1210
|
+
throw new Error('message must be Uint8Array or hex string');
|
|
1211
|
+
}
|
|
1188
1212
|
|
|
1189
1213
|
// mu = SHAKE256(tr || pre || m)
|
|
1190
1214
|
const mu = shake256.create({}).update(tr).update(pre).update(mBytes).xof(CRHBytes);
|
|
@@ -1263,7 +1287,7 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1263
1287
|
* This is the combined sign operation that produces a "signed message" containing
|
|
1264
1288
|
* both the signature and the original message (signature || message).
|
|
1265
1289
|
*
|
|
1266
|
-
* @param {Uint8Array} msg - Message to sign
|
|
1290
|
+
* @param {string|Uint8Array} msg - Message to sign (hex string, optional 0x prefix, or Uint8Array)
|
|
1267
1291
|
* @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1268
1292
|
* @param {boolean} randomizedSigning - If true, use random nonce; if false, deterministic
|
|
1269
1293
|
* @param {Uint8Array} [ctx=DEFAULT_CTX] - Context string for domain separation (max 255 bytes).
|
|
@@ -1276,12 +1300,17 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1276
1300
|
* // signedMsg contains: signature (4627 bytes) || message
|
|
1277
1301
|
*/
|
|
1278
1302
|
function cryptoSign(msg, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
1279
|
-
const
|
|
1280
|
-
|
|
1303
|
+
const msgBytes = messageToBytes(msg);
|
|
1304
|
+
if (!msgBytes) {
|
|
1305
|
+
throw new Error('message must be Uint8Array or hex string');
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
const sm = new Uint8Array(CryptoBytes + msgBytes.length);
|
|
1309
|
+
const mLen = msgBytes.length;
|
|
1281
1310
|
for (let i = 0; i < mLen; ++i) {
|
|
1282
|
-
sm[CryptoBytes + mLen - 1 - i] =
|
|
1311
|
+
sm[CryptoBytes + mLen - 1 - i] = msgBytes[mLen - 1 - i];
|
|
1283
1312
|
}
|
|
1284
|
-
const result = cryptoSignSignature(sm,
|
|
1313
|
+
const result = cryptoSignSignature(sm, msgBytes, sk, randomizedSigning, ctx);
|
|
1285
1314
|
|
|
1286
1315
|
if (result !== 0) {
|
|
1287
1316
|
throw new Error('failed to sign');
|
|
@@ -1296,7 +1325,7 @@ function cryptoSign(msg, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1296
1325
|
* The context must match the one used during signing.
|
|
1297
1326
|
*
|
|
1298
1327
|
* @param {Uint8Array} sig - Signature to verify (must be CryptoBytes = 4627 bytes)
|
|
1299
|
-
* @param {string|Uint8Array} m - Message that was signed (hex string or Uint8Array)
|
|
1328
|
+
* @param {string|Uint8Array} m - Message that was signed (hex string, optional 0x prefix, or Uint8Array)
|
|
1300
1329
|
* @param {Uint8Array} pk - Public key (must be CryptoPublicKeyBytes = 2592 bytes)
|
|
1301
1330
|
* @param {Uint8Array} [ctx=DEFAULT_CTX] - Context string used during signing (max 255 bytes).
|
|
1302
1331
|
* Defaults to "ZOND" for QRL compatibility.
|
|
@@ -1346,8 +1375,15 @@ function cryptoSignVerify(sig, m, pk, ctx = DEFAULT_CTX) {
|
|
|
1346
1375
|
pre[1] = ctx.length;
|
|
1347
1376
|
pre.set(ctx, 2);
|
|
1348
1377
|
|
|
1349
|
-
|
|
1350
|
-
|
|
1378
|
+
let mBytes;
|
|
1379
|
+
try {
|
|
1380
|
+
mBytes = messageToBytes(m);
|
|
1381
|
+
} catch {
|
|
1382
|
+
return false;
|
|
1383
|
+
}
|
|
1384
|
+
if (!mBytes) {
|
|
1385
|
+
return false;
|
|
1386
|
+
}
|
|
1351
1387
|
const muFull = shake256.create({}).update(tr).update(pre).update(mBytes).xof(CRHBytes);
|
|
1352
1388
|
mu.set(muFull);
|
|
1353
1389
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theqrl/mldsa87",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "ML-DSA-87 cryptography",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ml-dsa",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"eslint-config-prettier": "^10.1.8",
|
|
56
56
|
"eslint-plugin-import-x": "^4.15.0",
|
|
57
57
|
"eslint-plugin-prettier": "^5.5.4",
|
|
58
|
-
"globals": "^
|
|
58
|
+
"globals": "^17.0.0",
|
|
59
59
|
"mocha": "^11.7.5",
|
|
60
60
|
"prettier": "^3.7.4",
|
|
61
61
|
"rollup": "^4.55.1"
|
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
|
|
61
|
+
* @param m - Message to sign (hex string or Uint8Array)
|
|
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")
|
|
@@ -67,7 +67,7 @@ export function cryptoSignKeypair(
|
|
|
67
67
|
*/
|
|
68
68
|
export function cryptoSignSignature(
|
|
69
69
|
sig: Uint8Array,
|
|
70
|
-
m: string,
|
|
70
|
+
m: Uint8Array | string,
|
|
71
71
|
sk: Uint8Array,
|
|
72
72
|
randomizedSigning: boolean,
|
|
73
73
|
ctx?: Uint8Array
|
|
@@ -83,7 +83,7 @@ export function cryptoSignSignature(
|
|
|
83
83
|
* @throws Error if signing fails
|
|
84
84
|
*/
|
|
85
85
|
export function cryptoSign(
|
|
86
|
-
msg: Uint8Array,
|
|
86
|
+
msg: Uint8Array | string,
|
|
87
87
|
sk: Uint8Array,
|
|
88
88
|
randomizedSigning: boolean,
|
|
89
89
|
ctx?: Uint8Array
|
|
@@ -92,14 +92,14 @@ 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
|
|
95
|
+
* @param m - Message that was signed (hex string or Uint8Array)
|
|
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
|
|
99
99
|
*/
|
|
100
100
|
export function cryptoSignVerify(
|
|
101
101
|
sig: Uint8Array,
|
|
102
|
-
m: string,
|
|
102
|
+
m: Uint8Array | string,
|
|
103
103
|
pk: Uint8Array,
|
|
104
104
|
ctx?: Uint8Array
|
|
105
105
|
): boolean;
|