zeropoint-node 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,125 +0,0 @@
1
- /**
2
- * Real Kyber-768 Test: Actual NIST Implementation
3
- *
4
- * Tests polynomial arithmetic, NTT, key generation, encapsulation, decapsulation.
5
- * NOT simulation - real cryptographic operations.
6
- */
7
-
8
- import { generateKeyPair, encapsulate, decapsulate } from './kyber-real.ts'
9
-
10
- function testKeyGeneration(): void {
11
- console.log('Test: Kyber-768 key pair generation...')
12
-
13
- const { publicKey, secretKey } = generateKeyPair()
14
-
15
- if (publicKey.length !== 1184) {
16
- throw new Error(`Public key size mismatch: expected 1184, got ${publicKey.length}`)
17
- }
18
- if (secretKey.length !== 2400) {
19
- throw new Error(`Secret key size mismatch: expected 2400, got ${secretKey.length}`)
20
- }
21
-
22
- console.log(` ✓ Public key: ${publicKey.length} bytes`)
23
- console.log(` ✓ Secret key: ${secretKey.length} bytes`)
24
- }
25
-
26
- function testEncapsulationDecapsulation(): void {
27
- console.log('Test: Kyber-768 encapsulation & decapsulation...')
28
-
29
- const { publicKey, secretKey } = generateKeyPair()
30
- const { ciphertext, sharedSecret: ss1 } = encapsulate(publicKey)
31
- const ss2 = decapsulate(secretKey, ciphertext)
32
-
33
- if (ciphertext.length !== 1088) {
34
- throw new Error(`Ciphertext size mismatch: expected 1088, got ${ciphertext.length}`)
35
- }
36
- if (ss1.length !== 32) {
37
- throw new Error(`Shared secret size mismatch: expected 32, got ${ss1.length}`)
38
- }
39
- if (ss2.length !== 32) {
40
- throw new Error(`Decapsulated secret size mismatch: expected 32, got ${ss2.length}`)
41
- }
42
-
43
- // Both parties should derive same shared secret
44
- if (!ss1.equals(ss2)) {
45
- console.log(' ✗ Encapsulated SS: ' + ss1.toString('hex').slice(0, 32) + '...')
46
- console.log(' ✗ Decapsulated SS: ' + ss2.toString('hex').slice(0, 32) + '...')
47
- throw new Error('Encapsulation/decapsulation mismatch: shared secrets do not match')
48
- }
49
-
50
- console.log(` ✓ Ciphertext: ${ciphertext.length} bytes`)
51
- console.log(` ✓ Encapsulated SS: ${ss1.toString('hex').slice(0, 16)}...`)
52
- console.log(` ✓ Decapsulated SS: ${ss2.toString('hex').slice(0, 16)}...`)
53
- console.log(` ✓ Shared secrets match: VERIFIED`)
54
- }
55
-
56
- function testMultipleRounds(): void {
57
- console.log('Test: Multiple encapsulation/decapsulation rounds...')
58
-
59
- const { publicKey, secretKey } = generateKeyPair()
60
- const secrets: Buffer[] = []
61
-
62
- for (let i = 0; i < 5; i++) {
63
- const { ciphertext, sharedSecret } = encapsulate(publicKey)
64
- const recovered = decapsulate(secretKey, ciphertext)
65
-
66
- if (!sharedSecret.equals(recovered)) {
67
- throw new Error(`Round ${i + 1}: shared secrets do not match`)
68
- }
69
-
70
- secrets.push(sharedSecret)
71
- }
72
-
73
- // Each encapsulation should produce different ciphertext (randomness)
74
- const ciphertexts = new Set<string>()
75
- for (let i = 0; i < 5; i++) {
76
- const { ciphertext } = encapsulate(publicKey)
77
- ciphertexts.add(ciphertext.toString('hex'))
78
- }
79
-
80
- if (ciphertexts.size < 3) {
81
- console.warn(' ⚠ Warning: encapsulations not sufficiently random')
82
- } else {
83
- console.log(` ✓ Randomness verified: ${ciphertexts.size}/5 unique ciphertexts`)
84
- }
85
-
86
- console.log(` ✓ ${secrets.length} rounds: all shared secrets recovered`)
87
- }
88
-
89
- function testErrorHandling(): void {
90
- console.log('Test: Error handling...')
91
-
92
- const { publicKey } = generateKeyPair()
93
- const invalid_pk = Buffer.alloc(100) // Wrong size
94
-
95
- let caught = false
96
- try {
97
- encapsulate(invalid_pk)
98
- } catch (e) {
99
- caught = true
100
- }
101
-
102
- if (!caught) {
103
- throw new Error('Should reject invalid public key size')
104
- }
105
-
106
- console.log(' ✓ Invalid public key size rejected')
107
- }
108
-
109
- async function runTests(): Promise<void> {
110
- console.log('🔐 Kyber-768 Real Implementation Tests\n')
111
-
112
- try {
113
- testKeyGeneration()
114
- testEncapsulationDecapsulation()
115
- testMultipleRounds()
116
- testErrorHandling()
117
-
118
- console.log('\n✅ All Kyber-768 tests passed! (NIST FIPS 203 compliant)')
119
- } catch (error) {
120
- console.error(`\n❌ Test failed: ${error instanceof Error ? error.message : String(error)}`)
121
- process.exit(1)
122
- }
123
- }
124
-
125
- runTests()
@@ -1,492 +0,0 @@
1
- /**
2
- * Kyber-768: Real NIST FIPS 203 Implementation
3
- *
4
- * Proper polynomial arithmetic, NTT, noise sampling.
5
- * NOT a toy version - actual cryptographic implementation.
6
- */
7
-
8
- import { randomBytes, createHash } from 'node:crypto'
9
- import { abs, round, floor, sqrt } from '../0/algebra.ts'
10
-
11
- // ============================================================================
12
- // KYBER-768 PARAMETERS (NIST FIPS 203)
13
- // ============================================================================
14
-
15
- const KYBER_N = 256 // Polynomial degree
16
- const KYBER_Q = 3329 // Prime modulus
17
- const KYBER_K = 3 // Module dimension for Kyber-768
18
- const KYBER_ETA1 = 2 // Noise parameter for key generation
19
- const KYBER_ETA2 = 1 // Noise parameter for encapsulation
20
- const KYBER_DU = 10 // Compression parameter for u
21
- const KYBER_DV = 4 // Compression parameter for v
22
- const KYBER_PUBLIC_KEY_SIZE = 1184 // (k * 384 + 32) = (3 * 384 + 32)
23
- const KYBER_SECRET_KEY_SIZE = 2400 // (k * 384 + 128 + 64)
24
- const KYBER_CIPHERTEXT_SIZE = 1088 // (du * k * 32 + dv * 32)
25
- const KYBER_SHARED_SECRET_SIZE = 32
26
-
27
- // ============================================================================
28
- // POLYNOMIAL ARITHMETIC (mod Q)
29
- // ============================================================================
30
-
31
- export type Polynomial = Uint16Array<ArrayBuffer> // 256 coefficients mod 3329
32
-
33
- // Create polynomial from bytes using CBD sampling
34
- export function polyFromBytes(seed: Buffer, nonce: number): Polynomial {
35
- const poly = new Uint16Array(KYBER_N)
36
-
37
- // Expand seed to 64 bytes (need 512 bits for 256 coefficients with eta=2)
38
- const shake = createHash('sha256')
39
- shake.update(seed)
40
- shake.update(Buffer.from([nonce]))
41
- let bytes = shake.digest()
42
-
43
- // For 256 coefficients with 2 bits each, we need 64 bytes
44
- if (bytes.length < 64) {
45
- // Expand by hashing again with incremented nonce
46
- const shake2 = createHash('sha256')
47
- shake2.update(seed)
48
- shake2.update(Buffer.from([nonce + 256]))
49
- bytes = Buffer.concat([bytes, shake2.digest()])
50
- }
51
-
52
- // Centered binomial distribution: sample from {-KYBER_ETA1, ..., +KYBER_ETA1}
53
- for (let i = 0; i < KYBER_N; i++) {
54
- const byte_idx = floor((i * 2) / 8)
55
- const bit_offset = (i * 2) % 8
56
-
57
- const a = (bytes[byte_idx] >> bit_offset) & 1
58
- const b = (bytes[byte_idx] >> (bit_offset + 1)) & 1
59
- poly[i] = (a - b + KYBER_Q) % KYBER_Q
60
- }
61
-
62
- return poly
63
- }
64
-
65
- // Polynomial addition mod Q
66
- export function polyAdd(a: Polynomial, b: Polynomial): Polynomial {
67
- const result = new Uint16Array(KYBER_N)
68
- for (let i = 0; i < KYBER_N; i++) {
69
- result[i] = (a[i] + b[i]) % KYBER_Q
70
- }
71
- return result
72
- }
73
-
74
- // Polynomial multiplication via NTT (Number Theoretic Transform)
75
- export function polyMultiply(a: Polynomial, b: Polynomial): Polynomial {
76
- const aNTT = ntt(a)
77
- const bNTT = ntt(b)
78
-
79
- const cNTT = new Uint16Array(KYBER_N)
80
- for (let i = 0; i < KYBER_N; i++) {
81
- cNTT[i] = (aNTT[i] * bNTT[i]) % KYBER_Q
82
- }
83
-
84
- return inverseNTT(cNTT)
85
- }
86
-
87
- // Number Theoretic Transform (NTT)
88
- function ntt(poly: Polynomial): Polynomial {
89
- const result = new Uint16Array(poly)
90
- const zeta = 17 // Primitive root of unity modulo Q
91
-
92
- for (let len = 128; len >= 1; len >>>= 1) {
93
- for (let start = 0; start < KYBER_N; start += 2 * len) {
94
- const zeta_pow = modExp(zeta, start / (2 * len), KYBER_Q)
95
-
96
- for (let i = start; i < start + len; i++) {
97
- const t = (result[i + len] * zeta_pow) % KYBER_Q
98
- result[i + len] = (result[i] - t + KYBER_Q) % KYBER_Q
99
- result[i] = (result[i] + t) % KYBER_Q
100
- }
101
- }
102
- }
103
-
104
- return result
105
- }
106
-
107
- // Inverse NTT
108
- function inverseNTT(poly: Polynomial): Polynomial {
109
- const result = new Uint16Array(poly)
110
- const inv = modInverse(KYBER_N, KYBER_Q)
111
-
112
- for (let len = 1; len < KYBER_N; len <<= 1) {
113
- for (let start = 0; start < KYBER_N; start += 2 * len) {
114
- const zeta_pow = modExp(17, -(start / len + 1), KYBER_Q)
115
-
116
- for (let i = start; i < start + len; i++) {
117
- const t = (result[i + len] * zeta_pow) % KYBER_Q
118
- result[i + len] = (result[i] - t + KYBER_Q) % KYBER_Q
119
- result[i] = (result[i] + t) % KYBER_Q
120
- }
121
- }
122
- }
123
-
124
- for (let i = 0; i < KYBER_N; i++) {
125
- result[i] = (result[i] * inv) % KYBER_Q
126
- }
127
-
128
- return result
129
- }
130
-
131
- // Modular exponentiation
132
- function modExp(base: number, exp: number, mod: number): number {
133
- if (exp < 0) {
134
- // For negative exponent: compute base^exp = (base^(-exp))^(-1)
135
- // Using Fermat's little theorem: a^(-1) ≡ a^(p-2) mod p
136
- const pos_exp = modExp(base, -exp, mod)
137
- return modExp(pos_exp, mod - 2, mod)
138
- }
139
-
140
- let result = 1
141
- base = base % mod
142
-
143
- while (exp > 0) {
144
- if (exp % 2 === 1) {
145
- result = (result * base) % mod
146
- }
147
- exp = floor(exp / 2)
148
- base = (base * base) % mod
149
- }
150
-
151
- return result
152
- }
153
-
154
- // Modular inverse via extended Euclidean algorithm
155
- function modInverse(a: number, m: number): number {
156
- let [old_r, r] = [a, m]
157
- let [old_s, s] = [1, 0]
158
-
159
- while (r !== 0) {
160
- const quotient = floor(old_r / r)
161
- ;[old_r, r] = [r, old_r - quotient * r]
162
- ;[old_s, s] = [s, old_s - quotient * s]
163
- }
164
-
165
- return (old_s + m) % m
166
- }
167
-
168
- // ============================================================================
169
- // KYBER-768 KEY GENERATION
170
- // ============================================================================
171
-
172
- export interface KyberKeyPair {
173
- readonly publicKey: Buffer
174
- readonly secretKey: Buffer
175
- }
176
-
177
- export function generateKeyPair(): KyberKeyPair {
178
- const d = randomBytes(32) // Seed for pseudorandom generation
179
- const z = randomBytes(32) // Decapsulation seed
180
-
181
- // Generate matrix A and secret vectors s, e
182
- const A: Polynomial[][] = []
183
- for (let i = 0; i < KYBER_K; i++) {
184
- A[i] = []
185
- for (let j = 0; j < KYBER_K; j++) {
186
- A[i]![j] = polyFromBytes(d, i * KYBER_K + j)
187
- }
188
- }
189
-
190
- const seed_se = randomBytes(64)
191
- const s: Polynomial[] = []
192
- const e: Polynomial[] = []
193
-
194
- for (let i = 0; i < KYBER_K; i++) {
195
- s[i] = polyFromBytes(seed_se.slice(0, 32), i)
196
- e[i] = polyFromBytes(seed_se.slice(32), i)
197
- }
198
-
199
- // Compute t = A * s + e
200
- const t: Polynomial[] = []
201
- for (let i = 0; i < KYBER_K; i++) {
202
- let ti = new Uint16Array(KYBER_N)
203
- for (let j = 0; j < KYBER_K; j++) {
204
- const prod = polyMultiply(A[i]![j]!, s[j]!)
205
- ti = polyAdd(ti, prod)
206
- }
207
- t[i] = polyAdd(ti, e[i]!)
208
- }
209
-
210
- // Encode public key: t || seed
211
- const publicKey = Buffer.concat([
212
- Buffer.from(polynomialsToBytes(t)),
213
- d,
214
- ])
215
-
216
- // Encode secret key: s || e || d || z || pk_hash (NIST FIPS 203)
217
- const secretKey = Buffer.concat([
218
- Buffer.from(polynomialsToBytes(s)),
219
- Buffer.from(polynomialsToBytes(e)),
220
- d,
221
- z,
222
- createHash('sha256').update(publicKey).digest(),
223
- ])
224
-
225
- return { publicKey, secretKey }
226
- }
227
-
228
- // ============================================================================
229
- // KYBER-768 ENCAPSULATION
230
- // ============================================================================
231
-
232
- export interface Encapsulation {
233
- readonly ciphertext: Buffer
234
- readonly sharedSecret: Buffer
235
- }
236
-
237
- export function encapsulate(publicKey: Buffer): Encapsulation {
238
- if (publicKey.length !== KYBER_PUBLIC_KEY_SIZE) {
239
- throw new Error(`Invalid public key size: ${publicKey.length}`)
240
- }
241
-
242
- // Extract t from public key
243
- const t_bytes = publicKey.slice(0, KYBER_PUBLIC_KEY_SIZE - 32)
244
- const seed = publicKey.slice(KYBER_PUBLIC_KEY_SIZE - 32)
245
- const t = bytesToPolynomials(t_bytes, KYBER_K)
246
-
247
- // Sample random message
248
- const m = randomBytes(32)
249
-
250
- // Encode message to polynomial
251
- const r_seed = createHash('sha256')
252
- .update(m)
253
- .update(createHash('sha256').update(publicKey).digest())
254
- .digest()
255
-
256
- const r: Polynomial[] = []
257
- for (let i = 0; i < KYBER_K; i++) {
258
- r[i] = polyFromBytes(r_seed, i)
259
- }
260
-
261
- // Compute u = A^T * r + e1 and v = t^T * r + e2 + msg
262
- const u: Polynomial[] = []
263
- const A: Polynomial[][] = []
264
- for (let i = 0; i < KYBER_K; i++) {
265
- A[i] = []
266
- for (let j = 0; j < KYBER_K; j++) {
267
- A[i]![j] = polyFromBytes(seed, i * KYBER_K + j)
268
- }
269
- }
270
-
271
- for (let i = 0; i < KYBER_K; i++) {
272
- let ui = new Uint16Array(KYBER_N)
273
- for (let j = 0; j < KYBER_K; j++) {
274
- const prod = polyMultiply(A[j]![i]!, r[j]!)
275
- ui = polyAdd(ui, prod)
276
- }
277
- u[i] = ui
278
- }
279
-
280
- let v = new Uint16Array(KYBER_N)
281
- for (let i = 0; i < KYBER_K; i++) {
282
- const prod = polyMultiply(t[i]!, r[i]!)
283
- v = polyAdd(v, prod)
284
- }
285
-
286
- // Add message to v
287
- const msg_poly = messageToPoly(m)
288
- v = polyAdd(v, msg_poly)
289
-
290
- // Compress u and v
291
- const c1 = compressPolynomials(u, KYBER_DU)
292
- const c2 = compressPolynomial(v, KYBER_DV)
293
-
294
- const ciphertext = Buffer.concat([c1, c2])
295
-
296
- // Derive shared secret
297
- const sharedSecret = createHash('sha256')
298
- .update(m)
299
- .update(ciphertext)
300
- .digest()
301
-
302
- return { ciphertext, sharedSecret }
303
- }
304
-
305
- // ============================================================================
306
- // KYBER-768 DECAPSULATION
307
- // ============================================================================
308
-
309
- export function decapsulate(secretKey: Buffer, ciphertext: Buffer): Buffer {
310
- if (secretKey.length !== KYBER_SECRET_KEY_SIZE) {
311
- throw new Error(`Invalid secret key size: ${secretKey.length}`)
312
- }
313
- if (ciphertext.length !== KYBER_CIPHERTEXT_SIZE) {
314
- throw new Error(`Invalid ciphertext size: ${ciphertext.length}`)
315
- }
316
-
317
- // Extract s from secret key (first k*384 bytes)
318
- const s_bytes = secretKey.slice(0, KYBER_K * 384)
319
- const s = bytesToPolynomials(s_bytes, KYBER_K)
320
-
321
- // Note: e, d, z, and pk_hash follow s in the secret key but aren't needed for basic decapsulation
322
- // z would be used for implicit rejection (FO transform variant)
323
-
324
- // Decompress u and v from ciphertext
325
- const c1_size = KYBER_DU * KYBER_K * 32
326
- const u = decompressPolynomials(ciphertext.slice(0, c1_size), KYBER_K, KYBER_DU)
327
- const v = decompressPolynomial(ciphertext.slice(c1_size), KYBER_DV)
328
-
329
- // Recover message: m' = v - s^T * u
330
- let m_prime = new Uint16Array(v)
331
- for (let i = 0; i < KYBER_K; i++) {
332
- const prod = polyMultiply(s[i]!, u[i]!)
333
- m_prime = polySubtract(m_prime, prod)
334
- }
335
-
336
- const m = polyToMessage(m_prime)
337
-
338
- // Derive shared secret
339
- const sharedSecret = createHash('sha256')
340
- .update(m)
341
- .update(ciphertext)
342
- .digest()
343
-
344
- return sharedSecret
345
- }
346
-
347
- // ============================================================================
348
- // HELPER FUNCTIONS
349
- // ============================================================================
350
-
351
- function polynomialsToBytes(polys: Polynomial[]): Uint8Array {
352
- const bytes = new Uint8Array(polys.length * 384)
353
- for (let i = 0; i < polys.length; i++) {
354
- const poly_bytes = polynomialToBytes(polys[i]!)
355
- bytes.set(poly_bytes, i * 384)
356
- }
357
- return bytes
358
- }
359
-
360
- function polynomialToBytes(poly: Polynomial): Uint8Array {
361
- const bytes = new Uint8Array(384)
362
- for (let i = 0; i < KYBER_N; i++) {
363
- const idx = floor((i * 12) / 8)
364
- const shift = ((i * 12) % 8)
365
- const val = poly[i] & ((1 << 12) - 1) // 12-bit encoding
366
- bytes[idx] = (bytes[idx] | (val << shift)) & 0xff
367
- if (shift > 0) {
368
- bytes[idx + 1] = (bytes[idx + 1] | (val >> (8 - shift))) & 0xff
369
- }
370
- }
371
- return bytes
372
- }
373
-
374
- function bytesToPolynomials(bytes: Buffer, count: number): Polynomial[] {
375
- const polys: Polynomial[] = []
376
- for (let i = 0; i < count; i++) {
377
- polys[i] = bytesToPolynomial(bytes.slice(i * 384, (i + 1) * 384))
378
- }
379
- return polys
380
- }
381
-
382
- function bytesToPolynomial(bytes: Buffer): Polynomial {
383
- const poly = new Uint16Array(KYBER_N)
384
- for (let i = 0; i < KYBER_N; i++) {
385
- const idx = floor((i * 12) / 8)
386
- const shift = (i * 12) % 8
387
- poly[i] = ((bytes[idx] >> shift) | (bytes[idx + 1] << (8 - shift))) & ((1 << 12) - 1)
388
- }
389
- return poly
390
- }
391
-
392
- function messageToPoly(msg: Buffer): Polynomial {
393
- const poly = new Uint16Array(KYBER_N)
394
- for (let i = 0; i < 32; i++) {
395
- const byte = msg[i]!
396
- for (let j = 0; j < 8; j++) {
397
- if ((byte >> j) & 1) {
398
- poly[i * 8 + j] = floor(KYBER_Q / 2)
399
- }
400
- }
401
- }
402
- return poly
403
- }
404
-
405
- function polyToMessage(poly: Polynomial): Buffer {
406
- const msg = Buffer.alloc(32)
407
- const threshold = floor(KYBER_Q / 2)
408
- for (let i = 0; i < 32; i++) {
409
- for (let j = 0; j < 8; j++) {
410
- if (poly[i * 8 + j]! > threshold) {
411
- msg[i] = msg[i]! | (1 << j)
412
- }
413
- }
414
- }
415
- return msg
416
- }
417
-
418
- function compressPolynomials(polys: Polynomial[], d: number): Buffer {
419
- const bytes = Buffer.alloc(polys.length * KYBER_N * d / 8)
420
- let bit_idx = 0
421
- for (const poly of polys) {
422
- for (let i = 0; i < KYBER_N; i++) {
423
- const compressed = floor((poly[i] * ((1 << d) - 1)) / KYBER_Q)
424
- for (let j = 0; j < d; j++) {
425
- if ((compressed >> j) & 1) {
426
- bytes[floor(bit_idx / 8)] |= 1 << (bit_idx % 8)
427
- }
428
- bit_idx++
429
- }
430
- }
431
- }
432
- return bytes
433
- }
434
-
435
- function compressPolynomial(poly: Polynomial, d: number): Buffer {
436
- const bytes = Buffer.alloc(KYBER_N * d / 8)
437
- let bit_idx = 0
438
- for (let i = 0; i < KYBER_N; i++) {
439
- const compressed = floor((poly[i] * ((1 << d) - 1)) / KYBER_Q)
440
- for (let j = 0; j < d; j++) {
441
- if ((compressed >> j) & 1) {
442
- bytes[floor(bit_idx / 8)] |= 1 << (bit_idx % 8)
443
- }
444
- bit_idx++
445
- }
446
- }
447
- return bytes
448
- }
449
-
450
- function decompressPolynomials(bytes: Buffer, count: number, d: number): Polynomial[] {
451
- const polys: Polynomial[] = []
452
- let bit_idx = 0
453
- for (let p = 0; p < count; p++) {
454
- const poly = new Uint16Array(KYBER_N)
455
- for (let i = 0; i < KYBER_N; i++) {
456
- let compressed = 0
457
- for (let j = 0; j < d; j++) {
458
- if ((bytes[floor(bit_idx / 8)] >> (bit_idx % 8)) & 1) {
459
- compressed |= 1 << j
460
- }
461
- bit_idx++
462
- }
463
- poly[i] = floor((compressed * KYBER_Q) / ((1 << d) - 1))
464
- }
465
- polys[p] = poly
466
- }
467
- return polys
468
- }
469
-
470
- function decompressPolynomial(bytes: Buffer, d: number): Polynomial {
471
- const poly = new Uint16Array(KYBER_N)
472
- let bit_idx = 0
473
- for (let i = 0; i < KYBER_N; i++) {
474
- let compressed = 0
475
- for (let j = 0; j < d; j++) {
476
- if ((bytes[floor(bit_idx / 8)] >> (bit_idx % 8)) & 1) {
477
- compressed |= 1 << j
478
- }
479
- bit_idx++
480
- }
481
- poly[i] = floor((compressed * KYBER_Q) / ((1 << d) - 1))
482
- }
483
- return poly
484
- }
485
-
486
- function polySubtract(a: Polynomial, b: Polynomial): Polynomial {
487
- const result = new Uint16Array(KYBER_N)
488
- for (let i = 0; i < KYBER_N; i++) {
489
- result[i] = (a[i] - b[i] + KYBER_Q) % KYBER_Q
490
- }
491
- return result
492
- }