Repository navigation
Expand file tree
/
Copy pathPKeyDecoder2005.cs
More file actions
1542 lines (1397 loc) · 69.8 KB
/
Copy pathPKeyDecoder2005.cs
File metadata and controls
1542 lines (1397 loc) · 69.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace PidKeyPlugIn
{
// -------------------------------------------------------------------------
// 基域 GF(p):直接用 BigInteger,只做模约简
// -------------------------------------------------------------------------
/// <summary>
/// 公钥基域不一致。只在「多组公钥的 p 不同」时抛出 —— 见 <see cref="Gf.P"/>。
/// 单独一个类型是为了让 TryPubKey 能精确地把它放行,不跟公钥解析失败混在一起。
/// </summary>
public class BaseFieldMismatchException : InvalidOperationException
{
public BaseFieldMismatchException(string message) : base(message) { }
}
public static class Gf
{
static BigInteger _p;
static readonly object _pGate = new object();
/// <summary>
/// 基域素数 p,由 PubKeyParser.Parse 写入。
///
/// ★ 带一致性校验:首次写入生效;之后再写**必须相同**,否则抛
/// <see cref="BaseFieldMismatchException"/>。
///
/// 为什么需要:Parse 会写这个静态字段,而 Parallel.ForEach 里每个线程
/// 都会 Parse。如果配置里混了不同基域的公钥,p 会被互相覆盖、算到一半
/// 被换掉,结果是**错的而且不报错**。宁可在这里炸掉。
/// </summary>
public static BigInteger P
{
get { return _p; }
set
{
lock (_pGate)
{
if (_p.IsZero) { _p = value; return; }
if (_p != value)
throw new BaseFieldMismatchException(
"公钥基域不一致:此前已用 p=" + _p.ToString("X") +
",本组公钥是 p=" + value.ToString("X") +
"。多组公钥必须共用同一个基域;若配置里确实混了不同基域的公钥," +
"请先在循环外筛掉,否则并发解码会静默算错。");
}
}
}
public static BigInteger Mod(BigInteger x)
{
// 热路径里绝大多数输入已经是规范形式,先做一次廉价的范围判断,
// 命中就不必走 `%`(BigInteger 的除法比比较贵得多)。
if (x.Sign >= 0 && x < P) return x;
BigInteger r = x % P;
return r.Sign < 0 ? r + P : r;
}
/// <summary>
/// 模逆。用**二进制扩展欧几里得**而不是费马小定理的 ModPow:
/// 110 位下前者约 220 轮「位移/加减」,后者要做约 165 次模乘 ——
/// 而 Miller 循环每一比特都要调用一次,是配对里最热的一段。
/// </summary>
public static BigInteger Inv(BigInteger a)
{
a = Mod(a);
if (a.IsZero) throw new DivideByZeroException("Gf.Inv(0)");
if (a.IsOne) return BigInteger.One;
BigInteger u = a, v = P, x1 = BigInteger.One, x2 = BigInteger.Zero;
while (!u.IsOne && !v.IsOne)
{
while (u.IsEven) { u >>= 1; x1 = x1.IsEven ? (x1 >> 1) : ((x1 + P) >> 1); }
while (v.IsEven) { v >>= 1; x2 = x2.IsEven ? (x2 >> 1) : ((x2 + P) >> 1); }
if (u >= v) { u -= v; x1 -= x2; } else { v -= u; x2 -= x1; }
}
BigInteger r = u.IsOne ? x1 : x2;
r %= P;
return r.Sign < 0 ? r + P : r;
}
}
// -------------------------------------------------------------------------
// GF(p^3) = GF(p)[u] / (u^3 + u + 4)
// 元素 c0 + c1*u + c2*u^2
// 归约关系:u^3 = -(u + 4) = -4 - u
// -------------------------------------------------------------------------
public struct Fp3 : IEquatable<Fp3>
{
public BigInteger C0, C1, C2;
public Fp3(BigInteger c0, BigInteger c1, BigInteger c2)
{
C0 = Gf.Mod(c0); C1 = Gf.Mod(c1); C2 = Gf.Mod(c2);
}
// 注意:必须是属性而不是 static readonly 字段 —— 字段初始化会在 Gf.P
// 赋值之前跑(静态构造顺序),Fp3 的构造函数要取模,会除零。
public static Fp3 Zero { get { return new Fp3(0, 0, 0); } }
public static Fp3 One { get { return new Fp3(1, 0, 0); } }
public bool IsZero { get { return C0.IsZero && C1.IsZero && C2.IsZero; } }
public static Fp3 operator +(Fp3 a, Fp3 b)
{
return new Fp3(a.C0 + b.C0, a.C1 + b.C1, a.C2 + b.C2);
}
public static Fp3 operator -(Fp3 a, Fp3 b)
{
return new Fp3(a.C0 - b.C0, a.C1 - b.C1, a.C2 - b.C2);
}
public static Fp3 operator -(Fp3 a)
{
return new Fp3(-a.C0, -a.C1, -a.C2);
}
// 线程静态暂存:Fp3 乘法在热路径里被调用几百万次,每次 new 一个数组
// 会让 GC 变成瓶颈。operator* 不会重入,所以复用是安全的。
[ThreadStatic] static BigInteger[] _scratch;
public static Fp3 operator *(Fp3 a, Fp3 b)
{
BigInteger[] t = _scratch;
if (t == null) { t = new BigInteger[5]; _scratch = t; }
t[0] = BigInteger.Zero; t[1] = BigInteger.Zero; t[2] = BigInteger.Zero;
t[3] = BigInteger.Zero; t[4] = BigInteger.Zero;
for (int i = 0; i < 3; i++)
{
if (a.Get(i).IsZero) continue;
for (int j = 0; j < 3; j++)
t[i + j] += a.Get(i) * b.Get(j);
}
// 从高次往低次归约:u^k = u^(k-3) * u^3 = u^(k-3) * (-4 - u)
for (int k = 4; k >= 3; k--)
{
BigInteger c = t[k];
if (c.IsZero) continue;
t[k] = 0;
t[k - 3] -= 4 * c;
t[k - 2] -= c;
}
return new Fp3(t[0], t[1], t[2]);
}
public static Fp3 operator *(Fp3 a, BigInteger s)
{
return new Fp3(a.C0 * s, a.C1 * s, a.C2 * s);
}
public BigInteger Get(int i)
{
return i == 0 ? C0 : (i == 1 ? C1 : C2);
}
public Fp3 Pow(BigInteger e)
{
Fp3 r = One, b = this;
while (e > 0)
{
if (!(e & 1).IsZero) r = r * b;
b = b * b;
e >>= 1;
}
return r;
}
/// <summary>逆元:a^(p^3 - 2)</summary>
public Fp3 Inverse()
{
return Pow(BigInteger.Pow(Gf.P, 3) - 2);
}
public bool Equals(Fp3 o)
{
return C0 == o.C0 && C1 == o.C1 && C2 == o.C2;
}
public override bool Equals(object o) { return o is Fp3 && Equals((Fp3)o); }
public override int GetHashCode()
{
return C0.GetHashCode() ^ (C1.GetHashCode() << 1) ^ (C2.GetHashCode() << 2);
}
public override string ToString()
{
return "(" + C0 + ", " + C1 + ", " + C2 + ")";
}
}
// -------------------------------------------------------------------------
// GF(p^6) = GF(p^3)[y] / (y^2 + 2)
// 元素 c0 + c1*y,其中 c0/c1 是 Fp3;y^2 = -2
// -------------------------------------------------------------------------
public struct Fp6 : IEquatable<Fp6>
{
public Fp3 C0, C1;
public Fp6(Fp3 c0, Fp3 c1) { C0 = c0; C1 = c1; }
// 同上:用属性,避免静态构造顺序问题
public static Fp6 Zero { get { return new Fp6(Fp3.Zero, Fp3.Zero); } }
public static Fp6 One { get { return new Fp6(Fp3.One, Fp3.Zero); } }
/// <summary>把 K3 元素嵌进 K6(虚部为 0)</summary>
public static Fp6 FromFp3(Fp3 a) { return new Fp6(a, Fp3.Zero); }
public static Fp6 operator +(Fp6 a, Fp6 b)
{
return new Fp6(a.C0 + b.C0, a.C1 + b.C1);
}
public static Fp6 operator -(Fp6 a, Fp6 b)
{
return new Fp6(a.C0 - b.C0, a.C1 - b.C1);
}
public static Fp6 operator -(Fp6 a)
{
return new Fp6(-a.C0, -a.C1);
}
public static Fp6 operator *(Fp6 a, Fp6 b)
{
// y^2 = -2
// (a0 + a1 y)(b0 + b1 y) = (a0 b0 - 2 a1 b1) + (a0 b1 + a1 b0) y
// ★ 原来写成 `new Fp3(2,0,0) * (a1*b1)` —— 为了乘个常数 2
// 白做一整次 Fp3 乘法(9 次 BigInteger 乘法)+ 一次构造(3 次取模)。
// 直接对系数加倍即可。
Fp3 p1 = a.C1 * b.C1;
Fp3 two_p1 = new Fp3(p1.C0 + p1.C0, p1.C1 + p1.C1, p1.C2 + p1.C2);
Fp3 r0 = a.C0 * b.C0 - two_p1;
Fp3 r1 = a.C0 * b.C1 + a.C1 * b.C0;
return new Fp6(r0, r1);
}
public Fp6 Pow(BigInteger e)
{
Fp6 r = One, b = this;
while (e > 0)
{
if (!(e & 1).IsZero) r = r * b;
b = b * b;
e >>= 1;
}
return r;
}
public bool Equals(Fp6 o) { return C0.Equals(o.C0) && C1.Equals(o.C1); }
public override bool Equals(object o) { return o is Fp6 && Equals((Fp6)o); }
public override int GetHashCode() { return C0.GetHashCode() ^ C1.GetHashCode(); }
public override string ToString() { return C0 + " + " + C1 + "*y"; }
/// <summary>逆元:a^(p^6 - 2)</summary>
public Fp6 Inverse()
{
return Pow(BigInteger.Pow(Gf.P, 6) - 2);
}
public static Fp6 operator /(Fp6 a, Fp6 b) { return a * b.Inverse(); }
}
// -------------------------------------------------------------------------
// GF(p) 上的开平方(Tonelli-Shanks)
//
// 注意 p ≡ 1 (mod 4),所以**不能**用 a^((p+1)/4) 那个简化公式,必须走完整
// Tonelli-Shanks:
// p - 1 = q * 2^s (q 为奇数)
// 找非二次剩余 z,令 c = z^q, x = a^((q+1)/2), t = a^q, m = s
// while t != 1: 找最小的 i 使 t^(2^i) == 1; b = c^(2^(m-i-1));
// x *= b; t *= b^2; c = b^2; m = i
// -------------------------------------------------------------------------
public static class Fp
{
/// <summary>勒让德符号是否为 1(即是否为非零二次剩余)</summary>
public static bool IsQR(BigInteger a)
{
a = Gf.Mod(a);
if (a.IsZero) return true;
return BigInteger.ModPow(a, (Gf.P - 1) / 2, Gf.P).IsOne;
}
/// <summary>返回一个非二次剩余(用于 Tonelli-Shanks 的 z)</summary>
static BigInteger NonResidue()
{
for (BigInteger z = 2; ; z++)
if (!BigInteger.ModPow(z, (Gf.P - 1) / 2, Gf.P).IsOne)
return z;
}
/// <summary>模 p 开平方。不是二次剩余时返回 null;返回的是两根中较小的那个。</summary>
public static BigInteger? Sqrt(BigInteger a)
{
a = Gf.Mod(a);
if (a.IsZero) return BigInteger.Zero;
if (!IsQR(a)) return null;
// p - 1 = q * 2^s
BigInteger q = Gf.P - 1;
int s = 0;
while (q.IsEven) { q >>= 1; s++; }
BigInteger z = NonResidue();
BigInteger c = BigInteger.ModPow(z, q, Gf.P);
BigInteger x = BigInteger.ModPow(a, (q + 1) / 2, Gf.P);
BigInteger t = BigInteger.ModPow(a, q, Gf.P);
int m = s;
while (!t.IsOne)
{
// 找最小的 i (0 < i < m) 使 t^(2^i) == 1
int i = 0;
BigInteger tt = t;
while (!tt.IsOne)
{
tt = tt * tt % Gf.P;
i++;
if (i >= m) return null; // 理论上不会发生
}
// b = c^(2^(m-i-1))
BigInteger b = c;
for (int k = 0; k < m - i - 1; k++) b = b * b % Gf.P;
x = x * b % Gf.P;
BigInteger b2 = b * b % Gf.P;
t = t * b2 % Gf.P;
c = b2;
m = i;
}
BigInteger hi = Gf.P - x;
return x <= hi ? x : hi;
}
}
// -------------------------------------------------------------------------
// 曲线 y^2 = x^3 + a*x + b over GF(p)
// 公钥解析出来的 B 是 bEncryptArray(产品密钥的整数形式),
// 这里 x = B mod p,T = lift_x(x)。
// -------------------------------------------------------------------------
public static class Curve
{
public static BigInteger Rhs(BigInteger x, BigInteger a, BigInteger b)
{
x = Gf.Mod(x);
return Gf.Mod(x * x % Gf.P * x + a * x + b);
}
/// <summary>返回曲线上的两个点 (x, ±y),无解时返回 null。</summary>
public static BigInteger[] LiftX(BigInteger x, BigInteger a, BigInteger b)
{
x = Gf.Mod(x);
BigInteger? y = Fp.Sqrt(Rhs(x, a, b));
if (y == null) return null;
BigInteger yv = y.Value;
return new BigInteger[] { yv, Gf.Mod(-yv) };
}
}
// -------------------------------------------------------------------------
// E6 上的点(坐标在 K6 里)
// -------------------------------------------------------------------------
public struct Pt6
{
public Fp6 X, Y;
public bool Inf;
public static Pt6 Infinity { get { return new Pt6 { X = Fp6.Zero, Y = Fp6.Zero, Inf = true }; } }
public static Pt6 From(Fp6 x, Fp6 y) { return new Pt6 { X = x, Y = y, Inf = false }; }
/// <summary>倍点:λ = (3x² + a) / 2y</summary>
public Pt6 Dbl(BigInteger a)
{
if (Inf || Y.Equals(Fp6.Zero)) return Infinity;
Fp3 two = new Fp3(2, 0, 0);
Fp6 num = (X * X) * Fp6.FromFp3(new Fp3(3, 0, 0)) + Fp6.FromFp3(new Fp3(a, 0, 0));
Fp6 den = Y * Fp6.FromFp3(two);
Fp6 lam = num / den;
Fp6 x3 = lam * lam - X - X;
Fp6 y3 = lam * (X - x3) - Y;
return From(x3, y3);
}
public static Pt6 Add(Pt6 P, Pt6 Q)
{
if (P.Inf) return Q;
if (Q.Inf) return P;
if (P.X.Equals(Q.X))
return P.Y.Equals(Q.Y) ? throw new InvalidOperationException("Add: 需要倍点,请调用 Dbl")
: Infinity; // 互为逆元
Fp6 lam = (Q.Y - P.Y) / (Q.X - P.X);
Fp6 x3 = lam * lam - P.X - Q.X;
Fp6 y3 = lam * (P.X - x3) - P.Y;
return From(x3, y3);
}
public Pt6 Mul(BigInteger k, BigInteger a)
{
Pt6 r = Infinity, b = this;
while (k > 0)
{
if (!(k & 1).IsZero) r = r.Inf ? b : Add(r, b);
b = b.Dbl(a);
k >>= 1;
}
return r;
}
}
// -------------------------------------------------------------------------
// Tate 配对 e(T, Q) = f_{n,T}(Q) ^ ((p^6 - 1) / n)
//
// Miller 循环里把累加点 V 保持在 **E(K)** 上(坐标是普通 BigInteger,运算快),
// 只把直线函数在 **Q ∈ E6** 上求值。直线与垂直线都取完整形式(不做分母消除)——
// 分母能否消除取决于群阶与子域的关系,先不赌,两种都算、用结果对答案。
// -------------------------------------------------------------------------
// ★ 分母消除(denominator elimination)—— 本实现里最要紧的一处优化。
//
// Miller 递推是 f_{m+m'} = f_m · f_{m'} · l / v,其中 v 是垂直线。
// 在 Q 上求值时 v(Q) = Q.X − x([m]T)。本例中
// Q.X ∈ Fp3 (扭曲线映射 x' = x·t⁻² 把 K3 坐标嵌进 K6 的实部)
// x([m]T) ∈ GF(p) (累加点 V 始终在 E(K) 上)
// 所以所有分母都在真子域 Fp3* 里。而最终幂指数
// E = (p⁶−1)/n = (p³−1)·(p³+1)/n (n | p²−p+1,p³+1 = (p+1)(p²−p+1))
// 对任意 d ∈ Fp3* 给出 d^E = (d^(p³−1))^((p³+1)/n) = 1。
// ⇒ 分母被最终幂整个吃掉,可以**直接不除**。
//
// 这一步的收益是数量级的:原先每处理一位都要做一次 Fp6 除法,而 Fp6.Inverse()
// 是 Pow(p⁶−2),约 1000 次 Fp6 乘法 —— 110 位的循环就是 11 万次乘法,
// 而循环本身只需约 550 次。也就是说**求逆占了配对耗时的 99% 以上**。
public static class TatePairing
{
public static int BitLength(BigInteger v)
{
int n = 0;
while (v > 0) { v >>= 1; n++; }
return n;
}
static Fp6 Emb(BigInteger v) { return Fp6.FromFp3(new Fp3(v, 0, 0)); }
/// <summary>直线 l_{P1,P2}(Q),P1/P2 的坐标在 K 里(BigInteger),Q 在 E6 里</summary>
static Fp6 Line(BigInteger x1, BigInteger y1, Fp6 slopeNumFp6, Fp6 slopeDenFp6, Pt6 Q)
{
Fp6 lam = slopeNumFp6 / slopeDenFp6;
return (Q.Y - Emb(y1)) - lam * (Q.X - Emb(x1));
}
public static Fp6 Pair(BigInteger n, BigInteger Tx, BigInteger Ty, Pt6 Q, BigInteger a)
{
Fp6 f = Fp6.One;
BigInteger vx = Tx, vy = Ty;
bool vInf = false;
int L = BitLength(n);
for (int i = L - 2; i >= 0; i--)
{
// ---- 倍点 V -> 2V ----
if (!vInf)
{
if (vy == 0) { vInf = true; } // 2-挠点
else
{
BigInteger lam = Gf.Mod(Gf.Mod(3 * vx * vx + a) * Fpm.InvBig(Gf.Mod(2 * vy)));
Fp6 l = (Q.Y - Emb(vy)) - Emb(lam) * (Q.X - Emb(vx));
BigInteger x2 = Gf.Mod(lam * lam - 2 * vx);
BigInteger y2 = Gf.Mod(lam * (vx - x2) - vy);
f = (f * f) * l; // 分母消除,见上
vx = x2; vy = y2;
}
}
else f = f * f; // V = ∞:直线平凡
// ---- 按 n 的当前位决定是否加 T ----
if (!((n >> i) & BigInteger.One).IsZero)
{
if (vInf)
{
// ∞ + T = T,直线平凡
vx = Tx; vy = Ty; vInf = false;
}
else if (vx == Tx && vy == Ty)
{
// V == T:退化为倍点
BigInteger lam = Gf.Mod(Gf.Mod(3 * vx * vx + a) * Fpm.InvBig(Gf.Mod(2 * vy)));
Fp6 l = (Q.Y - Emb(vy)) - Emb(lam) * (Q.X - Emb(vx));
BigInteger x2 = Gf.Mod(lam * lam - 2 * vx);
BigInteger y2 = Gf.Mod(lam * (vx - x2) - vy);
f = f * l; // 分母消除
vx = x2; vy = y2;
}
else if (vx == Tx)
{
// V == -T:直线是垂直线,V + T = ∞,除数取 1
// (这是**正常**情形 —— 循环最后一轮必然出现:
// V = [(n-1)/2]T 倍点后 = [(n-1)]T = -T)
f = f * (Q.X - Emb(vx));
vInf = true;
}
else
{
BigInteger lam = Gf.Mod(Gf.Mod(vy - Ty) * Fpm.InvBig(Gf.Mod(vx - Tx)));
Fp6 l = (Q.Y - Emb(vy)) - Emb(lam) * (Q.X - Emb(vx));
BigInteger x3 = Gf.Mod(lam * lam - vx - Tx);
BigInteger y3 = Gf.Mod(lam * (vx - x3) - vy);
f = f * l; // 分母消除
vx = x3; vy = y3;
}
}
}
// ---- 最终幂 ----
BigInteger e = (BigInteger.Pow(Gf.P, 6) - 1) / n;
// 最终幂占了单次配对约 3/4 的时间,而这里还在用 BigInteger 版 Fp6。
// 接到 Montgomery 层后 14 次配对从 ~0.50s 降到 ~0.1s。
return Fp6mOp.PowBigM(f, e);
}
public static BigInteger Order; // 由调用方设置
}
// -------------------------------------------------------------------------
// 公钥
// -------------------------------------------------------------------------
public sealed class PubKey
{
public int SizeModulus, SizeOrder, ExtDeg1, ExtDeg2;
public byte[] H1Bases;
public BigInteger Modulus, Order;
public byte[] K3Minpoly, K6Minpoly;
public BigInteger CurveA, CurveB;
public List<Fp3[]> Points = new List<Fp3[]>(); // 每项 = [x, y]
public Fp3[] PairingVal; // K6 = [c0, c1]
}
public static class PubKeyParser
{
public static PubKey Parse(byte[] data)
{
int pos = 0;
Func<int, uint> U32 = off => (uint)(data[off] | (data[off + 1] << 8) |
(data[off + 2] << 16) | (data[off + 3] << 24));
Func<int, int, BigInteger> Big = (off, n) =>
{
byte[] b = new byte[n + 1];
Array.Copy(data, off, b, 0, n);
b[n] = 0; // 补 0 保证正数(小端)
return new BigInteger(b);
};
if (U32(0) != 0x44556677u) throw new InvalidDataException("bad magic1");
uint fieldDataSize = U32(8);
if (U32(12) != 0x00112233u) throw new InvalidDataException("bad magic2");
pos = 20;
byte mustBe0 = data[pos];
int sizeModulus = data[pos + 1];
int sizeOrder = data[pos + 2];
if (mustBe0 != 0) throw new InvalidDataException("bad field data");
uint[] dataList = new uint[9];
for (int i = 0; i < 9; i++)
dataList[i] = U32(pos + 3 + 4 * i);
int extDeg1 = (int)dataList[0];
int extDeg2 = (int)dataList[1];
pos = 20 + 3 + 36;
PubKey k = new PubKey();
k.SizeModulus = sizeModulus;
k.SizeOrder = sizeOrder;
k.ExtDeg1 = extDeg1;
k.ExtDeg2 = extDeg2;
pos += 1; // 跳过 1 字节
k.H1Bases = new byte[sizeModulus];
Array.Copy(data, pos, k.H1Bases, 0, sizeModulus); pos += sizeModulus;
k.Modulus = Big(pos, sizeModulus); pos += sizeModulus;
// Fp3 的构造函数要用 Gf.P 取模,所以必须在这里就设好
Gf.P = k.Modulus;
k.Order = Big(pos, sizeOrder); pos += sizeOrder;
k.K3Minpoly = new byte[extDeg1 + 1];
Array.Copy(data, pos, k.K3Minpoly, 0, extDeg1 + 1); pos += extDeg1 + 1;
k.K6Minpoly = new byte[extDeg2 + 1];
Array.Copy(data, pos, k.K6Minpoly, 0, extDeg2 + 1); pos += extDeg2 + 1;
pos += sizeModulus * 2; // 跳过一段
k.CurveA = Big(pos, sizeModulus); pos += sizeModulus;
k.CurveB = Big(pos, sizeModulus); pos += sizeModulus;
// 点与 pairing_val 从 fieldDataSize + 12 开始
pos = (int)fieldDataSize + 12;
for (int i = 0; i < sizeModulus; i++)
{
Fp3 x = new Fp3(Big(pos, sizeModulus), Big(pos + sizeModulus, sizeModulus),
Big(pos + 2 * sizeModulus, sizeModulus));
pos += sizeModulus * extDeg1;
Fp3 y = new Fp3(Big(pos, sizeModulus), Big(pos + sizeModulus, sizeModulus),
Big(pos + 2 * sizeModulus, sizeModulus));
pos += sizeModulus * extDeg1;
k.Points.Add(new Fp3[] { x, y });
}
Fp3 pv0 = new Fp3(Big(pos, sizeModulus), Big(pos + sizeModulus, sizeModulus),
Big(pos + 2 * sizeModulus, sizeModulus));
pos += sizeModulus * extDeg1;
Fp3 pv1 = new Fp3(Big(pos, sizeModulus), Big(pos + sizeModulus, sizeModulus),
Big(pos + 2 * sizeModulus, sizeModulus));
pos += sizeModulus * extDeg1;
k.PairingVal = new Fp3[] { pv0, pv1 };
return k;
}
}
// =========================================================================
// ExtractM 的 C# 复刻
//
// C++ 侧真正参与的就两段:
// CalculateH1: h1Coeffs[0] = keyByte; h1Coeffs[1] = 1;
// h1Coeffs[2] = keyByte mod (H1Bases[1]+1) ← mp_extract_byte_digits 第 1 位
// h1Coeffs[i4+1] = bufB[i4] (i4 = dw(a1,40)+1 .. n-1)
// ExtractM: extract_scalar_digits(h1Coeffs + 2, dataOff + 2, sizeModulus - 1, ...)
//
// extract_scalar_digits 是个 Horner 循环(k 从 n-1 递减到 0):
// acc = acc * (radix[k] + 1) + digit[k]
// 其中
// digit[k] = h1Coeffs[2 + k]
// radix[k] = *(dataOff + 2 + k) = H1Bases[1 + k]
//
// 为什么是 H1Bases[1+k]:dataOff 指向公钥里那个被跳过的字节(偏移 59),
// 所以 dataOff+2 = 偏移 61 = H1Bases[1]。这一条已用三组黄金 M 验证。
// =========================================================================
public static class PKeyCalc
{
/// <summary>
/// ★ 单个候选公钥的完整判定:公钥 + 密钥 → h1Coeffs / uid。
///
/// 调用方自己用 Parallel.ForEach 遍历公钥字典,groupId 由字典键取得,例如:
///
/// int groupId = 0; byte[] h1 = null, uid = null; object gate = new object();
/// Parallel.ForEach(ConfigData2005.PublicKeyPart2, (item, state) =>
/// {
/// byte[] pk = ConfigData2005.PublicKeyPart1.Concat(item.Value).ToArray();
/// int seq; string cfg; byte[] h, u;
/// if (!PKeyCalc.TryPubKey(pk, bEncryptArray, out seq, out cfg, out h, out u)) return;
/// lock (gate) { if (groupId != 0) return; groupId = item.Key; h1 = h; uid = u; }
/// state.Stop();
/// });
///
/// 说明:
/// · 内部**不持有任何跨调用状态**,可安全并发调用;不需要任何 Prepare/初始化,
/// Fpm.Init() 是线程安全的懒初始化,第一次用到时自动完成。
/// · 内层 MITM 固定单线程(外层已经按候选铺满了核),两层都并行会超订。
/// · 公钥无效、或这把密钥不属于这组公钥,返回 false。
/// </summary>
/// <param name="publicKey">完整公钥字节 = PublicKeyPart1 + 该组的 Part2</param>
/// <param name="bEncryptArray">产品密钥的 16 字节整数形式</param>
/// <param name="channelSeq">输出的渠道序号</param>
/// <param name="actPkeyConfig">输出的 Base64 配置串</param>
/// <param name="h1Coeffs">输出的 15 字节 h1 系数</param>
/// <param name="uid">输出的 8 字节 uid</param>
public static bool TryPubKey(byte[] publicKey, byte[] bEncryptArray,
out int channelSeq, out string actPkeyConfig,
out byte[] h1Coeffs, out byte[] uid)
{
channelSeq = 0; actPkeyConfig = null; h1Coeffs = null; uid = null;
if (publicKey == null || publicKey.Length == 0) return false;
if (bEncryptArray == null || bEncryptArray.Length != 16) return false;
PubKey k;
try { k = PubKeyParser.Parse(publicKey); } // 会写静态 Gf.P;各组公钥的 p 相同,并发写同值是安全的
catch (BaseFieldMismatchException) { throw; } // ★ 基域不一致必须让调用方看到,不能吞
catch { return false; }
if (k == null) return false;
byte[] h1, u;
if (!TryCandidate(k, bEncryptArray, out h1, out u)) return false;
h1Coeffs = h1; uid = u;
byte[] cfg = new byte[12];
Buffer.BlockCopy(u, 0, cfg, 0, 8); // 尾部 4 字节保持 0
actPkeyConfig = Convert.ToBase64String(cfg);
int ev = u[0] | ((u[1] | ((u[2] | ((u[3] & 0x7F) << 8)) << 8)) << 8);
channelSeq = ((ev >> 1) % 1000000) + (1000000 * ((ev >> 1) / 1000000));
return true;
}
// ---- 分段计时(单线程测量用;并发调用时互相覆盖,只在 bench 里读)----
public static double TLastPairMs, TLastMitmMs;
/// <summary>候选判定主体:由密钥推出 T,做 14 次配对,再用 MITM 搜 h1Coeffs[4..14]。</summary>
static bool TryCandidate(PubKey k, byte[] enc, out byte[] h1, out byte[] uid)
{
h1 = null; uid = null;
byte[] eb = new byte[enc.Length + 1]; // 补 0 保证 BigInteger 为正
Buffer.BlockCopy(enc, 0, eb, 0, enc.Length);
BigInteger B = new BigInteger(eb);
BigInteger xT = Gf.Mod(B); // 余数 = x(T)
int keyByte = (int)((B / Gf.P) & 0xFF); // 商 = h1Coeffs[0]
BigInteger[] T = Curve.LiftX(xT, k.CurveA, k.CurveB);
if (T == null) return false;
// h1Coeffs[0..3]:keyByte、常量 1、keyByte 的两个混合进制位
h1 = new byte[15];
int b1 = k.H1Bases[1] + 1, b2 = k.H1Bases[2] + 1;
h1[0] = (byte)keyByte;
h1[1] = 1;
h1[2] = (byte)(keyByte % b1);
h1[3] = (byte)((keyByte / b1) % b2);
// 14 个配对 g_i = e(T, Q_i)
var swPair = System.Diagnostics.Stopwatch.StartNew();
Fp3 inv2 = new Fp3(-2, 0, 0).Inverse();
Fp3 inv2sq = inv2 * inv2;
int np = k.Points.Count;
Fp6[] g = new Fp6[np];
for (int i = 0; i < np; i++)
{
Pt6 Qi = Pt6.From(Fp6.FromFp3(k.Points[i][0] * inv2),
new Fp6(Fp3.Zero, k.Points[i][1] * inv2sq));
g[i] = TatePairing.Pair(k.Order, xT, T[0], Qi, k.CurveA);
}
swPair.Stop();
TLastPairMs = swPair.Elapsed.TotalMilliseconds;
// MITM 搜 h1Coeffs[4..14]
// h_k = h1Coeffs[1+k],所以搜索范围 lo[0..2] 取的是 h1Coeffs[1..3]
int[] lo = new int[np], hi = new int[np];
lo[0] = h1[1]; lo[1] = h1[2]; lo[2] = h1[3];
for (int i = 0; i < 3; i++) hi[i] = lo[i];
for (int i = 3; i < np; i++) hi[i] = k.H1Bases[i];
Fp6 pv = new Fp6(k.PairingVal[0], k.PairingVal[1]);
string note;
var swMitm = System.Diagnostics.Stopwatch.StartNew();
int[] sol = H1Search.Solve(g, Fp6.One / pv, lo, hi, out note, 1);
swMitm.Stop();
TLastMitmMs = swMitm.Elapsed.TotalMilliseconds;
if (sol == null) return false; // Solve 内部已同时查过 pv 与其逆元
for (int i = 3; i < np; i++) h1[4 + i - 3] = (byte)sol[i];
uid = ExtractMBytes(k, h1, 8);
return true;
}
/// <summary>公钥里被 `pos += 1` 跳过的那 1 字节 = C++ 的 dataOff。</summary>
public const int DataOff = 59;
/// <summary>
/// M = extract_scalar_digits(h1Coeffs+2, dataOff+2, sizeModulus-1)。
/// 每一位必须严格小于自己的进制,否则 C++ 侧会置 error_code 6 并返回 0。
/// </summary>
public static BigInteger ExtractM(PubKey k, byte[] h1Coeffs)
{
int n = k.SizeModulus - 1; // v6 - 1
BigInteger acc = 0;
for (int i = n - 1; i >= 0; i--)
{
int radix = k.H1Bases[1 + i] + 1;
int digit = h1Coeffs[2 + i];
if (digit >= radix)
throw new InvalidDataException(
"h1Coeffs[" + (2 + i) + "] = " + digit + " >= 进制 " + radix);
acc = acc * radix + digit;
}
return acc;
}
/// <summary>同 ExtractM,但按 DLL 的输出约定写成 nBytes 字节(小端)。</summary>
public static byte[] ExtractMBytes(PubKey k, byte[] h1Coeffs, int nBytes)
{
byte[] src = ExtractM(k, h1Coeffs).ToByteArray(); // 小端,可能多带一个 0x00
byte[] r = new byte[nBytes];
Array.Copy(src, r, Math.Min(src.Length, nBytes));
return r;
}
}
// =========================================================================
// sa_match_entry 的摘要内核
//
// sa_checksum4(a1, a2, a3, a4) —— PKeyKernelBackend.cpp:2298
// 这是 sa_match_entry 整个闭包里**唯一的摘要函数**:
//
// v4 = 0
// for i in 0..3:
// if (i < *a2) v4 ^= a1[i]; // 最多 4 个 dword(16 字节)
// a3[i] = (byte)v4; // 每轮先写低字节
// v4 = 123456789 * (v4 >> 8); // mod 2^32
//
// 实测输入是 4 个 limb(n=12 但只有前 4 个 dword 参与),最高 limb 很小
// (d3 最大 0x26D4),量级正好是 110 位的 p —— 即被摘要的是一个 GF(p) 元素。
//
// 排序键(两个独立位置算出,互为印证):
// sa_count_sort2 : 256 * rec[0] + rec[1] (记录前两字节)
// sa_match_block : ((ck >> 8) & 0xFF) + ((ck & 0xFF) << 8)
// 两者都等于 256*ck[0] + ck[1],即摘要前两字节按大端拼成的 16 位值 → 65536 个桶。
// =========================================================================
public static class SaChecksum
{
}
// =========================================================================
// 加速层:Montgomery 形式的 GF(p) / GF(p^3) / GF(p^6)
//
// 动机:MITM 要算约 65 万次 Fp6 乘法 = 约 1700 万次模乘。用 BigInteger 时
// **每次取模都要分配堆对象**,GC 成了瓶颈 —— 实测把建表并行到 8 核只拿到
// 约 2× 加速,正是这个原因。换成定长 limb + Montgomery 归约后零堆分配。
//
// p < 2^110 ⇒ 4 个 32 位 limb;R = 2^128。
// 只在 H1Search 内部使用,边界处与 BigInteger 版互转,不影响其它代码。
// =========================================================================
public struct Fpx
{
public ulong A0, A1, A2, A3;
public static Fpx Make(ulong a0, ulong a1, ulong a2, ulong a3)
{ Fpx r; r.A0 = a0; r.A1 = a1; r.A2 = a2; r.A3 = a3; return r; }
public bool Eq(in Fpx o)
{ return A0 == o.A0 && A1 == o.A1 && A2 == o.A2 && A3 == o.A3; }
}
public static class Fpm
{
const ulong M = 0xFFFFFFFFUL;
public static ulong PB0_, PB1_, PB2_, PB3_;
static ulong N0INV;
public static Fpx One;
static volatile bool _init;
static readonly object _initGate = new object();
/// <summary>
/// 建立 p 的 Montgomery 常数。★ 线程安全 —— 任何线程第一次用到时自动完成,
/// 你在外面不用做任何初始化,Parallel.ForEach 直接开跑即可。
/// 原来是把 _init 先置 true 再计算,多线程下别的线程会立刻返回、
/// 读到 PB0_..PB3_ 还是 0,算出来的全是垃圾。这里改成全部算完再发布。
/// </summary>
public static void Init()
{
if (_init) return;
lock (_initGate)
{
if (_init) return;
BigInteger p = Gf.P;
ulong b0 = (ulong)(p & M), b1 = (ulong)((p >> 32) & M);
ulong b2 = (ulong)((p >> 64) & M), b3 = (ulong)((p >> 96) & M);
// N0INV = -p0^{-1} mod 2^32;牛顿迭代 5 次,每步精度翻倍
ulong inv = 1;
for (int i = 0; i < 5; i++) inv = (inv * (2 - b0 * inv)) & M;
Fpx one = FromBig(1);
BigInteger rinv = BigInteger.ModPow(BigInteger.One << 128, p - 2, p);
PB0_ = b0; PB1_ = b1; PB2_ = b2; PB3_ = b3;
N0INV = (0x100000000UL - inv) & M;
One = one;
_rinv = rinv;
_init = true; // ★ 最后才发布
}
}
public static Fpx FromBig(BigInteger x)
{
BigInteger v = x % Gf.P;
if (v.Sign < 0) v += Gf.P;
v = (v << 128) % Gf.P; // 转 Montgomery 形式
return Fpx.Make((ulong)(v & M), (ulong)((v >> 32) & M),
(ulong)((v >> 64) & M), (ulong)((v >> 96) & M));
}
static BigInteger _rinv; // R^{-1},在 Init() 里算好
public static BigInteger ToBig(in Fpx a)
{
BigInteger v = a.A0 | ((BigInteger)a.A1 << 32) | ((BigInteger)a.A2 << 64)
| ((BigInteger)a.A3 << 96);
// R^{-1} 只跟 p 有关,已在 Init() 里算好 —— 原来这里是懒加载,
// 多线程并发时会重复算,而且 _rinv 的写入不原子。
Init();
return v * _rinv % Gf.P;
}
// 一次 CIOS 迭代:t[0..4] += a * bi,再做一次 Montgomery 归约并右移 32 位
/// <summary>
/// 求逆。a 已是 Montgomery 形式 aR,而
/// (aR)^(p−2) = a^(p−2) · R^(p−2) = a^(−1) · R · R^(p−1) = a^(−1) R
/// (R ∈ GF(p)* ⇒ R^(p−1) = 1),所以直接幂就得到「逆的 Montgomery 形式」。
/// 110 位指数 ≈ 165 次 Fpm.Mul,约 18µs;而 BigInteger 版要 70µs。
/// </summary>
public static Fpx Inv(in Fpx a)
{
Fpx r = One, b = a;
BigInteger e = Gf.P - 2;
while (e > 0)
{
if (!(e & 1).IsZero) r = Mul(r, b);
b = Mul(b, b);
e >>= 1;
}
return r;
}
/// <summary>BigInteger 进出的求逆,供 TatePairing 这种还在用 BigInteger 的地方调用。</summary>
public static BigInteger InvBig(BigInteger a)
{
Init();
return ToBig(Inv(FromBig(a)));
}
public static Fpx Mul(in Fpx a, in Fpx b)
{
// ★ 这里刻意**不手工展开**。展开版同时有 5 个 ulong 累加器 + 4 个 limb
// ≈ 10 个活跃的 64 位值 = 需要 20 个寄存器:x64 有 16 个还凑合,
// x86 只有 8 个(esp/ebp 还占掉 2 个)必然疯狂 spill。而 .NET Framework
// 的 x86 用的是老 JIT32,寄存器分配比 x64 的 RyuJIT 差得多 ——
// 为 x64 展开的代码在 x86 上是负优化。循环版 IL 体积小得多,
// JIT32 才能好好分配寄存器。实测见 PKeyDecoder.cs 顶部说明。
ulong a0 = a.A0, a1 = a.A1, a2 = a.A2, a3 = a.A3;
ulong b0 = b.A0, b1 = b.A1, b2 = b.A2, b3 = b.A3;
ulong t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0;
for (int i = 0; i < 4; i++)
{
ulong bi = i == 0 ? b0 : i == 1 ? b1 : i == 2 ? b2 : b3;
ulong carry, q;
// t += a * bi
q = a0 * bi + t0; t0 = q & M; carry = q >> 32;
q = a1 * bi + t1 + carry; t1 = q & M; carry = q >> 32;
q = a2 * bi + t2 + carry; t2 = q & M; carry = q >> 32;
q = a3 * bi + t3 + carry; t3 = q & M; carry = q >> 32;
ulong s = t4 + carry; t4 = s & M; ulong t5 = s >> 32;
// Montgomery 归约:低 32 位必归零,直接丢弃
ulong m = (t0 * N0INV) & M;
carry = 0;
q = m * PB0_ + t0; carry = q >> 32;
q = m * PB1_ + t1 + carry; t0 = q & M; carry = q >> 32;
q = m * PB2_ + t2 + carry; t1 = q & M; carry = q >> 32;
q = m * PB3_ + t3 + carry; t2 = q & M; carry = q >> 32;
q = t4 + carry; t3 = q & M; carry = q >> 32;
t4 = carry + t5;
}
// 结果 < 2p,最多减一次 p
if (t4 != 0 || t3 > PB3_
|| (t3 == PB3_ && (t2 > PB2_
|| (t2 == PB2_ && (t1 > PB1_ || (t1 == PB1_ && t0 >= PB0_))))))
{
long c = 0, qq;
qq = (long)t0 - (long)PB0_; t0 = (ulong)qq & M; c = qq >> 32;
qq = (long)t1 - (long)PB1_ + c; t1 = (ulong)qq & M; c = qq >> 32;
qq = (long)t2 - (long)PB2_ + c; t2 = (ulong)qq & M; c = qq >> 32;
qq = (long)t3 - (long)PB3_ + c; t3 = (ulong)qq & M;
}
return Fpx.Make(t0, t1, t2, t3);
}
}
public struct Fp3m
{
public Fpx C0, C1, C2;
public static Fp3m FromFp3(Fp3 a)
{ Fp3m r; r.C0 = Fpm.FromBig(a.C0); r.C1 = Fpm.FromBig(a.C1); r.C2 = Fpm.FromBig(a.C2); return r; }
public bool Eq(in Fp3m o) { return C0.Eq(o.C0) && C1.Eq(o.C1) && C2.Eq(o.C2); }
}
public struct Fp6m
{
public Fp3m R, I; // 实部 + 虚部*y,y^2 = -2
public static Fp6m FromFp6(Fp6 a)
{ Fp6m r; r.R = Fp3m.FromFp3(a.C0); r.I = Fp3m.FromFp3(a.C1); return r; }
public bool Eq(in Fp6m o) { return R.Eq(o.R) && I.Eq(o.I); }
}
public static class Fp6mOp
{
// ---- 模加减:limb 表示要求每位 < 2^32,且域里加减必须环绕,不能裸做 ----
const ulong M32 = 0xFFFFFFFFUL;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool GeP(in Fpx a) // a >= p ?
{
if (a.A3 != Fpm.PB3_) return a.A3 > Fpm.PB3_;
if (a.A2 != Fpm.PB2_) return a.A2 > Fpm.PB2_;
if (a.A1 != Fpm.PB1_) return a.A1 > Fpm.PB1_;
return a.A0 >= Fpm.PB0_;
}
static void SubP(ref Fpx a)
{
long c = 0, t;
t = (long)a.A0 - (long)Fpm.PB0_; a.A0 = (ulong)(t & (long)M32); c = t >> 32;
t = (long)a.A1 - (long)Fpm.PB1_ + c; a.A1 = (ulong)(t & (long)M32); c = t >> 32;
t = (long)a.A2 - (long)Fpm.PB2_ + c; a.A2 = (ulong)(t & (long)M32); c = t >> 32;
t = (long)a.A3 - (long)Fpm.PB3_ + c; a.A3 = (ulong)(t & (long)M32);
}
static void AddP(ref Fpx a)