@theqrl/dilithium5 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var sha3 = require('@noble/hashes/sha3');
3
+ var sha3_js = require('@noble/hashes/sha3.js');
4
4
  var pkg = require('randombytes');
5
+ var utils_js = require('@noble/hashes/utils.js');
5
6
 
6
7
  const Shake128Rate = 168;
7
8
  const Shake256Rate = 136;
@@ -85,7 +86,7 @@ class KeccakState {
85
86
  // SHAKE-128 functions
86
87
 
87
88
  function shake128Init(state) {
88
- state.hasher = sha3.shake128.create({});
89
+ state.hasher = sha3_js.shake128.create({});
89
90
  state.finalized = false;
90
91
  }
91
92
 
@@ -107,7 +108,7 @@ function shake128SqueezeBlocks(out, outputOffset, nBlocks, state) {
107
108
  // SHAKE-256 functions
108
109
 
109
110
  function shake256Init(state) {
110
- state.hasher = sha3.shake256.create({});
111
+ state.hasher = sha3_js.shake256.create({});
111
112
  state.finalized = false;
112
113
  }
113
114
 
@@ -1019,18 +1020,36 @@ function unpackSig(cP, z, hP, sig) {
1019
1020
  const randomBytes = pkg;
1020
1021
 
1021
1022
  /**
1022
- * Convert hex string to Uint8Array
1023
- * @param {string} hex - Hex-encoded string
1024
- * @returns {Uint8Array} Decoded bytes
1023
+ * Convert hex string to Uint8Array with strict validation.
1024
+ * @param {string} hex - Hex string (optional 0x prefix, even length).
1025
+ * @returns {Uint8Array} Decoded bytes.
1025
1026
  * @private
1026
1027
  */
1027
1028
  function hexToBytes(hex) {
1028
- const len = hex.length / 2;
1029
- const result = new Uint8Array(len);
1030
- for (let i = 0; i < len; i++) {
1031
- result[i] = parseInt(hex.substr(i * 2, 2), 16);
1029
+ if (typeof hex !== 'string') {
1030
+ throw new Error('message must be a hex string');
1032
1031
  }
1033
- return result;
1032
+ let clean = hex.trim();
1033
+ if (clean.startsWith('0x') || clean.startsWith('0X')) {
1034
+ clean = clean.slice(2);
1035
+ }
1036
+ if (clean.length % 2 !== 0) {
1037
+ throw new Error('hex string must have an even length');
1038
+ }
1039
+ if (!/^[0-9a-fA-F]*$/.test(clean)) {
1040
+ throw new Error('hex string contains non-hex characters');
1041
+ }
1042
+ return utils_js.hexToBytes(clean);
1043
+ }
1044
+
1045
+ function messageToBytes(message) {
1046
+ if (typeof message === 'string') {
1047
+ return hexToBytes(message);
1048
+ }
1049
+ if (message instanceof Uint8Array) {
1050
+ return message;
1051
+ }
1052
+ return null;
1034
1053
  }
1035
1054
 
1036
1055
  /**
@@ -1081,7 +1100,7 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
1081
1100
  const seed = passedSeed || randomBytes(SeedBytes);
1082
1101
 
1083
1102
  const outputLength = 2 * SeedBytes + CRHBytes;
1084
- const seedBuf = sha3.shake256.create({}).update(seed).xof(outputLength);
1103
+ const seedBuf = sha3_js.shake256.create({}).update(seed).xof(outputLength);
1085
1104
  const rho = seedBuf.slice(0, SeedBytes);
1086
1105
  const rhoPrime = seedBuf.slice(SeedBytes, SeedBytes + CRHBytes);
1087
1106
  const key = seedBuf.slice(SeedBytes + CRHBytes);
@@ -1110,7 +1129,7 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
1110
1129
  packPk(pk, rho, t1);
1111
1130
 
1112
1131
  // Compute H(rho, t1) and write secret key
1113
- const tr = sha3.shake256.create({}).update(pk).xof(TRBytes);
1132
+ const tr = sha3_js.shake256.create({}).update(pk).xof(TRBytes);
1114
1133
  packSk(sk, rho, tr, key, t0, s1, s2);
1115
1134
 
1116
1135
  return seed;
@@ -1122,7 +1141,7 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
1122
1141
  * Uses the Dilithium-5 (Round 3) signing algorithm with rejection sampling.
1123
1142
  *
1124
1143
  * @param {Uint8Array} sig - Output buffer for signature (must be at least CryptoBytes = 4595 bytes)
1125
- * @param {string|Uint8Array} m - Message to sign (hex string or Uint8Array)
1144
+ * @param {string|Uint8Array} m - Message to sign (hex string, optional 0x prefix, or Uint8Array)
1126
1145
  * @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
1127
1146
  * @param {boolean} randomizedSigning - If true, use random nonce for hedged signing.
1128
1147
  * If false, use deterministic nonce derived from message and key.
@@ -1134,10 +1153,18 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
1134
1153
  * cryptoSignSignature(sig, message, sk, false);
1135
1154
  */
1136
1155
  function cryptoSignSignature(sig, m, sk, randomizedSigning) {
1156
+ if (!sig || sig.length < CryptoBytes) {
1157
+ throw new Error(`sig must be at least ${CryptoBytes} bytes`);
1158
+ }
1137
1159
  if (sk.length !== CryptoSecretKeyBytes) {
1138
1160
  throw new Error(`invalid sk length ${sk.length} | Expected length ${CryptoSecretKeyBytes}`);
1139
1161
  }
1140
1162
 
1163
+ const mBytes = messageToBytes(m);
1164
+ if (!mBytes) {
1165
+ throw new Error('message must be Uint8Array or hex string');
1166
+ }
1167
+
1141
1168
  const rho = new Uint8Array(SeedBytes);
1142
1169
  const tr = new Uint8Array(TRBytes);
1143
1170
  const key = new Uint8Array(SeedBytes);
@@ -1158,14 +1185,12 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
1158
1185
 
1159
1186
  unpackSk(rho, tr, key, t0, s1, s2, sk);
1160
1187
 
1161
- // Convert hex message to bytes
1162
- const mBytes = typeof m === 'string' ? hexToBytes(m) : m;
1163
- const mu = sha3.shake256.create({}).update(tr).update(mBytes).xof(CRHBytes);
1188
+ const mu = sha3_js.shake256.create({}).update(tr).update(mBytes).xof(CRHBytes);
1164
1189
 
1165
1190
  if (randomizedSigning) {
1166
1191
  rhoPrime = new Uint8Array(randomBytes(CRHBytes));
1167
1192
  } else {
1168
- rhoPrime = sha3.shake256.create({}).update(key).update(mu).xof(CRHBytes);
1193
+ rhoPrime = sha3_js.shake256.create({}).update(key).update(mu).xof(CRHBytes);
1169
1194
  }
1170
1195
 
1171
1196
  polyVecMatrixExpand(mat, rho);
@@ -1187,7 +1212,7 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
1187
1212
  polyVecKDecompose(w1, w0, w1);
1188
1213
  polyVecKPackW1(sig, w1);
1189
1214
 
1190
- const cHash = sha3.shake256
1215
+ const cHash = sha3_js.shake256
1191
1216
  .create({})
1192
1217
  .update(mu)
1193
1218
  .update(sig.slice(0, K * PolyW1PackedBytes))
@@ -1238,7 +1263,7 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
1238
1263
  * This is the combined sign operation that produces a "signed message" containing
1239
1264
  * both the signature and the original message (signature || message).
1240
1265
  *
1241
- * @param {Uint8Array} msg - Message to sign
1266
+ * @param {string|Uint8Array} msg - Message to sign (hex string, optional 0x prefix, or Uint8Array)
1242
1267
  * @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
1243
1268
  * @param {boolean} randomizedSigning - If true, use random nonce; if false, deterministic
1244
1269
  * @returns {Uint8Array} Signed message (CryptoBytes + msg.length bytes)
@@ -1249,12 +1274,17 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
1249
1274
  * // signedMsg contains: signature (4595 bytes) || message
1250
1275
  */
1251
1276
  function cryptoSign(msg, sk, randomizedSigning) {
1252
- const sm = new Uint8Array(CryptoBytes + msg.length);
1253
- const mLen = msg.length;
1277
+ const msgBytes = messageToBytes(msg);
1278
+ if (!msgBytes) {
1279
+ throw new Error('message must be Uint8Array or hex string');
1280
+ }
1281
+
1282
+ const sm = new Uint8Array(CryptoBytes + msgBytes.length);
1283
+ const mLen = msgBytes.length;
1254
1284
  for (let i = 0; i < mLen; ++i) {
1255
- sm[CryptoBytes + mLen - 1 - i] = msg[mLen - 1 - i];
1285
+ sm[CryptoBytes + mLen - 1 - i] = msgBytes[mLen - 1 - i];
1256
1286
  }
1257
- const result = cryptoSignSignature(sm, msg, sk, randomizedSigning);
1287
+ const result = cryptoSignSignature(sm, msgBytes, sk, randomizedSigning);
1258
1288
 
1259
1289
  if (result !== 0) {
1260
1290
  throw new Error('failed to sign');
@@ -1268,7 +1298,7 @@ function cryptoSign(msg, sk, randomizedSigning) {
1268
1298
  * Performs constant-time verification to prevent timing side-channel attacks.
1269
1299
  *
1270
1300
  * @param {Uint8Array} sig - Signature to verify (must be CryptoBytes = 4595 bytes)
1271
- * @param {string|Uint8Array} m - Message that was signed (hex string or Uint8Array)
1301
+ * @param {string|Uint8Array} m - Message that was signed (hex string, optional 0x prefix, or Uint8Array)
1272
1302
  * @param {Uint8Array} pk - Public key (must be CryptoPublicKeyBytes = 2592 bytes)
1273
1303
  * @returns {boolean} true if signature is valid, false otherwise
1274
1304
  *
@@ -1308,12 +1338,19 @@ function cryptoSignVerify(sig, m, pk) {
1308
1338
  }
1309
1339
 
1310
1340
  /* Compute CRH(H(rho, t1), msg) */
1311
- const tr = sha3.shake256.create({}).update(pk).xof(TRBytes);
1341
+ const tr = sha3_js.shake256.create({}).update(pk).xof(TRBytes);
1312
1342
  mu.set(tr);
1313
1343
 
1314
- // Convert hex message to bytes
1315
- const mBytes = typeof m === 'string' ? hexToBytes(m) : m;
1316
- const muFull = sha3.shake256.create({}).update(mu.slice(0, TRBytes)).update(mBytes).xof(CRHBytes);
1344
+ let mBytes;
1345
+ try {
1346
+ mBytes = messageToBytes(m);
1347
+ } catch {
1348
+ return false;
1349
+ }
1350
+ if (!mBytes) {
1351
+ return false;
1352
+ }
1353
+ const muFull = sha3_js.shake256.create({}).update(mu.slice(0, TRBytes)).update(mBytes).xof(CRHBytes);
1317
1354
  mu.set(muFull);
1318
1355
 
1319
1356
  /* Matrix-vector multiplication; compute Az - c2^dt1 */
@@ -1338,7 +1375,7 @@ function cryptoSignVerify(sig, m, pk) {
1338
1375
  polyVecKPackW1(buf, w1);
1339
1376
 
1340
1377
  /* Call random oracle and verify challenge */
1341
- const c2Hash = sha3.shake256.create({}).update(mu).update(buf).xof(SeedBytes);
1378
+ const c2Hash = sha3_js.shake256.create({}).update(mu).update(buf).xof(SeedBytes);
1342
1379
  c2.set(c2Hash);
1343
1380
 
1344
1381
  // Constant-time comparison to prevent timing attacks
@@ -1,5 +1,6 @@
1
- import { shake128, shake256 } from '@noble/hashes/sha3';
1
+ import { shake128, shake256 } from '@noble/hashes/sha3.js';
2
2
  import pkg from 'randombytes';
3
+ import { hexToBytes as hexToBytes$1 } from '@noble/hashes/utils.js';
3
4
 
4
5
  const Shake128Rate = 168;
5
6
  const Shake256Rate = 136;
@@ -1017,18 +1018,36 @@ function unpackSig(cP, z, hP, sig) {
1017
1018
  const randomBytes = pkg;
1018
1019
 
1019
1020
  /**
1020
- * Convert hex string to Uint8Array
1021
- * @param {string} hex - Hex-encoded string
1022
- * @returns {Uint8Array} Decoded bytes
1021
+ * Convert hex string to Uint8Array with strict validation.
1022
+ * @param {string} hex - Hex string (optional 0x prefix, even length).
1023
+ * @returns {Uint8Array} Decoded bytes.
1023
1024
  * @private
1024
1025
  */
1025
1026
  function hexToBytes(hex) {
1026
- const len = hex.length / 2;
1027
- const result = new Uint8Array(len);
1028
- for (let i = 0; i < len; i++) {
1029
- result[i] = parseInt(hex.substr(i * 2, 2), 16);
1027
+ if (typeof hex !== 'string') {
1028
+ throw new Error('message must be a hex string');
1030
1029
  }
1031
- return result;
1030
+ let clean = hex.trim();
1031
+ if (clean.startsWith('0x') || clean.startsWith('0X')) {
1032
+ clean = clean.slice(2);
1033
+ }
1034
+ if (clean.length % 2 !== 0) {
1035
+ throw new Error('hex string must have an even length');
1036
+ }
1037
+ if (!/^[0-9a-fA-F]*$/.test(clean)) {
1038
+ throw new Error('hex string contains non-hex characters');
1039
+ }
1040
+ return hexToBytes$1(clean);
1041
+ }
1042
+
1043
+ function messageToBytes(message) {
1044
+ if (typeof message === 'string') {
1045
+ return hexToBytes(message);
1046
+ }
1047
+ if (message instanceof Uint8Array) {
1048
+ return message;
1049
+ }
1050
+ return null;
1032
1051
  }
1033
1052
 
1034
1053
  /**
@@ -1120,7 +1139,7 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
1120
1139
  * Uses the Dilithium-5 (Round 3) signing algorithm with rejection sampling.
1121
1140
  *
1122
1141
  * @param {Uint8Array} sig - Output buffer for signature (must be at least CryptoBytes = 4595 bytes)
1123
- * @param {string|Uint8Array} m - Message to sign (hex string or Uint8Array)
1142
+ * @param {string|Uint8Array} m - Message to sign (hex string, optional 0x prefix, or Uint8Array)
1124
1143
  * @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
1125
1144
  * @param {boolean} randomizedSigning - If true, use random nonce for hedged signing.
1126
1145
  * If false, use deterministic nonce derived from message and key.
@@ -1132,10 +1151,18 @@ function cryptoSignKeypair(passedSeed, pk, sk) {
1132
1151
  * cryptoSignSignature(sig, message, sk, false);
1133
1152
  */
1134
1153
  function cryptoSignSignature(sig, m, sk, randomizedSigning) {
1154
+ if (!sig || sig.length < CryptoBytes) {
1155
+ throw new Error(`sig must be at least ${CryptoBytes} bytes`);
1156
+ }
1135
1157
  if (sk.length !== CryptoSecretKeyBytes) {
1136
1158
  throw new Error(`invalid sk length ${sk.length} | Expected length ${CryptoSecretKeyBytes}`);
1137
1159
  }
1138
1160
 
1161
+ const mBytes = messageToBytes(m);
1162
+ if (!mBytes) {
1163
+ throw new Error('message must be Uint8Array or hex string');
1164
+ }
1165
+
1139
1166
  const rho = new Uint8Array(SeedBytes);
1140
1167
  const tr = new Uint8Array(TRBytes);
1141
1168
  const key = new Uint8Array(SeedBytes);
@@ -1156,8 +1183,6 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
1156
1183
 
1157
1184
  unpackSk(rho, tr, key, t0, s1, s2, sk);
1158
1185
 
1159
- // Convert hex message to bytes
1160
- const mBytes = typeof m === 'string' ? hexToBytes(m) : m;
1161
1186
  const mu = shake256.create({}).update(tr).update(mBytes).xof(CRHBytes);
1162
1187
 
1163
1188
  if (randomizedSigning) {
@@ -1236,7 +1261,7 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
1236
1261
  * This is the combined sign operation that produces a "signed message" containing
1237
1262
  * both the signature and the original message (signature || message).
1238
1263
  *
1239
- * @param {Uint8Array} msg - Message to sign
1264
+ * @param {string|Uint8Array} msg - Message to sign (hex string, optional 0x prefix, or Uint8Array)
1240
1265
  * @param {Uint8Array} sk - Secret key (must be CryptoSecretKeyBytes = 4896 bytes)
1241
1266
  * @param {boolean} randomizedSigning - If true, use random nonce; if false, deterministic
1242
1267
  * @returns {Uint8Array} Signed message (CryptoBytes + msg.length bytes)
@@ -1247,12 +1272,17 @@ function cryptoSignSignature(sig, m, sk, randomizedSigning) {
1247
1272
  * // signedMsg contains: signature (4595 bytes) || message
1248
1273
  */
1249
1274
  function cryptoSign(msg, sk, randomizedSigning) {
1250
- const sm = new Uint8Array(CryptoBytes + msg.length);
1251
- const mLen = msg.length;
1275
+ const msgBytes = messageToBytes(msg);
1276
+ if (!msgBytes) {
1277
+ throw new Error('message must be Uint8Array or hex string');
1278
+ }
1279
+
1280
+ const sm = new Uint8Array(CryptoBytes + msgBytes.length);
1281
+ const mLen = msgBytes.length;
1252
1282
  for (let i = 0; i < mLen; ++i) {
1253
- sm[CryptoBytes + mLen - 1 - i] = msg[mLen - 1 - i];
1283
+ sm[CryptoBytes + mLen - 1 - i] = msgBytes[mLen - 1 - i];
1254
1284
  }
1255
- const result = cryptoSignSignature(sm, msg, sk, randomizedSigning);
1285
+ const result = cryptoSignSignature(sm, msgBytes, sk, randomizedSigning);
1256
1286
 
1257
1287
  if (result !== 0) {
1258
1288
  throw new Error('failed to sign');
@@ -1266,7 +1296,7 @@ function cryptoSign(msg, sk, randomizedSigning) {
1266
1296
  * Performs constant-time verification to prevent timing side-channel attacks.
1267
1297
  *
1268
1298
  * @param {Uint8Array} sig - Signature to verify (must be CryptoBytes = 4595 bytes)
1269
- * @param {string|Uint8Array} m - Message that was signed (hex string or Uint8Array)
1299
+ * @param {string|Uint8Array} m - Message that was signed (hex string, optional 0x prefix, or Uint8Array)
1270
1300
  * @param {Uint8Array} pk - Public key (must be CryptoPublicKeyBytes = 2592 bytes)
1271
1301
  * @returns {boolean} true if signature is valid, false otherwise
1272
1302
  *
@@ -1309,8 +1339,15 @@ function cryptoSignVerify(sig, m, pk) {
1309
1339
  const tr = shake256.create({}).update(pk).xof(TRBytes);
1310
1340
  mu.set(tr);
1311
1341
 
1312
- // Convert hex message to bytes
1313
- const mBytes = typeof m === 'string' ? hexToBytes(m) : m;
1342
+ let mBytes;
1343
+ try {
1344
+ mBytes = messageToBytes(m);
1345
+ } catch {
1346
+ return false;
1347
+ }
1348
+ if (!mBytes) {
1349
+ return false;
1350
+ }
1314
1351
  const muFull = shake256.create({}).update(mu.slice(0, TRBytes)).update(mBytes).xof(CRHBytes);
1315
1352
  mu.set(muFull);
1316
1353
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theqrl/dilithium5",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Dilithium-5 cryptography",
5
5
  "keywords": [
6
6
  "dilithium",
@@ -48,19 +48,20 @@
48
48
  },
49
49
  "type": "module",
50
50
  "devDependencies": {
51
+ "@eslint/js": "^9.39.0",
51
52
  "c8": "^10.1.3",
52
- "chai": "^5.2.1",
53
- "eslint": "^8.57.1",
54
- "eslint-config-airbnb": "^19.0.4",
55
- "eslint-config-prettier": "^9.1.0",
56
- "eslint-plugin-import": "^2.32.0",
53
+ "chai": "^6.2.2",
54
+ "eslint": "^9.39.2",
55
+ "eslint-config-prettier": "^10.1.8",
56
+ "eslint-plugin-import-x": "^4.15.0",
57
57
  "eslint-plugin-prettier": "^5.5.4",
58
- "mocha": "^10.8.2",
58
+ "globals": "^17.0.0",
59
+ "mocha": "^11.7.5",
59
60
  "prettier": "^3.7.4",
60
61
  "rollup": "^4.55.1"
61
62
  },
62
63
  "dependencies": {
63
- "@noble/hashes": "^1.7.1",
64
+ "@noble/hashes": "^2.0.1",
64
65
  "randombytes": "^2.1.0"
65
66
  }
66
67
  }
package/src/index.d.ts CHANGED
@@ -56,7 +56,7 @@ export function cryptoSignKeypair(
56
56
  /**
57
57
  * Create a signature for a message
58
58
  * @param sig - Output buffer for signature (must be CryptoBytes length minimum)
59
- * @param m - Message to sign (hex-encoded string)
59
+ * @param m - Message to sign (hex string or Uint8Array)
60
60
  * @param sk - Secret key
61
61
  * @param randomizedSigning - If true, use random nonce; if false, deterministic
62
62
  * @returns 0 on success
@@ -64,7 +64,7 @@ export function cryptoSignKeypair(
64
64
  */
65
65
  export function cryptoSignSignature(
66
66
  sig: Uint8Array,
67
- m: string,
67
+ m: Uint8Array | string,
68
68
  sk: Uint8Array,
69
69
  randomizedSigning: boolean
70
70
  ): number;
@@ -78,7 +78,7 @@ export function cryptoSignSignature(
78
78
  * @throws Error if signing fails
79
79
  */
80
80
  export function cryptoSign(
81
- msg: Uint8Array,
81
+ msg: Uint8Array | string,
82
82
  sk: Uint8Array,
83
83
  randomizedSigning: boolean
84
84
  ): Uint8Array;
@@ -86,13 +86,13 @@ export function cryptoSign(
86
86
  /**
87
87
  * Verify a signature
88
88
  * @param sig - Signature to verify
89
- * @param m - Message that was signed (hex-encoded string)
89
+ * @param m - Message that was signed (hex string or Uint8Array)
90
90
  * @param pk - Public key
91
91
  * @returns true if signature is valid, false otherwise
92
92
  */
93
93
  export function cryptoSignVerify(
94
94
  sig: Uint8Array,
95
- m: string,
95
+ m: Uint8Array | string,
96
96
  pk: Uint8Array
97
97
  ): boolean;
98
98