| ... | @@ -472,6 +472,21 @@ fn Fe_(comptime bits: comptime_int) type { | ... | @@ -472,6 +472,21 @@ fn Fe_(comptime bits: comptime_int) type { |
| 472 | return fe; | 472 | return fe; |
| 473 | } | 473 | } |
| 474 | | 474 | |
| | 475 | /// Decodes and reduces a byte string whose value is below 2^m.bits(). |
| | 476 | pub fn fromBytesReduced(m: Modulus(bits), bytes: []const u8, comptime endian: Endian) OverflowError!Self { |
| | 477 | var fe = Self{ .v = FeUint.zero }; |
| | 478 | const m_limbs_count = m.limbs_count(); |
| | 479 | const fe_limbs = fe.v.limbs()[0..m_limbs_count]; |
| | 480 | var acc = decodeBytes(fe_limbs, bytes, endian); |
| | 481 | |
| | 482 | acc |= math.shr(Limb, fe_limbs[m_limbs_count - 1], t_bits - m.leading); |
| | 483 | if (!ct.eql(acc, 0)) return error.Overflow; |
| | 484 | fe.v.limbs_len = m_limbs_count; |
| | 485 | |
| | 486 | _ = fe.v.conditionalSubWithOverflow(ct.limbsCmpGeq(fe.v, m.v), m.v); |
| | 487 | return fe; |
| | 488 | } |
| | 489 | |
| 475 | /// Converts the field element to a byte string. | 490 | /// Converts the field element to a byte string. |
| 476 | /// Returns an error if the element is in Montgomery form. | 491 | /// Returns an error if the element is in Montgomery form. |
| 477 | pub fn toBytes(self: Self, bytes: []u8, comptime endian: Endian) (OverflowError || RepresentationError)!void { | 492 | pub fn toBytes(self: Self, bytes: []u8, comptime endian: Endian) (OverflowError || RepresentationError)!void { |
| ... | @@ -586,6 +601,12 @@ pub fn Modulus(comptime max_bits: comptime_int) type { | ... | @@ -586,6 +601,12 @@ pub fn Modulus(comptime max_bits: comptime_int) type { |
| 586 | | 601 | |
| 587 | const FeUint = Fe.FeUint; | 602 | const FeUint = Fe.FeUint; |
| 588 | | 603 | |
| | 604 | pub const Options = struct { |
| | 605 | /// Accept even moduli and treat their parity as public. |
| | 606 | /// Disabled by default: with even moduli, we can't use Montgomery form, so multiplication is slower. |
| | 607 | allow_even: bool = false, |
| | 608 | }; |
| | 609 | |
| 589 | /// The neutral element. | 610 | /// The neutral element. |
| 590 | zero: Fe, | 611 | zero: Fe, |
| 591 | | 612 | |
| ... | @@ -683,15 +704,71 @@ pub fn Modulus(comptime max_bits: comptime_int) type { | ... | @@ -683,15 +704,71 @@ pub fn Modulus(comptime max_bits: comptime_int) type { |
| 683 | /// Creates a new modulus from a primitive value. | 704 | /// Creates a new modulus from a primitive value. |
| 684 | /// The modulus must be odd and larger than 2. | 705 | /// The modulus must be odd and larger than 2. |
| 685 | pub fn fromPrimitive(comptime T: type, x: T) (InvalidModulusError || OverflowError)!Self { | 706 | pub fn fromPrimitive(comptime T: type, x: T) (InvalidModulusError || OverflowError)!Self { |
| 686 | comptime assert(@bitSizeOf(T) <= max_bits); // Primitive type is larger than the modulus type. | 707 | return fromPrimitiveWithOptions(T, x, .{}); |
| 687 | const v = try FeUint.fromPrimitive(T, x); | | |
| 688 | return try Self.fromUint(v); | | |
| 689 | } | 708 | } |
| 690 | | 709 | |
| 691 | /// Creates a new modulus from a byte string. | 710 | /// Creates a new modulus from a byte string. |
| 692 | pub fn fromBytes(bytes: []const u8, comptime endian: Endian) (InvalidModulusError || OverflowError)!Self { | 711 | pub fn fromBytes(bytes: []const u8, comptime endian: Endian) (InvalidModulusError || OverflowError)!Self { |
| | 712 | return fromBytesWithOptions(bytes, endian, .{}); |
| | 713 | } |
| | 714 | |
| | 715 | /// Creates a new modulus from a Uint value, with options. |
| | 716 | pub fn fromUintWithOptions(v_: FeUint, options: Options) InvalidModulusError!Self { |
| | 717 | if (v_.isOdd()) return fromUint(v_); |
| | 718 | if (!options.allow_even) return error.EvenModulus; |
| | 719 | |
| | 720 | const v = v_.normalize(); |
| | 721 | if (v.limbs_len < 2 and v.limbsConst()[0] < 2) { |
| | 722 | return error.ModulusTooSmall; |
| | 723 | } |
| | 724 | const hi = v.limbsConst()[v.limbs_len - 1]; |
| | 725 | |
| | 726 | var m = initWithoutRR(v, @clz(hi) - carry_bits, 0); |
| | 727 | m.rr = m.zero; |
| | 728 | |
| | 729 | return m; |
| | 730 | } |
| | 731 | |
| | 732 | /// Creates a new big integer from a primitive type, with options. |
| | 733 | pub fn fromPrimitiveWithOptions(comptime T: type, x: T, options: Options) (InvalidModulusError || OverflowError)!Self { |
| | 734 | comptime assert(@bitSizeOf(T) <= max_bits); |
| | 735 | const v = try FeUint.fromPrimitive(T, x); |
| | 736 | return try Self.fromUintWithOptions(v, options); |
| | 737 | } |
| | 738 | |
| | 739 | /// Creates a new big integer from a byte array, with options. |
| | 740 | pub fn fromBytesWithOptions(bytes: []const u8, comptime endian: Endian, options: Options) (InvalidModulusError || OverflowError)!Self { |
| 693 | const v = try FeUint.fromBytes(bytes, endian); | 741 | const v = try FeUint.fromBytes(bytes, endian); |
| 694 | return try Self.fromUint(v); | 742 | return try Self.fromUintWithOptions(v, options); |
| | 743 | } |
| | 744 | |
| | 745 | const ProductUint = Uint(2 * FeUint.capacity_bits); |
| | 746 | |
| | 747 | fn fullProduct(a: []const Limb, b: []const Limb) ProductUint { |
| | 748 | var out = ProductUint.zero; |
| | 749 | out.limbs_len = a.len + b.len; |
| | 750 | mulAddWide(out.limbs(), a, b); |
| | 751 | return out; |
| | 752 | } |
| | 753 | |
| | 754 | /// Creates an odd modulus from the product of two integers. |
| | 755 | pub fn fromProduct(a: FeUint, b: FeUint) (InvalidModulusError || OverflowError)!Self { |
| | 756 | return fromProductWithOptions(a, b, .{}); |
| | 757 | } |
| | 758 | |
| | 759 | /// Creates a modulus from the product of two integers, with options (set `allow_even` to `true` to allow the product to be even) |
| | 760 | pub fn fromProductWithOptions(a: FeUint, b: FeUint, options: Options) (InvalidModulusError || OverflowError)!Self { |
| | 761 | const product = fullProduct(a.limbsConst(), b.limbsConst()); |
| | 762 | |
| | 763 | if (orLimbs(product.limbs_buffer[FeUint.max_limbs_count..]) != 0) { |
| | 764 | return error.Overflow; |
| | 765 | } |
| | 766 | |
| | 767 | const v = FeUint{ |
| | 768 | .limbs_buffer = product.limbs_buffer[0..FeUint.max_limbs_count].*, |
| | 769 | .limbs_len = FeUint.max_limbs_count, |
| | 770 | }; |
| | 771 | return try Self.fromUintWithOptions(v, options); |
| 695 | } | 772 | } |
| 696 | | 773 | |
| 697 | /// Serializes the modulus to a byte string. | 774 | /// Serializes the modulus to a byte string. |
| ... | @@ -852,8 +929,9 @@ pub fn Modulus(comptime max_bits: comptime_int) type { | ... | @@ -852,8 +929,9 @@ pub fn Modulus(comptime max_bits: comptime_int) type { |
| 852 | } | 929 | } |
| 853 | | 930 | |
| 854 | /// Converts a field element to the Montgomery form. | 931 | /// Converts a field element to the Montgomery form. |
| | 932 | /// Rejects even moduli, which have no Montgomery form. |
| 855 | pub fn toMontgomery(self: Self, x: *Fe) RepresentationError!void { | 933 | pub fn toMontgomery(self: Self, x: *Fe) RepresentationError!void { |
| 856 | if (x.montgomery) { | 934 | if (x.montgomery or !self.v.isOdd()) { |
| 857 | return error.UnexpectedRepresentation; | 935 | return error.UnexpectedRepresentation; |
| 858 | } | 936 | } |
| 859 | self.shrink(x) catch unreachable; | 937 | self.shrink(x) catch unreachable; |
| ... | @@ -862,8 +940,9 @@ pub fn Modulus(comptime max_bits: comptime_int) type { | ... | @@ -862,8 +940,9 @@ pub fn Modulus(comptime max_bits: comptime_int) type { |
| 862 | } | 940 | } |
| 863 | | 941 | |
| 864 | /// Takes a field element out of the Montgomery form. | 942 | /// Takes a field element out of the Montgomery form. |
| | 943 | /// Rejects even moduli, which have no Montgomery form. |
| 865 | pub fn fromMontgomery(self: Self, x: *Fe) RepresentationError!void { | 944 | pub fn fromMontgomery(self: Self, x: *Fe) RepresentationError!void { |
| 866 | if (!x.montgomery) { | 945 | if (!x.montgomery or !self.v.isOdd()) { |
| 867 | return error.UnexpectedRepresentation; | 946 | return error.UnexpectedRepresentation; |
| 868 | } | 947 | } |
| 869 | self.shrink(x) catch unreachable; | 948 | self.shrink(x) catch unreachable; |
| ... | @@ -970,110 +1049,99 @@ pub fn Modulus(comptime max_bits: comptime_int) type { | ... | @@ -970,110 +1049,99 @@ pub fn Modulus(comptime max_bits: comptime_int) type { |
| 970 | return d; | 1049 | return d; |
| 971 | } | 1050 | } |
| 972 | | 1051 | |
| 973 | // Returns x^e (mod m), with the exponent provided as a byte string. | 1052 | fn plainMul(self: Self, x: Fe, y: Fe) Fe { |
| 974 | // `public` must be set to `false` if the exponent it secret. | 1053 | assert(x.limbs_count() == self.limbs_count()); |
| | 1054 | assert(y.limbs_count() == self.limbs_count()); |
| | 1055 | return self.reduce(fullProduct(x.v.limbsConst(), y.v.limbsConst())); |
| | 1056 | } |
| | 1057 | |
| | 1058 | // Set `public` to `false` for secret exponents. |
| 975 | fn powWithEncodedExponentInternal(self: Self, x: Fe, e: []const u8, endian: Endian, comptime public: bool) NullExponentError!Fe { | 1059 | fn powWithEncodedExponentInternal(self: Self, x: Fe, e: []const u8, endian: Endian, comptime public: bool) NullExponentError!Fe { |
| 976 | var acc: u8 = 0; | 1060 | var acc: u8 = 0; |
| 977 | for (e) |b| acc |= b; | 1061 | for (e) |b| acc |= b; |
| 978 | if (acc == 0) return error.NullExponent; | 1062 | if (acc == 0) return error.NullExponent; |
| 979 | | 1063 | |
| 980 | const was_montgomery = x.montgomery; | 1064 | // Modulus parity is public; even moduli need plain arithmetic. |
| | 1065 | if (!self.v.isOdd()) { |
| | 1066 | return self.powLadder(x, e, endian, public, false); |
| | 1067 | } |
| 981 | | 1068 | |
| 982 | var out = self.one(); | 1069 | var x_m = x; |
| 983 | self.toMontgomery(&out) catch unreachable; | 1070 | if (!x.montgomery) { |
| | 1071 | self.toMontgomery(&x_m) catch unreachable; |
| | 1072 | } |
| | 1073 | var out = self.powLadder(x_m, e, endian, public, true); |
| | 1074 | if (!x.montgomery) { |
| | 1075 | self.fromMontgomery(&out) catch unreachable; |
| | 1076 | } |
| | 1077 | return out; |
| | 1078 | } |
| 984 | | 1079 | |
| | 1080 | // `x` and the result use the representation selected by `montgomery`; the exponent must be nonzero. |
| | 1081 | fn powLadder(self: Self, x: Fe, e: []const u8, endian: Endian, comptime public: bool, comptime montgomery: bool) Fe { |
| 985 | if (public and | 1082 | if (public and |
| 986 | (e.len < 3 or (e.len == 3 and e[if (endian == .big) 0 else 2] <= 0b1111))) | 1083 | (e.len < 3 or (e.len == 3 and e[if (endian == .big) 0 else 2] <= 0b1111))) |
| 987 | { | 1084 | { |
| 988 | // Do not use a precomputation table for short, public exponents | 1085 | // Do not use a precomputation table for short, public exponents |
| 989 | var x_m = x; | 1086 | var e_int: u32 = 0; |
| 990 | if (!x.montgomery) { | 1087 | for (0..e.len) |i| { |
| 991 | self.toMontgomery(&x_m) catch unreachable; | 1088 | e_int = (e_int << 8) | e[if (endian == .big) i else e.len - 1 - i]; |
| 992 | } | 1089 | } |
| 993 | var s = switch (endian) { | 1090 | var out = x; |
| 994 | .big => 0, | 1091 | var j = math.log2_int(u32, e_int); |
| 995 | .little => e.len - 1, | 1092 | while (j > 0) { |
| 996 | }; | 1093 | j -= 1; |
| 997 | while (true) { | 1094 | out = if (montgomery) self.montgomerySq(out) else self.plainMul(out, out); |
| 998 | const b = e[s]; | 1095 | if ((e_int >> j) & 1 != 0) { |
| 999 | var j: u3 = 7; | 1096 | out = if (montgomery) self.montgomeryMul(out, x) else self.plainMul(out, x); |
| 1000 | while (true) : (j -= 1) { | | |
| 1001 | out = self.montgomerySq(out); | | |
| 1002 | const k: u1 = @truncate(b >> j); | | |
| 1003 | if (k != 0) { | | |
| 1004 | const t = self.montgomeryMul(out, x_m); | | |
| 1005 | @memcpy(out.v.limbs(), t.v.limbsConst()); | | |
| 1006 | } | | |
| 1007 | if (j == 0) break; | | |
| 1008 | } | | |
| 1009 | switch (endian) { | | |
| 1010 | .big => { | | |
| 1011 | s += 1; | | |
| 1012 | if (s == e.len) break; | | |
| 1013 | }, | | |
| 1014 | .little => { | | |
| 1015 | if (s == 0) break; | | |
| 1016 | s -= 1; | | |
| 1017 | }, | | |
| 1018 | } | 1097 | } |
| 1019 | } | 1098 | } |
| 1020 | } else { | 1099 | return out; |
| 1021 | // Use a precomputation table for large exponents | 1100 | } |
| 1022 | var pc: [15]Fe = [1]Fe{x} ++ @as([14]Fe, @splat(self.zero)); | 1101 | |
| 1023 | if (!x.montgomery) { | 1102 | var pc: [15]Fe = [1]Fe{x} ++ @as([14]Fe, @splat(self.zero)); |
| 1024 | self.toMontgomery(&pc[0]) catch unreachable; | 1103 | for (1..pc.len) |i| { |
| 1025 | } | 1104 | pc[i] = if (montgomery) self.montgomeryMul(pc[i - 1], pc[0]) else self.plainMul(pc[i - 1], pc[0]); |
| 1026 | for (1..pc.len) |i| { | 1105 | } |
| 1027 | pc[i] = self.montgomeryMul(pc[i - 1], pc[0]); | 1106 | var out = self.one(); |
| 1028 | } | 1107 | if (montgomery) { |
| 1029 | var t0 = self.zero; | 1108 | self.toMontgomery(&out) catch unreachable; |
| 1030 | var s = switch (endian) { | 1109 | } |
| 1031 | .big => 0, | 1110 | |
| 1032 | .little => e.len - 1, | 1111 | var t0 = self.zero; |
| 1033 | }; | 1112 | t0.montgomery = montgomery; |
| 1034 | while (true) { | 1113 | for (0..e.len) |s| { |
| 1035 | const b = e[s]; | 1114 | const b = e[if (endian == .big) s else e.len - 1 - s]; |
| 1036 | for ([_]u3{ 4, 0 }) |j| { | 1115 | for ([_]u3{ 4, 0 }) |j| { |
| 1037 | for (0..4) |_| { | 1116 | for (0..4) |_| { |
| 1038 | out = self.montgomerySq(out); | 1117 | out = if (montgomery) self.montgomerySq(out) else self.plainMul(out, out); |
| 1039 | } | 1118 | } |
| 1040 | const k = (b >> j) & 0b1111; | 1119 | const k = (b >> j) & 0b1111; |
| 1041 | if (public or std.options.side_channels_mitigations == .none) { | 1120 | if (public or std.options.side_channels_mitigations == .none) { |
| 1042 | if (k == 0) continue; | 1121 | if (k == 0) continue; |
| 1043 | t0 = pc[k - 1]; | 1122 | t0 = pc[k - 1]; |
| 1044 | } else { | 1123 | } else { |
| 1045 | for (pc, 0..) |t, i| { | 1124 | for (pc, 0..) |t, i| { |
| 1046 | t0.v.cmov(ct.eql(k, @as(u8, @truncate(i + 1))), t.v); | 1125 | t0.v.cmov(ct.eql(k, @truncate(i + 1)), t.v); |
| 1047 | } | | |
| 1048 | } | | |
| 1049 | const t1 = self.montgomeryMul(out, t0); | | |
| 1050 | if (public) { | | |
| 1051 | @memcpy(out.v.limbs(), t1.v.limbsConst()); | | |
| 1052 | } else { | | |
| 1053 | out.v.cmov(!ct.eql(k, 0), t1.v); | | |
| 1054 | } | 1126 | } |
| 1055 | } | 1127 | } |
| 1056 | switch (endian) { | 1128 | const t1 = if (montgomery) self.montgomeryMul(out, t0) else self.plainMul(out, t0); |
| 1057 | .big => { | 1129 | if (public) { |
| 1058 | s += 1; | 1130 | @memcpy(out.v.limbs(), t1.v.limbsConst()); |
| 1059 | if (s == e.len) break; | 1131 | } else { |
| 1060 | }, | 1132 | out.v.cmov(!ct.eql(k, 0), t1.v); |
| 1061 | .little => { | | |
| 1062 | if (s == 0) break; | | |
| 1063 | s -= 1; | | |
| 1064 | }, | | |
| 1065 | } | 1133 | } |
| 1066 | } | 1134 | } |
| 1067 | } | 1135 | } |
| 1068 | if (!was_montgomery) { | | |
| 1069 | self.fromMontgomery(&out) catch unreachable; | | |
| 1070 | } | | |
| 1071 | return out; | 1136 | return out; |
| 1072 | } | 1137 | } |
| 1073 | | 1138 | |
| 1074 | /// Multiplies two field elements. | 1139 | /// Multiplies two field elements. |
| 1075 | /// Result preserves the first operand's form. | 1140 | /// Result preserves the first operand's form. |
| 1076 | pub fn mul(self: Self, x: Fe, y: Fe) Fe { | 1141 | pub fn mul(self: Self, x: Fe, y: Fe) Fe { |
| | 1142 | if (!self.v.isOdd()) { |
| | 1143 | return self.plainMul(x, y); |
| | 1144 | } |
| 1077 | if (x.montgomery) { | 1145 | if (x.montgomery) { |
| 1078 | const y_ = if (!y.montgomery) blk: { | 1146 | const y_ = if (!y.montgomery) blk: { |
| 1079 | var yy = y; | 1147 | var yy = y; |
| ... | @@ -1098,6 +1166,9 @@ pub fn Modulus(comptime max_bits: comptime_int) type { | ... | @@ -1098,6 +1166,9 @@ pub fn Modulus(comptime max_bits: comptime_int) type { |
| 1098 | | 1166 | |
| 1099 | /// Squares a field element. | 1167 | /// Squares a field element. |
| 1100 | pub fn sq(self: Self, x: Fe) Fe { | 1168 | pub fn sq(self: Self, x: Fe) Fe { |
| | 1169 | if (!self.v.isOdd()) { |
| | 1170 | return self.plainMul(x, x); |
| | 1171 | } |
| 1101 | if (x.montgomery) { | 1172 | if (x.montgomery) { |
| 1102 | return self.montgomerySq(x); | 1173 | return self.montgomerySq(x); |
| 1103 | } else { | 1174 | } else { |
| ... | @@ -1612,3 +1683,56 @@ test "field element constants and subOne" { | ... | @@ -1612,3 +1683,56 @@ test "field element constants and subOne" { |
| 1612 | try testing.expectEqual(4, try four.toPrimitive(u8)); | 1683 | try testing.expectEqual(4, try four.toPrimitive(u8)); |
| 1613 | } | 1684 | } |
| 1614 | } | 1685 | } |
| | 1686 | |
| | 1687 | test "modulus from a product" { |
| | 1688 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; |
| | 1689 | |
| | 1690 | const M = Modulus(256); |
| | 1691 | const U = Uint(256); |
| | 1692 | const one = try U.fromPrimitive(u8, 1); |
| | 1693 | const a = (try U.fromPrimitive(u32, 65537)).normalize(); |
| | 1694 | const b = try U.fromPrimitive(u256, (1 << 200) + 12345); |
| | 1695 | const m = try M.fromProduct(a, b); |
| | 1696 | try testing.expectEqual(65537 * ((1 << 200) + 12345), try m.toUint().toPrimitive(u256)); |
| | 1697 | try testing.expect((try M.fromProduct(b, a)).v.eql(m.v)); |
| | 1698 | try testing.expectError(error.EvenModulus, M.fromProduct(U.zero, a)); |
| | 1699 | try testing.expectError(error.ModulusTooSmall, M.fromProduct(one, one)); |
| | 1700 | const two = try U.fromPrimitive(u8, 2); |
| | 1701 | try testing.expectError(error.EvenModulus, M.fromProduct(a, two)); |
| | 1702 | try testing.expectEqual(2 * 65537, try (try M.fromProductWithOptions(a, two, .{ .allow_even = true })).toUint().toPrimitive(u32)); |
| | 1703 | try testing.expectError(error.ModulusTooSmall, M.fromProductWithOptions(U.zero, a, .{ .allow_even = true })); |
| | 1704 | |
| | 1705 | const full = try U.fromBytes(&maxUintBytes(U), .big); |
| | 1706 | try testing.expect((try M.fromProduct(full, one)).toUint().eql(full)); |
| | 1707 | try testing.expectError(error.Overflow, M.fromProduct(full, a)); |
| | 1708 | try testing.expectError(error.Overflow, M.fromProduct(a, full)); |
| | 1709 | const power = try U.fromPrimitive(u256, 1 << 160); |
| | 1710 | try testing.expectError(error.Overflow, M.fromProductWithOptions(power, power, .{ .allow_even = true })); |
| | 1711 | } |
| | 1712 | |
| | 1713 | test "even modulus arithmetic" { |
| | 1714 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; |
| | 1715 | |
| | 1716 | const M = Modulus(256); |
| | 1717 | const opts: M.Options = .{ .allow_even = true }; |
| | 1718 | try testing.expectError(error.ModulusTooSmall, M.fromPrimitiveWithOptions(u8, 0, opts)); |
| | 1719 | |
| | 1720 | const m2 = try M.fromPrimitiveWithOptions(u8, 2, opts); |
| | 1721 | var one = m2.one(); |
| | 1722 | try testing.expect(m2.isOne(m2.mul(one, one))); |
| | 1723 | try testing.expectError(error.UnexpectedRepresentation, m2.toMontgomery(&one)); |
| | 1724 | one.montgomery = true; |
| | 1725 | try testing.expectError(error.UnexpectedRepresentation, m2.fromMontgomery(&one)); |
| | 1726 | |
| | 1727 | const m = try M.fromPrimitiveWithOptions(u256, ((1 << 127) - 1) * 2, opts); |
| | 1728 | const x = try M.Fe.fromPrimitive(u256, m, (1 << 126) + 12345); |
| | 1729 | const e = [_]u8{ 0x0f, 0xff, 0xff }; |
| | 1730 | const expected: u256 = 13725650424532690062485077506887663427; |
| | 1731 | try testing.expectEqual(expected, try (try m.powWithEncodedPublicExponent(x, &e, .big)).toPrimitive(u256)); |
| | 1732 | try testing.expectEqual(expected, try (try m.powWithEncodedExponent(x, &e, .big)).toPrimitive(u256)); |
| | 1733 | |
| | 1734 | var cap = maxUintBytes(Uint(256)); |
| | 1735 | cap[cap.len - 1] = 0xfe; |
| | 1736 | const mc = try M.fromBytesWithOptions(&cap, .big, opts); |
| | 1737 | try testing.expect(mc.isOne(mc.sq(mc.subOne(mc.zero)))); |
| | 1738 | } |