@theqrl/mldsa87 2.0.0 → 2.0.1
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 +8 -1
- package/dist/cjs/mldsa87.js +69 -28
- package/dist/mjs/mldsa87.js +69 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ Context is a required `Uint8Array` and can be 0-255 bytes. Use an empty `Uint8Ar
|
|
|
74
74
|
|
|
75
75
|
Generate a keypair from a seed.
|
|
76
76
|
|
|
77
|
-
- `seed`: `Uint8Array(32)` or `
|
|
77
|
+
- `seed`: `Uint8Array(32)`, `null`, or `undefined` for random
|
|
78
78
|
- `pk`: `Uint8Array(2592)` - output buffer for public key
|
|
79
79
|
- `sk`: `Uint8Array(4896)` - output buffer for secret key
|
|
80
80
|
- Returns: The seed used (useful when `seed` is `null`)
|
|
@@ -125,10 +125,17 @@ Verify a detached signature.
|
|
|
125
125
|
|
|
126
126
|
Zero out sensitive data (best-effort, see security notes).
|
|
127
127
|
|
|
128
|
+
- `buffer`: `Uint8Array` - the buffer to zero
|
|
129
|
+
- Throws: `TypeError` if buffer is not a `Uint8Array`
|
|
130
|
+
|
|
128
131
|
#### `isZero(buffer)`
|
|
129
132
|
|
|
130
133
|
Check if buffer is all zeros (constant-time).
|
|
131
134
|
|
|
135
|
+
- `buffer`: `Uint8Array` - the buffer to check
|
|
136
|
+
- Returns: `true` if all bytes are zero
|
|
137
|
+
- Throws: `TypeError` if buffer is not a `Uint8Array`
|
|
138
|
+
|
|
132
139
|
## Interoperability
|
|
133
140
|
|
|
134
141
|
Both this library and go-qrllib process ML-DSA-87 seeds identically. Raw seeds produce matching keys:
|
package/dist/cjs/mldsa87.js
CHANGED
|
@@ -539,12 +539,16 @@ function montgomeryReduce(a) {
|
|
|
539
539
|
return t;
|
|
540
540
|
}
|
|
541
541
|
|
|
542
|
+
// Partial reduction modulo Q. Input must satisfy |a| < 2^31 - 2^22.
|
|
543
|
+
// Output is in (-Q, Q). Mirrors the reference C implementation.
|
|
542
544
|
function reduce32(a) {
|
|
543
545
|
let t = (a + (1 << 22)) >> 23;
|
|
544
546
|
t = a - t * Q;
|
|
545
547
|
return t;
|
|
546
548
|
}
|
|
547
549
|
|
|
550
|
+
// Conditional add Q: if a is negative, add Q. Input must satisfy -Q < a < 2^31.
|
|
551
|
+
// Output is in [0, Q). Mirrors the reference C implementation.
|
|
548
552
|
function cAddQ(a) {
|
|
549
553
|
let ar = a;
|
|
550
554
|
ar += (ar >> 31) & Q;
|
|
@@ -706,10 +710,7 @@ function polyChkNorm(a, b) {
|
|
|
706
710
|
}
|
|
707
711
|
|
|
708
712
|
for (let i = 0; i < N; i++) {
|
|
709
|
-
|
|
710
|
-
t = a.coeffs[i] - (t & (2 * a.coeffs[i]));
|
|
711
|
-
|
|
712
|
-
if (t >= b) {
|
|
713
|
+
if (Math.abs(a.coeffs[i]) >= b) {
|
|
713
714
|
return 1;
|
|
714
715
|
}
|
|
715
716
|
}
|
|
@@ -1250,6 +1251,9 @@ function packPk(pkp, rho, t1) {
|
|
|
1250
1251
|
}
|
|
1251
1252
|
|
|
1252
1253
|
function unpackPk(rhop, t1, pk) {
|
|
1254
|
+
if (!(pk instanceof Uint8Array) || pk.length !== CryptoPublicKeyBytes) {
|
|
1255
|
+
throw new Error(`pk must be a Uint8Array of ${CryptoPublicKeyBytes} bytes`);
|
|
1256
|
+
}
|
|
1253
1257
|
const rho = rhop;
|
|
1254
1258
|
for (let i = 0; i < SeedBytes; ++i) {
|
|
1255
1259
|
rho[i] = pk[i];
|
|
@@ -1294,6 +1298,9 @@ function packSk(skp, rho, tr, key, t0, s1, s2) {
|
|
|
1294
1298
|
}
|
|
1295
1299
|
|
|
1296
1300
|
function unpackSk(rhoP, trP, keyP, t0, s1, s2, sk) {
|
|
1301
|
+
if (!(sk instanceof Uint8Array) || sk.length !== CryptoSecretKeyBytes) {
|
|
1302
|
+
throw new Error(`sk must be a Uint8Array of ${CryptoSecretKeyBytes} bytes`);
|
|
1303
|
+
}
|
|
1297
1304
|
let skOffset = 0;
|
|
1298
1305
|
const rho = rhoP;
|
|
1299
1306
|
const tr = trP;
|
|
@@ -1357,7 +1364,12 @@ function packSig(sigP, ctilde, z, h) {
|
|
|
1357
1364
|
}
|
|
1358
1365
|
}
|
|
1359
1366
|
|
|
1367
|
+
// Returns 0 on success, 1 on failure. On failure, output buffers (c, z, h)
|
|
1368
|
+
// may contain partial data and must not be used.
|
|
1360
1369
|
function unpackSig(cP, z, hP, sig) {
|
|
1370
|
+
if (!(sig instanceof Uint8Array) || sig.length !== CryptoBytes) {
|
|
1371
|
+
throw new Error(`sig must be a Uint8Array of ${CryptoBytes} bytes`);
|
|
1372
|
+
}
|
|
1361
1373
|
let sigOffset = 0;
|
|
1362
1374
|
const c = cP; // ctilde
|
|
1363
1375
|
const h = hP;
|
|
@@ -1452,6 +1464,8 @@ function randomBytes(size) {
|
|
|
1452
1464
|
*
|
|
1453
1465
|
* @param {Uint8Array} buffer - The buffer to zero
|
|
1454
1466
|
* @returns {void}
|
|
1467
|
+
* @throws {TypeError} If buffer is not a Uint8Array
|
|
1468
|
+
* @throws {Error} If zeroization verification fails
|
|
1455
1469
|
*/
|
|
1456
1470
|
function zeroize(buffer) {
|
|
1457
1471
|
if (!(buffer instanceof Uint8Array)) {
|
|
@@ -1474,6 +1488,7 @@ function zeroize(buffer) {
|
|
|
1474
1488
|
*
|
|
1475
1489
|
* @param {Uint8Array} buffer - The buffer to check
|
|
1476
1490
|
* @returns {boolean} True if all bytes are zero
|
|
1491
|
+
* @throws {TypeError} If buffer is not a Uint8Array
|
|
1477
1492
|
*/
|
|
1478
1493
|
function isZero(buffer) {
|
|
1479
1494
|
if (!(buffer instanceof Uint8Array)) {
|
|
@@ -1489,16 +1504,12 @@ function isZero(buffer) {
|
|
|
1489
1504
|
/**
|
|
1490
1505
|
* Convert hex string to Uint8Array with strict validation.
|
|
1491
1506
|
*
|
|
1492
|
-
*
|
|
1493
|
-
*
|
|
1494
|
-
* mask input errors. Applications requiring strict format validation should
|
|
1495
|
-
* validate hex format before calling cryptographic functions, e.g.:
|
|
1496
|
-
* - Reject strings with 0x prefix if raw hex is expected
|
|
1497
|
-
* - Reject strings with whitespace
|
|
1498
|
-
* - Enforce consistent casing (lowercase/uppercase)
|
|
1507
|
+
* Accepts an optional 0x/0X prefix. Leading/trailing whitespace is rejected.
|
|
1508
|
+
* Empty strings and whitespace-only strings are rejected.
|
|
1499
1509
|
*
|
|
1500
|
-
* @param {string} hex - Hex string (optional 0x prefix, even length).
|
|
1510
|
+
* @param {string} hex - Hex string (optional 0x prefix, even length, no whitespace).
|
|
1501
1511
|
* @returns {Uint8Array} Decoded bytes.
|
|
1512
|
+
* @throws {Error} If input is not a valid hex string
|
|
1502
1513
|
* @private
|
|
1503
1514
|
*/
|
|
1504
1515
|
function hexToBytes(hex) {
|
|
@@ -1507,11 +1518,16 @@ function hexToBytes(hex) {
|
|
|
1507
1518
|
throw new Error('message must be a hex string');
|
|
1508
1519
|
}
|
|
1509
1520
|
/* c8 ignore stop */
|
|
1510
|
-
|
|
1511
|
-
|
|
1521
|
+
if (hex !== hex.trim()) {
|
|
1522
|
+
throw new Error('hex string must not have leading or trailing whitespace');
|
|
1523
|
+
}
|
|
1524
|
+
let clean = hex;
|
|
1512
1525
|
if (clean.startsWith('0x') || clean.startsWith('0X')) {
|
|
1513
1526
|
clean = clean.slice(2);
|
|
1514
1527
|
}
|
|
1528
|
+
if (clean.length === 0) {
|
|
1529
|
+
throw new Error('hex string must not be empty');
|
|
1530
|
+
}
|
|
1515
1531
|
if (clean.length % 2 !== 0) {
|
|
1516
1532
|
throw new Error('hex string must have an even length');
|
|
1517
1533
|
}
|
|
@@ -1521,6 +1537,14 @@ function hexToBytes(hex) {
|
|
|
1521
1537
|
return hexToBytes$1(clean);
|
|
1522
1538
|
}
|
|
1523
1539
|
|
|
1540
|
+
/**
|
|
1541
|
+
* Convert a message to Uint8Array.
|
|
1542
|
+
*
|
|
1543
|
+
* @param {string|Uint8Array} message - Message as hex string (optional 0x prefix) or Uint8Array.
|
|
1544
|
+
* @returns {Uint8Array} Message bytes.
|
|
1545
|
+
* @throws {Error} If message is not a Uint8Array or valid hex string
|
|
1546
|
+
* @private
|
|
1547
|
+
*/
|
|
1524
1548
|
function messageToBytes(message) {
|
|
1525
1549
|
if (typeof message === 'string') {
|
|
1526
1550
|
return hexToBytes(message);
|
|
@@ -1537,8 +1561,8 @@ function messageToBytes(message) {
|
|
|
1537
1561
|
* Key generation follows FIPS 204, using domain separator [K, L] during
|
|
1538
1562
|
* seed expansion to ensure algorithm binding.
|
|
1539
1563
|
*
|
|
1540
|
-
* @param {Uint8Array|null} passedSeed - Optional 32-byte seed for deterministic key generation.
|
|
1541
|
-
* Pass null for random key generation.
|
|
1564
|
+
* @param {Uint8Array|null} [passedSeed=null] - Optional 32-byte seed for deterministic key generation.
|
|
1565
|
+
* Pass null or undefined for random key generation.
|
|
1542
1566
|
* @param {Uint8Array} pk - Output buffer for public key (must be CryptoPublicKeyBytes = 2592 bytes)
|
|
1543
1567
|
* @param {Uint8Array} sk - Output buffer for secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1544
1568
|
* @returns {Uint8Array} The seed used for key generation (useful when passedSeed is null)
|
|
@@ -1630,7 +1654,7 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1630
1654
|
}
|
|
1631
1655
|
|
|
1632
1656
|
/**
|
|
1633
|
-
* Create a detached signature for a message with
|
|
1657
|
+
* Create a detached signature for a message with context.
|
|
1634
1658
|
*
|
|
1635
1659
|
* Uses the ML-DSA-87 (FIPS 204) signing algorithm with rejection sampling.
|
|
1636
1660
|
* The context parameter provides domain separation as required by FIPS 204.
|
|
@@ -1640,9 +1664,16 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1640
1664
|
* @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1641
1665
|
* @param {boolean} randomizedSigning - If true, use random nonce for hedged signing.
|
|
1642
1666
|
* If false, use deterministic nonce derived from message and key.
|
|
1643
|
-
* @param {Uint8Array} ctx - Context string for domain separation (max 255 bytes).
|
|
1667
|
+
* @param {Uint8Array} ctx - Context string for domain separation (required, max 255 bytes).
|
|
1668
|
+
* Pass an empty Uint8Array for no context.
|
|
1644
1669
|
* @returns {number} 0 on success
|
|
1645
|
-
* @throws {
|
|
1670
|
+
* @throws {TypeError} If sig is not a Uint8Array or is smaller than CryptoBytes
|
|
1671
|
+
* @throws {TypeError} If sk is not a Uint8Array
|
|
1672
|
+
* @throws {TypeError} If ctx is not a Uint8Array
|
|
1673
|
+
* @throws {TypeError} If randomizedSigning is not a boolean
|
|
1674
|
+
* @throws {Error} If ctx exceeds 255 bytes
|
|
1675
|
+
* @throws {Error} If sk length does not equal CryptoSecretKeyBytes
|
|
1676
|
+
* @throws {Error} If message is not a Uint8Array or valid hex string
|
|
1646
1677
|
*
|
|
1647
1678
|
* @example
|
|
1648
1679
|
* const sig = new Uint8Array(CryptoBytes);
|
|
@@ -1650,13 +1681,19 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1650
1681
|
* cryptoSignSignature(sig, message, sk, false, ctx);
|
|
1651
1682
|
*/
|
|
1652
1683
|
function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx) {
|
|
1653
|
-
if (!sig || sig.length < CryptoBytes) {
|
|
1654
|
-
throw new
|
|
1684
|
+
if (!(sig instanceof Uint8Array) || sig.length < CryptoBytes) {
|
|
1685
|
+
throw new TypeError(`sig must be at least ${CryptoBytes} bytes and a Uint8Array`);
|
|
1686
|
+
}
|
|
1687
|
+
if (!(sk instanceof Uint8Array)) {
|
|
1688
|
+
throw new TypeError('sk must be a Uint8Array');
|
|
1655
1689
|
}
|
|
1656
1690
|
if (!(ctx instanceof Uint8Array)) {
|
|
1657
1691
|
throw new TypeError('ctx is required and must be a Uint8Array');
|
|
1658
1692
|
}
|
|
1659
1693
|
if (ctx.length > 255) throw new Error(`invalid context length: ${ctx.length} (max 255)`);
|
|
1694
|
+
if (typeof randomizedSigning !== 'boolean') {
|
|
1695
|
+
throw new TypeError('randomizedSigning must be a boolean');
|
|
1696
|
+
}
|
|
1660
1697
|
if (sk.length !== CryptoSecretKeyBytes) {
|
|
1661
1698
|
throw new Error(`invalid sk length ${sk.length} | Expected length ${CryptoSecretKeyBytes}`);
|
|
1662
1699
|
}
|
|
@@ -1782,9 +1819,11 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx) {
|
|
|
1782
1819
|
* @param {string|Uint8Array} msg - Message to sign (hex string, optional 0x prefix, or Uint8Array)
|
|
1783
1820
|
* @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1784
1821
|
* @param {boolean} randomizedSigning - If true, use random nonce; if false, deterministic
|
|
1785
|
-
* @param {Uint8Array} ctx - Context string for domain separation (max 255 bytes).
|
|
1822
|
+
* @param {Uint8Array} ctx - Context string for domain separation (required, max 255 bytes).
|
|
1786
1823
|
* @returns {Uint8Array} Signed message (CryptoBytes + msg.length bytes)
|
|
1787
|
-
* @throws {
|
|
1824
|
+
* @throws {TypeError} If ctx is not a Uint8Array
|
|
1825
|
+
* @throws {TypeError} If sk or randomizedSigning fail type validation (see cryptoSignSignature)
|
|
1826
|
+
* @throws {Error} If signing fails or message/sk/ctx are invalid
|
|
1788
1827
|
*
|
|
1789
1828
|
* @example
|
|
1790
1829
|
* const signedMsg = cryptoSign(message, sk, false, ctx);
|
|
@@ -1812,7 +1851,7 @@ function cryptoSign(msg, sk, randomizedSigning, ctx) {
|
|
|
1812
1851
|
}
|
|
1813
1852
|
|
|
1814
1853
|
/**
|
|
1815
|
-
* Verify a detached signature with
|
|
1854
|
+
* Verify a detached signature with context.
|
|
1816
1855
|
*
|
|
1817
1856
|
* Performs constant-time verification to prevent timing side-channel attacks.
|
|
1818
1857
|
* The context must match the one used during signing.
|
|
@@ -1820,8 +1859,9 @@ function cryptoSign(msg, sk, randomizedSigning, ctx) {
|
|
|
1820
1859
|
* @param {Uint8Array} sig - Signature to verify (must be CryptoBytes = 4627 bytes)
|
|
1821
1860
|
* @param {string|Uint8Array} m - Message that was signed (hex string, optional 0x prefix, or Uint8Array)
|
|
1822
1861
|
* @param {Uint8Array} pk - Public key (must be CryptoPublicKeyBytes = 2592 bytes)
|
|
1823
|
-
* @param {Uint8Array} ctx - Context string used during signing (max 255 bytes).
|
|
1862
|
+
* @param {Uint8Array} ctx - Context string used during signing (required, max 255 bytes).
|
|
1824
1863
|
* @returns {boolean} true if signature is valid, false otherwise
|
|
1864
|
+
* @throws {TypeError} If ctx is not a Uint8Array
|
|
1825
1865
|
*
|
|
1826
1866
|
* @example
|
|
1827
1867
|
* const isValid = cryptoSignVerify(signature, message, pk, ctx);
|
|
@@ -1847,10 +1887,10 @@ function cryptoSignVerify(sig, m, pk, ctx) {
|
|
|
1847
1887
|
const w1 = new PolyVecK();
|
|
1848
1888
|
const h = new PolyVecK();
|
|
1849
1889
|
|
|
1850
|
-
if (sig.length !== CryptoBytes) {
|
|
1890
|
+
if (!(sig instanceof Uint8Array) || sig.length !== CryptoBytes) {
|
|
1851
1891
|
return false;
|
|
1852
1892
|
}
|
|
1853
|
-
if (pk.length !== CryptoPublicKeyBytes) {
|
|
1893
|
+
if (!(pk instanceof Uint8Array) || pk.length !== CryptoPublicKeyBytes) {
|
|
1854
1894
|
return false;
|
|
1855
1895
|
}
|
|
1856
1896
|
|
|
@@ -1920,8 +1960,9 @@ function cryptoSignVerify(sig, m, pk, ctx) {
|
|
|
1920
1960
|
*
|
|
1921
1961
|
* @param {Uint8Array} sm - Signed message (signature || message)
|
|
1922
1962
|
* @param {Uint8Array} pk - Public key (must be CryptoPublicKeyBytes = 2592 bytes)
|
|
1923
|
-
* @param {Uint8Array} ctx - Context string used during signing (max 255 bytes).
|
|
1963
|
+
* @param {Uint8Array} ctx - Context string used during signing (required, max 255 bytes).
|
|
1924
1964
|
* @returns {Uint8Array|undefined} The original message if valid, undefined if verification fails
|
|
1965
|
+
* @throws {TypeError} If ctx is not a Uint8Array
|
|
1925
1966
|
*
|
|
1926
1967
|
* @example
|
|
1927
1968
|
* const message = cryptoSignOpen(signedMsg, pk, ctx);
|
package/dist/mjs/mldsa87.js
CHANGED
|
@@ -160,12 +160,16 @@ function montgomeryReduce(a) {
|
|
|
160
160
|
return t;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
// Partial reduction modulo Q. Input must satisfy |a| < 2^31 - 2^22.
|
|
164
|
+
// Output is in (-Q, Q). Mirrors the reference C implementation.
|
|
163
165
|
function reduce32(a) {
|
|
164
166
|
let t = (a + (1 << 22)) >> 23;
|
|
165
167
|
t = a - t * Q;
|
|
166
168
|
return t;
|
|
167
169
|
}
|
|
168
170
|
|
|
171
|
+
// Conditional add Q: if a is negative, add Q. Input must satisfy -Q < a < 2^31.
|
|
172
|
+
// Output is in [0, Q). Mirrors the reference C implementation.
|
|
169
173
|
function cAddQ(a) {
|
|
170
174
|
let ar = a;
|
|
171
175
|
ar += (ar >> 31) & Q;
|
|
@@ -327,10 +331,7 @@ function polyChkNorm(a, b) {
|
|
|
327
331
|
}
|
|
328
332
|
|
|
329
333
|
for (let i = 0; i < N; i++) {
|
|
330
|
-
|
|
331
|
-
t = a.coeffs[i] - (t & (2 * a.coeffs[i]));
|
|
332
|
-
|
|
333
|
-
if (t >= b) {
|
|
334
|
+
if (Math.abs(a.coeffs[i]) >= b) {
|
|
334
335
|
return 1;
|
|
335
336
|
}
|
|
336
337
|
}
|
|
@@ -871,6 +872,9 @@ function packPk(pkp, rho, t1) {
|
|
|
871
872
|
}
|
|
872
873
|
|
|
873
874
|
function unpackPk(rhop, t1, pk) {
|
|
875
|
+
if (!(pk instanceof Uint8Array) || pk.length !== CryptoPublicKeyBytes) {
|
|
876
|
+
throw new Error(`pk must be a Uint8Array of ${CryptoPublicKeyBytes} bytes`);
|
|
877
|
+
}
|
|
874
878
|
const rho = rhop;
|
|
875
879
|
for (let i = 0; i < SeedBytes; ++i) {
|
|
876
880
|
rho[i] = pk[i];
|
|
@@ -915,6 +919,9 @@ function packSk(skp, rho, tr, key, t0, s1, s2) {
|
|
|
915
919
|
}
|
|
916
920
|
|
|
917
921
|
function unpackSk(rhoP, trP, keyP, t0, s1, s2, sk) {
|
|
922
|
+
if (!(sk instanceof Uint8Array) || sk.length !== CryptoSecretKeyBytes) {
|
|
923
|
+
throw new Error(`sk must be a Uint8Array of ${CryptoSecretKeyBytes} bytes`);
|
|
924
|
+
}
|
|
918
925
|
let skOffset = 0;
|
|
919
926
|
const rho = rhoP;
|
|
920
927
|
const tr = trP;
|
|
@@ -978,7 +985,12 @@ function packSig(sigP, ctilde, z, h) {
|
|
|
978
985
|
}
|
|
979
986
|
}
|
|
980
987
|
|
|
988
|
+
// Returns 0 on success, 1 on failure. On failure, output buffers (c, z, h)
|
|
989
|
+
// may contain partial data and must not be used.
|
|
981
990
|
function unpackSig(cP, z, hP, sig) {
|
|
991
|
+
if (!(sig instanceof Uint8Array) || sig.length !== CryptoBytes) {
|
|
992
|
+
throw new Error(`sig must be a Uint8Array of ${CryptoBytes} bytes`);
|
|
993
|
+
}
|
|
982
994
|
let sigOffset = 0;
|
|
983
995
|
const c = cP; // ctilde
|
|
984
996
|
const h = hP;
|
|
@@ -1073,6 +1085,8 @@ function randomBytes(size) {
|
|
|
1073
1085
|
*
|
|
1074
1086
|
* @param {Uint8Array} buffer - The buffer to zero
|
|
1075
1087
|
* @returns {void}
|
|
1088
|
+
* @throws {TypeError} If buffer is not a Uint8Array
|
|
1089
|
+
* @throws {Error} If zeroization verification fails
|
|
1076
1090
|
*/
|
|
1077
1091
|
function zeroize(buffer) {
|
|
1078
1092
|
if (!(buffer instanceof Uint8Array)) {
|
|
@@ -1095,6 +1109,7 @@ function zeroize(buffer) {
|
|
|
1095
1109
|
*
|
|
1096
1110
|
* @param {Uint8Array} buffer - The buffer to check
|
|
1097
1111
|
* @returns {boolean} True if all bytes are zero
|
|
1112
|
+
* @throws {TypeError} If buffer is not a Uint8Array
|
|
1098
1113
|
*/
|
|
1099
1114
|
function isZero(buffer) {
|
|
1100
1115
|
if (!(buffer instanceof Uint8Array)) {
|
|
@@ -1110,16 +1125,12 @@ function isZero(buffer) {
|
|
|
1110
1125
|
/**
|
|
1111
1126
|
* Convert hex string to Uint8Array with strict validation.
|
|
1112
1127
|
*
|
|
1113
|
-
*
|
|
1114
|
-
*
|
|
1115
|
-
* mask input errors. Applications requiring strict format validation should
|
|
1116
|
-
* validate hex format before calling cryptographic functions, e.g.:
|
|
1117
|
-
* - Reject strings with 0x prefix if raw hex is expected
|
|
1118
|
-
* - Reject strings with whitespace
|
|
1119
|
-
* - Enforce consistent casing (lowercase/uppercase)
|
|
1128
|
+
* Accepts an optional 0x/0X prefix. Leading/trailing whitespace is rejected.
|
|
1129
|
+
* Empty strings and whitespace-only strings are rejected.
|
|
1120
1130
|
*
|
|
1121
|
-
* @param {string} hex - Hex string (optional 0x prefix, even length).
|
|
1131
|
+
* @param {string} hex - Hex string (optional 0x prefix, even length, no whitespace).
|
|
1122
1132
|
* @returns {Uint8Array} Decoded bytes.
|
|
1133
|
+
* @throws {Error} If input is not a valid hex string
|
|
1123
1134
|
* @private
|
|
1124
1135
|
*/
|
|
1125
1136
|
function hexToBytes(hex) {
|
|
@@ -1128,11 +1139,16 @@ function hexToBytes(hex) {
|
|
|
1128
1139
|
throw new Error('message must be a hex string');
|
|
1129
1140
|
}
|
|
1130
1141
|
/* c8 ignore stop */
|
|
1131
|
-
|
|
1132
|
-
|
|
1142
|
+
if (hex !== hex.trim()) {
|
|
1143
|
+
throw new Error('hex string must not have leading or trailing whitespace');
|
|
1144
|
+
}
|
|
1145
|
+
let clean = hex;
|
|
1133
1146
|
if (clean.startsWith('0x') || clean.startsWith('0X')) {
|
|
1134
1147
|
clean = clean.slice(2);
|
|
1135
1148
|
}
|
|
1149
|
+
if (clean.length === 0) {
|
|
1150
|
+
throw new Error('hex string must not be empty');
|
|
1151
|
+
}
|
|
1136
1152
|
if (clean.length % 2 !== 0) {
|
|
1137
1153
|
throw new Error('hex string must have an even length');
|
|
1138
1154
|
}
|
|
@@ -1142,6 +1158,14 @@ function hexToBytes(hex) {
|
|
|
1142
1158
|
return hexToBytes$1(clean);
|
|
1143
1159
|
}
|
|
1144
1160
|
|
|
1161
|
+
/**
|
|
1162
|
+
* Convert a message to Uint8Array.
|
|
1163
|
+
*
|
|
1164
|
+
* @param {string|Uint8Array} message - Message as hex string (optional 0x prefix) or Uint8Array.
|
|
1165
|
+
* @returns {Uint8Array} Message bytes.
|
|
1166
|
+
* @throws {Error} If message is not a Uint8Array or valid hex string
|
|
1167
|
+
* @private
|
|
1168
|
+
*/
|
|
1145
1169
|
function messageToBytes(message) {
|
|
1146
1170
|
if (typeof message === 'string') {
|
|
1147
1171
|
return hexToBytes(message);
|
|
@@ -1158,8 +1182,8 @@ function messageToBytes(message) {
|
|
|
1158
1182
|
* Key generation follows FIPS 204, using domain separator [K, L] during
|
|
1159
1183
|
* seed expansion to ensure algorithm binding.
|
|
1160
1184
|
*
|
|
1161
|
-
* @param {Uint8Array|null} passedSeed - Optional 32-byte seed for deterministic key generation.
|
|
1162
|
-
* Pass null for random key generation.
|
|
1185
|
+
* @param {Uint8Array|null} [passedSeed=null] - Optional 32-byte seed for deterministic key generation.
|
|
1186
|
+
* Pass null or undefined for random key generation.
|
|
1163
1187
|
* @param {Uint8Array} pk - Output buffer for public key (must be CryptoPublicKeyBytes = 2592 bytes)
|
|
1164
1188
|
* @param {Uint8Array} sk - Output buffer for secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1165
1189
|
* @returns {Uint8Array} The seed used for key generation (useful when passedSeed is null)
|
|
@@ -1251,7 +1275,7 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1251
1275
|
}
|
|
1252
1276
|
|
|
1253
1277
|
/**
|
|
1254
|
-
* Create a detached signature for a message with
|
|
1278
|
+
* Create a detached signature for a message with context.
|
|
1255
1279
|
*
|
|
1256
1280
|
* Uses the ML-DSA-87 (FIPS 204) signing algorithm with rejection sampling.
|
|
1257
1281
|
* The context parameter provides domain separation as required by FIPS 204.
|
|
@@ -1261,9 +1285,16 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1261
1285
|
* @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1262
1286
|
* @param {boolean} randomizedSigning - If true, use random nonce for hedged signing.
|
|
1263
1287
|
* If false, use deterministic nonce derived from message and key.
|
|
1264
|
-
* @param {Uint8Array} ctx - Context string for domain separation (max 255 bytes).
|
|
1288
|
+
* @param {Uint8Array} ctx - Context string for domain separation (required, max 255 bytes).
|
|
1289
|
+
* Pass an empty Uint8Array for no context.
|
|
1265
1290
|
* @returns {number} 0 on success
|
|
1266
|
-
* @throws {
|
|
1291
|
+
* @throws {TypeError} If sig is not a Uint8Array or is smaller than CryptoBytes
|
|
1292
|
+
* @throws {TypeError} If sk is not a Uint8Array
|
|
1293
|
+
* @throws {TypeError} If ctx is not a Uint8Array
|
|
1294
|
+
* @throws {TypeError} If randomizedSigning is not a boolean
|
|
1295
|
+
* @throws {Error} If ctx exceeds 255 bytes
|
|
1296
|
+
* @throws {Error} If sk length does not equal CryptoSecretKeyBytes
|
|
1297
|
+
* @throws {Error} If message is not a Uint8Array or valid hex string
|
|
1267
1298
|
*
|
|
1268
1299
|
* @example
|
|
1269
1300
|
* const sig = new Uint8Array(CryptoBytes);
|
|
@@ -1271,13 +1302,19 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1271
1302
|
* cryptoSignSignature(sig, message, sk, false, ctx);
|
|
1272
1303
|
*/
|
|
1273
1304
|
function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx) {
|
|
1274
|
-
if (!sig || sig.length < CryptoBytes) {
|
|
1275
|
-
throw new
|
|
1305
|
+
if (!(sig instanceof Uint8Array) || sig.length < CryptoBytes) {
|
|
1306
|
+
throw new TypeError(`sig must be at least ${CryptoBytes} bytes and a Uint8Array`);
|
|
1307
|
+
}
|
|
1308
|
+
if (!(sk instanceof Uint8Array)) {
|
|
1309
|
+
throw new TypeError('sk must be a Uint8Array');
|
|
1276
1310
|
}
|
|
1277
1311
|
if (!(ctx instanceof Uint8Array)) {
|
|
1278
1312
|
throw new TypeError('ctx is required and must be a Uint8Array');
|
|
1279
1313
|
}
|
|
1280
1314
|
if (ctx.length > 255) throw new Error(`invalid context length: ${ctx.length} (max 255)`);
|
|
1315
|
+
if (typeof randomizedSigning !== 'boolean') {
|
|
1316
|
+
throw new TypeError('randomizedSigning must be a boolean');
|
|
1317
|
+
}
|
|
1281
1318
|
if (sk.length !== CryptoSecretKeyBytes) {
|
|
1282
1319
|
throw new Error(`invalid sk length ${sk.length} | Expected length ${CryptoSecretKeyBytes}`);
|
|
1283
1320
|
}
|
|
@@ -1403,9 +1440,11 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx) {
|
|
|
1403
1440
|
* @param {string|Uint8Array} msg - Message to sign (hex string, optional 0x prefix, or Uint8Array)
|
|
1404
1441
|
* @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1405
1442
|
* @param {boolean} randomizedSigning - If true, use random nonce; if false, deterministic
|
|
1406
|
-
* @param {Uint8Array} ctx - Context string for domain separation (max 255 bytes).
|
|
1443
|
+
* @param {Uint8Array} ctx - Context string for domain separation (required, max 255 bytes).
|
|
1407
1444
|
* @returns {Uint8Array} Signed message (CryptoBytes + msg.length bytes)
|
|
1408
|
-
* @throws {
|
|
1445
|
+
* @throws {TypeError} If ctx is not a Uint8Array
|
|
1446
|
+
* @throws {TypeError} If sk or randomizedSigning fail type validation (see cryptoSignSignature)
|
|
1447
|
+
* @throws {Error} If signing fails or message/sk/ctx are invalid
|
|
1409
1448
|
*
|
|
1410
1449
|
* @example
|
|
1411
1450
|
* const signedMsg = cryptoSign(message, sk, false, ctx);
|
|
@@ -1433,7 +1472,7 @@ function cryptoSign(msg, sk, randomizedSigning, ctx) {
|
|
|
1433
1472
|
}
|
|
1434
1473
|
|
|
1435
1474
|
/**
|
|
1436
|
-
* Verify a detached signature with
|
|
1475
|
+
* Verify a detached signature with context.
|
|
1437
1476
|
*
|
|
1438
1477
|
* Performs constant-time verification to prevent timing side-channel attacks.
|
|
1439
1478
|
* The context must match the one used during signing.
|
|
@@ -1441,8 +1480,9 @@ function cryptoSign(msg, sk, randomizedSigning, ctx) {
|
|
|
1441
1480
|
* @param {Uint8Array} sig - Signature to verify (must be CryptoBytes = 4627 bytes)
|
|
1442
1481
|
* @param {string|Uint8Array} m - Message that was signed (hex string, optional 0x prefix, or Uint8Array)
|
|
1443
1482
|
* @param {Uint8Array} pk - Public key (must be CryptoPublicKeyBytes = 2592 bytes)
|
|
1444
|
-
* @param {Uint8Array} ctx - Context string used during signing (max 255 bytes).
|
|
1483
|
+
* @param {Uint8Array} ctx - Context string used during signing (required, max 255 bytes).
|
|
1445
1484
|
* @returns {boolean} true if signature is valid, false otherwise
|
|
1485
|
+
* @throws {TypeError} If ctx is not a Uint8Array
|
|
1446
1486
|
*
|
|
1447
1487
|
* @example
|
|
1448
1488
|
* const isValid = cryptoSignVerify(signature, message, pk, ctx);
|
|
@@ -1468,10 +1508,10 @@ function cryptoSignVerify(sig, m, pk, ctx) {
|
|
|
1468
1508
|
const w1 = new PolyVecK();
|
|
1469
1509
|
const h = new PolyVecK();
|
|
1470
1510
|
|
|
1471
|
-
if (sig.length !== CryptoBytes) {
|
|
1511
|
+
if (!(sig instanceof Uint8Array) || sig.length !== CryptoBytes) {
|
|
1472
1512
|
return false;
|
|
1473
1513
|
}
|
|
1474
|
-
if (pk.length !== CryptoPublicKeyBytes) {
|
|
1514
|
+
if (!(pk instanceof Uint8Array) || pk.length !== CryptoPublicKeyBytes) {
|
|
1475
1515
|
return false;
|
|
1476
1516
|
}
|
|
1477
1517
|
|
|
@@ -1541,8 +1581,9 @@ function cryptoSignVerify(sig, m, pk, ctx) {
|
|
|
1541
1581
|
*
|
|
1542
1582
|
* @param {Uint8Array} sm - Signed message (signature || message)
|
|
1543
1583
|
* @param {Uint8Array} pk - Public key (must be CryptoPublicKeyBytes = 2592 bytes)
|
|
1544
|
-
* @param {Uint8Array} ctx - Context string used during signing (max 255 bytes).
|
|
1584
|
+
* @param {Uint8Array} ctx - Context string used during signing (required, max 255 bytes).
|
|
1545
1585
|
* @returns {Uint8Array|undefined} The original message if valid, undefined if verification fails
|
|
1586
|
+
* @throws {TypeError} If ctx is not a Uint8Array
|
|
1546
1587
|
*
|
|
1547
1588
|
* @example
|
|
1548
1589
|
* const message = cryptoSignOpen(signedMsg, pk, ctx);
|