@noble/post-quantum 0.1.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/LICENSE +21 -0
- package/README.md +300 -0
- package/_crystals.d.ts +34 -0
- package/_crystals.d.ts.map +1 -0
- package/_crystals.js +171 -0
- package/_crystals.js.map +1 -0
- package/esm/_crystals.js +167 -0
- package/esm/_crystals.js.map +1 -0
- package/esm/index.js +3 -0
- package/esm/index.js.map +1 -0
- package/esm/ml-dsa.js +529 -0
- package/esm/ml-dsa.js.map +1 -0
- package/esm/ml-kem.js +361 -0
- package/esm/ml-kem.js.map +1 -0
- package/esm/package.json +10 -0
- package/esm/slh-dsa.js +602 -0
- package/esm/slh-dsa.js.map +1 -0
- package/esm/utils.js +86 -0
- package/esm/utils.js.map +1 -0
- package/index.d.ts +1 -0
- package/index.d.ts.map +1 -0
- package/index.js +3 -0
- package/index.js.map +1 -0
- package/ml-dsa.d.ts +37 -0
- package/ml-dsa.d.ts.map +1 -0
- package/ml-dsa.js +532 -0
- package/ml-dsa.js.map +1 -0
- package/ml-kem.d.ts +134 -0
- package/ml-kem.d.ts.map +1 -0
- package/ml-kem.js +364 -0
- package/ml-kem.js.map +1 -0
- package/package.json +100 -0
- package/slh-dsa.d.ts +70 -0
- package/slh-dsa.d.ts.map +1 -0
- package/slh-dsa.js +605 -0
- package/slh-dsa.js.map +1 -0
- package/src/_crystals.ts +197 -0
- package/src/index.ts +1 -0
- package/src/ml-dsa.ts +569 -0
- package/src/ml-kem.ts +403 -0
- package/src/package.json +3 -0
- package/src/slh-dsa.ts +771 -0
- package/src/utils.ts +113 -0
- package/utils.d.ts +38 -0
- package/utils.d.ts.map +1 -0
- package/utils.js +94 -0
- package/utils.js.map +1 -0
package/src/_crystals.ts
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
/*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */
|
2
|
+
import { unsafe } from '@noble/ciphers/aes';
|
3
|
+
import { shake128, shake256 } from '@noble/hashes/sha3';
|
4
|
+
import type { TypedArray } from '@noble/hashes/utils';
|
5
|
+
import { BytesCoderLen, Coder, getMask } from './utils.js';
|
6
|
+
|
7
|
+
export type XOF = (
|
8
|
+
seed: Uint8Array,
|
9
|
+
blockLen?: number
|
10
|
+
) => {
|
11
|
+
stats: () => { calls: number; xofs: number };
|
12
|
+
get: (x: number, y: number) => () => Uint8Array; // return block aligned to blockLen and 3
|
13
|
+
clean: () => void;
|
14
|
+
};
|
15
|
+
|
16
|
+
export type CrystalOpts<T extends TypedArray> = {
|
17
|
+
newPoly: TypedCons<T>;
|
18
|
+
N: number; // poly size, 256
|
19
|
+
Q: number; // modulo
|
20
|
+
F: number; // 256**−1 mod q for dilithium, 128**−1 mod q for kyber
|
21
|
+
ROOT_OF_UNITY: number;
|
22
|
+
brvBits: number; // bits for bitReversal
|
23
|
+
isKyber: boolean;
|
24
|
+
};
|
25
|
+
|
26
|
+
export type TypedCons<T extends TypedArray> = (n: number) => T;
|
27
|
+
|
28
|
+
// TODO: benchmark
|
29
|
+
function bitReversal(n: number, bits: number = 8) {
|
30
|
+
const padded = n.toString(2).padStart(8, '0');
|
31
|
+
const sliced = padded.slice(-bits).padStart(7, '0');
|
32
|
+
const revrsd = sliced.split('').reverse().join('');
|
33
|
+
return Number.parseInt(revrsd, 2);
|
34
|
+
}
|
35
|
+
|
36
|
+
export const genCrystals = <T extends TypedArray>(opts: CrystalOpts<T>) => {
|
37
|
+
// isKyber: true means Kyber, false means Dilithium
|
38
|
+
const { newPoly, N, Q, F, ROOT_OF_UNITY, brvBits, isKyber } = opts;
|
39
|
+
const mod = (a: number, modulo = Q): number => {
|
40
|
+
const result = a % modulo | 0;
|
41
|
+
return (result >= 0 ? result | 0 : (modulo + result) | 0) | 0;
|
42
|
+
};
|
43
|
+
// -(Q-1)/2 < a <= (Q-1)/2
|
44
|
+
const smod = (a: number, modulo = Q): number => {
|
45
|
+
const r = mod(a, modulo) | 0;
|
46
|
+
return (r > modulo >> 1 ? (r - modulo) | 0 : r) | 0;
|
47
|
+
};
|
48
|
+
// Generate zettas
|
49
|
+
function getZettas() {
|
50
|
+
const out = newPoly(N);
|
51
|
+
for (let i = 0; i < N; i++) {
|
52
|
+
const b = bitReversal(i, brvBits);
|
53
|
+
const p = BigInt(ROOT_OF_UNITY) ** BigInt(b) % BigInt(Q);
|
54
|
+
out[i] = Number(p) | 0;
|
55
|
+
}
|
56
|
+
return out;
|
57
|
+
}
|
58
|
+
const nttZetas = getZettas();
|
59
|
+
|
60
|
+
// Number-Theoretic Transform
|
61
|
+
// Explained: https://electricdusk.com/ntt.html
|
62
|
+
|
63
|
+
// Kyber has slightly different params, since there is no 512th primitive root of unity mod q,
|
64
|
+
// only 256th primitive root of unity mod. Which also complicates MultiplyNTT.
|
65
|
+
// TODO: there should be less ugly way to define this.
|
66
|
+
const LEN1 = isKyber ? 128 : N;
|
67
|
+
const LEN2 = isKyber ? 1 : 0;
|
68
|
+
const NTT = {
|
69
|
+
encode: (r: T) => {
|
70
|
+
for (let k = 1, len = 128; len > LEN2; len >>= 1) {
|
71
|
+
for (let start = 0; start < N; start += 2 * len) {
|
72
|
+
const zeta = nttZetas[k++];
|
73
|
+
for (let j = start; j < start + len; j++) {
|
74
|
+
const t = mod(zeta * r[j + len]);
|
75
|
+
r[j + len] = mod(r[j] - t) | 0;
|
76
|
+
r[j] = mod(r[j] + t) | 0;
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
return r;
|
81
|
+
},
|
82
|
+
decode: (r: T) => {
|
83
|
+
for (let k = LEN1 - 1, len = 1 + LEN2; len < LEN1 + LEN2; len <<= 1) {
|
84
|
+
for (let start = 0; start < N; start += 2 * len) {
|
85
|
+
const zeta = nttZetas[k--];
|
86
|
+
for (let j = start; j < start + len; j++) {
|
87
|
+
const t = r[j];
|
88
|
+
r[j] = mod(t + r[j + len]);
|
89
|
+
r[j + len] = mod(zeta * (r[j + len] - t));
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
for (let i = 0; i < r.length; i++) r[i] = mod(F * r[i]);
|
94
|
+
return r;
|
95
|
+
},
|
96
|
+
};
|
97
|
+
// Encode polynominal as bits
|
98
|
+
const bitsCoder = (d: number, c: Coder<number, number>): BytesCoderLen<T> => {
|
99
|
+
const mask = getMask(d);
|
100
|
+
const bytesLen = d * (N / 8);
|
101
|
+
return {
|
102
|
+
bytesLen,
|
103
|
+
encode: (poly: T): Uint8Array => {
|
104
|
+
const r = new Uint8Array(bytesLen);
|
105
|
+
for (let i = 0, buf = 0, bufLen = 0, pos = 0; i < poly.length; i++) {
|
106
|
+
buf |= (c.encode(poly[i]) & mask) << bufLen;
|
107
|
+
bufLen += d;
|
108
|
+
for (; bufLen >= 8; bufLen -= 8, buf >>= 8) r[pos++] = buf & getMask(bufLen);
|
109
|
+
}
|
110
|
+
return r;
|
111
|
+
},
|
112
|
+
decode: (bytes: Uint8Array): T => {
|
113
|
+
const r = newPoly(N);
|
114
|
+
for (let i = 0, buf = 0, bufLen = 0, pos = 0; i < bytes.length; i++) {
|
115
|
+
buf |= bytes[i] << bufLen;
|
116
|
+
bufLen += 8;
|
117
|
+
for (; bufLen >= d; bufLen -= d, buf >>= d) r[pos++] = c.decode(buf & mask);
|
118
|
+
}
|
119
|
+
return r;
|
120
|
+
},
|
121
|
+
};
|
122
|
+
};
|
123
|
+
|
124
|
+
return { mod, smod, nttZetas, NTT, bitsCoder };
|
125
|
+
};
|
126
|
+
|
127
|
+
const createXofShake =
|
128
|
+
(shake: typeof shake128): XOF =>
|
129
|
+
(seed: Uint8Array, blockLen?: number) => {
|
130
|
+
if (!blockLen) blockLen = shake.blockLen;
|
131
|
+
// Optimizations that won't mater:
|
132
|
+
// - cached seed update (two .update(), on start and on the end)
|
133
|
+
// - another cache which cloned into working copy
|
134
|
+
|
135
|
+
// Faster than multiple updates, since seed less than blockLen
|
136
|
+
const _seed = new Uint8Array(seed.length + 2);
|
137
|
+
_seed.set(seed);
|
138
|
+
const seedLen = seed.length;
|
139
|
+
const buf = new Uint8Array(blockLen); // == shake128.blockLen
|
140
|
+
let h = shake.create({});
|
141
|
+
let calls = 0;
|
142
|
+
let xofs = 0;
|
143
|
+
return {
|
144
|
+
stats: () => ({ calls, xofs }),
|
145
|
+
get: (x: number, y: number) => {
|
146
|
+
_seed[seedLen + 0] = x;
|
147
|
+
_seed[seedLen + 1] = y;
|
148
|
+
h.destroy();
|
149
|
+
h = shake.create({}).update(_seed);
|
150
|
+
calls++;
|
151
|
+
return () => {
|
152
|
+
xofs++;
|
153
|
+
return h.xofInto(buf);
|
154
|
+
};
|
155
|
+
},
|
156
|
+
clean: () => {
|
157
|
+
h.destroy();
|
158
|
+
buf.fill(0);
|
159
|
+
_seed.fill(0);
|
160
|
+
},
|
161
|
+
};
|
162
|
+
};
|
163
|
+
|
164
|
+
export const XOF128 = /* @__PURE__ */ createXofShake(shake128);
|
165
|
+
export const XOF256 = /* @__PURE__ */ createXofShake(shake256);
|
166
|
+
|
167
|
+
const createXofAes =
|
168
|
+
(aes: typeof unsafe): XOF =>
|
169
|
+
(seed: Uint8Array, blockLen?: number) => {
|
170
|
+
if (!blockLen) blockLen = 16 * 3; // 288
|
171
|
+
const nonce = new Uint8Array(16);
|
172
|
+
const xk = aes.expandKeyLE(seed.subarray(0, 32));
|
173
|
+
const block = new Uint8Array(blockLen);
|
174
|
+
const out = block.slice();
|
175
|
+
let calls = 0;
|
176
|
+
let xofs = 0;
|
177
|
+
return {
|
178
|
+
stats: () => ({ calls, xofs }),
|
179
|
+
get: (x: number, y: number) => {
|
180
|
+
nonce.fill(0); // clean counter
|
181
|
+
nonce[0] = x;
|
182
|
+
nonce[1] = y;
|
183
|
+
calls++;
|
184
|
+
return () => {
|
185
|
+
xofs++;
|
186
|
+
return aes.ctrCounter(xk, nonce, block, out);
|
187
|
+
};
|
188
|
+
},
|
189
|
+
clean: () => {
|
190
|
+
nonce.fill(0);
|
191
|
+
xk.fill(0);
|
192
|
+
out.fill(0);
|
193
|
+
},
|
194
|
+
};
|
195
|
+
};
|
196
|
+
|
197
|
+
export const XOF_AES = /* @__PURE__ */ createXofAes(unsafe);
|
package/src/index.ts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
throw new Error('noble-post-quantum has no entry-point: consult README for usage');
|