@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 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 `null` for random
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:
@@ -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
- 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)) {
@@ -1489,16 +1504,12 @@ function isZero(buffer) {
1489
1504
  /**
1490
1505
  * Convert hex string to Uint8Array with strict validation.
1491
1506
  *
1492
- * NOTE: This function accepts multiple hex formats (with/without 0x prefix,
1493
- * leading/trailing whitespace). While user-friendly, this flexibility could
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
- let clean = hex.trim();
1511
- // 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;
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 optional context.
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 {Error} If ctx is missing, 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
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 Error(`sig must be at least ${CryptoBytes} bytes`);
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 {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
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 optional context.
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);
@@ -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
- 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)) {
@@ -1110,16 +1125,12 @@ function isZero(buffer) {
1110
1125
  /**
1111
1126
  * Convert hex string to Uint8Array with strict validation.
1112
1127
  *
1113
- * NOTE: This function accepts multiple hex formats (with/without 0x prefix,
1114
- * leading/trailing whitespace). While user-friendly, this flexibility could
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
- let clean = hex.trim();
1132
- // 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;
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 optional context.
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 {Error} If ctx is missing, 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
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 Error(`sig must be at least ${CryptoBytes} bytes`);
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 {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
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 optional context.
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theqrl/mldsa87",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "ML-DSA-87 cryptography",
5
5
  "keywords": [
6
6
  "ml-dsa",