@noble/post-quantum 0.4.1 → 0.5.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 +53 -33
- package/_crystals.d.ts +1 -1
- package/_crystals.d.ts.map +1 -1
- package/_crystals.js +31 -46
- package/_crystals.js.map +1 -1
- package/hybrid.d.ts +102 -0
- package/hybrid.d.ts.map +1 -0
- package/hybrid.js +283 -0
- package/hybrid.js.map +1 -0
- package/index.d.ts +1 -0
- package/index.js +4 -4
- package/index.js.map +1 -1
- package/ml-dsa.d.ts +16 -8
- package/ml-dsa.d.ts.map +1 -1
- package/ml-dsa.js +126 -68
- package/ml-dsa.js.map +1 -1
- package/ml-kem.d.ts +1 -14
- package/ml-kem.d.ts.map +1 -1
- package/ml-kem.js +70 -54
- package/ml-kem.js.map +1 -1
- package/package.json +38 -85
- package/slh-dsa.d.ts +4 -3
- package/slh-dsa.d.ts.map +1 -1
- package/slh-dsa.js +113 -86
- package/slh-dsa.js.map +1 -1
- package/src/_crystals.ts +30 -41
- package/src/hybrid.ts +373 -0
- package/src/index.ts +3 -3
- package/src/ml-dsa.ts +129 -42
- package/src/ml-kem.ts +52 -49
- package/src/slh-dsa.ts +97 -56
- package/src/utils.ts +86 -50
- package/utils.d.ts +53 -11
- package/utils.d.ts.map +1 -1
- package/utils.js +54 -60
- package/utils.js.map +1 -1
- package/esm/_crystals.d.ts +0 -34
- package/esm/_crystals.d.ts.map +0 -1
- package/esm/_crystals.js +0 -141
- package/esm/_crystals.js.map +0 -1
- package/esm/index.d.ts +0 -2
- package/esm/index.d.ts.map +0 -1
- package/esm/index.js +0 -21
- package/esm/index.js.map +0 -1
- package/esm/ml-dsa.d.ts +0 -25
- package/esm/ml-dsa.d.ts.map +0 -1
- package/esm/ml-dsa.js +0 -525
- package/esm/ml-dsa.js.map +0 -1
- package/esm/ml-kem.d.ts +0 -34
- package/esm/ml-kem.d.ts.map +0 -1
- package/esm/ml-kem.js +0 -306
- package/esm/ml-kem.js.map +0 -1
- package/esm/package.json +0 -10
- package/esm/slh-dsa.d.ts +0 -62
- package/esm/slh-dsa.d.ts.map +0 -1
- package/esm/slh-dsa.js +0 -596
- package/esm/slh-dsa.js.map +0 -1
- package/esm/utils.d.ts +0 -40
- package/esm/utils.d.ts.map +0 -1
- package/esm/utils.js +0 -133
- package/esm/utils.js.map +0 -1
- package/src/package.json +0 -3
package/src/ml-kem.ts
CHANGED
@@ -20,36 +20,22 @@
|
|
20
20
|
* @module
|
21
21
|
*/
|
22
22
|
/*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */
|
23
|
-
import { sha3_256, sha3_512, shake256 } from '@noble/hashes/sha3';
|
24
|
-
import {
|
23
|
+
import { sha3_256, sha3_512, shake256 } from '@noble/hashes/sha3.js';
|
24
|
+
import { type CHash, u32 } from '@noble/hashes/utils.js';
|
25
25
|
import { genCrystals, type XOF, XOF128 } from './_crystals.ts';
|
26
26
|
import {
|
27
|
+
abytes,
|
27
28
|
cleanBytes,
|
28
29
|
type Coder,
|
29
|
-
|
30
|
+
copyBytes,
|
30
31
|
equalBytes,
|
32
|
+
type KEM,
|
31
33
|
randomBytes,
|
32
34
|
splitCoder,
|
33
35
|
vecCoder,
|
34
36
|
} from './utils.ts';
|
35
37
|
|
36
38
|
/** Key encapsulation mechanism interface */
|
37
|
-
export type KEM = {
|
38
|
-
publicKeyLen: number;
|
39
|
-
msgLen: number;
|
40
|
-
keygen: (seed?: Uint8Array) => {
|
41
|
-
publicKey: Uint8Array;
|
42
|
-
secretKey: Uint8Array;
|
43
|
-
};
|
44
|
-
encapsulate: (
|
45
|
-
publicKey: Uint8Array,
|
46
|
-
msg?: Uint8Array
|
47
|
-
) => {
|
48
|
-
cipherText: Uint8Array;
|
49
|
-
sharedSecret: Uint8Array;
|
50
|
-
};
|
51
|
-
decapsulate: (cipherText: Uint8Array, secretKey: Uint8Array) => Uint8Array;
|
52
|
-
};
|
53
39
|
|
54
40
|
const N = 256; // Kyber (not FIPS-203) supports different lengths, but all std modes were using 256
|
55
41
|
const Q = 3329; // 13*(2**8)+1, modulo prime
|
@@ -60,7 +46,7 @@ const { mod, nttZetas, NTT, bitsCoder } = genCrystals({
|
|
60
46
|
Q,
|
61
47
|
F,
|
62
48
|
ROOT_OF_UNITY,
|
63
|
-
newPoly: (n: number) => new Uint16Array(n),
|
49
|
+
newPoly: (n: number): Uint16Array => new Uint16Array(n),
|
64
50
|
brvBits: 7,
|
65
51
|
isKyber: true,
|
66
52
|
});
|
@@ -106,7 +92,7 @@ const compress = (d: number): Coder<number, number> => {
|
|
106
92
|
const polyCoder = (d: number) => bitsCoder(d, compress(d));
|
107
93
|
|
108
94
|
// Poly is mod Q, so 12 bits
|
109
|
-
type Poly = Uint16Array
|
95
|
+
type Poly = Uint16Array<any>;
|
110
96
|
|
111
97
|
function polyAdd(a: Poly, b: Poly) {
|
112
98
|
for (let i = 0; i < N; i++) a[i] = mod(a[i] + b[i]); // a += b
|
@@ -137,14 +123,13 @@ function MultiplyNTTs(f: Poly, g: Poly): Poly {
|
|
137
123
|
|
138
124
|
type PRF = (l: number, key: Uint8Array, nonce: number) => Uint8Array;
|
139
125
|
|
140
|
-
type Hash = ReturnType<typeof wrapConstructor>;
|
141
|
-
type HashWOpts = ReturnType<typeof wrapConstructorWithOpts>;
|
142
126
|
type XofGet = ReturnType<ReturnType<XOF>['get']>;
|
143
127
|
|
144
128
|
type KyberOpts = KEMParam & {
|
145
|
-
HASH256:
|
146
|
-
HASH512:
|
147
|
-
KDF:
|
129
|
+
HASH256: CHash;
|
130
|
+
HASH512: CHash;
|
131
|
+
// KDF: CHash<Keccak, ShakeOpts>;
|
132
|
+
KDF: any;
|
148
133
|
XOF: XOF; // (seed: Uint8Array, len: number, x: number, y: number) => Uint8Array;
|
149
134
|
PRF: PRF;
|
150
135
|
};
|
@@ -199,17 +184,19 @@ const genKPKE = (opts: KyberOpts) => {
|
|
199
184
|
const poly1 = polyCoder(1);
|
200
185
|
const polyV = polyCoder(dv);
|
201
186
|
const polyU = polyCoder(du);
|
202
|
-
const publicCoder = splitCoder(vecCoder(polyCoder(12), K), 32);
|
187
|
+
const publicCoder = splitCoder('publicKey', vecCoder(polyCoder(12), K), 32);
|
203
188
|
const secretCoder = vecCoder(polyCoder(12), K);
|
204
|
-
const cipherCoder = splitCoder(vecCoder(polyU, K), polyV);
|
205
|
-
const seedCoder = splitCoder(32, 32);
|
189
|
+
const cipherCoder = splitCoder('ciphertext', vecCoder(polyU, K), polyV);
|
190
|
+
const seedCoder = splitCoder('seed', 32, 32);
|
206
191
|
return {
|
207
192
|
secretCoder,
|
208
|
-
|
209
|
-
|
210
|
-
|
193
|
+
lengths: {
|
194
|
+
secretKey: secretCoder.bytesLen,
|
195
|
+
publicKey: publicCoder.bytesLen,
|
196
|
+
cipherText: cipherCoder.bytesLen,
|
197
|
+
},
|
211
198
|
keygen: (seed: Uint8Array) => {
|
212
|
-
|
199
|
+
abytes(seed, 32, 'seed');
|
213
200
|
const seedDst = new Uint8Array(33);
|
214
201
|
seedDst.set(seed);
|
215
202
|
seedDst[32] = K;
|
@@ -253,7 +240,7 @@ const genKPKE = (opts: KyberOpts) => {
|
|
253
240
|
polyAdd(e1, NTT.decode(tmp)); // e1 += tmp
|
254
241
|
u.push(e1);
|
255
242
|
polyAdd(tmp2, MultiplyNTTs(tHat[i], rHat[i])); // t2 += tHat[i] * rHat[i]
|
256
|
-
tmp
|
243
|
+
cleanBytes(tmp);
|
257
244
|
}
|
258
245
|
x.clean();
|
259
246
|
const e2 = sampleCBD(PRF, seed, 2 * K, ETA2);
|
@@ -278,16 +265,21 @@ const genKPKE = (opts: KyberOpts) => {
|
|
278
265
|
function createKyber(opts: KyberOpts) {
|
279
266
|
const KPKE = genKPKE(opts);
|
280
267
|
const { HASH256, HASH512, KDF } = opts;
|
281
|
-
const { secretCoder: KPKESecretCoder,
|
282
|
-
const
|
283
|
-
const secretCoder = splitCoder(KPKE.secretKeyLen, KPKE.publicKeyLen, 32, 32);
|
284
|
-
const secretKeyLen = secretCoder.bytesLen;
|
268
|
+
const { secretCoder: KPKESecretCoder, lengths } = KPKE;
|
269
|
+
const secretCoder = splitCoder('secretKey', lengths.secretKey, lengths.publicKey, 32, 32);
|
285
270
|
const msgLen = 32;
|
271
|
+
const seedLen = 64;
|
286
272
|
return {
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
273
|
+
info: { type: 'ml-kem' },
|
274
|
+
lengths: {
|
275
|
+
...lengths,
|
276
|
+
seed: 64,
|
277
|
+
msg: msgLen,
|
278
|
+
msgRand: msgLen,
|
279
|
+
secretKey: secretCoder.bytesLen,
|
280
|
+
},
|
281
|
+
keygen: (seed = randomBytes(seedLen)) => {
|
282
|
+
abytes(seed, seedLen, 'seed');
|
291
283
|
const { publicKey, secretKey: sk } = KPKE.keygen(seed.subarray(0, 32));
|
292
284
|
const publicKeyHash = HASH256(publicKey);
|
293
285
|
// (dkPKE||ek||H(ek)||z)
|
@@ -295,13 +287,17 @@ function createKyber(opts: KyberOpts) {
|
|
295
287
|
cleanBytes(sk, publicKeyHash);
|
296
288
|
return { publicKey, secretKey };
|
297
289
|
},
|
298
|
-
|
299
|
-
|
300
|
-
|
290
|
+
getPublicKey: (secretKey: Uint8Array) => {
|
291
|
+
const [_sk, publicKey, _publicKeyHash, _z] = secretCoder.decode(secretKey);
|
292
|
+
return Uint8Array.from(publicKey);
|
293
|
+
},
|
294
|
+
encapsulate: (publicKey: Uint8Array, msg = randomBytes(msgLen)) => {
|
295
|
+
abytes(publicKey, lengths.publicKey, 'publicKey');
|
296
|
+
abytes(msg, msgLen, 'message');
|
301
297
|
|
302
298
|
// FIPS-203 includes additional verification check for modulus
|
303
299
|
const eke = publicKey.subarray(0, 384 * opts.K);
|
304
|
-
const ek = KPKESecretCoder.encode(KPKESecretCoder.decode(eke
|
300
|
+
const ek = KPKESecretCoder.encode(KPKESecretCoder.decode(copyBytes(eke))); // Copy because of inplace encoding
|
305
301
|
// (Modulus check.) Perform the computation ek ← ByteEncode12(ByteDecode12(eke)).
|
306
302
|
// If ek = ̸ eke, the input is invalid. (See Section 4.2.1.)
|
307
303
|
if (!equalBytes(ek, eke)) {
|
@@ -311,12 +307,19 @@ function createKyber(opts: KyberOpts) {
|
|
311
307
|
cleanBytes(ek);
|
312
308
|
const kr = HASH512.create().update(msg).update(HASH256(publicKey)).digest(); // derive randomness
|
313
309
|
const cipherText = KPKE.encrypt(publicKey, msg, kr.subarray(32, 64));
|
314
|
-
kr.subarray(32)
|
310
|
+
cleanBytes(kr.subarray(32));
|
315
311
|
return { cipherText, sharedSecret: kr.subarray(0, 32) };
|
316
312
|
},
|
317
313
|
decapsulate: (cipherText: Uint8Array, secretKey: Uint8Array) => {
|
318
|
-
|
319
|
-
|
314
|
+
abytes(secretKey, secretCoder.bytesLen, 'secretKey'); // 768*k + 96
|
315
|
+
abytes(cipherText, lengths.cipherText, 'cipherText'); // 32(du*k + dv)
|
316
|
+
// test ← H(dk[384𝑘 ∶ 768𝑘 + 32])) .
|
317
|
+
const k768 = secretCoder.bytesLen - 96;
|
318
|
+
const start = k768 + 32;
|
319
|
+
const test = HASH256(secretKey.subarray(k768 / 2, start));
|
320
|
+
// If test ≠ dk[768𝑘 + 32 ∶ 768𝑘 + 64], then input checking has failed.
|
321
|
+
if (!equalBytes(test, secretKey.subarray(start, start + 32)))
|
322
|
+
throw new Error('invalid secretKey: hash check failed');
|
320
323
|
const [sk, publicKey, publicKeyHash, z] = secretCoder.decode(secretKey);
|
321
324
|
const msg = KPKE.decrypt(cipherText, sk);
|
322
325
|
const kr = HASH512.create().update(msg).update(publicKeyHash).digest(); // derive randomness, Khat, rHat = G(mHat || h)
|
package/src/slh-dsa.ts
CHANGED
@@ -27,23 +27,33 @@
|
|
27
27
|
* @module
|
28
28
|
*/
|
29
29
|
/*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */
|
30
|
-
import {
|
31
|
-
import {
|
32
|
-
import {
|
33
|
-
import { shake256 } from '@noble/hashes/sha3';
|
34
|
-
import { bytesToHex, concatBytes, createView, hexToBytes } from '@noble/hashes/utils';
|
30
|
+
import { hmac } from '@noble/hashes/hmac.js';
|
31
|
+
import { sha256, sha512 } from '@noble/hashes/sha2.js';
|
32
|
+
import { shake256 } from '@noble/hashes/sha3.js';
|
35
33
|
import {
|
36
|
-
|
37
|
-
|
34
|
+
bytesToHex,
|
35
|
+
concatBytes,
|
36
|
+
createView,
|
37
|
+
hexToBytes,
|
38
|
+
type CHash,
|
39
|
+
} from '@noble/hashes/utils.js';
|
40
|
+
import {
|
41
|
+
abytes,
|
42
|
+
checkHash,
|
38
43
|
cleanBytes,
|
39
|
-
|
44
|
+
copyBytes,
|
40
45
|
equalBytes,
|
41
46
|
getMask,
|
42
47
|
getMessage,
|
43
48
|
getMessagePrehash,
|
44
49
|
randomBytes,
|
45
50
|
splitCoder,
|
51
|
+
validateSigOpts,
|
52
|
+
validateVerOpts,
|
46
53
|
vecCoder,
|
54
|
+
type Signer,
|
55
|
+
type SigOpts,
|
56
|
+
type VerOpts,
|
47
57
|
} from './utils.ts';
|
48
58
|
|
49
59
|
/**
|
@@ -58,6 +68,7 @@ export type SphincsOpts = {
|
|
58
68
|
D: number;
|
59
69
|
K: number;
|
60
70
|
A: number;
|
71
|
+
securityLevel: number;
|
61
72
|
};
|
62
73
|
|
63
74
|
export type SphincsHashOpts = {
|
@@ -67,12 +78,12 @@ export type SphincsHashOpts = {
|
|
67
78
|
|
68
79
|
/** Winternitz signature params. */
|
69
80
|
export const PARAMS: Record<string, SphincsOpts> = {
|
70
|
-
'128f': { W: 16, N: 16, H: 66, D: 22, K: 33, A: 6 },
|
71
|
-
'128s': { W: 16, N: 16, H: 63, D: 7, K: 14, A: 12 },
|
72
|
-
'192f': { W: 16, N: 24, H: 66, D: 22, K: 33, A: 8 },
|
73
|
-
'192s': { W: 16, N: 24, H: 63, D: 7, K: 17, A: 14 },
|
74
|
-
'256f': { W: 16, N: 32, H: 68, D: 17, K: 35, A: 9 },
|
75
|
-
'256s': { W: 16, N: 32, H: 64, D: 8, K: 22, A: 14 },
|
81
|
+
'128f': { W: 16, N: 16, H: 66, D: 22, K: 33, A: 6, securityLevel: 128 },
|
82
|
+
'128s': { W: 16, N: 16, H: 63, D: 7, K: 14, A: 12, securityLevel: 128 },
|
83
|
+
'192f': { W: 16, N: 24, H: 66, D: 22, K: 33, A: 8, securityLevel: 192 },
|
84
|
+
'192s': { W: 16, N: 24, H: 63, D: 7, K: 17, A: 14, securityLevel: 192 },
|
85
|
+
'256f': { W: 16, N: 32, H: 68, D: 17, K: 35, A: 9, securityLevel: 256 },
|
86
|
+
'256s': { W: 16, N: 32, H: 64, D: 8, K: 22, A: 14, securityLevel: 256 },
|
76
87
|
} as const;
|
77
88
|
|
78
89
|
const AddressType = {
|
@@ -135,13 +146,14 @@ function getMaskBig(bits: number) {
|
|
135
146
|
return (1n << BigInt(bits)) - 1n; // 4 -> 0b1111
|
136
147
|
}
|
137
148
|
|
138
|
-
export type SphincsSigner = Signer & {
|
149
|
+
export type SphincsSigner = Signer & {
|
139
150
|
internal: Signer;
|
140
|
-
|
151
|
+
securityLevel: number;
|
152
|
+
prehash: (hash: CHash) => Signer;
|
141
153
|
};
|
142
154
|
|
143
155
|
function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
144
|
-
const { N, W, H, D, K, A } = opts;
|
156
|
+
const { N, W, H, D, K, A, securityLevel: securityLevel } = opts;
|
145
157
|
const getContext = hashOpts.getContext(opts);
|
146
158
|
if (W !== 16) throw new Error('Unsupported Winternitz parameter');
|
147
159
|
const WOTS_LOGW = 4;
|
@@ -197,7 +209,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
197
209
|
if (hash !== undefined) addr[OFFSET_HASH_ADDR] = hash;
|
198
210
|
if (index !== undefined) v.setUint32(OFFSET_TREE_INDEX, index, false);
|
199
211
|
if (subtreeAddr) addr.set(subtreeAddr.subarray(0, OFFSET_TREE + 8));
|
200
|
-
if (tree !== undefined) setBigUint64(
|
212
|
+
if (tree !== undefined) v.setBigUint64(OFFSET_TREE, tree, false);
|
201
213
|
if (keypair !== undefined) {
|
202
214
|
addr[OFFSET_KP_ADDR1] = keypair;
|
203
215
|
if (TREE_HEIGHT > 8) addr[OFFSET_KP_ADDR2] = keypair >>> 8;
|
@@ -229,6 +241,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
229
241
|
const TREE_BITS = TREE_HEIGHT * (D - 1);
|
230
242
|
const LEAF_BITS = TREE_HEIGHT;
|
231
243
|
const hashMsgCoder = splitCoder(
|
244
|
+
'hashedMessage',
|
232
245
|
Math.ceil((A * K) / 8),
|
233
246
|
Math.ceil(TREE_BITS / 8),
|
234
247
|
Math.ceil(TREE_HEIGHT / 8)
|
@@ -315,7 +328,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
315
328
|
wotsAddr: ADRS,
|
316
329
|
treeAddr: ADRS,
|
317
330
|
leafIdx: number,
|
318
|
-
prevRoot = new Uint8Array(N)
|
331
|
+
prevRoot: Uint8Array = new Uint8Array(N)
|
319
332
|
) => {
|
320
333
|
setAddr({ type: AddressType.HASHTREE }, treeAddr);
|
321
334
|
// State variables
|
@@ -374,16 +387,24 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
374
387
|
return context.thashN(2, buffer, addr);
|
375
388
|
};
|
376
389
|
|
377
|
-
const seedCoder = splitCoder(N, N, N);
|
378
|
-
const publicCoder = splitCoder(N, N);
|
379
|
-
const secretCoder = splitCoder(N, N, publicCoder.bytesLen);
|
380
|
-
const forsCoder = vecCoder(splitCoder(N, N * A), K);
|
381
|
-
const wotsCoder = vecCoder(splitCoder(WOTS_LEN * N, TREE_HEIGHT * N), D);
|
382
|
-
const sigCoder = splitCoder(N, forsCoder, wotsCoder); // random || fors || wots
|
390
|
+
const seedCoder = splitCoder('seed', N, N, N);
|
391
|
+
const publicCoder = splitCoder('publicKey', N, N);
|
392
|
+
const secretCoder = splitCoder('secretKey', N, N, publicCoder.bytesLen);
|
393
|
+
const forsCoder = vecCoder(splitCoder('fors', N, N * A), K);
|
394
|
+
const wotsCoder = vecCoder(splitCoder('wots', WOTS_LEN * N, TREE_HEIGHT * N), D);
|
395
|
+
const sigCoder = splitCoder('signature', N, forsCoder, wotsCoder); // random || fors || wots
|
383
396
|
const internal: Signer = {
|
384
|
-
|
397
|
+
info: { type: 'internal-slh-dsa' },
|
398
|
+
lengths: {
|
399
|
+
publicKey: publicCoder.bytesLen,
|
400
|
+
secretKey: secretCoder.bytesLen,
|
401
|
+
signature: sigCoder.bytesLen,
|
402
|
+
seed: seedCoder.bytesLen,
|
403
|
+
signRand: N,
|
404
|
+
},
|
385
405
|
keygen(seed?: Uint8Array) {
|
386
|
-
|
406
|
+
if (seed !== undefined) abytes(seed, seedCoder.bytesLen, 'seed');
|
407
|
+
seed = seed === undefined ? randomBytes(seedCoder.bytesLen) : copyBytes(seed);
|
387
408
|
// Set SK.seed, SK.prf, and PK.seed to random n-byte
|
388
409
|
const [secretSeed, secretPRF, publicSeed] = seedCoder.decode(seed);
|
389
410
|
const context = getContext(publicSeed, secretSeed);
|
@@ -398,12 +419,20 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
398
419
|
cleanBytes(secretSeed, secretPRF, root, wotsAddr, topTreeAddr);
|
399
420
|
return { publicKey, secretKey };
|
400
421
|
},
|
401
|
-
|
422
|
+
getPublicKey: (secretKey: Uint8Array) => {
|
423
|
+
const [_skSeed, _skPRF, pk] = secretCoder.decode(secretKey);
|
424
|
+
return Uint8Array.from(pk);
|
425
|
+
},
|
426
|
+
sign: (msg: Uint8Array, sk: Uint8Array, opts: SigOpts = {}) => {
|
427
|
+
validateSigOpts(opts);
|
428
|
+
let { extraEntropy: random } = opts;
|
402
429
|
const [skSeed, skPRF, pk] = secretCoder.decode(sk); // todo: fix
|
403
430
|
const [pkSeed, _] = publicCoder.decode(pk);
|
404
431
|
// Set opt_rand to either PK.seed or to a random n-byte string
|
405
|
-
if (
|
406
|
-
|
432
|
+
if (random === false) random = copyBytes(pkSeed);
|
433
|
+
else if (random === undefined) random = randomBytes(N);
|
434
|
+
else random = copyBytes(random);
|
435
|
+
abytes(random, N);
|
407
436
|
const context = getContext(pkSeed, skSeed);
|
408
437
|
// Generate randomizer
|
409
438
|
const R = context.PRFmsg(skPRF, random, msg); // R ← PRFmsg(SK.prf, opt_rand, M)
|
@@ -458,7 +487,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
458
487
|
root: r,
|
459
488
|
} = merkleSign(context, wotsAddr, treeAddr, leafIdx, root);
|
460
489
|
root.set(r);
|
461
|
-
r
|
490
|
+
cleanBytes(r);
|
462
491
|
wots.push([sigWots, sigAuth]);
|
463
492
|
leafIdx = Number(tree & getMaskBig(TREE_HEIGHT));
|
464
493
|
}
|
@@ -467,7 +496,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
467
496
|
cleanBytes(R, random, treeAddr, wotsAddr, forsLeaf, forsTreeAddr, indices, roots);
|
468
497
|
return SIG;
|
469
498
|
},
|
470
|
-
verify: (
|
499
|
+
verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array) => {
|
471
500
|
const [pkSeed, pubRoot] = publicCoder.decode(publicKey);
|
472
501
|
const [random, forsVec, wotsVec] = sigCoder.decode(sig);
|
473
502
|
const pk = publicKey;
|
@@ -528,33 +557,43 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
528
557
|
},
|
529
558
|
};
|
530
559
|
return {
|
560
|
+
info: { type: 'slh-dsa' },
|
531
561
|
internal,
|
532
|
-
|
562
|
+
securityLevel: securityLevel,
|
563
|
+
lengths: internal.lengths,
|
533
564
|
keygen: internal.keygen,
|
534
|
-
|
535
|
-
sign: (
|
536
|
-
|
537
|
-
const
|
538
|
-
|
565
|
+
getPublicKey: internal.getPublicKey,
|
566
|
+
sign: (msg: Uint8Array, secretKey: Uint8Array, opts: SigOpts = {}) => {
|
567
|
+
validateSigOpts(opts);
|
568
|
+
const M = getMessage(msg, opts.context);
|
569
|
+
const res = internal.sign(M, secretKey, opts);
|
570
|
+
cleanBytes(M);
|
539
571
|
return res;
|
540
572
|
},
|
541
|
-
verify: (
|
542
|
-
|
573
|
+
verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array, opts: VerOpts = {}) => {
|
574
|
+
validateVerOpts(opts);
|
575
|
+
return internal.verify(sig, getMessage(msg, opts.context), publicKey);
|
576
|
+
},
|
577
|
+
prehash: (hash: CHash) => {
|
578
|
+
checkHash(hash, securityLevel);
|
579
|
+
return {
|
580
|
+
info: { type: 'hashslh-dsa' },
|
581
|
+
lengths: internal.lengths,
|
582
|
+
keygen: internal.keygen,
|
583
|
+
getPublicKey: internal.getPublicKey,
|
584
|
+
sign: (msg: Uint8Array, secretKey: Uint8Array, opts: SigOpts = {}) => {
|
585
|
+
validateSigOpts(opts);
|
586
|
+
const M = getMessagePrehash(hash, msg, opts.context);
|
587
|
+
const res = internal.sign(M, secretKey, opts);
|
588
|
+
cleanBytes(M);
|
589
|
+
return res;
|
590
|
+
},
|
591
|
+
verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array, opts: VerOpts = {}) => {
|
592
|
+
validateVerOpts(opts);
|
593
|
+
return internal.verify(sig, getMessagePrehash(hash, msg, opts.context), publicKey);
|
594
|
+
},
|
595
|
+
};
|
543
596
|
},
|
544
|
-
prehash: (hashName: string) => ({
|
545
|
-
seedLen: seedCoder.bytesLen,
|
546
|
-
keygen: internal.keygen,
|
547
|
-
signRandBytes: internal.signRandBytes,
|
548
|
-
sign: (secretKey: Uint8Array, msg: Uint8Array, ctx = EMPTY, random?: Uint8Array) => {
|
549
|
-
const M = getMessagePrehash(hashName, msg, ctx);
|
550
|
-
const res = internal.sign(secretKey, M, random);
|
551
|
-
M.fill(0);
|
552
|
-
return res;
|
553
|
-
},
|
554
|
-
verify: (publicKey: Uint8Array, msg: Uint8Array, sig: Uint8Array, ctx = EMPTY) => {
|
555
|
-
return internal.verify(publicKey, getMessagePrehash(hashName, msg, ctx), sig);
|
556
|
-
},
|
557
|
-
}),
|
558
597
|
};
|
559
598
|
}
|
560
599
|
|
@@ -640,16 +679,18 @@ const genSha =
|
|
640
679
|
const h0tmp = h0ps.clone();
|
641
680
|
const h1tmp = h1ps.clone();
|
642
681
|
|
682
|
+
// https://www.rfc-editor.org/rfc/rfc8017.html#appendix-B.2.1
|
643
683
|
function mgf1(seed: Uint8Array, length: number, hash: ShaType) {
|
644
684
|
stats.mgf1++;
|
645
685
|
const out = new Uint8Array(Math.ceil(length / hash.outputLen) * hash.outputLen);
|
686
|
+
// NOT 2^32-1
|
646
687
|
if (length > 2 ** 32) throw new Error('mask too long');
|
647
688
|
for (let counter = 0, o = out; o.length; counter++) {
|
648
689
|
counterV.setUint32(0, counter, false);
|
649
690
|
hash.create().update(seed).update(counterB).digestInto(o);
|
650
691
|
o = o.subarray(hash.outputLen);
|
651
692
|
}
|
652
|
-
out.subarray(length)
|
693
|
+
cleanBytes(out.subarray(length));
|
653
694
|
return out.subarray(0, length);
|
654
695
|
}
|
655
696
|
|
@@ -678,7 +719,7 @@ const genSha =
|
|
678
719
|
},
|
679
720
|
PRFmsg: (skPRF: Uint8Array, random: Uint8Array, msg: Uint8Array) => {
|
680
721
|
stats.gen_message_random++;
|
681
|
-
return
|
722
|
+
return hmac.create(h1, skPRF).update(random).update(msg).digest().subarray(0, N);
|
682
723
|
},
|
683
724
|
Hmsg: (R: Uint8Array, pk: Uint8Array, m: Uint8Array, outLen) => {
|
684
725
|
stats.hmsg++;
|
package/src/utils.ts
CHANGED
@@ -3,20 +3,18 @@
|
|
3
3
|
* @module
|
4
4
|
*/
|
5
5
|
/*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */
|
6
|
-
import { sha224, sha256, sha384, sha512, sha512_224, sha512_256 } from '@noble/hashes/sha2';
|
7
|
-
import { sha3_224, sha3_256, sha3_384, sha3_512, shake128, shake256 } from '@noble/hashes/sha3';
|
8
6
|
import {
|
7
|
+
type CHash,
|
9
8
|
type TypedArray,
|
10
9
|
abytes,
|
10
|
+
abytes as abytes_,
|
11
11
|
concatBytes,
|
12
|
-
|
12
|
+
isBytes,
|
13
13
|
randomBytes as randb,
|
14
|
-
|
15
|
-
} from '@noble/hashes/utils';
|
16
|
-
|
17
|
-
export const ensureBytes: typeof abytes = abytes;
|
14
|
+
} from '@noble/hashes/utils.js';
|
15
|
+
export { abytes } from '@noble/hashes/utils.js';
|
16
|
+
export { concatBytes };
|
18
17
|
export const randomBytes: typeof randb = randb;
|
19
|
-
export { concatBytes, utf8ToBytes };
|
20
18
|
|
21
19
|
// Compares 2 u8a-s in kinda constant time
|
22
20
|
export function equalBytes(a: Uint8Array, b: Uint8Array): boolean {
|
@@ -26,15 +24,60 @@ export function equalBytes(a: Uint8Array, b: Uint8Array): boolean {
|
|
26
24
|
return diff === 0;
|
27
25
|
}
|
28
26
|
|
27
|
+
// copy bytes to new u8a (aligned). Because Buffer.slice is broken.
|
28
|
+
export function copyBytes(bytes: Uint8Array): Uint8Array {
|
29
|
+
return Uint8Array.from(bytes);
|
30
|
+
}
|
31
|
+
|
32
|
+
export type CryptoKeys = {
|
33
|
+
info?: { type?: string };
|
34
|
+
lengths: { seed?: number; publicKey?: number; secretKey?: number };
|
35
|
+
keygen: (seed?: Uint8Array) => { secretKey: Uint8Array; publicKey: Uint8Array };
|
36
|
+
getPublicKey: (secretKey: Uint8Array) => Uint8Array;
|
37
|
+
};
|
38
|
+
|
39
|
+
export type VerOpts = {
|
40
|
+
context?: Uint8Array;
|
41
|
+
};
|
42
|
+
export type SigOpts = VerOpts & {
|
43
|
+
// Compatibility with @noble/curves: false to disable, enabled by default, user can pass U8A
|
44
|
+
extraEntropy?: Uint8Array | false;
|
45
|
+
};
|
46
|
+
|
47
|
+
export function validateOpts(opts: object): void {
|
48
|
+
// We try to catch u8a, since it was previously valid argument at this position
|
49
|
+
if (typeof opts !== 'object' || opts === null || isBytes(opts))
|
50
|
+
throw new Error('expected opts to be an object');
|
51
|
+
}
|
52
|
+
|
53
|
+
export function validateVerOpts(opts: VerOpts): void {
|
54
|
+
validateOpts(opts);
|
55
|
+
if (opts.context !== undefined) abytes(opts.context, undefined, 'opts.context');
|
56
|
+
}
|
57
|
+
|
58
|
+
export function validateSigOpts(opts: SigOpts): void {
|
59
|
+
validateVerOpts(opts);
|
60
|
+
if (opts.extraEntropy !== false && opts.extraEntropy !== undefined)
|
61
|
+
abytes(opts.extraEntropy, undefined, 'opts.extraEntropy');
|
62
|
+
}
|
63
|
+
|
29
64
|
/** Generic interface for signatures. Has keygen, sign and verify. */
|
30
|
-
export type Signer = {
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
65
|
+
export type Signer = CryptoKeys & {
|
66
|
+
lengths: { signRand?: number; signature?: number };
|
67
|
+
sign: (msg: Uint8Array, secretKey: Uint8Array, opts?: SigOpts) => Uint8Array;
|
68
|
+
verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array, opts?: VerOpts) => boolean;
|
69
|
+
};
|
70
|
+
|
71
|
+
export type KEM = CryptoKeys & {
|
72
|
+
lengths: { cipherText?: number; msg?: number; msgRand?: number };
|
73
|
+
encapsulate: (
|
74
|
+
publicKey: Uint8Array,
|
75
|
+
msg?: Uint8Array
|
76
|
+
) => {
|
77
|
+
cipherText: Uint8Array;
|
78
|
+
sharedSecret: Uint8Array;
|
35
79
|
};
|
36
|
-
|
37
|
-
verify: (publicKey: Uint8Array, msg: Uint8Array, sig: Uint8Array) => boolean;
|
80
|
+
decapsulate: (cipherText: Uint8Array, secretKey: Uint8Array) => Uint8Array;
|
38
81
|
};
|
39
82
|
|
40
83
|
export interface Coder<F, T> {
|
@@ -55,6 +98,7 @@ type SplitOut<T extends (number | BytesCoderLen<any>)[]> = {
|
|
55
98
|
[K in keyof T]: T[K] extends number ? Uint8Array : UnCoder<T[K]>;
|
56
99
|
};
|
57
100
|
export function splitCoder<T extends (number | BytesCoderLen<any>)[]>(
|
101
|
+
label: string,
|
58
102
|
...lengths: T
|
59
103
|
): BytesCoder<SplitOut<T>> & { bytesLen: number } {
|
60
104
|
const getLength = (c: number | BytesCoderLen<any>) => (typeof c === 'number' ? c : c.bytesLen);
|
@@ -67,7 +111,7 @@ export function splitCoder<T extends (number | BytesCoderLen<any>)[]>(
|
|
67
111
|
const c = lengths[i];
|
68
112
|
const l = getLength(c);
|
69
113
|
const b: Uint8Array = typeof c === 'number' ? (bufs[i] as any) : c.encode(bufs[i]);
|
70
|
-
|
114
|
+
abytes_(b, l, label);
|
71
115
|
res.set(b, pos);
|
72
116
|
if (typeof c !== 'number') b.fill(0); // clean
|
73
117
|
pos += l;
|
@@ -75,7 +119,7 @@ export function splitCoder<T extends (number | BytesCoderLen<any>)[]>(
|
|
75
119
|
return res;
|
76
120
|
},
|
77
121
|
decode: (buf: Uint8Array) => {
|
78
|
-
|
122
|
+
abytes_(buf, bytesLen, label);
|
79
123
|
const res = [];
|
80
124
|
for (const c of lengths) {
|
81
125
|
const l = getLength(c);
|
@@ -105,7 +149,7 @@ export function vecCoder<T>(c: BytesCoderLen<T>, vecLen: number): BytesCoderLen<
|
|
105
149
|
return res;
|
106
150
|
},
|
107
151
|
decode: (a: Uint8Array): T[] => {
|
108
|
-
|
152
|
+
abytes_(a, bytesLen);
|
109
153
|
const r: T[] = [];
|
110
154
|
for (let i = 0; i < a.length; i += c.bytesLen)
|
111
155
|
r.push(c.decode(a.subarray(i, i + c.bytesLen)));
|
@@ -114,7 +158,7 @@ export function vecCoder<T>(c: BytesCoderLen<T>, vecLen: number): BytesCoderLen<
|
|
114
158
|
};
|
115
159
|
}
|
116
160
|
|
117
|
-
// cleanBytes(
|
161
|
+
// cleanBytes(Uint8Array.of(), [Uint16Array.of(), Uint32Array.of()])
|
118
162
|
export function cleanBytes(...list: (TypedArray | TypedArray[])[]): void {
|
119
163
|
for (const t of list) {
|
120
164
|
if (Array.isArray(t)) for (const b of t) b.fill(0);
|
@@ -126,48 +170,40 @@ export function getMask(bits: number): number {
|
|
126
170
|
return (1 << bits) - 1; // 4 -> 0b1111
|
127
171
|
}
|
128
172
|
|
129
|
-
export const EMPTY: Uint8Array =
|
173
|
+
export const EMPTY: Uint8Array = Uint8Array.of();
|
130
174
|
|
131
175
|
export function getMessage(msg: Uint8Array, ctx: Uint8Array = EMPTY): Uint8Array {
|
132
|
-
|
133
|
-
|
176
|
+
abytes_(msg);
|
177
|
+
abytes_(ctx);
|
134
178
|
if (ctx.length > 255) throw new Error('context should be less than 255 bytes');
|
135
179
|
return concatBytes(new Uint8Array([0, ctx.length]), ctx, msg);
|
136
180
|
}
|
137
181
|
|
138
|
-
//
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
},
|
155
|
-
'SHAKE-256': {
|
156
|
-
oid: hexToBytes('060960864801650304020C'),
|
157
|
-
hash: (msg) => shake256(msg, { dkLen: 64 }),
|
158
|
-
},
|
159
|
-
};
|
182
|
+
// 06 09 60 86 48 01 65 03 04 02
|
183
|
+
const oidNistP = /* @__PURE__ */ Uint8Array.from([6, 9, 0x60, 0x86, 0x48, 1, 0x65, 3, 4, 2]);
|
184
|
+
|
185
|
+
export function checkHash(hash: CHash, requiredStrength: number = 0): void {
|
186
|
+
if (!hash.oid || !equalBytes(hash.oid.subarray(0, 10), oidNistP))
|
187
|
+
throw new Error('hash.oid is invalid: expected NIST hash');
|
188
|
+
const collisionResistance = (hash.outputLen * 8) / 2;
|
189
|
+
if (requiredStrength > collisionResistance) {
|
190
|
+
throw new Error(
|
191
|
+
'Pre-hash security strength too low: ' +
|
192
|
+
collisionResistance +
|
193
|
+
', required: ' +
|
194
|
+
requiredStrength
|
195
|
+
);
|
196
|
+
}
|
197
|
+
}
|
160
198
|
|
161
199
|
export function getMessagePrehash(
|
162
|
-
|
200
|
+
hash: CHash,
|
163
201
|
msg: Uint8Array,
|
164
202
|
ctx: Uint8Array = EMPTY
|
165
203
|
): Uint8Array {
|
166
|
-
|
167
|
-
|
204
|
+
abytes_(msg);
|
205
|
+
abytes_(ctx);
|
168
206
|
if (ctx.length > 255) throw new Error('context should be less than 255 bytes');
|
169
|
-
if (!HASHES[hashName]) throw new Error('unknown hash: ' + hashName);
|
170
|
-
const { oid, hash } = HASHES[hashName];
|
171
207
|
const hashed = hash(msg);
|
172
|
-
return concatBytes(new Uint8Array([1, ctx.length]), ctx, oid
|
208
|
+
return concatBytes(new Uint8Array([1, ctx.length]), ctx, hash.oid!, hashed);
|
173
209
|
}
|