@noble/post-quantum 0.4.0 → 0.5.0
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 +65 -64
- package/_crystals.d.ts +2 -2
- 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 +130 -66
- 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 -53
- package/ml-kem.js.map +1 -1
- package/package.json +39 -62
- package/slh-dsa.d.ts +5 -4
- package/slh-dsa.d.ts.map +1 -1
- package/slh-dsa.js +114 -86
- package/slh-dsa.js.map +1 -1
- package/src/_crystals.ts +30 -41
- package/src/hybrid.ts +372 -0
- package/src/index.ts +3 -3
- package/src/ml-dsa.ts +131 -41
- package/src/ml-kem.ts +51 -47
- package/src/slh-dsa.ts +92 -51
- package/src/utils.ts +86 -52
- package/utils.d.ts +47 -11
- package/utils.d.ts.map +1 -1
- package/utils.js +54 -62
- 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 -519
- 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 -305
- 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 -595
- package/esm/slh-dsa.js.map +0 -1
- package/esm/utils.d.ts +0 -46
- package/esm/utils.d.ts.map +0 -1
- package/esm/utils.js +0 -135
- 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 {
|
25
|
-
import { genCrystals, type XOF, XOF128 } from './_crystals.
|
23
|
+
import { sha3_256, sha3_512, shake256 } from '@noble/hashes/sha3.js';
|
24
|
+
import { type CHash, u32 } from '@noble/hashes/utils.js';
|
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
|
-
} from './utils.
|
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
|
};
|
@@ -205,10 +190,13 @@ const genKPKE = (opts: KyberOpts) => {
|
|
205
190
|
const seedCoder = splitCoder(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) => {
|
199
|
+
abytes(seed, 32);
|
212
200
|
const seedDst = new Uint8Array(33);
|
213
201
|
seedDst.set(seed);
|
214
202
|
seedDst[32] = K;
|
@@ -252,7 +240,7 @@ const genKPKE = (opts: KyberOpts) => {
|
|
252
240
|
polyAdd(e1, NTT.decode(tmp)); // e1 += tmp
|
253
241
|
u.push(e1);
|
254
242
|
polyAdd(tmp2, MultiplyNTTs(tHat[i], rHat[i])); // t2 += tHat[i] * rHat[i]
|
255
|
-
tmp
|
243
|
+
cleanBytes(tmp);
|
256
244
|
}
|
257
245
|
x.clean();
|
258
246
|
const e2 = sampleCBD(PRF, seed, 2 * K, ETA2);
|
@@ -277,16 +265,21 @@ const genKPKE = (opts: KyberOpts) => {
|
|
277
265
|
function createKyber(opts: KyberOpts) {
|
278
266
|
const KPKE = genKPKE(opts);
|
279
267
|
const { HASH256, HASH512, KDF } = opts;
|
280
|
-
const { secretCoder: KPKESecretCoder,
|
281
|
-
const
|
282
|
-
const secretCoder = splitCoder(KPKE.secretKeyLen, KPKE.publicKeyLen, 32, 32);
|
283
|
-
const secretKeyLen = secretCoder.bytesLen;
|
268
|
+
const { secretCoder: KPKESecretCoder, lengths } = KPKE;
|
269
|
+
const secretCoder = splitCoder(lengths.secretKey, lengths.publicKey, 32, 32);
|
284
270
|
const msgLen = 32;
|
271
|
+
const seedLen = 64;
|
285
272
|
return {
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
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);
|
290
283
|
const { publicKey, secretKey: sk } = KPKE.keygen(seed.subarray(0, 32));
|
291
284
|
const publicKeyHash = HASH256(publicKey);
|
292
285
|
// (dkPKE||ek||H(ek)||z)
|
@@ -294,13 +287,17 @@ function createKyber(opts: KyberOpts) {
|
|
294
287
|
cleanBytes(sk, publicKeyHash);
|
295
288
|
return { publicKey, secretKey };
|
296
289
|
},
|
297
|
-
|
298
|
-
|
299
|
-
|
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);
|
296
|
+
abytes(msg, msgLen);
|
300
297
|
|
301
298
|
// FIPS-203 includes additional verification check for modulus
|
302
299
|
const eke = publicKey.subarray(0, 384 * opts.K);
|
303
|
-
const ek = KPKESecretCoder.encode(KPKESecretCoder.decode(eke
|
300
|
+
const ek = KPKESecretCoder.encode(KPKESecretCoder.decode(copyBytes(eke))); // Copy because of inplace encoding
|
304
301
|
// (Modulus check.) Perform the computation ek ← ByteEncode12(ByteDecode12(eke)).
|
305
302
|
// If ek = ̸ eke, the input is invalid. (See Section 4.2.1.)
|
306
303
|
if (!equalBytes(ek, eke)) {
|
@@ -310,12 +307,19 @@ function createKyber(opts: KyberOpts) {
|
|
310
307
|
cleanBytes(ek);
|
311
308
|
const kr = HASH512.create().update(msg).update(HASH256(publicKey)).digest(); // derive randomness
|
312
309
|
const cipherText = KPKE.encrypt(publicKey, msg, kr.subarray(32, 64));
|
313
|
-
kr.subarray(32)
|
310
|
+
cleanBytes(kr.subarray(32));
|
314
311
|
return { cipherText, sharedSecret: kr.subarray(0, 32) };
|
315
312
|
},
|
316
313
|
decapsulate: (cipherText: Uint8Array, secretKey: Uint8Array) => {
|
317
|
-
|
318
|
-
|
314
|
+
abytes(secretKey, secretCoder.bytesLen); // 768*k + 96
|
315
|
+
abytes(cipherText, lengths.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');
|
319
323
|
const [sk, publicKey, publicKeyHash, z] = secretCoder.decode(secretKey);
|
320
324
|
const msg = KPKE.decrypt(cipherText, sk);
|
321
325
|
const kr = HASH512.create().update(msg).update(publicKeyHash).digest(); // derive randomness, Khat, rHat = G(mHat || h)
|
package/src/slh-dsa.ts
CHANGED
@@ -27,16 +27,23 @@
|
|
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,
|
43
|
+
validateSigOpts,
|
44
|
+
validateVerOpts,
|
38
45
|
cleanBytes,
|
39
|
-
|
46
|
+
copyBytes,
|
40
47
|
equalBytes,
|
41
48
|
getMask,
|
42
49
|
getMessage,
|
@@ -44,7 +51,10 @@ import {
|
|
44
51
|
randomBytes,
|
45
52
|
splitCoder,
|
46
53
|
vecCoder,
|
47
|
-
|
54
|
+
type Signer,
|
55
|
+
type SigOpts,
|
56
|
+
type VerOpts,
|
57
|
+
} from './utils.ts';
|
48
58
|
|
49
59
|
/**
|
50
60
|
* * N: Security parameter (in bytes). W: Winternitz parameter
|
@@ -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;
|
@@ -315,7 +327,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
315
327
|
wotsAddr: ADRS,
|
316
328
|
treeAddr: ADRS,
|
317
329
|
leafIdx: number,
|
318
|
-
prevRoot = new Uint8Array(N)
|
330
|
+
prevRoot: Uint8Array = new Uint8Array(N)
|
319
331
|
) => {
|
320
332
|
setAddr({ type: AddressType.HASHTREE }, treeAddr);
|
321
333
|
// State variables
|
@@ -381,8 +393,17 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
381
393
|
const wotsCoder = vecCoder(splitCoder(WOTS_LEN * N, TREE_HEIGHT * N), D);
|
382
394
|
const sigCoder = splitCoder(N, forsCoder, wotsCoder); // random || fors || wots
|
383
395
|
const internal: Signer = {
|
384
|
-
|
385
|
-
|
396
|
+
info: { type: 'internal-slh-dsa' },
|
397
|
+
lengths: {
|
398
|
+
publicKey: publicCoder.bytesLen,
|
399
|
+
secretKey: secretCoder.bytesLen,
|
400
|
+
signature: sigCoder.bytesLen,
|
401
|
+
seed: seedCoder.bytesLen,
|
402
|
+
signRand: N,
|
403
|
+
},
|
404
|
+
keygen(seed?: Uint8Array) {
|
405
|
+
if (seed !== undefined) abytes(seed, seedCoder.bytesLen);
|
406
|
+
seed = seed === undefined ? randomBytes(seedCoder.bytesLen) : copyBytes(seed);
|
386
407
|
// Set SK.seed, SK.prf, and PK.seed to random n-byte
|
387
408
|
const [secretSeed, secretPRF, publicSeed] = seedCoder.decode(seed);
|
388
409
|
const context = getContext(publicSeed, secretSeed);
|
@@ -397,12 +418,20 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
397
418
|
cleanBytes(secretSeed, secretPRF, root, wotsAddr, topTreeAddr);
|
398
419
|
return { publicKey, secretKey };
|
399
420
|
},
|
400
|
-
|
421
|
+
getPublicKey: (secretKey: Uint8Array) => {
|
422
|
+
const [_skSeed, _skPRF, pk] = secretCoder.decode(secretKey);
|
423
|
+
return Uint8Array.from(pk);
|
424
|
+
},
|
425
|
+
sign: (msg: Uint8Array, sk: Uint8Array, opts: SigOpts = {}) => {
|
426
|
+
validateSigOpts(opts);
|
427
|
+
let { extraEntropy: random } = opts;
|
401
428
|
const [skSeed, skPRF, pk] = secretCoder.decode(sk); // todo: fix
|
402
429
|
const [pkSeed, _] = publicCoder.decode(pk);
|
403
430
|
// Set opt_rand to either PK.seed or to a random n-byte string
|
404
|
-
if (
|
405
|
-
|
431
|
+
if (random === false) random = copyBytes(pkSeed);
|
432
|
+
else if (random === undefined) random = randomBytes(N);
|
433
|
+
else random = copyBytes(random);
|
434
|
+
abytes(random, N);
|
406
435
|
const context = getContext(pkSeed, skSeed);
|
407
436
|
// Generate randomizer
|
408
437
|
const R = context.PRFmsg(skPRF, random, msg); // R ← PRFmsg(SK.prf, opt_rand, M)
|
@@ -457,7 +486,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
457
486
|
root: r,
|
458
487
|
} = merkleSign(context, wotsAddr, treeAddr, leafIdx, root);
|
459
488
|
root.set(r);
|
460
|
-
r
|
489
|
+
cleanBytes(r);
|
461
490
|
wots.push([sigWots, sigAuth]);
|
462
491
|
leafIdx = Number(tree & getMaskBig(TREE_HEIGHT));
|
463
492
|
}
|
@@ -466,7 +495,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
466
495
|
cleanBytes(R, random, treeAddr, wotsAddr, forsLeaf, forsTreeAddr, indices, roots);
|
467
496
|
return SIG;
|
468
497
|
},
|
469
|
-
verify: (
|
498
|
+
verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array) => {
|
470
499
|
const [pkSeed, pubRoot] = publicCoder.decode(publicKey);
|
471
500
|
const [random, forsVec, wotsVec] = sigCoder.decode(sig);
|
472
501
|
const pk = publicKey;
|
@@ -527,33 +556,43 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
527
556
|
},
|
528
557
|
};
|
529
558
|
return {
|
559
|
+
info: { type: 'slh-dsa' },
|
530
560
|
internal,
|
531
|
-
|
561
|
+
securityLevel: securityLevel,
|
562
|
+
lengths: internal.lengths,
|
532
563
|
keygen: internal.keygen,
|
533
|
-
|
534
|
-
sign: (
|
535
|
-
|
536
|
-
const
|
537
|
-
|
564
|
+
getPublicKey: internal.getPublicKey,
|
565
|
+
sign: (msg: Uint8Array, secretKey: Uint8Array, opts: SigOpts = {}) => {
|
566
|
+
validateSigOpts(opts);
|
567
|
+
const M = getMessage(msg, opts.context);
|
568
|
+
const res = internal.sign(M, secretKey, opts);
|
569
|
+
cleanBytes(M);
|
538
570
|
return res;
|
539
571
|
},
|
540
|
-
verify: (
|
541
|
-
|
572
|
+
verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array, opts: VerOpts = {}) => {
|
573
|
+
validateVerOpts(opts);
|
574
|
+
return internal.verify(sig, getMessage(msg, opts.context), publicKey);
|
575
|
+
},
|
576
|
+
prehash: (hash: CHash) => {
|
577
|
+
checkHash(hash, securityLevel);
|
578
|
+
return {
|
579
|
+
info: { type: 'hashslh-dsa' },
|
580
|
+
lengths: internal.lengths,
|
581
|
+
keygen: internal.keygen,
|
582
|
+
getPublicKey: internal.getPublicKey,
|
583
|
+
sign: (msg: Uint8Array, secretKey: Uint8Array, opts: SigOpts = {}) => {
|
584
|
+
validateSigOpts(opts);
|
585
|
+
const M = getMessagePrehash(hash, msg, opts.context);
|
586
|
+
const res = internal.sign(M, secretKey, opts);
|
587
|
+
cleanBytes(M);
|
588
|
+
return res;
|
589
|
+
},
|
590
|
+
verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array, opts: VerOpts = {}) => {
|
591
|
+
validateVerOpts(opts);
|
592
|
+
return internal.verify(sig, getMessagePrehash(hash, msg, opts.context), publicKey);
|
593
|
+
},
|
594
|
+
};
|
542
595
|
},
|
543
|
-
prehash: (hashName: string) => ({
|
544
|
-
seedLen: seedCoder.bytesLen,
|
545
|
-
keygen: internal.keygen,
|
546
|
-
signRandBytes: internal.signRandBytes,
|
547
|
-
sign: (secretKey: Uint8Array, msg: Uint8Array, ctx = EMPTY, random?: Uint8Array) => {
|
548
|
-
const M = getMessagePrehash(hashName, msg, ctx);
|
549
|
-
const res = internal.sign(secretKey, M, random);
|
550
|
-
M.fill(0);
|
551
|
-
return res;
|
552
|
-
},
|
553
|
-
verify: (publicKey: Uint8Array, msg: Uint8Array, sig: Uint8Array, ctx = EMPTY) => {
|
554
|
-
return internal.verify(publicKey, getMessagePrehash(hashName, msg, ctx), sig);
|
555
|
-
},
|
556
|
-
}),
|
557
596
|
};
|
558
597
|
}
|
559
598
|
|
@@ -639,16 +678,18 @@ const genSha =
|
|
639
678
|
const h0tmp = h0ps.clone();
|
640
679
|
const h1tmp = h1ps.clone();
|
641
680
|
|
681
|
+
// https://www.rfc-editor.org/rfc/rfc8017.html#appendix-B.2.1
|
642
682
|
function mgf1(seed: Uint8Array, length: number, hash: ShaType) {
|
643
683
|
stats.mgf1++;
|
644
684
|
const out = new Uint8Array(Math.ceil(length / hash.outputLen) * hash.outputLen);
|
685
|
+
// NOT 2^32-1
|
645
686
|
if (length > 2 ** 32) throw new Error('mask too long');
|
646
687
|
for (let counter = 0, o = out; o.length; counter++) {
|
647
688
|
counterV.setUint32(0, counter, false);
|
648
689
|
hash.create().update(seed).update(counterB).digestInto(o);
|
649
690
|
o = o.subarray(hash.outputLen);
|
650
691
|
}
|
651
|
-
out.subarray(length)
|
692
|
+
cleanBytes(out.subarray(length));
|
652
693
|
return out.subarray(0, length);
|
653
694
|
}
|
654
695
|
|
@@ -677,7 +718,7 @@ const genSha =
|
|
677
718
|
},
|
678
719
|
PRFmsg: (skPRF: Uint8Array, random: Uint8Array, msg: Uint8Array) => {
|
679
720
|
stats.gen_message_random++;
|
680
|
-
return
|
721
|
+
return hmac.create(h1, skPRF).update(random).update(msg).digest().subarray(0, N);
|
681
722
|
},
|
682
723
|
Hmsg: (R: Uint8Array, pk: Uint8Array, m: Uint8Array, outLen) => {
|
683
724
|
stats.hmsg++;
|
package/src/utils.ts
CHANGED
@@ -3,21 +3,18 @@
|
|
3
3
|
* @module
|
4
4
|
*/
|
5
5
|
/*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */
|
6
|
-
import { abytes } from '@noble/hashes/_assert';
|
7
|
-
import { sha224, sha256 } from '@noble/hashes/sha256';
|
8
|
-
import { sha3_224, sha3_256, sha3_384, sha3_512, shake128, shake256 } from '@noble/hashes/sha3';
|
9
|
-
import { sha384, sha512, sha512_224, sha512_256 } from '@noble/hashes/sha512';
|
10
6
|
import {
|
7
|
+
type CHash,
|
11
8
|
type TypedArray,
|
9
|
+
abytes,
|
10
|
+
abytes as abytes_,
|
12
11
|
concatBytes,
|
13
|
-
|
12
|
+
isBytes,
|
14
13
|
randomBytes as randb,
|
15
|
-
|
16
|
-
} from '@noble/hashes/utils';
|
17
|
-
|
18
|
-
export const ensureBytes: typeof abytes = abytes;
|
14
|
+
} from '@noble/hashes/utils.js';
|
15
|
+
export { abytes } from '@noble/hashes/utils.js';
|
16
|
+
export { concatBytes };
|
19
17
|
export const randomBytes: typeof randb = randb;
|
20
|
-
export { concatBytes, utf8ToBytes };
|
21
18
|
|
22
19
|
// Compares 2 u8a-s in kinda constant time
|
23
20
|
export function equalBytes(a: Uint8Array, b: Uint8Array): boolean {
|
@@ -27,15 +24,60 @@ export function equalBytes(a: Uint8Array, b: Uint8Array): boolean {
|
|
27
24
|
return diff === 0;
|
28
25
|
}
|
29
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
|
+
|
30
64
|
/** Generic interface for signatures. Has keygen, sign and verify. */
|
31
|
-
export type Signer = {
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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;
|
36
79
|
};
|
37
|
-
|
38
|
-
verify: (publicKey: Uint8Array, msg: Uint8Array, sig: Uint8Array) => boolean;
|
80
|
+
decapsulate: (cipherText: Uint8Array, secretKey: Uint8Array) => Uint8Array;
|
39
81
|
};
|
40
82
|
|
41
83
|
export interface Coder<F, T> {
|
@@ -68,7 +110,7 @@ export function splitCoder<T extends (number | BytesCoderLen<any>)[]>(
|
|
68
110
|
const c = lengths[i];
|
69
111
|
const l = getLength(c);
|
70
112
|
const b: Uint8Array = typeof c === 'number' ? (bufs[i] as any) : c.encode(bufs[i]);
|
71
|
-
|
113
|
+
abytes_(b, l);
|
72
114
|
res.set(b, pos);
|
73
115
|
if (typeof c !== 'number') b.fill(0); // clean
|
74
116
|
pos += l;
|
@@ -76,7 +118,7 @@ export function splitCoder<T extends (number | BytesCoderLen<any>)[]>(
|
|
76
118
|
return res;
|
77
119
|
},
|
78
120
|
decode: (buf: Uint8Array) => {
|
79
|
-
|
121
|
+
abytes_(buf, bytesLen);
|
80
122
|
const res = [];
|
81
123
|
for (const c of lengths) {
|
82
124
|
const l = getLength(c);
|
@@ -106,7 +148,7 @@ export function vecCoder<T>(c: BytesCoderLen<T>, vecLen: number): BytesCoderLen<
|
|
106
148
|
return res;
|
107
149
|
},
|
108
150
|
decode: (a: Uint8Array): T[] => {
|
109
|
-
|
151
|
+
abytes_(a, bytesLen);
|
110
152
|
const r: T[] = [];
|
111
153
|
for (let i = 0; i < a.length; i += c.bytesLen)
|
112
154
|
r.push(c.decode(a.subarray(i, i + c.bytesLen)));
|
@@ -115,7 +157,7 @@ export function vecCoder<T>(c: BytesCoderLen<T>, vecLen: number): BytesCoderLen<
|
|
115
157
|
};
|
116
158
|
}
|
117
159
|
|
118
|
-
// cleanBytes(
|
160
|
+
// cleanBytes(Uint8Array.of(), [Uint16Array.of(), Uint32Array.of()])
|
119
161
|
export function cleanBytes(...list: (TypedArray | TypedArray[])[]): void {
|
120
162
|
for (const t of list) {
|
121
163
|
if (Array.isArray(t)) for (const b of t) b.fill(0);
|
@@ -127,48 +169,40 @@ export function getMask(bits: number): number {
|
|
127
169
|
return (1 << bits) - 1; // 4 -> 0b1111
|
128
170
|
}
|
129
171
|
|
130
|
-
export const EMPTY: Uint8Array =
|
172
|
+
export const EMPTY: Uint8Array = Uint8Array.of();
|
131
173
|
|
132
174
|
export function getMessage(msg: Uint8Array, ctx: Uint8Array = EMPTY): Uint8Array {
|
133
|
-
|
134
|
-
|
175
|
+
abytes_(msg);
|
176
|
+
abytes_(ctx);
|
135
177
|
if (ctx.length > 255) throw new Error('context should be less than 255 bytes');
|
136
178
|
return concatBytes(new Uint8Array([0, ctx.length]), ctx, msg);
|
137
179
|
}
|
138
180
|
|
139
|
-
//
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
},
|
156
|
-
'SHAKE-256': {
|
157
|
-
oid: hexToBytes('060960864801650304020C'),
|
158
|
-
hash: (msg) => shake256(msg, { dkLen: 64 }),
|
159
|
-
},
|
160
|
-
};
|
181
|
+
// 06 09 60 86 48 01 65 03 04 02
|
182
|
+
const oidNistP = /* @__PURE__ */ Uint8Array.from([6, 9, 0x60, 0x86, 0x48, 1, 0x65, 3, 4, 2]);
|
183
|
+
|
184
|
+
export function checkHash(hash: CHash, requiredStrength: number = 0): void {
|
185
|
+
if (!hash.oid || !equalBytes(hash.oid.subarray(0, 10), oidNistP))
|
186
|
+
throw new Error('hash.oid is invalid: expected NIST hash');
|
187
|
+
const collisionResistance = (hash.outputLen * 8) / 2;
|
188
|
+
if (requiredStrength > collisionResistance) {
|
189
|
+
throw new Error(
|
190
|
+
'Pre-hash security strength too low: ' +
|
191
|
+
collisionResistance +
|
192
|
+
', required: ' +
|
193
|
+
requiredStrength
|
194
|
+
);
|
195
|
+
}
|
196
|
+
}
|
161
197
|
|
162
198
|
export function getMessagePrehash(
|
163
|
-
|
199
|
+
hash: CHash,
|
164
200
|
msg: Uint8Array,
|
165
201
|
ctx: Uint8Array = EMPTY
|
166
202
|
): Uint8Array {
|
167
|
-
|
168
|
-
|
203
|
+
abytes_(msg);
|
204
|
+
abytes_(ctx);
|
169
205
|
if (ctx.length > 255) throw new Error('context should be less than 255 bytes');
|
170
|
-
if (!HASHES[hashName]) throw new Error('unknown hash: ' + hashName);
|
171
|
-
const { oid, hash } = HASHES[hashName];
|
172
206
|
const hashed = hash(msg);
|
173
|
-
return concatBytes(new Uint8Array([1, ctx.length]), ctx, oid
|
207
|
+
return concatBytes(new Uint8Array([1, ctx.length]), ctx, hash.oid!, hashed);
|
174
208
|
}
|