@noble/post-quantum 0.4.1 → 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 +47 -32
- 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 +39 -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 +372 -0
- package/src/index.ts +3 -3
- package/src/ml-dsa.ts +125 -39
- package/src/ml-kem.ts +49 -46
- package/src/slh-dsa.ts +90 -50
- package/src/utils.ts +85 -50
- package/utils.d.ts +52 -10
- 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
|
};
|
@@ -205,11 +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) => {
|
212
|
-
|
199
|
+
abytes(seed, 32);
|
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(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);
|
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);
|
296
|
+
abytes(msg, msgLen);
|
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); // 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');
|
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,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,6 +51,9 @@ import {
|
|
44
51
|
randomBytes,
|
45
52
|
splitCoder,
|
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;
|
@@ -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,9 +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
|
-
|
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
|
+
},
|
385
404
|
keygen(seed?: Uint8Array) {
|
386
|
-
|
405
|
+
if (seed !== undefined) abytes(seed, seedCoder.bytesLen);
|
406
|
+
seed = seed === undefined ? randomBytes(seedCoder.bytesLen) : copyBytes(seed);
|
387
407
|
// Set SK.seed, SK.prf, and PK.seed to random n-byte
|
388
408
|
const [secretSeed, secretPRF, publicSeed] = seedCoder.decode(seed);
|
389
409
|
const context = getContext(publicSeed, secretSeed);
|
@@ -398,12 +418,20 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
398
418
|
cleanBytes(secretSeed, secretPRF, root, wotsAddr, topTreeAddr);
|
399
419
|
return { publicKey, secretKey };
|
400
420
|
},
|
401
|
-
|
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;
|
402
428
|
const [skSeed, skPRF, pk] = secretCoder.decode(sk); // todo: fix
|
403
429
|
const [pkSeed, _] = publicCoder.decode(pk);
|
404
430
|
// Set opt_rand to either PK.seed or to a random n-byte string
|
405
|
-
if (
|
406
|
-
|
431
|
+
if (random === false) random = copyBytes(pkSeed);
|
432
|
+
else if (random === undefined) random = randomBytes(N);
|
433
|
+
else random = copyBytes(random);
|
434
|
+
abytes(random, N);
|
407
435
|
const context = getContext(pkSeed, skSeed);
|
408
436
|
// Generate randomizer
|
409
437
|
const R = context.PRFmsg(skPRF, random, msg); // R ← PRFmsg(SK.prf, opt_rand, M)
|
@@ -458,7 +486,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
458
486
|
root: r,
|
459
487
|
} = merkleSign(context, wotsAddr, treeAddr, leafIdx, root);
|
460
488
|
root.set(r);
|
461
|
-
r
|
489
|
+
cleanBytes(r);
|
462
490
|
wots.push([sigWots, sigAuth]);
|
463
491
|
leafIdx = Number(tree & getMaskBig(TREE_HEIGHT));
|
464
492
|
}
|
@@ -467,7 +495,7 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
467
495
|
cleanBytes(R, random, treeAddr, wotsAddr, forsLeaf, forsTreeAddr, indices, roots);
|
468
496
|
return SIG;
|
469
497
|
},
|
470
|
-
verify: (
|
498
|
+
verify: (sig: Uint8Array, msg: Uint8Array, publicKey: Uint8Array) => {
|
471
499
|
const [pkSeed, pubRoot] = publicCoder.decode(publicKey);
|
472
500
|
const [random, forsVec, wotsVec] = sigCoder.decode(sig);
|
473
501
|
const pk = publicKey;
|
@@ -528,33 +556,43 @@ function gen(opts: SphincsOpts, hashOpts: SphincsHashOpts): SphincsSigner {
|
|
528
556
|
},
|
529
557
|
};
|
530
558
|
return {
|
559
|
+
info: { type: 'slh-dsa' },
|
531
560
|
internal,
|
532
|
-
|
561
|
+
securityLevel: securityLevel,
|
562
|
+
lengths: internal.lengths,
|
533
563
|
keygen: internal.keygen,
|
534
|
-
|
535
|
-
sign: (
|
536
|
-
|
537
|
-
const
|
538
|
-
|
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);
|
539
570
|
return res;
|
540
571
|
},
|
541
|
-
verify: (
|
542
|
-
|
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
|
+
};
|
543
595
|
},
|
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
596
|
};
|
559
597
|
}
|
560
598
|
|
@@ -640,16 +678,18 @@ const genSha =
|
|
640
678
|
const h0tmp = h0ps.clone();
|
641
679
|
const h1tmp = h1ps.clone();
|
642
680
|
|
681
|
+
// https://www.rfc-editor.org/rfc/rfc8017.html#appendix-B.2.1
|
643
682
|
function mgf1(seed: Uint8Array, length: number, hash: ShaType) {
|
644
683
|
stats.mgf1++;
|
645
684
|
const out = new Uint8Array(Math.ceil(length / hash.outputLen) * hash.outputLen);
|
685
|
+
// NOT 2^32-1
|
646
686
|
if (length > 2 ** 32) throw new Error('mask too long');
|
647
687
|
for (let counter = 0, o = out; o.length; counter++) {
|
648
688
|
counterV.setUint32(0, counter, false);
|
649
689
|
hash.create().update(seed).update(counterB).digestInto(o);
|
650
690
|
o = o.subarray(hash.outputLen);
|
651
691
|
}
|
652
|
-
out.subarray(length)
|
692
|
+
cleanBytes(out.subarray(length));
|
653
693
|
return out.subarray(0, length);
|
654
694
|
}
|
655
695
|
|
@@ -678,7 +718,7 @@ const genSha =
|
|
678
718
|
},
|
679
719
|
PRFmsg: (skPRF: Uint8Array, random: Uint8Array, msg: Uint8Array) => {
|
680
720
|
stats.gen_message_random++;
|
681
|
-
return
|
721
|
+
return hmac.create(h1, skPRF).update(random).update(msg).digest().subarray(0, N);
|
682
722
|
},
|
683
723
|
Hmsg: (R: Uint8Array, pk: Uint8Array, m: Uint8Array, outLen) => {
|
684
724
|
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> {
|
@@ -67,7 +110,7 @@ export function splitCoder<T extends (number | BytesCoderLen<any>)[]>(
|
|
67
110
|
const c = lengths[i];
|
68
111
|
const l = getLength(c);
|
69
112
|
const b: Uint8Array = typeof c === 'number' ? (bufs[i] as any) : c.encode(bufs[i]);
|
70
|
-
|
113
|
+
abytes_(b, l);
|
71
114
|
res.set(b, pos);
|
72
115
|
if (typeof c !== 'number') b.fill(0); // clean
|
73
116
|
pos += l;
|
@@ -75,7 +118,7 @@ export function splitCoder<T extends (number | BytesCoderLen<any>)[]>(
|
|
75
118
|
return res;
|
76
119
|
},
|
77
120
|
decode: (buf: Uint8Array) => {
|
78
|
-
|
121
|
+
abytes_(buf, bytesLen);
|
79
122
|
const res = [];
|
80
123
|
for (const c of lengths) {
|
81
124
|
const l = getLength(c);
|
@@ -105,7 +148,7 @@ export function vecCoder<T>(c: BytesCoderLen<T>, vecLen: number): BytesCoderLen<
|
|
105
148
|
return res;
|
106
149
|
},
|
107
150
|
decode: (a: Uint8Array): T[] => {
|
108
|
-
|
151
|
+
abytes_(a, bytesLen);
|
109
152
|
const r: T[] = [];
|
110
153
|
for (let i = 0; i < a.length; i += c.bytesLen)
|
111
154
|
r.push(c.decode(a.subarray(i, i + c.bytesLen)));
|
@@ -114,7 +157,7 @@ export function vecCoder<T>(c: BytesCoderLen<T>, vecLen: number): BytesCoderLen<
|
|
114
157
|
};
|
115
158
|
}
|
116
159
|
|
117
|
-
// cleanBytes(
|
160
|
+
// cleanBytes(Uint8Array.of(), [Uint16Array.of(), Uint32Array.of()])
|
118
161
|
export function cleanBytes(...list: (TypedArray | TypedArray[])[]): void {
|
119
162
|
for (const t of list) {
|
120
163
|
if (Array.isArray(t)) for (const b of t) b.fill(0);
|
@@ -126,48 +169,40 @@ export function getMask(bits: number): number {
|
|
126
169
|
return (1 << bits) - 1; // 4 -> 0b1111
|
127
170
|
}
|
128
171
|
|
129
|
-
export const EMPTY: Uint8Array =
|
172
|
+
export const EMPTY: Uint8Array = Uint8Array.of();
|
130
173
|
|
131
174
|
export function getMessage(msg: Uint8Array, ctx: Uint8Array = EMPTY): Uint8Array {
|
132
|
-
|
133
|
-
|
175
|
+
abytes_(msg);
|
176
|
+
abytes_(ctx);
|
134
177
|
if (ctx.length > 255) throw new Error('context should be less than 255 bytes');
|
135
178
|
return concatBytes(new Uint8Array([0, ctx.length]), ctx, msg);
|
136
179
|
}
|
137
180
|
|
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
|
-
};
|
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
|
+
}
|
160
197
|
|
161
198
|
export function getMessagePrehash(
|
162
|
-
|
199
|
+
hash: CHash,
|
163
200
|
msg: Uint8Array,
|
164
201
|
ctx: Uint8Array = EMPTY
|
165
202
|
): Uint8Array {
|
166
|
-
|
167
|
-
|
203
|
+
abytes_(msg);
|
204
|
+
abytes_(ctx);
|
168
205
|
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
206
|
const hashed = hash(msg);
|
172
|
-
return concatBytes(new Uint8Array([1, ctx.length]), ctx, oid
|
207
|
+
return concatBytes(new Uint8Array([1, ctx.length]), ctx, hash.oid!, hashed);
|
173
208
|
}
|