@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 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 (uses default "ZOND" context)
29
+ // Sign a message
30
30
  const message = new TextEncoder().encode('Hello, quantum world!');
31
- const signedMessage = cryptoSign(message, sk, false); // false = deterministic
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 supports a context parameter for domain separation (FIPS 204 feature). This allows the same keypair to be used safely across different applications.
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 custom context
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
- cryptoSignOpen(signed, pk); // undefined - wrong context (default "ZOND")
53
- cryptoSignOpen(signed, pk, ctx); // message - correct context
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
- The default context is `"ZOND"` (for QRL Zond network compatibility). Context can be 0-255 bytes.
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 `null` for random
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` (optional) - context string, 0-255 bytes. Default: `"ZOND"`
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` (optional) - must match signing context
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` (optional) - context string, 0-255 bytes
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` (optional) - must match signing context
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:
@@ -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 = 0;
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 = 0;
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
- let t = a.coeffs[i] >> 31;
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
- * NOTE: This function accepts multiple hex formats (with/without 0x prefix,
1500
- * leading/trailing whitespace). While user-friendly, this flexibility could
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
- let clean = hex.trim();
1518
- // Accepts both "0x..." and raw hex formats for convenience
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 optional context.
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} [ctx=DEFAULT_CTX] - Context string for domain separation (max 255 bytes).
1651
- * Defaults to "ZOND" for QRL compatibility.
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 {Error} If sk is wrong size or context exceeds 255 bytes
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
- * cryptoSignSignature(sig, message, sk, false);
1658
- * // Or with custom context:
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 = DEFAULT_CTX) {
1662
- if (!sig || sig.length < CryptoBytes) {
1663
- throw new Error(`sig must be at least ${CryptoBytes} bytes`);
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} [ctx=DEFAULT_CTX] - Context string for domain separation (max 255 bytes).
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 {Error} If signing fails
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 = DEFAULT_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 optional context.
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} [ctx=DEFAULT_CTX] - Context string used during signing (max 255 bytes).
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 = DEFAULT_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} [ctx=DEFAULT_CTX] - Context string used during signing (max 255 bytes).
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 = DEFAULT_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
  }
@@ -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 = 0;
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 = 0;
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
- let t = a.coeffs[i] >> 31;
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
- * NOTE: This function accepts multiple hex formats (with/without 0x prefix,
1121
- * leading/trailing whitespace). While user-friendly, this flexibility could
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
- let clean = hex.trim();
1139
- // Accepts both "0x..." and raw hex formats for convenience
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 optional context.
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} [ctx=DEFAULT_CTX] - Context string for domain separation (max 255 bytes).
1272
- * Defaults to "ZOND" for QRL compatibility.
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 {Error} If sk is wrong size or context exceeds 255 bytes
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
- * cryptoSignSignature(sig, message, sk, false);
1279
- * // Or with custom context:
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 = DEFAULT_CTX) {
1283
- if (!sig || sig.length < CryptoBytes) {
1284
- throw new Error(`sig must be at least ${CryptoBytes} bytes`);
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} [ctx=DEFAULT_CTX] - Context string for domain separation (max 255 bytes).
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 {Error} If signing fails
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 = DEFAULT_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 optional context.
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} [ctx=DEFAULT_CTX] - Context string used during signing (max 255 bytes).
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 = DEFAULT_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} [ctx=DEFAULT_CTX] - Context string used during signing (max 255 bytes).
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 = DEFAULT_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": "1.1.1",
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": "^10.0.1",
57
- "@rollup/plugin-node-resolve": "^16.0.3",
58
- "c8": "^10.1.3",
59
- "chai": "^6.2.2",
60
- "eslint": "^10.0.0",
61
- "eslint-config-prettier": "^10.1.8",
62
- "eslint-plugin-import-x": "^4.15.0",
63
- "eslint-plugin-prettier": "^5.5.4",
64
- "globals": "^17.3.0",
65
- "mocha": "^11.7.5",
66
- "prettier": "^3.8.1",
67
- "rollup": "^4.57.1"
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": "^2.0.1"
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 with optional context
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 - Optional context string (max 255 bytes, defaults to "ZOND")
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?: Uint8Array
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 - Optional context string (max 255 bytes, defaults to "ZOND")
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?: Uint8Array
89
+ ctx: Uint8Array
90
90
  ): Uint8Array;
91
91
 
92
92
  /**
93
- * Verify a signature with optional context
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 - Optional context string (max 255 bytes, defaults to "ZOND")
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?: Uint8Array
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 - Optional context string (max 255 bytes, defaults to "ZOND")
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?: Uint8Array
117
+ ctx: Uint8Array
118
118
  ): Uint8Array | undefined;
119
119
 
120
120
  // Utility functions