@theqrl/mldsa87 1.1.1 → 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 +27 -18
- package/dist/cjs/mldsa87.js +94 -53
- package/dist/mjs/mldsa87.js +94 -53
- package/package.json +33 -14
- package/src/index.d.ts +10 -10
package/README.md
CHANGED
|
@@ -26,12 +26,13 @@ const pk = new Uint8Array(CryptoPublicKeyBytes); // 2592 bytes
|
|
|
26
26
|
const sk = new Uint8Array(CryptoSecretKeyBytes); // 4896 bytes
|
|
27
27
|
cryptoSignKeypair(null, pk, sk); // null = random seed
|
|
28
28
|
|
|
29
|
-
// Sign a message
|
|
29
|
+
// Sign a message
|
|
30
30
|
const message = new TextEncoder().encode('Hello, quantum world!');
|
|
31
|
-
const
|
|
31
|
+
const ctx = new Uint8Array([0x5a, 0x4f, 0x4e, 0x44]); // "ZOND"
|
|
32
|
+
const signedMessage = cryptoSign(message, sk, false, ctx); // false = deterministic
|
|
32
33
|
|
|
33
|
-
// Verify and extract
|
|
34
|
-
const extracted = cryptoSignOpen(signedMessage, pk);
|
|
34
|
+
// Verify and extract (context must match)
|
|
35
|
+
const extracted = cryptoSignOpen(signedMessage, pk, ctx);
|
|
35
36
|
if (extracted === undefined) {
|
|
36
37
|
throw new Error('Invalid signature');
|
|
37
38
|
}
|
|
@@ -40,20 +41,21 @@ console.log(new TextDecoder().decode(extracted)); // "Hello, quantum world!"
|
|
|
40
41
|
|
|
41
42
|
## Context Parameter
|
|
42
43
|
|
|
43
|
-
ML-DSA-87
|
|
44
|
+
ML-DSA-87 requires a context parameter for domain separation (FIPS 204 feature). This allows the same keypair to be used safely across different applications.
|
|
44
45
|
|
|
45
46
|
```javascript
|
|
46
|
-
// With
|
|
47
|
+
// With application-specific context
|
|
47
48
|
const ctx = new TextEncoder().encode('my-app-v1');
|
|
48
49
|
const signed = cryptoSign(message, sk, false, ctx);
|
|
49
50
|
const extracted = cryptoSignOpen(signed, pk, ctx);
|
|
50
51
|
|
|
51
52
|
// Context must match for verification
|
|
52
|
-
|
|
53
|
-
cryptoSignOpen(signed, pk,
|
|
53
|
+
const wrongCtx = new Uint8Array(0);
|
|
54
|
+
cryptoSignOpen(signed, pk, wrongCtx); // undefined - wrong context
|
|
55
|
+
cryptoSignOpen(signed, pk, ctx); // message - correct context
|
|
54
56
|
```
|
|
55
57
|
|
|
56
|
-
|
|
58
|
+
Context is a required `Uint8Array` and can be 0-255 bytes. Use an empty `Uint8Array(0)` if no domain separation is needed.
|
|
57
59
|
|
|
58
60
|
## API
|
|
59
61
|
|
|
@@ -72,31 +74,31 @@ The default context is `"ZOND"` (for QRL Zond network compatibility). Context ca
|
|
|
72
74
|
|
|
73
75
|
Generate a keypair from a seed.
|
|
74
76
|
|
|
75
|
-
- `seed`: `Uint8Array(32)` or `
|
|
77
|
+
- `seed`: `Uint8Array(32)`, `null`, or `undefined` for random
|
|
76
78
|
- `pk`: `Uint8Array(2592)` - output buffer for public key
|
|
77
79
|
- `sk`: `Uint8Array(4896)` - output buffer for secret key
|
|
78
80
|
- Returns: The seed used (useful when `seed` is `null`)
|
|
79
81
|
|
|
80
|
-
#### `cryptoSign(message, sk, randomized, context
|
|
82
|
+
#### `cryptoSign(message, sk, randomized, context)`
|
|
81
83
|
|
|
82
84
|
Sign a message (combined mode: returns signature || message).
|
|
83
85
|
|
|
84
86
|
- `message`: `Uint8Array` or `string` - message bytes; if `string`, it must be hex only (optional `0x`, even length). Plain-text strings are not accepted.
|
|
85
87
|
- `sk`: `Uint8Array(4896)` - secret key
|
|
86
88
|
- `randomized`: `boolean` - `true` for hedged signing, `false` for deterministic
|
|
87
|
-
- `context`: `Uint8Array`
|
|
89
|
+
- `context`: `Uint8Array` - context string for domain separation, 0-255 bytes
|
|
88
90
|
- Returns: `Uint8Array` containing signature + message
|
|
89
91
|
|
|
90
|
-
#### `cryptoSignOpen(signedMessage, pk, context
|
|
92
|
+
#### `cryptoSignOpen(signedMessage, pk, context)`
|
|
91
93
|
|
|
92
94
|
Verify and extract message from signed message.
|
|
93
95
|
|
|
94
96
|
- `signedMessage`: `Uint8Array` - output from `cryptoSign()`
|
|
95
97
|
- `pk`: `Uint8Array(2592)` - public key
|
|
96
|
-
- `context`: `Uint8Array`
|
|
98
|
+
- `context`: `Uint8Array` - must match signing context
|
|
97
99
|
- Returns: Original message if valid, `undefined` if verification fails
|
|
98
100
|
|
|
99
|
-
#### `cryptoSignSignature(sig, message, sk, randomized, context
|
|
101
|
+
#### `cryptoSignSignature(sig, message, sk, randomized, context)`
|
|
100
102
|
|
|
101
103
|
Create a detached signature.
|
|
102
104
|
|
|
@@ -104,17 +106,17 @@ Create a detached signature.
|
|
|
104
106
|
- `message`: `Uint8Array` or `string` - message bytes; if `string`, it must be hex only (optional `0x`, even length). Plain-text strings are not accepted.
|
|
105
107
|
- `sk`: `Uint8Array(4896)` - secret key
|
|
106
108
|
- `randomized`: `boolean` - `true` for hedged, `false` for deterministic
|
|
107
|
-
- `context`: `Uint8Array`
|
|
109
|
+
- `context`: `Uint8Array` - context string for domain separation, 0-255 bytes
|
|
108
110
|
- Returns: `0` on success
|
|
109
111
|
|
|
110
|
-
#### `cryptoSignVerify(sig, message, pk, context
|
|
112
|
+
#### `cryptoSignVerify(sig, message, pk, context)`
|
|
111
113
|
|
|
112
114
|
Verify a detached signature.
|
|
113
115
|
|
|
114
116
|
- `sig`: `Uint8Array(4627)` - signature to verify
|
|
115
117
|
- `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
118
|
- `pk`: `Uint8Array(2592)` - public key
|
|
117
|
-
- `context`: `Uint8Array`
|
|
119
|
+
- `context`: `Uint8Array` - must match signing context
|
|
118
120
|
- Returns: `true` if valid, `false` otherwise
|
|
119
121
|
|
|
120
122
|
**Note:** To sign or verify plain text, convert it to bytes (e.g., `new TextEncoder().encode('Hello')`). String inputs are interpreted as hex only.
|
|
@@ -123,10 +125,17 @@ Verify a detached signature.
|
|
|
123
125
|
|
|
124
126
|
Zero out sensitive data (best-effort, see security notes).
|
|
125
127
|
|
|
128
|
+
- `buffer`: `Uint8Array` - the buffer to zero
|
|
129
|
+
- Throws: `TypeError` if buffer is not a `Uint8Array`
|
|
130
|
+
|
|
126
131
|
#### `isZero(buffer)`
|
|
127
132
|
|
|
128
133
|
Check if buffer is all zeros (constant-time).
|
|
129
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
|
+
|
|
130
139
|
## Interoperability
|
|
131
140
|
|
|
132
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;
|
|
@@ -553,7 +557,7 @@ function cAddQ(a) {
|
|
|
553
557
|
|
|
554
558
|
function ntt(a) {
|
|
555
559
|
let k = 0;
|
|
556
|
-
let j
|
|
560
|
+
let j;
|
|
557
561
|
|
|
558
562
|
for (let len = 128; len > 0; len >>= 1) {
|
|
559
563
|
for (let start = 0; start < N; start = j + len) {
|
|
@@ -569,7 +573,7 @@ function ntt(a) {
|
|
|
569
573
|
|
|
570
574
|
function invNTTToMont(a) {
|
|
571
575
|
const f = 41978n; // mont^2/256
|
|
572
|
-
let j
|
|
576
|
+
let j;
|
|
573
577
|
let k = 256;
|
|
574
578
|
|
|
575
579
|
for (let len = 1; len < N; len <<= 1) {
|
|
@@ -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)) {
|
|
@@ -1486,26 +1501,15 @@ function isZero(buffer) {
|
|
|
1486
1501
|
return acc === 0;
|
|
1487
1502
|
}
|
|
1488
1503
|
|
|
1489
|
-
/**
|
|
1490
|
-
* Default signing context ("ZOND" in ASCII).
|
|
1491
|
-
* Used for domain separation per FIPS 204.
|
|
1492
|
-
* @constant {Uint8Array}
|
|
1493
|
-
*/
|
|
1494
|
-
const DEFAULT_CTX = new Uint8Array([0x5a, 0x4f, 0x4e, 0x44]); // "ZOND"
|
|
1495
|
-
|
|
1496
1504
|
/**
|
|
1497
1505
|
* Convert hex string to Uint8Array with strict validation.
|
|
1498
1506
|
*
|
|
1499
|
-
*
|
|
1500
|
-
*
|
|
1501
|
-
* mask input errors. Applications requiring strict format validation should
|
|
1502
|
-
* validate hex format before calling cryptographic functions, e.g.:
|
|
1503
|
-
* - Reject strings with 0x prefix if raw hex is expected
|
|
1504
|
-
* - Reject strings with whitespace
|
|
1505
|
-
* - 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.
|
|
1506
1509
|
*
|
|
1507
|
-
* @param {string} hex - Hex string (optional 0x prefix, even length).
|
|
1510
|
+
* @param {string} hex - Hex string (optional 0x prefix, even length, no whitespace).
|
|
1508
1511
|
* @returns {Uint8Array} Decoded bytes.
|
|
1512
|
+
* @throws {Error} If input is not a valid hex string
|
|
1509
1513
|
* @private
|
|
1510
1514
|
*/
|
|
1511
1515
|
function hexToBytes(hex) {
|
|
@@ -1514,11 +1518,16 @@ function hexToBytes(hex) {
|
|
|
1514
1518
|
throw new Error('message must be a hex string');
|
|
1515
1519
|
}
|
|
1516
1520
|
/* c8 ignore stop */
|
|
1517
|
-
|
|
1518
|
-
|
|
1521
|
+
if (hex !== hex.trim()) {
|
|
1522
|
+
throw new Error('hex string must not have leading or trailing whitespace');
|
|
1523
|
+
}
|
|
1524
|
+
let clean = hex;
|
|
1519
1525
|
if (clean.startsWith('0x') || clean.startsWith('0X')) {
|
|
1520
1526
|
clean = clean.slice(2);
|
|
1521
1527
|
}
|
|
1528
|
+
if (clean.length === 0) {
|
|
1529
|
+
throw new Error('hex string must not be empty');
|
|
1530
|
+
}
|
|
1522
1531
|
if (clean.length % 2 !== 0) {
|
|
1523
1532
|
throw new Error('hex string must have an even length');
|
|
1524
1533
|
}
|
|
@@ -1528,6 +1537,14 @@ function hexToBytes(hex) {
|
|
|
1528
1537
|
return hexToBytes$1(clean);
|
|
1529
1538
|
}
|
|
1530
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
|
+
*/
|
|
1531
1548
|
function messageToBytes(message) {
|
|
1532
1549
|
if (typeof message === 'string') {
|
|
1533
1550
|
return hexToBytes(message);
|
|
@@ -1544,8 +1561,8 @@ function messageToBytes(message) {
|
|
|
1544
1561
|
* Key generation follows FIPS 204, using domain separator [K, L] during
|
|
1545
1562
|
* seed expansion to ensure algorithm binding.
|
|
1546
1563
|
*
|
|
1547
|
-
* @param {Uint8Array|null} passedSeed - Optional 32-byte seed for deterministic key generation.
|
|
1548
|
-
* 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.
|
|
1549
1566
|
* @param {Uint8Array} pk - Output buffer for public key (must be CryptoPublicKeyBytes = 2592 bytes)
|
|
1550
1567
|
* @param {Uint8Array} sk - Output buffer for secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1551
1568
|
* @returns {Uint8Array} The seed used for key generation (useful when passedSeed is null)
|
|
@@ -1566,9 +1583,9 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1566
1583
|
}
|
|
1567
1584
|
} catch (e) {
|
|
1568
1585
|
if (e instanceof TypeError) {
|
|
1569
|
-
throw new Error(`pk/sk cannot be null
|
|
1586
|
+
throw new Error(`pk/sk cannot be null`, { cause: e });
|
|
1570
1587
|
} else {
|
|
1571
|
-
throw new Error(`${e.message}
|
|
1588
|
+
throw new Error(`${e.message}`, { cause: e });
|
|
1572
1589
|
}
|
|
1573
1590
|
}
|
|
1574
1591
|
|
|
@@ -1637,7 +1654,7 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1637
1654
|
}
|
|
1638
1655
|
|
|
1639
1656
|
/**
|
|
1640
|
-
* Create a detached signature for a message with
|
|
1657
|
+
* Create a detached signature for a message with context.
|
|
1641
1658
|
*
|
|
1642
1659
|
* Uses the ML-DSA-87 (FIPS 204) signing algorithm with rejection sampling.
|
|
1643
1660
|
* The context parameter provides domain separation as required by FIPS 204.
|
|
@@ -1647,22 +1664,36 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1647
1664
|
* @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1648
1665
|
* @param {boolean} randomizedSigning - If true, use random nonce for hedged signing.
|
|
1649
1666
|
* If false, use deterministic nonce derived from message and key.
|
|
1650
|
-
* @param {Uint8Array}
|
|
1651
|
-
*
|
|
1667
|
+
* @param {Uint8Array} ctx - Context string for domain separation (required, max 255 bytes).
|
|
1668
|
+
* Pass an empty Uint8Array for no context.
|
|
1652
1669
|
* @returns {number} 0 on success
|
|
1653
|
-
* @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
|
|
1654
1677
|
*
|
|
1655
1678
|
* @example
|
|
1656
1679
|
* const sig = new Uint8Array(CryptoBytes);
|
|
1657
|
-
*
|
|
1658
|
-
*
|
|
1659
|
-
* cryptoSignSignature(sig, message, sk, false, new Uint8Array([0x01, 0x02]));
|
|
1680
|
+
* const ctx = new Uint8Array([0x01, 0x02]);
|
|
1681
|
+
* cryptoSignSignature(sig, message, sk, false, ctx);
|
|
1660
1682
|
*/
|
|
1661
|
-
function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx
|
|
1662
|
-
if (!sig || sig.length < CryptoBytes) {
|
|
1663
|
-
throw new
|
|
1683
|
+
function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx) {
|
|
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');
|
|
1689
|
+
}
|
|
1690
|
+
if (!(ctx instanceof Uint8Array)) {
|
|
1691
|
+
throw new TypeError('ctx is required and must be a Uint8Array');
|
|
1664
1692
|
}
|
|
1665
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
|
+
}
|
|
1666
1697
|
if (sk.length !== CryptoSecretKeyBytes) {
|
|
1667
1698
|
throw new Error(`invalid sk length ${sk.length} | Expected length ${CryptoSecretKeyBytes}`);
|
|
1668
1699
|
}
|
|
@@ -1788,16 +1819,20 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1788
1819
|
* @param {string|Uint8Array} msg - Message to sign (hex string, optional 0x prefix, or Uint8Array)
|
|
1789
1820
|
* @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1790
1821
|
* @param {boolean} randomizedSigning - If true, use random nonce; if false, deterministic
|
|
1791
|
-
* @param {Uint8Array}
|
|
1792
|
-
* Defaults to "ZOND" for QRL compatibility.
|
|
1822
|
+
* @param {Uint8Array} ctx - Context string for domain separation (required, max 255 bytes).
|
|
1793
1823
|
* @returns {Uint8Array} Signed message (CryptoBytes + msg.length bytes)
|
|
1794
|
-
* @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
|
|
1795
1827
|
*
|
|
1796
1828
|
* @example
|
|
1797
|
-
* const signedMsg = cryptoSign(message, sk, false);
|
|
1829
|
+
* const signedMsg = cryptoSign(message, sk, false, ctx);
|
|
1798
1830
|
* // signedMsg contains: signature (4627 bytes) || message
|
|
1799
1831
|
*/
|
|
1800
|
-
function cryptoSign(msg, sk, randomizedSigning, ctx
|
|
1832
|
+
function cryptoSign(msg, sk, randomizedSigning, ctx) {
|
|
1833
|
+
if (!(ctx instanceof Uint8Array)) {
|
|
1834
|
+
throw new TypeError('ctx is required and must be a Uint8Array');
|
|
1835
|
+
}
|
|
1801
1836
|
const msgBytes = messageToBytes(msg);
|
|
1802
1837
|
|
|
1803
1838
|
const sm = new Uint8Array(CryptoBytes + msgBytes.length);
|
|
@@ -1816,7 +1851,7 @@ function cryptoSign(msg, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1816
1851
|
}
|
|
1817
1852
|
|
|
1818
1853
|
/**
|
|
1819
|
-
* Verify a detached signature with
|
|
1854
|
+
* Verify a detached signature with context.
|
|
1820
1855
|
*
|
|
1821
1856
|
* Performs constant-time verification to prevent timing side-channel attacks.
|
|
1822
1857
|
* The context must match the one used during signing.
|
|
@@ -1824,17 +1859,20 @@ function cryptoSign(msg, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1824
1859
|
* @param {Uint8Array} sig - Signature to verify (must be CryptoBytes = 4627 bytes)
|
|
1825
1860
|
* @param {string|Uint8Array} m - Message that was signed (hex string, optional 0x prefix, or Uint8Array)
|
|
1826
1861
|
* @param {Uint8Array} pk - Public key (must be CryptoPublicKeyBytes = 2592 bytes)
|
|
1827
|
-
* @param {Uint8Array}
|
|
1828
|
-
* Defaults to "ZOND" for QRL compatibility.
|
|
1862
|
+
* @param {Uint8Array} ctx - Context string used during signing (required, max 255 bytes).
|
|
1829
1863
|
* @returns {boolean} true if signature is valid, false otherwise
|
|
1864
|
+
* @throws {TypeError} If ctx is not a Uint8Array
|
|
1830
1865
|
*
|
|
1831
1866
|
* @example
|
|
1832
|
-
* const isValid = cryptoSignVerify(signature, message, pk);
|
|
1867
|
+
* const isValid = cryptoSignVerify(signature, message, pk, ctx);
|
|
1833
1868
|
* if (!isValid) {
|
|
1834
1869
|
* throw new Error('Invalid signature');
|
|
1835
1870
|
* }
|
|
1836
1871
|
*/
|
|
1837
|
-
function cryptoSignVerify(sig, m, pk, ctx
|
|
1872
|
+
function cryptoSignVerify(sig, m, pk, ctx) {
|
|
1873
|
+
if (!(ctx instanceof Uint8Array)) {
|
|
1874
|
+
throw new TypeError('ctx is required and must be a Uint8Array');
|
|
1875
|
+
}
|
|
1838
1876
|
if (ctx.length > 255) return false;
|
|
1839
1877
|
let i;
|
|
1840
1878
|
const buf = new Uint8Array(K * PolyW1PackedBytes);
|
|
@@ -1849,10 +1887,10 @@ function cryptoSignVerify(sig, m, pk, ctx = DEFAULT_CTX) {
|
|
|
1849
1887
|
const w1 = new PolyVecK();
|
|
1850
1888
|
const h = new PolyVecK();
|
|
1851
1889
|
|
|
1852
|
-
if (sig.length !== CryptoBytes) {
|
|
1890
|
+
if (!(sig instanceof Uint8Array) || sig.length !== CryptoBytes) {
|
|
1853
1891
|
return false;
|
|
1854
1892
|
}
|
|
1855
|
-
if (pk.length !== CryptoPublicKeyBytes) {
|
|
1893
|
+
if (!(pk instanceof Uint8Array) || pk.length !== CryptoPublicKeyBytes) {
|
|
1856
1894
|
return false;
|
|
1857
1895
|
}
|
|
1858
1896
|
|
|
@@ -1922,17 +1960,20 @@ function cryptoSignVerify(sig, m, pk, ctx = DEFAULT_CTX) {
|
|
|
1922
1960
|
*
|
|
1923
1961
|
* @param {Uint8Array} sm - Signed message (signature || message)
|
|
1924
1962
|
* @param {Uint8Array} pk - Public key (must be CryptoPublicKeyBytes = 2592 bytes)
|
|
1925
|
-
* @param {Uint8Array}
|
|
1926
|
-
* Defaults to "ZOND" for QRL compatibility.
|
|
1963
|
+
* @param {Uint8Array} ctx - Context string used during signing (required, max 255 bytes).
|
|
1927
1964
|
* @returns {Uint8Array|undefined} The original message if valid, undefined if verification fails
|
|
1965
|
+
* @throws {TypeError} If ctx is not a Uint8Array
|
|
1928
1966
|
*
|
|
1929
1967
|
* @example
|
|
1930
|
-
* const message = cryptoSignOpen(signedMsg, pk);
|
|
1968
|
+
* const message = cryptoSignOpen(signedMsg, pk, ctx);
|
|
1931
1969
|
* if (message === undefined) {
|
|
1932
1970
|
* throw new Error('Invalid signature');
|
|
1933
1971
|
* }
|
|
1934
1972
|
*/
|
|
1935
|
-
function cryptoSignOpen(sm, pk, ctx
|
|
1973
|
+
function cryptoSignOpen(sm, pk, ctx) {
|
|
1974
|
+
if (!(ctx instanceof Uint8Array)) {
|
|
1975
|
+
throw new TypeError('ctx is required and must be a Uint8Array');
|
|
1976
|
+
}
|
|
1936
1977
|
if (sm.length < CryptoBytes) {
|
|
1937
1978
|
return undefined;
|
|
1938
1979
|
}
|
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;
|
|
@@ -174,7 +178,7 @@ function cAddQ(a) {
|
|
|
174
178
|
|
|
175
179
|
function ntt(a) {
|
|
176
180
|
let k = 0;
|
|
177
|
-
let j
|
|
181
|
+
let j;
|
|
178
182
|
|
|
179
183
|
for (let len = 128; len > 0; len >>= 1) {
|
|
180
184
|
for (let start = 0; start < N; start = j + len) {
|
|
@@ -190,7 +194,7 @@ function ntt(a) {
|
|
|
190
194
|
|
|
191
195
|
function invNTTToMont(a) {
|
|
192
196
|
const f = 41978n; // mont^2/256
|
|
193
|
-
let j
|
|
197
|
+
let j;
|
|
194
198
|
let k = 256;
|
|
195
199
|
|
|
196
200
|
for (let len = 1; len < N; len <<= 1) {
|
|
@@ -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)) {
|
|
@@ -1107,26 +1122,15 @@ function isZero(buffer) {
|
|
|
1107
1122
|
return acc === 0;
|
|
1108
1123
|
}
|
|
1109
1124
|
|
|
1110
|
-
/**
|
|
1111
|
-
* Default signing context ("ZOND" in ASCII).
|
|
1112
|
-
* Used for domain separation per FIPS 204.
|
|
1113
|
-
* @constant {Uint8Array}
|
|
1114
|
-
*/
|
|
1115
|
-
const DEFAULT_CTX = new Uint8Array([0x5a, 0x4f, 0x4e, 0x44]); // "ZOND"
|
|
1116
|
-
|
|
1117
1125
|
/**
|
|
1118
1126
|
* Convert hex string to Uint8Array with strict validation.
|
|
1119
1127
|
*
|
|
1120
|
-
*
|
|
1121
|
-
*
|
|
1122
|
-
* mask input errors. Applications requiring strict format validation should
|
|
1123
|
-
* validate hex format before calling cryptographic functions, e.g.:
|
|
1124
|
-
* - Reject strings with 0x prefix if raw hex is expected
|
|
1125
|
-
* - Reject strings with whitespace
|
|
1126
|
-
* - 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.
|
|
1127
1130
|
*
|
|
1128
|
-
* @param {string} hex - Hex string (optional 0x prefix, even length).
|
|
1131
|
+
* @param {string} hex - Hex string (optional 0x prefix, even length, no whitespace).
|
|
1129
1132
|
* @returns {Uint8Array} Decoded bytes.
|
|
1133
|
+
* @throws {Error} If input is not a valid hex string
|
|
1130
1134
|
* @private
|
|
1131
1135
|
*/
|
|
1132
1136
|
function hexToBytes(hex) {
|
|
@@ -1135,11 +1139,16 @@ function hexToBytes(hex) {
|
|
|
1135
1139
|
throw new Error('message must be a hex string');
|
|
1136
1140
|
}
|
|
1137
1141
|
/* c8 ignore stop */
|
|
1138
|
-
|
|
1139
|
-
|
|
1142
|
+
if (hex !== hex.trim()) {
|
|
1143
|
+
throw new Error('hex string must not have leading or trailing whitespace');
|
|
1144
|
+
}
|
|
1145
|
+
let clean = hex;
|
|
1140
1146
|
if (clean.startsWith('0x') || clean.startsWith('0X')) {
|
|
1141
1147
|
clean = clean.slice(2);
|
|
1142
1148
|
}
|
|
1149
|
+
if (clean.length === 0) {
|
|
1150
|
+
throw new Error('hex string must not be empty');
|
|
1151
|
+
}
|
|
1143
1152
|
if (clean.length % 2 !== 0) {
|
|
1144
1153
|
throw new Error('hex string must have an even length');
|
|
1145
1154
|
}
|
|
@@ -1149,6 +1158,14 @@ function hexToBytes(hex) {
|
|
|
1149
1158
|
return hexToBytes$1(clean);
|
|
1150
1159
|
}
|
|
1151
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
|
+
*/
|
|
1152
1169
|
function messageToBytes(message) {
|
|
1153
1170
|
if (typeof message === 'string') {
|
|
1154
1171
|
return hexToBytes(message);
|
|
@@ -1165,8 +1182,8 @@ function messageToBytes(message) {
|
|
|
1165
1182
|
* Key generation follows FIPS 204, using domain separator [K, L] during
|
|
1166
1183
|
* seed expansion to ensure algorithm binding.
|
|
1167
1184
|
*
|
|
1168
|
-
* @param {Uint8Array|null} passedSeed - Optional 32-byte seed for deterministic key generation.
|
|
1169
|
-
* 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.
|
|
1170
1187
|
* @param {Uint8Array} pk - Output buffer for public key (must be CryptoPublicKeyBytes = 2592 bytes)
|
|
1171
1188
|
* @param {Uint8Array} sk - Output buffer for secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1172
1189
|
* @returns {Uint8Array} The seed used for key generation (useful when passedSeed is null)
|
|
@@ -1187,9 +1204,9 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1187
1204
|
}
|
|
1188
1205
|
} catch (e) {
|
|
1189
1206
|
if (e instanceof TypeError) {
|
|
1190
|
-
throw new Error(`pk/sk cannot be null
|
|
1207
|
+
throw new Error(`pk/sk cannot be null`, { cause: e });
|
|
1191
1208
|
} else {
|
|
1192
|
-
throw new Error(`${e.message}
|
|
1209
|
+
throw new Error(`${e.message}`, { cause: e });
|
|
1193
1210
|
}
|
|
1194
1211
|
}
|
|
1195
1212
|
|
|
@@ -1258,7 +1275,7 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1258
1275
|
}
|
|
1259
1276
|
|
|
1260
1277
|
/**
|
|
1261
|
-
* Create a detached signature for a message with
|
|
1278
|
+
* Create a detached signature for a message with context.
|
|
1262
1279
|
*
|
|
1263
1280
|
* Uses the ML-DSA-87 (FIPS 204) signing algorithm with rejection sampling.
|
|
1264
1281
|
* The context parameter provides domain separation as required by FIPS 204.
|
|
@@ -1268,22 +1285,36 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
|
|
|
1268
1285
|
* @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1269
1286
|
* @param {boolean} randomizedSigning - If true, use random nonce for hedged signing.
|
|
1270
1287
|
* If false, use deterministic nonce derived from message and key.
|
|
1271
|
-
* @param {Uint8Array}
|
|
1272
|
-
*
|
|
1288
|
+
* @param {Uint8Array} ctx - Context string for domain separation (required, max 255 bytes).
|
|
1289
|
+
* Pass an empty Uint8Array for no context.
|
|
1273
1290
|
* @returns {number} 0 on success
|
|
1274
|
-
* @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
|
|
1275
1298
|
*
|
|
1276
1299
|
* @example
|
|
1277
1300
|
* const sig = new Uint8Array(CryptoBytes);
|
|
1278
|
-
*
|
|
1279
|
-
*
|
|
1280
|
-
* cryptoSignSignature(sig, message, sk, false, new Uint8Array([0x01, 0x02]));
|
|
1301
|
+
* const ctx = new Uint8Array([0x01, 0x02]);
|
|
1302
|
+
* cryptoSignSignature(sig, message, sk, false, ctx);
|
|
1281
1303
|
*/
|
|
1282
|
-
function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx
|
|
1283
|
-
if (!sig || sig.length < CryptoBytes) {
|
|
1284
|
-
throw new
|
|
1304
|
+
function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx) {
|
|
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');
|
|
1310
|
+
}
|
|
1311
|
+
if (!(ctx instanceof Uint8Array)) {
|
|
1312
|
+
throw new TypeError('ctx is required and must be a Uint8Array');
|
|
1285
1313
|
}
|
|
1286
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
|
+
}
|
|
1287
1318
|
if (sk.length !== CryptoSecretKeyBytes) {
|
|
1288
1319
|
throw new Error(`invalid sk length ${sk.length} | Expected length ${CryptoSecretKeyBytes}`);
|
|
1289
1320
|
}
|
|
@@ -1409,16 +1440,20 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1409
1440
|
* @param {string|Uint8Array} msg - Message to sign (hex string, optional 0x prefix, or Uint8Array)
|
|
1410
1441
|
* @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
|
|
1411
1442
|
* @param {boolean} randomizedSigning - If true, use random nonce; if false, deterministic
|
|
1412
|
-
* @param {Uint8Array}
|
|
1413
|
-
* Defaults to "ZOND" for QRL compatibility.
|
|
1443
|
+
* @param {Uint8Array} ctx - Context string for domain separation (required, max 255 bytes).
|
|
1414
1444
|
* @returns {Uint8Array} Signed message (CryptoBytes + msg.length bytes)
|
|
1415
|
-
* @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
|
|
1416
1448
|
*
|
|
1417
1449
|
* @example
|
|
1418
|
-
* const signedMsg = cryptoSign(message, sk, false);
|
|
1450
|
+
* const signedMsg = cryptoSign(message, sk, false, ctx);
|
|
1419
1451
|
* // signedMsg contains: signature (4627 bytes) || message
|
|
1420
1452
|
*/
|
|
1421
|
-
function cryptoSign(msg, sk, randomizedSigning, ctx
|
|
1453
|
+
function cryptoSign(msg, sk, randomizedSigning, ctx) {
|
|
1454
|
+
if (!(ctx instanceof Uint8Array)) {
|
|
1455
|
+
throw new TypeError('ctx is required and must be a Uint8Array');
|
|
1456
|
+
}
|
|
1422
1457
|
const msgBytes = messageToBytes(msg);
|
|
1423
1458
|
|
|
1424
1459
|
const sm = new Uint8Array(CryptoBytes + msgBytes.length);
|
|
@@ -1437,7 +1472,7 @@ function cryptoSign(msg, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1437
1472
|
}
|
|
1438
1473
|
|
|
1439
1474
|
/**
|
|
1440
|
-
* Verify a detached signature with
|
|
1475
|
+
* Verify a detached signature with context.
|
|
1441
1476
|
*
|
|
1442
1477
|
* Performs constant-time verification to prevent timing side-channel attacks.
|
|
1443
1478
|
* The context must match the one used during signing.
|
|
@@ -1445,17 +1480,20 @@ function cryptoSign(msg, sk, randomizedSigning, ctx = DEFAULT_CTX) {
|
|
|
1445
1480
|
* @param {Uint8Array} sig - Signature to verify (must be CryptoBytes = 4627 bytes)
|
|
1446
1481
|
* @param {string|Uint8Array} m - Message that was signed (hex string, optional 0x prefix, or Uint8Array)
|
|
1447
1482
|
* @param {Uint8Array} pk - Public key (must be CryptoPublicKeyBytes = 2592 bytes)
|
|
1448
|
-
* @param {Uint8Array}
|
|
1449
|
-
* Defaults to "ZOND" for QRL compatibility.
|
|
1483
|
+
* @param {Uint8Array} ctx - Context string used during signing (required, max 255 bytes).
|
|
1450
1484
|
* @returns {boolean} true if signature is valid, false otherwise
|
|
1485
|
+
* @throws {TypeError} If ctx is not a Uint8Array
|
|
1451
1486
|
*
|
|
1452
1487
|
* @example
|
|
1453
|
-
* const isValid = cryptoSignVerify(signature, message, pk);
|
|
1488
|
+
* const isValid = cryptoSignVerify(signature, message, pk, ctx);
|
|
1454
1489
|
* if (!isValid) {
|
|
1455
1490
|
* throw new Error('Invalid signature');
|
|
1456
1491
|
* }
|
|
1457
1492
|
*/
|
|
1458
|
-
function cryptoSignVerify(sig, m, pk, ctx
|
|
1493
|
+
function cryptoSignVerify(sig, m, pk, ctx) {
|
|
1494
|
+
if (!(ctx instanceof Uint8Array)) {
|
|
1495
|
+
throw new TypeError('ctx is required and must be a Uint8Array');
|
|
1496
|
+
}
|
|
1459
1497
|
if (ctx.length > 255) return false;
|
|
1460
1498
|
let i;
|
|
1461
1499
|
const buf = new Uint8Array(K * PolyW1PackedBytes);
|
|
@@ -1470,10 +1508,10 @@ function cryptoSignVerify(sig, m, pk, ctx = DEFAULT_CTX) {
|
|
|
1470
1508
|
const w1 = new PolyVecK();
|
|
1471
1509
|
const h = new PolyVecK();
|
|
1472
1510
|
|
|
1473
|
-
if (sig.length !== CryptoBytes) {
|
|
1511
|
+
if (!(sig instanceof Uint8Array) || sig.length !== CryptoBytes) {
|
|
1474
1512
|
return false;
|
|
1475
1513
|
}
|
|
1476
|
-
if (pk.length !== CryptoPublicKeyBytes) {
|
|
1514
|
+
if (!(pk instanceof Uint8Array) || pk.length !== CryptoPublicKeyBytes) {
|
|
1477
1515
|
return false;
|
|
1478
1516
|
}
|
|
1479
1517
|
|
|
@@ -1543,17 +1581,20 @@ function cryptoSignVerify(sig, m, pk, ctx = DEFAULT_CTX) {
|
|
|
1543
1581
|
*
|
|
1544
1582
|
* @param {Uint8Array} sm - Signed message (signature || message)
|
|
1545
1583
|
* @param {Uint8Array} pk - Public key (must be CryptoPublicKeyBytes = 2592 bytes)
|
|
1546
|
-
* @param {Uint8Array}
|
|
1547
|
-
* Defaults to "ZOND" for QRL compatibility.
|
|
1584
|
+
* @param {Uint8Array} ctx - Context string used during signing (required, max 255 bytes).
|
|
1548
1585
|
* @returns {Uint8Array|undefined} The original message if valid, undefined if verification fails
|
|
1586
|
+
* @throws {TypeError} If ctx is not a Uint8Array
|
|
1549
1587
|
*
|
|
1550
1588
|
* @example
|
|
1551
|
-
* const message = cryptoSignOpen(signedMsg, pk);
|
|
1589
|
+
* const message = cryptoSignOpen(signedMsg, pk, ctx);
|
|
1552
1590
|
* if (message === undefined) {
|
|
1553
1591
|
* throw new Error('Invalid signature');
|
|
1554
1592
|
* }
|
|
1555
1593
|
*/
|
|
1556
|
-
function cryptoSignOpen(sm, pk, ctx
|
|
1594
|
+
function cryptoSignOpen(sm, pk, ctx) {
|
|
1595
|
+
if (!(ctx instanceof Uint8Array)) {
|
|
1596
|
+
throw new TypeError('ctx is required and must be a Uint8Array');
|
|
1597
|
+
}
|
|
1557
1598
|
if (sm.length < CryptoBytes) {
|
|
1558
1599
|
return undefined;
|
|
1559
1600
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theqrl/mldsa87",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "ML-DSA-87 cryptography",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ml-dsa",
|
|
@@ -53,20 +53,39 @@
|
|
|
53
53
|
"node": ">=20.19.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@eslint/js": "
|
|
57
|
-
"@rollup/plugin-node-resolve": "
|
|
58
|
-
"c8": "
|
|
59
|
-
"chai": "
|
|
60
|
-
"eslint": "
|
|
61
|
-
"eslint-config-prettier": "
|
|
62
|
-
"eslint-plugin-import-x": "
|
|
63
|
-
"eslint-plugin-prettier": "
|
|
64
|
-
"globals": "
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
56
|
+
"@eslint/js": "10.0.1",
|
|
57
|
+
"@rollup/plugin-node-resolve": "16.0.3",
|
|
58
|
+
"c8": "11.0.0",
|
|
59
|
+
"chai": "6.2.2",
|
|
60
|
+
"eslint": "10.0.3",
|
|
61
|
+
"eslint-config-prettier": "10.1.8",
|
|
62
|
+
"eslint-plugin-import-x": "4.16.2",
|
|
63
|
+
"eslint-plugin-prettier": "5.5.5",
|
|
64
|
+
"globals": "17.4.0",
|
|
65
|
+
"minimatch": "10.2.4",
|
|
66
|
+
"mocha": "11.7.5",
|
|
67
|
+
"prettier": "3.8.1",
|
|
68
|
+
"rollup": "4.59.0",
|
|
69
|
+
"serialize-javascript": "7.0.4",
|
|
70
|
+
"tar": "7.5.11"
|
|
68
71
|
},
|
|
69
72
|
"dependencies": {
|
|
70
|
-
"@noble/hashes": "
|
|
73
|
+
"@noble/hashes": "2.0.1"
|
|
74
|
+
},
|
|
75
|
+
"overrides": {
|
|
76
|
+
"diff": "8.0.3",
|
|
77
|
+
"minimatch": "10.2.4"
|
|
78
|
+
},
|
|
79
|
+
"c8": {
|
|
80
|
+
"include": [
|
|
81
|
+
"src/**"
|
|
82
|
+
],
|
|
83
|
+
"exclude": [
|
|
84
|
+
"**/dist/**",
|
|
85
|
+
"**/test/**",
|
|
86
|
+
"**/browser-tests/**",
|
|
87
|
+
"**/*.d.ts"
|
|
88
|
+
],
|
|
89
|
+
"all": true
|
|
71
90
|
}
|
|
72
91
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -56,12 +56,12 @@ export function cryptoSignKeypair(
|
|
|
56
56
|
): Uint8Array;
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
|
-
* Create a signature for a message
|
|
59
|
+
* Create a signature for a message
|
|
60
60
|
* @param sig - Output buffer for signature (must be CryptoBytes length minimum)
|
|
61
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
|
-
* @param ctx -
|
|
64
|
+
* @param ctx - Context string (max 255 bytes)
|
|
65
65
|
* @returns 0 on success
|
|
66
66
|
* @throws Error if sk is wrong size or context too long
|
|
67
67
|
*/
|
|
@@ -70,7 +70,7 @@ export function cryptoSignSignature(
|
|
|
70
70
|
m: Uint8Array | string,
|
|
71
71
|
sk: Uint8Array,
|
|
72
72
|
randomizedSigning: boolean,
|
|
73
|
-
ctx
|
|
73
|
+
ctx: Uint8Array
|
|
74
74
|
): number;
|
|
75
75
|
|
|
76
76
|
/**
|
|
@@ -78,7 +78,7 @@ export function cryptoSignSignature(
|
|
|
78
78
|
* @param msg - Message to sign
|
|
79
79
|
* @param sk - Secret key
|
|
80
80
|
* @param randomizedSigning - If true, use random nonce; if false, deterministic
|
|
81
|
-
* @param ctx -
|
|
81
|
+
* @param ctx - Context string (max 255 bytes)
|
|
82
82
|
* @returns Signed message (signature || message)
|
|
83
83
|
* @throws Error if signing fails
|
|
84
84
|
*/
|
|
@@ -86,35 +86,35 @@ export function cryptoSign(
|
|
|
86
86
|
msg: Uint8Array | string,
|
|
87
87
|
sk: Uint8Array,
|
|
88
88
|
randomizedSigning: boolean,
|
|
89
|
-
ctx
|
|
89
|
+
ctx: Uint8Array
|
|
90
90
|
): Uint8Array;
|
|
91
91
|
|
|
92
92
|
/**
|
|
93
|
-
* Verify a signature
|
|
93
|
+
* Verify a signature
|
|
94
94
|
* @param sig - Signature to verify
|
|
95
95
|
* @param m - Message that was signed (hex string or Uint8Array; strings are parsed as hex only)
|
|
96
96
|
* @param pk - Public key
|
|
97
|
-
* @param ctx -
|
|
97
|
+
* @param ctx - Context string (max 255 bytes)
|
|
98
98
|
* @returns true if signature is valid, false otherwise
|
|
99
99
|
*/
|
|
100
100
|
export function cryptoSignVerify(
|
|
101
101
|
sig: Uint8Array,
|
|
102
102
|
m: Uint8Array | string,
|
|
103
103
|
pk: Uint8Array,
|
|
104
|
-
ctx
|
|
104
|
+
ctx: Uint8Array
|
|
105
105
|
): boolean;
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
108
|
* Open a signed message (verify and extract message)
|
|
109
109
|
* @param sm - Signed message (signature || message)
|
|
110
110
|
* @param pk - Public key
|
|
111
|
-
* @param ctx -
|
|
111
|
+
* @param ctx - Context string (max 255 bytes)
|
|
112
112
|
* @returns Message if valid, undefined if verification fails
|
|
113
113
|
*/
|
|
114
114
|
export function cryptoSignOpen(
|
|
115
115
|
sm: Uint8Array,
|
|
116
116
|
pk: Uint8Array,
|
|
117
|
-
ctx
|
|
117
|
+
ctx: Uint8Array
|
|
118
118
|
): Uint8Array | undefined;
|
|
119
119
|
|
|
120
120
|
// Utility functions
|