Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
ML_KEM.cs
1using System;
3
4namespace Waher.Security.PQC
5{
10 public class ML_KEM : ML_Common
11 {
16 public static readonly ML_KEM ML_KEM_512 = new ML_KEM(2, 3, 2, 10, 4);
17
22 public static readonly ML_KEM ML_KEM_768 = new ML_KEM(3, 2, 2, 10, 4);
23
28 public static readonly ML_KEM ML_KEM_1024 = new ML_KEM(4, 2, 2, 11, 5);
29
36 public static ML_KEM GetModel(string Name)
37 {
38 switch (Name.ToUpper())
39 {
40 case "ML-KEM-512":
41 return ML_KEM_512;
42
43 case "ML-KEM-768":
44 return ML_KEM_768;
45
46 case "ML-KEM-1024":
47 return ML_KEM_1024;
48
49 default:
50 throw new ArgumentException("Unknown model name: " + Name, nameof(Name));
51 }
52 }
53
58 private static readonly ushort[] nttTransformZeta =
59 {
60 1, 1729, 2580, 3289, 2642, 630, 1897, 848,
61 1062, 1919, 193, 797, 2786, 3260, 569, 1746,
62 296, 2447, 1339, 1476, 3046, 56, 2240, 1333,
63 1426, 2094, 535, 2882, 2393, 2879, 1974, 821,
64 289, 331, 3253, 1756, 1197, 2304, 2277, 2055,
65 650, 1977, 2513, 632, 2865, 33, 1320, 1915,
66 2319, 1435, 807, 452, 1438, 2868, 1534, 2402,
67 2647, 2617, 1481, 648, 2474, 3110, 1227, 910,
68 17, 2761, 583, 2649, 1637, 723, 2288, 1100,
69 1409, 2662, 3281, 233, 756, 2156, 3015, 3050,
70 1703, 1651, 2789, 1789, 1847, 952, 1461, 2687,
71 939, 2308, 2437, 2388, 733, 2337, 268, 641,
72 1584, 2298, 2037, 3220, 375, 2549, 2090, 1645,
73 1063, 319, 2773, 757, 2099, 561, 2466, 2594,
74 2804, 1092, 403, 1026, 1143, 2150, 2775, 886,
75 1722, 1212, 1874, 1029, 2110, 2935, 885, 2154
76 };
77
82 private static readonly ushort[] nttTransformZeta2 =
83 {
84 17, q - 17, 2761, q - 2761, 583, q - 583, 2649, q - 2649,
85 1637, q - 1637, 723, q - 723, 2288, q - 2288, 1100, q - 1100,
86 1409, q - 1409, 2662, q - 2662, 3281, q - 3281, 233, q - 233,
87 756, q - 756, 2156, q - 2156, 3015, q - 3015, 3050, q - 3050,
88 1703, q - 1703, 1651, q - 1651, 2789, q - 2789, 1789, q - 1789,
89 1847, q - 1847, 952, q - 952, 1461, q - 1461, 2687, q - 2687,
90 939, q - 939, 2308, q - 2308, 2437, q - 2437, 2388, q - 2388,
91 733, q - 733, 2337, q - 2337, 268, q - 268, 641, q - 641,
92 1584, q - 1584, 2298, q - 2298, 2037, q - 2037, 3220, q - 3220,
93 375, q - 375, 2549, q - 2549, 2090, q - 2090, 1645, q - 1645,
94 1063, q - 1063, 319, q - 319, 2773, q - 2773, 757, q - 757,
95 2099, q - 2099, 561, q - 561, 2466, q - 2466, 2594, q - 2594,
96 2804, q - 2804, 1092, q - 1092, 403, q - 403, 1026, q - 1026,
97 1143, q - 1143, 2150, q - 2150, 2775, q - 2775, 886, q - 886,
98 1722, q - 1722, 1212, q - 1212, 1874, q - 1874, 1029, q - 1029,
99 2110, q - 2110, 2935, q - 2935, 885, q - 885, 2154, q - 2154
100 };
101
102 private const int n = 256;
103 private const ushort q = 3329;
104 private const ushort half_q_rounded = (q + 1) >> 1;
105
106 private readonly int k384;
107 private readonly int cipherTextLength;
108 private readonly byte k;
109 private readonly int η1;
110 private readonly int η2;
111 private readonly int dᵤ;
112 private readonly int dᵥ;
113
123 public ML_KEM(byte k, int η1, int η2, int dᵤ, int dᵥ)
124 {
125 this.k = k;
126 this.η1 = η1;
127 this.η2 = η2;
128 this.dᵤ = dᵤ;
129 this.dᵥ = dᵥ;
130
131 this.k384 = this.k * 384;
132 this.cipherTextLength = 32 * (this.dᵤ * this.k + this.dᵥ);
133 }
134
138 public override int PublicKeyLength => 384 * this.k + 32;
139
143 public override int PrivateKeyLength => 768 * this.k + 96;
144
152 public static byte[] PRF(byte[] Seed, byte Data, int η)
153 {
154 SHAKE256 HashFunction = new SHAKE256(8 * 64 * η);
155 int c = Seed.Length;
156 byte[] Bin = new byte[c + 1];
157 Buffer.BlockCopy(Seed, 0, Bin, 0, c);
158 Bin[c] = Data;
159 byte[] Result = HashFunction.ComputeVariable(Bin);
160 Clear(Bin);
161 return Result;
162 }
163
169 public static byte[] H(byte[] Data)
170 {
171 return new SHA3_256().ComputeVariable(Data);
172 }
173
179 public static byte[] J(byte[] Data)
180 {
181 return new SHAKE256(n).ComputeVariable(Data);
182 }
183
189 public static byte[] G(byte[] Data)
190 {
191 return new SHA3_512().ComputeVariable(Data);
192 }
193
200 public static byte[] XOF(byte[] Input, int OutputLength)
201 {
202 return new SHAKE128(OutputLength << 3).ComputeVariable(Input);
203 }
204
212 public static ushort Compress(ushort Value, int d)
213 {
214 if (d < 1 || d > 12)
215 throw new ArgumentOutOfRangeException(nameof(d), "d must be between 1 and 12.");
216
217 uint i = Value;
218 i <<= d + 1;
219 i /= q;
220 i++;
221 i >>= 1;
222 return (ushort)(i & ushortBitMask[d]);
223 }
224
231 public static void Compress(ushort[] Value, int d)
232 {
233 if (d < 1 || d > 12)
234 throw new ArgumentOutOfRangeException(nameof(d), "d must be between 1 and 12.");
235
236 int j, c = Value.Length;
237 uint i;
238
239 d++;
240
241 for (j = 0; j < c; j++)
242 {
243 i = Value[j];
244 i <<= d;
245 i /= q;
246 i++;
247 i >>= 1;
248 Value[j] = (ushort)i;
249 }
250 }
251
258 public static void Compress(ushort[][] Value, int d)
259 {
260 int i, c = Value.Length;
261
262 for (i = 0; i < c; i++)
263 Compress(Value[i], d);
264 }
265
273 public static ushort Decompress(ushort Value, int d)
274 {
275 if (d < 1 || d > 12)
276 throw new ArgumentOutOfRangeException(nameof(d), "d must be between 1 and 12.");
277
278 uint i = Value;
279 i *= q;
280 i >>= d - 1;
281 i++;
282 i >>= 1;
283 return (ushort)i;
284 }
285
292 public static void Decompress(ushort[] Value, int d)
293 {
294 if (d < 1 || d > 12)
295 throw new ArgumentOutOfRangeException(nameof(d), "d must be between 1 and 12.");
296
297 int j, c = Value.Length;
298 uint i;
299
300 for (j = 0; j < c; j++)
301 {
302 i = Value[j];
303 i *= q;
304 i >>= d - 1;
305 i++;
306 i >>= 1;
307 Value[j] = (ushort)i;
308 }
309 }
310
319 public static byte[] ByteEncode(ushort[] Values, int d)
320 {
321 int c = Values.Length;
322 int NrBits = c * d;
323 int NrBytes = (NrBits + 7) >> 3;
324 byte[] Result = new byte[NrBytes];
325
326 ByteEncode(Values, d, Result, 0);
327
328 return Result;
329 }
330
341 public static int ByteEncode(ushort[] Values, int d, byte[] Output, int Index)
342 {
343 if (d < 1 || d > 12)
344 throw new ArgumentOutOfRangeException(nameof(d), "d must be between 1 and 12.");
345
346 int i, c = Values.Length;
347 int BitOffset = 0;
348 int Index0 = Index;
349
350 for (i = 0; i < c; i++)
351 {
352 ushort Value = Values[i];
353
354 Value &= ushortBitMask[d];
355 Output[Index] |= (byte)(Value << BitOffset);
356 BitOffset += d;
357 while (BitOffset >= 8)
358 {
359 Index++;
360 BitOffset -= 8;
361
362 if (BitOffset > 0)
363 Output[Index] = (byte)(Value >> (d - BitOffset));
364 }
365 }
366
367 return Index - Index0;
368 }
369
380 public static int ByteEncode(ushort[][] Values, int d, byte[] Output, int Index)
381 {
382 int Pos = Index;
383 int i, c = Values.Length;
384
385 for (i = 0; i < c; i++)
386 {
387 int NrBytes = ByteEncode(Values[i], d, Output, Pos);
388 Pos += NrBytes;
389 }
390
391 return Pos - Index;
392 }
393
402 public static ushort[] ByteDecode(byte[] Data, int d)
403 {
404 return ByteDecode(Data, 0, Data.Length, d);
405 }
406
417 public static ushort[] ByteDecode(byte[] Data, int Offset, int Length, int d)
418 {
419 if (d < 1 || d > 12)
420 throw new ArgumentOutOfRangeException(nameof(d), "d must be between 1 and 12.");
421
422 int NrBits = Length << 3;
423 int NrIntegers = (NrBits + d - 1) / d;
424 ushort[] Result = new ushort[NrIntegers];
425 int Pos = 0;
426 int BitOffset = 0;
427 int BitsLeft;
428 int BitsToUse;
429
430 while (Length-- > 0)
431 {
432 byte b = Data[Offset++];
433 BitsLeft = 8;
434
435 while (BitsLeft > 0)
436 {
437 BitsToUse = d - BitOffset;
438 if (BitsToUse > BitsLeft)
439 BitsToUse = BitsLeft;
440
441 Result[Pos] |= (ushort)((b & ushortBitMask[BitsToUse]) << BitOffset);
442 BitOffset += BitsToUse;
443 BitsLeft -= BitsToUse;
444 b >>= BitsToUse;
445
446 if (BitOffset >= d)
447 {
448 Pos++;
449 BitOffset = 0;
450 }
451 }
452 }
453
454 return Result;
455 }
456
465 private static ushort[] SampleNTT(byte[] Seed)
466 {
468 Keccak1600.Context Context = HashFunction.Absorb(Seed);
469 ushort[] Result = new ushort[n];
470 int Pos = 0;
471
472 while (Pos < n)
473 {
474 byte[] C = Context.Squeeze(3);
475 ushort d1 = (ushort)(C[0] + ((C[1] & 15) << 8));
476 ushort d2 = (ushort)((C[1] >> 4) + (C[2] << 4));
477
478 if (d1 < q)
479 Result[Pos++] = d1;
480
481 if (d2 < q && Pos < n)
482 Result[Pos++] = d2;
483 }
484
485 return Result;
486 }
487
495 public static ushort[] SamplePolyCBD(int η)
496 {
497 if (η < 2 || η > 3)
498 throw new ArgumentOutOfRangeException(nameof(η), "η must be either 2 or 3.");
499
500 return SamplePolyCBD(CreateSeed(η << 6));
501 }
502
510 public static ushort[] SamplePolyCBD(byte[] Seed)
511 {
512 int c = Seed.Length;
513
514 if (c != 128 && c != 192)
515 throw new ArgumentOutOfRangeException(nameof(Seed), "Seed must be either 128 or 192 bytes.");
516
517 int η = c >> 6;
518 int i, j, k;
519 ushort x, y;
520 ushort dx, dy;
521 ushort[] Result = new ushort[n];
522
523 for (i = 0; i < n; i++)
524 {
525 for (j = x = y = 0; j < η; j++)
526 {
527 k = 2 * i * η + j;
528 dx = (Seed[k >> 3] & (1 << (k & 7))) != 0 ? (ushort)1 : (ushort)0;
529 x += dx; // To avoid different CPU instructions to execute based on if bit is 0 or 1.
530
531 k += η;
532 dy = (Seed[k >> 3] & (1 << (k & 7))) != 0 ? (ushort)1 : (ushort)0;
533 y += dy; // To avoid different CPU instructions to execute based on if bit is 0 or 1.
534 }
535
536 if (x < y)
537 Result[i] = (ushort)(q + x - y);
538 else
539 Result[i] = (ushort)(x - y);
540 }
541
542 return Result;
543 }
544
550 public static void NTT(ushort[] f)
551 {
552 if (f.Length != n)
553 throw new ArgumentException("Polynomial must have " + n + " coefficients.", nameof(f));
554
555 int i = 1;
556 int j;
557 int Len;
558 int Start;
559 ushort ζ;
560 ushort t;
561
562 for (Len = n >> 1; Len >= 2; Len >>= 1)
563 {
564 for (Start = 0; Start < n; Start += Len << 1)
565 {
566 ζ = nttTransformZeta[i++];
567
568 for (j = Start; j < Start + Len; j++)
569 {
570 t = (ushort)(ζ * f[j + Len] % q);
571 f[j + Len] = (ushort)((f[j] + q - t) % q);
572 f[j] = (ushort)((f[j] + t) % q);
573 }
574 }
575 }
576 }
577
582 public static void NTT(ushort[][] f)
583 {
584 int i, c = f.Length;
585
586 for (i = 0; i < c; i++)
587 NTT(f[i]);
588 }
589
595 public static void InverseNTT(ushort[] f)
596 {
597 if (f.Length != n)
598 throw new ArgumentException("Polynomial must have " + n + " coefficients.", nameof(f));
599
600 int i = 127;
601 int j;
602 int Len;
603 int Len2;
604 int StartLen;
605 int Start;
606 ushort ζ;
607 ushort t;
608
609 for (Len = 2; Len <= 128; Len <<= 1)
610 {
611 Len2 = Len << 1;
612
613 for (Start = 0; Start < n; Start += Len2)
614 {
615 ζ = nttTransformZeta[i--];
616
617 StartLen = Start + Len;
618 for (j = Start; j < StartLen; j++)
619 {
620 t = f[j];
621 f[j] = (ushort)((t + f[j + Len]) % q);
622 f[j + Len] = (ushort)(ζ * (f[j + Len] + q - t) % q);
623 }
624 }
625 }
626
627 for (i = 0; i < n; i++)
628 f[i] = (ushort)(3303 * f[i] % q); // 3303 = 128^-1 mod q
629 }
630
635 public static void InverseNTT(ushort[][] f)
636 {
637 int i, c = f.Length;
638
639 for (i = 0; i < c; i++)
640 InverseNTT(f[i]);
641 }
642
651 public static ushort[] MultiplyNTTs(ushort[] f, ushort[] g)
652 {
653 ushort[] Result = new ushort[n];
654 MultiplyNTTsAndAdd(f, g, Result);
655 return Result;
656 }
657
667 public static void MultiplyNTTsAndAdd(ushort[] f, ushort[] g, ushort[] Result)
668 {
669 if (f.Length != n || g.Length != n)
670 throw new ArgumentException("Polynomials must have " + n + " coefficients.", nameof(f));
671
672 int i;
673 ushort a0, a1, b0, b1, γ;
674
675 for (i = 0; i < n; i += 2)
676 {
677 a0 = f[i];
678 a1 = f[i + 1];
679 b0 = g[i];
680 b1 = g[i + 1];
681 γ = nttTransformZeta2[i >> 1];
682
683 Result[i] = (ushort)((Result[i] + a0 * b0 + γ * a1 % q * b1) % q); // Three multiplications without modulus can generate integer overflow.
684 Result[i + 1] = (ushort)((Result[i + 1] + a0 * b1 + a1 * b0) % q);
685 }
686 }
687
693 public static void AddTo(ushort[] f, ushort[] g)
694 {
695 int i;
696
697 for (i = 0; i < n; i++)
698 f[i] = (ushort)((f[i] + g[i]) % q);
699 }
700
706 public static void AddTo(ushort[][] f, ushort[][] g)
707 {
708 int i, c = f.Length;
709 if (g.Length != c)
710 throw new ArgumentException("Vectors must have the same number of polynomials.", nameof(g));
711
712 for (i = 0; i < c; i++)
713 AddTo(f[i], g[i]);
714 }
715
720 public static void Negate(ushort[] f)
721 {
722 int i, c = f.Length;
723
724 for (i = 0; i < c; i++)
725 f[i] = (ushort)((q - f[i]) % q); // To avoid different CPU instructions to execute based on if bit is 0 or 1.
726 }
727
735 public static ushort[] DotProductNTT(ushort[][] v1, ushort[][] v2)
736 {
737 int i, c = v1.Length;
738
739 if (v2.Length != c)
740 throw new ArgumentException("Vectors must have the same number of polynomials.", nameof(v2));
741
742 ushort[] Result = new ushort[n];
743
744 for (i = 0; i < c; i++)
745 MultiplyNTTsAndAdd(v1[i], v2[i], Result);
746
747 return Result;
748 }
749
757 {
758 byte[] Seed = CreateSeed();
759
760 K_PKE_Keys Result = this.K_PKE_KeyGen(Seed);
761 Clear(Seed);
762
763 return Result;
764 }
765
773 public K_PKE_Keys K_PKE_KeyGen(byte[] d)
774 {
775 if (d.Length != 32)
776 throw new ArgumentException("Seed must be 32 bytes long.", nameof(d));
777
778 byte[] Temp = new byte[33];
779 Buffer.BlockCopy(d, 0, Temp, 0, 32);
780 Temp[32] = this.k;
781 byte[] Bin = G(Temp);
782 Clear(Temp);
783
784 byte[] ρ = new byte[32];
785 Buffer.BlockCopy(Bin, 0, ρ, 0, 32);
786
787 byte[] σ = new byte[32];
788 Buffer.BlockCopy(Bin, 32, σ, 0, 32);
789 Clear(Bin);
790
791 byte N = 0;
792 int i, j;
793 ushort[,][] Â = new ushort[this.k, this.k][];
794 ushort[][] s = new ushort[this.k][];
795 ushort[][] e = new ushort[this.k][];
796
797 byte[] B = new byte[34];
798 Buffer.BlockCopy(ρ, 0, B, 0, 32);
799
800 for (i = 0; i < this.k; i++)
801 {
802 B[33] = (byte)i;
803
804 for (j = 0; j < this.k; j++)
805 {
806 B[32] = (byte)j;
807 Â[i, j] = SampleNTT(B);
808 }
809 }
810
811 Clear(B);
812
813 for (i = 0; i < this.k; i++)
814 s[i] = SamplePolyCBD(PRF(σ, N++, this.η1));
815
816 for (i = 0; i < this.k; i++)
817 e[i] = SamplePolyCBD(PRF(σ, N++, this.η1));
818
819 NTT(s);
820 NTT(e);
821
822 ushort[][] t = new ushort[this.k][];
823 ushort[] f;
824
825 for (i = 0; i < this.k; i++)
826 {
827 t[i] = f = (ushort[])e[i].Clone();
828
829 for (j = 0; j < this.k; j++)
830 MultiplyNTTsAndAdd(Â[i, j], s[j], f);
831 }
832
833 byte[] EncryptionKey = new byte[32 + this.k384];
834 byte[] DecryptionKey = new byte[this.k384];
835 int Pos = 0;
836
837 for (i = 0; i < this.k; i++)
838 {
839 ByteEncode(t[i], 12, EncryptionKey, Pos);
840 ByteEncode(s[i], 12, DecryptionKey, Pos);
841 Pos += 384;
842 }
843
844 Buffer.BlockCopy(ρ, 0, EncryptionKey, Pos, 32);
845
846 Clear(ρ);
847 Clear(σ);
848 Clear(s);
849 Clear(t);
850 Clear(e);
851
852 return new K_PKE_Keys(Â, EncryptionKey, DecryptionKey);
853 }
854
862 public byte[] K_PKE_Encrypt(byte[] EncryptionKey, byte[] Message)
863 {
864 return this.K_PKE_Encrypt(ML_KEM_Keys.FromEncapsulationKey(EncryptionKey), Message);
865 }
866
874 public byte[] K_PKE_Encrypt(ML_KEM_Keys Keys, byte[] Message)
875 {
876 byte[] Seed = CreateSeed();
877
878 byte[] Result = this.K_PKE_Encrypt(Keys, Message, Seed);
879 Clear(Seed);
880
881 return Result;
882 }
883
892 public byte[] K_PKE_Encrypt(byte[] EncryptionKey, byte[] Message, byte[] Seed)
893 {
894 return this.K_PKE_Encrypt(ML_KEM_Keys.FromEncapsulationKey(EncryptionKey), Message, Seed);
895 }
896
905 public byte[] K_PKE_Encrypt(ML_KEM_Keys Keys, byte[] Message, byte[] Seed)
906 {
907 if (Keys is null)
908 throw new ArgumentNullException(nameof(Keys), "Encryption key cannot be null.");
909
910 if (Keys.EncapsulationKey.Length != this.k384 + 32)
911 throw new ArgumentException("Encryption key must be 384k+32 bytes long.", nameof(Keys));
912
913 if (Message.Length != 32)
914 throw new ArgumentException("Message must be 32 bytes long.", nameof(Message));
915
916 ushort[][] t = new ushort[this.k][];
917 byte[] ρ = new byte[32];
918 int Pos;
919 int i, j;
920 byte N = 0;
921 byte k;
922
923 for (i = Pos = 0; i < this.k; i++)
924 {
925 t[i] = ByteDecode(Keys.EncapsulationKey, Pos, 384, 12);
926 Pos += 384;
927 }
928
929 Buffer.BlockCopy(Keys.EncapsulationKey, Pos, ρ, 0, 32);
930
931 ushort[][] y = new ushort[this.k][];
932 ushort[][] e1 = new ushort[this.k][];
933 ushort[] e2;
934 ushort[,][] Â = Keys.Â;
935
936 if (Â is null)
937 {
938 Keys. =  = new ushort[this.k, this.k][];
939
940 byte[] B = new byte[34];
941 Buffer.BlockCopy(ρ, 0, B, 0, 32);
942
943 for (i = 0; i < this.k; i++)
944 {
945 B[33] = (byte)i;
946
947 for (j = 0; j < this.k; j++)
948 {
949 B[32] = (byte)j;
950 Â[i, j] = SampleNTT(B);
951 }
952 }
953
954 Clear(B);
955 }
956 else if (Â.GetLength(0) != this.k || Â.GetLength(1) != this.k)
957 throw new ArgumentException("Matrix  must be " + this.k + "x" + this.k + ".", nameof(Â));
958
959 for (i = 0; i < this.k; i++)
960 y[i] = SamplePolyCBD(PRF(Seed, N++, this.η1));
961
962 for (i = 0; i < this.k; i++)
963 e1[i] = SamplePolyCBD(PRF(Seed, N++, this.η2));
964
965 e2 = SamplePolyCBD(PRF(Seed, N++, this.η2));
966
967 NTT(y);
968
969 ushort[][] u = new ushort[this.k][];
970 ushort[] f;
971
972 for (i = 0; i < this.k; i++)
973 {
974 u[i] = f = new ushort[n];
975
976 for (j = 0; j < this.k; j++)
977 MultiplyNTTsAndAdd(Â[j, i], y[j], f);
978 }
979
980 InverseNTT(u);
981 AddTo(u, e1);
982
983 ushort[] v = DotProductNTT(t, y);
984
985 InverseNTT(v);
986
987 ushort[] μ = new ushort[n];
988
989 for (i = j = 0, k = 1; i < n; i++)
990 {
991 μ[i] = (Message[j] & k) != 0 ? half_q_rounded : (ushort)0;
992
993 k <<= 1;
994 if (k == 0)
995 {
996 k = 1;
997 j++;
998 }
999 }
1000
1001 AddTo(v, e2);
1002 AddTo(v, μ);
1003
1004 int dᵤk32 = this.dᵤ * this.k << 5;
1005 int dᵥ32 = this.dᵥ << 5;
1006 byte[] CipherText = new byte[dᵤk32 + dᵥ32];
1007
1008 Compress(u, this.dᵤ);
1009 ByteEncode(u, this.dᵤ, CipherText, 0);
1010
1011 Compress(v, this.dᵥ);
1012 ByteEncode(v, this.dᵥ, CipherText, dᵤk32);
1013
1014 return CipherText;
1015 }
1016
1024 public byte[] K_PKE_Decrypt(byte[] DecryptionKey, byte[] CipherText)
1025 {
1026 if (DecryptionKey is null)
1027 throw new ArgumentNullException(nameof(DecryptionKey), "Decryption key cannot be null.");
1028
1029 if (DecryptionKey.Length != this.k384)
1030 throw new ArgumentException("Decryption key must be 384k bytes long.", nameof(DecryptionKey));
1031
1032 int dᵤ32 = this.dᵤ << 5;
1033 int dᵤk32 = dᵤ32 * this.k;
1034 int dᵥ32 = this.dᵥ << 5;
1035
1036 if (CipherText.Length != dᵤk32 + dᵥ32)
1037 throw new ArgumentException("Message must be " + (dᵤk32 + dᵥ32) + " bytes long.", nameof(CipherText));
1038
1039 ushort[][] u = new ushort[this.k][];
1040 ushort[] v;
1041 int Pos;
1042 int i;
1043
1044 for (i = Pos = 0; i < this.k; i++)
1045 {
1046 u[i] = ByteDecode(CipherText, Pos, dᵤ32, this.dᵤ);
1047 Decompress(u[i], this.dᵤ);
1048 Pos += dᵤ32;
1049 }
1050
1051 v = ByteDecode(CipherText, Pos, dᵥ32, this.dᵥ);
1052 Decompress(v, this.dᵥ);
1053
1054 ushort[][] s = new ushort[this.k][];
1055
1056 for (i = Pos = 0; i < this.k; i++)
1057 {
1058 s[i] = ByteDecode(DecryptionKey, Pos, 384, 12);
1059 Pos += 384;
1060 }
1061
1062 NTT(u);
1063
1064 ushort[] w = DotProductNTT(s, u);
1065
1066 InverseNTT(w);
1067 Negate(w);
1068 AddTo(w, v);
1069
1070 Compress(w, 1);
1071 return ByteEncode(w, 1);
1072 }
1073
1081 {
1082 return this.KeyGen(false);
1083 }
1084
1091 public ML_KEM_Keys KeyGen(bool ReturnSeed)
1092 {
1093 byte[] d = CreateSeed();
1094 byte[] z = CreateSeed();
1095
1096 ML_KEM_Keys Result = this.KeyGen_Internal(d, z, ReturnSeed);
1097
1098 if (!ReturnSeed)
1099 {
1100 Clear(d);
1101 Clear(z);
1102 }
1103
1104 return Result;
1105 }
1106
1115 public ML_KEM_Keys KeyGen_Internal(byte[] d, byte[] z)
1116 {
1117 return this.KeyGen_Internal(d, z, false);
1118 }
1119
1129 public ML_KEM_Keys KeyGen_Internal(byte[] d, byte[] z, bool ReturnSeed)
1130 {
1131 if (z.Length != 32)
1132 throw new ArgumentException("Seed must be 32 bytes long.", nameof(z));
1133
1134 K_PKE_Keys Keys = this.K_PKE_KeyGen(d);
1135 byte[] DecryptionKey = new byte[768 * this.k + 96];
1136 int Pos;
1137
1138 Buffer.BlockCopy(Keys.DecryptionKey, 0, DecryptionKey, 0, Pos = this.k384);
1139 Buffer.BlockCopy(Keys.EncryptionKey, 0, DecryptionKey, Pos, Pos + 32);
1140 Pos += Pos + 32;
1141
1142 byte[] Bin = H(Keys.EncryptionKey);
1143 Buffer.BlockCopy(Bin, 0, DecryptionKey, Pos, 32);
1144 Pos += 32;
1145
1146 Buffer.BlockCopy(z, 0, DecryptionKey, Pos, 32);
1147
1148 if (!ReturnSeed)
1149 return new ML_KEM_Keys(Keys, DecryptionKey, null);
1150
1151 byte[] Seed = new byte[64];
1152
1153 Buffer.BlockCopy(d, 0, Seed, 0, 32);
1154 Buffer.BlockCopy(z, 0, Seed, 32, 32);
1155
1156 return new ML_KEM_Keys(Keys, DecryptionKey, Seed);
1157 }
1158
1165 public ML_KEM_Keys KeyGen_FromSeed(byte[] Seed, bool ReturnSeed)
1166 {
1167 if (Seed is null)
1168 throw new ArgumentNullException(nameof(Seed));
1169
1170 if (Seed.Length != 64)
1171 throw new ArgumentException("Seed must be 64 bytes long.", nameof(Seed));
1172
1173 byte[] d = new byte[32];
1174 byte[] z = new byte[32];
1175
1176 Buffer.BlockCopy(Seed, 0, d, 0, 32);
1177 Buffer.BlockCopy(Seed, 32, z, 0, 32);
1178
1179 return this.KeyGen_Internal(d, z, ReturnSeed);
1180 }
1181
1188 public ML_KEM_Encapsulation Encapsulate(byte[] EncapsulationKey)
1189 {
1190 return this.Encapsulate(ML_KEM_Keys.FromEncapsulationKey(EncapsulationKey));
1191 }
1192
1200 {
1201 if (Keys.EncapsulationKey.Length < 384)
1202 throw new ArgumentException("Encapsulation key length mismatch.", nameof(Keys));
1203
1204 ushort[] x = ByteDecode(Keys.EncapsulationKey, 0, 384, 12);
1205 int i, c = x.Length;
1206
1207 for (i = 0; i < c; i++)
1208 {
1209 if (x[i] >= q)
1210 throw new ArgumentException("Invalid encapsulation key.", nameof(Keys));
1211 }
1212
1213 byte[] m = CreateSeed();
1214
1215 ML_KEM_Encapsulation Result = this.Encapsulate_Internal(Keys, m);
1216 Clear(m);
1217
1218 return Result;
1219 }
1220
1228 public ML_KEM_Encapsulation Encapsulate_Internal(byte[] EncapsulationKey, byte[] m)
1229 {
1230 return this.Encapsulate_Internal(ML_KEM_Keys.FromEncapsulationKey(EncapsulationKey), m);
1231 }
1232
1241 {
1242 if (Keys.EncapsulationKey.Length != this.k384 + 32)
1243 throw new ArgumentException("Encapsulation key length mismatch.", nameof(Keys));
1244
1245 if (m.Length != 32)
1246 throw new ArgumentException("Message length mismatch.", nameof(m));
1247
1248 byte[] Bin = new byte[64];
1249
1250 Buffer.BlockCopy(m, 0, Bin, 0, 32);
1251 Buffer.BlockCopy(H(Keys.EncapsulationKey), 0, Bin, 32, 32);
1252
1253 byte[] Bin2 = G(Bin);
1254 Clear(Bin);
1255
1256 byte[] K = new byte[32];
1257 Buffer.BlockCopy(Bin2, 0, K, 0, 32);
1258
1259 byte[] r = new byte[32];
1260 Buffer.BlockCopy(Bin2, 32, r, 0, 32);
1261 Clear(Bin2);
1262
1263 byte[] c = this.K_PKE_Encrypt(Keys, m, r);
1264 Clear(r);
1265
1266 return new ML_KEM_Encapsulation(K, c);
1267 }
1268
1276 public byte[] Decapsulate(byte[] DecapsulationKey, byte[] c)
1277 {
1278 if (DecapsulationKey.Length != 768 * this.k + 96)
1279 throw new ArgumentException("Decapsulation key length mismatch.", nameof(DecapsulationKey));
1280
1281 byte[] Bin = new byte[this.k384 + 32];
1282 Buffer.BlockCopy(DecapsulationKey, this.k384, Bin, 0, this.k384 + 32);
1283
1284 byte[] Test = H(Bin);
1285 Clear(Bin);
1286
1287 for (int i = 0, j = (this.k384 << 1) + 32; i < 32; i++, j++)
1288 {
1289 if (Test[i] != DecapsulationKey[j])
1290 throw new ArgumentException("Invalid decapsulation key.", nameof(DecapsulationKey));
1291 }
1292
1293 return this.Decapsulate_Internal(DecapsulationKey, c);
1294 }
1295
1299 public int CipherTextLength => this.cipherTextLength;
1300
1308 public byte[] Decapsulate_Internal(byte[] DecapsulationKey, byte[] c)
1309 {
1310 if (DecapsulationKey.Length != 768 * this.k + 96)
1311 throw new ArgumentException("Decapsulation key length mismatch.", nameof(DecapsulationKey));
1312
1313 if (c.Length != this.cipherTextLength)
1314 throw new ArgumentException("Cipher text length mismatch.", nameof(c));
1315
1316 int Pos;
1317 byte[] DecryptionKey = new byte[Pos = this.k384];
1318 Buffer.BlockCopy(DecapsulationKey, 0, DecryptionKey, 0, Pos);
1319
1320 int Len;
1321 byte[] EncryptionKey = new byte[Len = (768 - 384) * this.k + 32];
1322 Buffer.BlockCopy(DecapsulationKey, Pos, EncryptionKey, 0, Len);
1323 Pos += Len;
1324
1325 byte[] h = new byte[32];
1326 Buffer.BlockCopy(DecapsulationKey, Pos, h, 0, 32);
1327
1328 byte[] m = this.K_PKE_Decrypt(DecryptionKey, c);
1329
1330 byte[] Bin = new byte[64];
1331 Buffer.BlockCopy(m, 0, Bin, 0, 32);
1332 Buffer.BlockCopy(h, 0, Bin, 32, 32);
1333
1334 byte[] Bin2 = G(Bin);
1335
1336 byte[] K = new byte[32];
1337 Buffer.BlockCopy(Bin2, 0, K, 0, 32);
1338
1339 byte[] r = new byte[32];
1340 Buffer.BlockCopy(Bin2, 32, r, 0, 32);
1341
1342 Clear(Bin2);
1343
1344 // Fix of the decapsulation method to comply with FIPS-203 (Algorithm 18) and
1345 // to avoid a weakened IND-CCA2 attack, and a constant-time comparison check,
1346 // provided and republished, with permission, by Frank von der Loos.
1347
1348 byte[] zc = new byte[32 + c.Length]; // z ‖ full c (32 + cipherTextLength)
1349 Buffer.BlockCopy(DecapsulationKey, Pos + 32, zc, 0, 32); // z
1350 Buffer.BlockCopy(c, 0, zc, 32, c.Length); // full c
1351 byte[] K2 = J(zc);
1352 Clear(zc);
1353
1354 byte[] c2 = this.K_PKE_Encrypt(EncryptionKey, m, r);
1355 Clear(m);
1356 Clear(r);
1357
1358 int diff = 0;
1359
1360 for (int i = 0; i < c.Length; i++) // full ciphertext, no early break, time constant
1361 diff |= c[i] ^ c2[i];
1362
1363 Clear(c2);
1364
1365 // diff == 0 -> ciphertext valid -> K (real secret)
1366 // diff != 0 -> implicit rejection -> K2 (pseudo-random)
1367 byte mask = (byte)((diff | -diff) >> 31); // 0x00 for equal, 0xFF is different
1368
1369 byte[] Result = new byte[32];
1370 for (int i = 0; i < 32; i++)
1371 Result[i] = (byte)((K[i] & ~mask) | (K2[i] & mask));
1372
1373 Clear(K);
1374 Clear(K2);
1375
1376 return Result;
1377 }
1378 }
1379}
K-PKE encryption and decryption keys, as defined in §5.1.
Definition: K_PKE_Keys.cs:7
byte[] DecryptionKey
Encoded decryption key, as defined in §5.1.
Definition: K_PKE_Keys.cs:34
byte[] EncryptionKey
Encoded encryption key, as defined in §5.1.
Definition: K_PKE_Keys.cs:29
Methods common to ML algorithms.
Definition: ML_Common.cs:10
static void Clear(byte[] Bin)
Clears a byte array.
Definition: ML_Common.cs:53
static readonly ushort[] ushortBitMask
Bit masks corresponding to mod 2^d arithmetic, where d is the index of the mask in the array.
Definition: ML_Common.cs:83
static byte[] CreateSeed()
Creates a random seed value.
Definition: ML_Common.cs:44
ML_KEM encapsulation keys, as defined in §7.2.
ML_KEM encryption and decryption keys, as defined in §6.1.
Definition: ML_KEM_Keys.cs:7
byte[] EncapsulationKey
Encoded encapsulation key, as defined in §6.1.
Definition: ML_KEM_Keys.cs:46
static ML_KEM_Keys FromEncapsulationKey(byte[] EncapsulationKey)
Creates a key object instance from an encapsulation key.
Definition: ML_KEM_Keys.cs:68
ushort[,][] Â
Matrix of encryption keys, as defined in §5.1.
Definition: ML_KEM_Keys.cs:38
Implements the ML-KEM algorithm for post-quantum cryptography, as defined in NIST FIPS 203: https://n...
Definition: ML_KEM.cs:11
ML_KEM_Encapsulation Encapsulate(byte[] EncapsulationKey)
The method (Algorithm 17) accepts an encapsulation key and a random byte array as input and outputs a...
Definition: ML_KEM.cs:1188
override int PublicKeyLength
Length of the public key.
Definition: ML_KEM.cs:138
byte[] K_PKE_Encrypt(byte[] EncryptionKey, byte[] Message)
Uses the encryption key to encrypt a plaintext message using the given randomness and algorithm 14 (K...
Definition: ML_KEM.cs:862
static ushort[] MultiplyNTTs(ushort[] f, ushort[] g)
Computes the product (in the ring 𝑇𝑞) of two NTT representations. (Algorithm 11 in §4....
Definition: ML_KEM.cs:651
static byte[] ByteEncode(ushort[] Values, int d)
Encodes an array of integers (mod 2^d) into a byte array, as defined by Algorithm 5 in §4....
Definition: ML_KEM.cs:319
static readonly ML_KEM ML_KEM_512
Model parameters for a required RBG strength 128 (cryptographic security strength),...
Definition: ML_KEM.cs:16
ML_KEM(byte k, int η1, int η2, int dᵤ, int dᵥ)
Implements the ML-KEM algorithm for post-quantum cryptography, as defined in NIST FIPS 203: https://n...
Definition: ML_KEM.cs:123
ML_KEM_Encapsulation Encapsulate_Internal(byte[] EncapsulationKey, byte[] m)
The method (Algorithm 17) accepts an encapsulation key and a random byte array as input and outputs a...
Definition: ML_KEM.cs:1228
static void Compress(ushort[][] Value, int d)
Canonical extension of Compress function, as defined in §4.2.1.
Definition: ML_KEM.cs:258
static int ByteEncode(ushort[][] Values, int d, byte[] Output, int Index)
Encodes an array of vectors of integers (mod 2^d) into a byte array. Canonical extension of ByteEncod...
Definition: ML_KEM.cs:380
static ushort Decompress(ushort Value, int d)
Decompress function, as defined in §4.2.1.
Definition: ML_KEM.cs:273
int CipherTextLength
Expected cipher text length for the decapsulation method.
Definition: ML_KEM.cs:1299
K_PKE_Keys K_PKE_KeyGen()
Uses randomness to generate an encryption key and a corresponding decryption key. (Algorithm 13 K-PKE...
Definition: ML_KEM.cs:756
static void InverseNTT(ushort[] f)
Computes the NTT^-1 representation f of the given polynomial f̂ ∈ 𝑇𝑞. (Algorithm 10 in §4....
Definition: ML_KEM.cs:595
static void MultiplyNTTsAndAdd(ushort[] f, ushort[] g, ushort[] Result)
Computes the product (in the ring 𝑇𝑞) of two NTT representations (Algorithm 11 in §4....
Definition: ML_KEM.cs:667
ML_KEM_Encapsulation Encapsulate(ML_KEM_Keys Keys)
The method (Algorithm 17) accepts an encapsulation key and a random byte array as input and outputs a...
Definition: ML_KEM.cs:1199
static ushort Compress(ushort Value, int d)
Compress function, as defined in §4.2.1.
Definition: ML_KEM.cs:212
ML_KEM_Keys KeyGen_Internal(byte[] d, byte[] z, bool ReturnSeed)
Uses randomness to generate an encryption key and a corresponding decryption key. (Algorithm 16 ML-KE...
Definition: ML_KEM.cs:1129
static ushort[] DotProductNTT(ushort[][] v1, ushort[][] v2)
Computes the dot product of two vectors of polynomials in 𝑇𝑞.
Definition: ML_KEM.cs:735
override int PrivateKeyLength
Length of the private key.
Definition: ML_KEM.cs:143
static int ByteEncode(ushort[] Values, int d, byte[] Output, int Index)
Encodes an array of integers (mod 2^d) into a byte array, as defined by Algorithm 5 in §4....
Definition: ML_KEM.cs:341
static ushort[] SamplePolyCBD(int η)
The algorithm SamplePolyCBD (Algorithm 8, §4.2.1) samples the coefficient array of a polynomial 𝑓 ∈ 𝑅...
Definition: ML_KEM.cs:495
byte[] K_PKE_Encrypt(byte[] EncryptionKey, byte[] Message, byte[] Seed)
Uses the encryption key to encrypt a plaintext message using the given randomness and algorithm 14 (K...
Definition: ML_KEM.cs:892
byte[] Decapsulate(byte[] DecapsulationKey, byte[] c)
Uses the decapsulation key to produce a shared secret key from a ciphertext. Algorithm 21 ML-KEM....
Definition: ML_KEM.cs:1276
byte[] K_PKE_Encrypt(ML_KEM_Keys Keys, byte[] Message)
Uses the encryption key to encrypt a plaintext message using the given randomness and algorithm 14 (K...
Definition: ML_KEM.cs:874
static void InverseNTT(ushort[][] f)
Canonical extension of InverseNTT(ushort[]).
Definition: ML_KEM.cs:635
static readonly ML_KEM ML_KEM_768
Model parameters for a required RBG strength 192 (cryptographic security strength),...
Definition: ML_KEM.cs:22
ML_KEM_Keys KeyGen(bool ReturnSeed)
Generates an encapsulation key and a corresponding decapsulation key. (Algorithm 19 ML-KEM....
Definition: ML_KEM.cs:1091
byte[] K_PKE_Decrypt(byte[] DecryptionKey, byte[] CipherText)
Uses the decryption key to decrypt a ciphertext message using algorithm 15 (K-PKE....
Definition: ML_KEM.cs:1024
static void Decompress(ushort[] Value, int d)
Canonical extension of Decompress function, as defined in §4.2.1.
Definition: ML_KEM.cs:292
byte[] Decapsulate_Internal(byte[] DecapsulationKey, byte[] c)
The method (Algorithm 18) accepts a decapsulation key and a cipher text as input, does not use any ra...
Definition: ML_KEM.cs:1308
static ushort[] ByteDecode(byte[] Data, int Offset, int Length, int d)
Decodes an array of integers (mod 2^d) from a byte array, as defined by Algorithm 6 in §4....
Definition: ML_KEM.cs:417
static byte[] XOF(byte[] Input, int OutputLength)
eXtendable-Output Function (XOF), as defined in §4.1.
Definition: ML_KEM.cs:200
ML_KEM_Encapsulation Encapsulate_Internal(ML_KEM_Keys Keys, byte[] m)
The method (Algorithm 17) accepts an encapsulation key and a random byte array as input and outputs a...
Definition: ML_KEM.cs:1240
static byte[] G(byte[] Data)
Hash function G, as defined in §4.1.
Definition: ML_KEM.cs:189
static void NTT(ushort[] f)
Computes the NTT representation f̂ of the given polynomial f ∈ 𝑅𝑞. (Algorithm 9 in §4....
Definition: ML_KEM.cs:550
static byte[] J(byte[] Data)
Hash function J, as defined in §4.1.
Definition: ML_KEM.cs:179
static ML_KEM GetModel(string Name)
Gets a model by name, as defined in §8.
Definition: ML_KEM.cs:36
static byte[] PRF(byte[] Seed, byte Data, int η)
Pseudorandom function (PRF), as defined in §4.1.
Definition: ML_KEM.cs:152
byte[] K_PKE_Encrypt(ML_KEM_Keys Keys, byte[] Message, byte[] Seed)
Uses the encryption key to encrypt a plaintext message using the given randomness and algorithm 14 (K...
Definition: ML_KEM.cs:905
static byte[] H(byte[] Data)
Hash function H, as defined in §4.1.
Definition: ML_KEM.cs:169
static void AddTo(ushort[] f, ushort[] g)
Adds g to f .
Definition: ML_KEM.cs:693
K_PKE_Keys K_PKE_KeyGen(byte[] d)
Uses randomness to generate an encryption key and a corresponding decryption key. (Algorithm 13 K-PKE...
Definition: ML_KEM.cs:773
static void Compress(ushort[] Value, int d)
Canonical extension of Compress function, as defined in §4.2.1.
Definition: ML_KEM.cs:231
static void NTT(ushort[][] f)
Canonical extension of NTT(ushort[]).
Definition: ML_KEM.cs:582
static void Negate(ushort[] f)
Negates a polynomial in 𝑅𝑞.
Definition: ML_KEM.cs:720
ML_KEM_Keys KeyGen_Internal(byte[] d, byte[] z)
Uses randomness to generate an encryption key and a corresponding decryption key. (Algorithm 16 ML-KE...
Definition: ML_KEM.cs:1115
ML_KEM_Keys KeyGen_FromSeed(byte[] Seed, bool ReturnSeed)
Creates a key object instance from a seed.
Definition: ML_KEM.cs:1165
static ushort[] ByteDecode(byte[] Data, int d)
Decodes an array of integers (mod 2^d) from a byte array, as defined by Algorithm 6 in §4....
Definition: ML_KEM.cs:402
static readonly ML_KEM ML_KEM_1024
Model parameters for a required RBG strength 256 (cryptographic security strength),...
Definition: ML_KEM.cs:28
static ushort[] SamplePolyCBD(byte[] Seed)
The algorithm SamplePolyCBD (Algorithm 8, §4.2.1) samples the coefficient array of a polynomial 𝑓 ∈ 𝑅...
Definition: ML_KEM.cs:510
ML_KEM_Keys KeyGen()
Generates an encapsulation key and a corresponding decapsulation key. (Algorithm 19 ML-KEM....
Definition: ML_KEM.cs:1080
static void AddTo(ushort[][] f, ushort[][] g)
Adds vector g to vector f .
Definition: ML_KEM.cs:706
Hash digest computation context.
Definition: Keccak1600.cs:558
byte[] Squeeze(int NrBytes)
Calculates another NrBytes number of bytes of the digest.
Definition: Keccak1600.cs:581
byte[] ComputeVariable(byte[] N)
Computes the SPONGE function, as defined in section 4 of NIST FIPS 202.
Definition: Keccak1600.cs:408
Implements the SHA3-256 hash function, as defined in section 6.1 in the NIST FIPS 202: https://nvlpub...
Definition: SHA3_256.cs:9
Implements the SHA3-512 hash function, as defined in section 6.1 in the NIST FIPS 202: https://nvlpub...
Definition: SHA3_512.cs:9
Implements the SHA3 SHAKE128 extendable-output functions, as defined in section 6....
Definition: SHAKE128.cs:9
Implements the SHA3 SHAKE256 extendable-output functions, as defined in section 6....
Definition: SHAKE256.cs:9
HashFunction
Hash method enumeration.
Definition: Hashes.cs:26