| ... | @@ -55,14 +55,18 @@ pub const RepresentationError = error{UnexpectedRepresentation}; | ... | @@ -55,14 +55,18 @@ pub const RepresentationError = error{UnexpectedRepresentation}; |
| 55 | pub const Error = OverflowError || InvalidModulusError || NullExponentError || FieldElementError || RepresentationError; | 55 | pub const Error = OverflowError || InvalidModulusError || NullExponentError || FieldElementError || RepresentationError; |
| 56 | | 56 | |
| 57 | /// An unsigned big integer with a fixed maximum size (`max_bits`), suitable for cryptographic operations. | 57 | /// An unsigned big integer with a fixed maximum size (`max_bits`), suitable for cryptographic operations. |
| | 58 | /// Storage rounds up to whole limbs and can hold up to `capacity_bits` bits. |
| 58 | /// Unless side-channels mitigations are explicitly disabled, operations are designed to be constant-time. | 59 | /// Unless side-channels mitigations are explicitly disabled, operations are designed to be constant-time. |
| 59 | pub fn Uint(comptime max_bits: comptime_int) type { | 60 | pub fn Uint(comptime max_bits: comptime_int) type { |
| 60 | comptime assert(@bitSizeOf(Limb) % 8 == 0); // Limb size must be a multiple of 8 | 61 | comptime assert(@bitSizeOf(Limb) % 8 == 0); // Limb size must be a multiple of 8 |
| | 62 | comptime assert(max_bits > 0); |
| 61 | | 63 | |
| 62 | return struct { | 64 | return struct { |
| 63 | const Self = @This(); | 65 | const Self = @This(); |
| 64 | const max_limbs_count = @divCeil(max_bits, t_bits); | 66 | const max_limbs_count = @divCeil(max_bits, t_bits); |
| 65 | | 67 | |
| | 68 | pub const capacity_bits = max_limbs_count * t_bits; |
| | 69 | |
| 66 | limbs_buffer: [max_limbs_count]Limb, | 70 | limbs_buffer: [max_limbs_count]Limb, |
| 67 | /// The number of active limbs. | 71 | /// The number of active limbs. |
| 68 | limbs_len: usize, | 72 | limbs_len: usize, |
| ... | @@ -102,14 +106,15 @@ pub fn Uint(comptime max_bits: comptime_int) type { | ... | @@ -102,14 +106,15 @@ pub fn Uint(comptime max_bits: comptime_int) type { |
| 102 | /// Creates a new big integer from a primitive type. | 106 | /// Creates a new big integer from a primitive type. |
| 103 | /// This function may not run in constant time. | 107 | /// This function may not run in constant time. |
| 104 | pub fn fromPrimitive(comptime T: type, init_value: T) OverflowError!Self { | 108 | pub fn fromPrimitive(comptime T: type, init_value: T) OverflowError!Self { |
| 105 | var x = init_value; | 109 | const U = @Int(.unsigned, @bitSizeOf(T)); |
| | 110 | var x = math.cast(U, init_value) orelse return error.Overflow; |
| 106 | var out: Self = .{ | 111 | var out: Self = .{ |
| 107 | .limbs_buffer = undefined, | 112 | .limbs_buffer = undefined, |
| 108 | .limbs_len = max_limbs_count, | 113 | .limbs_len = max_limbs_count, |
| 109 | }; | 114 | }; |
| 110 | for (&out.limbs_buffer) |*limb| { | 115 | for (&out.limbs_buffer) |*limb| { |
| 111 | limb.* = if (@bitSizeOf(T) > t_bits) @as(TLimb, @truncate(x)) else x; | 116 | limb.* = if (@bitSizeOf(T) > t_bits) @as(TLimb, @truncate(x)) else x; |
| 112 | x = math.shr(T, x, t_bits); | 117 | x = math.shr(U, x, t_bits); |
| 113 | } | 118 | } |
| 114 | if (x != 0) { | 119 | if (x != 0) { |
| 115 | return error.Overflow; | 120 | return error.Overflow; |
| ... | @@ -120,18 +125,21 @@ pub fn Uint(comptime max_bits: comptime_int) type { | ... | @@ -120,18 +125,21 @@ pub fn Uint(comptime max_bits: comptime_int) type { |
| 120 | /// Converts a big integer to a primitive type. | 125 | /// Converts a big integer to a primitive type. |
| 121 | /// This function may not run in constant time. | 126 | /// This function may not run in constant time. |
| 122 | pub fn toPrimitive(self: Self, comptime T: type) OverflowError!T { | 127 | pub fn toPrimitive(self: Self, comptime T: type) OverflowError!T { |
| 123 | var x: T = 0; | 128 | const U = @Int(.unsigned, @bitSizeOf(T)); |
| | 129 | var x: U = 0; |
| 124 | var i = self.limbs_len - 1; | 130 | var i = self.limbs_len - 1; |
| 125 | while (true) : (i -= 1) { | 131 | while (true) : (i -= 1) { |
| 126 | if (@bitSizeOf(T) >= t_bits and math.shr(T, x, @bitSizeOf(T) - t_bits) != 0) { | 132 | // Check for overflow before shifting, even when the destination is narrower than a limb. |
| | 133 | const discarded = if (@bitSizeOf(U) >= t_bits) math.shr(U, x, @bitSizeOf(U) - t_bits) else x; |
| | 134 | if (discarded != 0) { |
| 127 | return error.Overflow; | 135 | return error.Overflow; |
| 128 | } | 136 | } |
| 129 | x = math.shl(T, x, t_bits); | 137 | x = math.shl(U, x, t_bits); |
| 130 | const v = math.cast(T, self.limbsConst()[i]) orelse return error.Overflow; | 138 | const v = math.cast(U, self.limbsConst()[i]) orelse return error.Overflow; |
| 131 | x |= v; | 139 | x |= v; |
| 132 | if (i == 0) break; | 140 | if (i == 0) break; |
| 133 | } | 141 | } |
| 134 | return x; | 142 | return math.cast(T, x) orelse error.Overflow; |
| 135 | } | 143 | } |
| 136 | | 144 | |
| 137 | /// Encodes a big integer into a byte array. | 145 | /// Encodes a big integer into a byte array. |
| ... | @@ -150,15 +158,15 @@ pub fn Uint(comptime max_bits: comptime_int) type { | ... | @@ -150,15 +158,15 @@ pub fn Uint(comptime max_bits: comptime_int) type { |
| 150 | var remaining_bits = t_bits; | 158 | var remaining_bits = t_bits; |
| 151 | var limb = self.limbsConst()[i]; | 159 | var limb = self.limbsConst()[i]; |
| 152 | while (remaining_bits >= 8) { | 160 | while (remaining_bits >= 8) { |
| 153 | bytes[out_i] |= math.shl(u8, @as(u8, @truncate(limb)), shift); | 161 | bytes[out_i] |= math.shl(u8, @truncate(limb), shift); |
| 154 | const consumed = 8 - shift; | 162 | const consumed = 8 - shift; |
| 155 | limb >>= @as(u4, @truncate(consumed)); | 163 | limb >>= @truncate(consumed); |
| 156 | remaining_bits -= consumed; | 164 | remaining_bits -= consumed; |
| 157 | shift = 0; | 165 | shift = 0; |
| 158 | switch (endian) { | 166 | switch (endian) { |
| 159 | .big => { | 167 | .big => { |
| 160 | if (out_i == 0) { | 168 | if (out_i == 0) { |
| 161 | if (i != self.limbs_len - 1 or limb != 0) { | 169 | if (limb | orLimbs(self.limbsConst()[i + 1 ..]) != 0) { |
| 162 | return error.Overflow; | 170 | return error.Overflow; |
| 163 | } | 171 | } |
| 164 | return; | 172 | return; |
| ... | @@ -168,7 +176,7 @@ pub fn Uint(comptime max_bits: comptime_int) type { | ... | @@ -168,7 +176,7 @@ pub fn Uint(comptime max_bits: comptime_int) type { |
| 168 | .little => { | 176 | .little => { |
| 169 | out_i += 1; | 177 | out_i += 1; |
| 170 | if (out_i == bytes.len) { | 178 | if (out_i == bytes.len) { |
| 171 | if (i != self.limbs_len - 1 or limb != 0) { | 179 | if (limb | orLimbs(self.limbsConst()[i + 1 ..]) != 0) { |
| 172 | return error.Overflow; | 180 | return error.Overflow; |
| 173 | } | 181 | } |
| 174 | return; | 182 | return; |
| ... | @@ -176,48 +184,16 @@ pub fn Uint(comptime max_bits: comptime_int) type { | ... | @@ -176,48 +184,16 @@ pub fn Uint(comptime max_bits: comptime_int) type { |
| 176 | }, | 184 | }, |
| 177 | } | 185 | } |
| 178 | } | 186 | } |
| 179 | bytes[out_i] |= @as(u8, @truncate(limb)); | 187 | bytes[out_i] |= @truncate(limb); |
| 180 | shift = remaining_bits; | 188 | shift = remaining_bits; |
| 181 | } | 189 | } |
| 182 | } | 190 | } |
| 183 | | 191 | |
| 184 | /// Creates a new big integer from a byte array. | 192 | /// Creates a new big integer from a byte array. |
| 185 | pub fn fromBytes(bytes: []const u8, comptime endian: Endian) OverflowError!Self { | 193 | pub fn fromBytes(bytes: []const u8, comptime endian: Endian) OverflowError!Self { |
| 186 | if (bytes.len == 0) return Self.zero; | | |
| 187 | var shift: usize = 0; | | |
| 188 | var out = Self.zero; | 194 | var out = Self.zero; |
| 189 | var out_i: usize = 0; | 195 | if (decodeBytes(out.limbs(), bytes, endian) != 0) { |
| 190 | var i: usize = switch (endian) { | 196 | return error.Overflow; |
| 191 | .big => bytes.len - 1, | | |
| 192 | .little => 0, | | |
| 193 | }; | | |
| 194 | while (true) { | | |
| 195 | const bi = bytes[i]; | | |
| 196 | out.limbs()[out_i] |= math.shl(Limb, bi, shift); | | |
| 197 | shift += 8; | | |
| 198 | if (shift >= t_bits) { | | |
| 199 | shift -= t_bits; | | |
| 200 | out.limbs()[out_i] = @as(TLimb, @truncate(out.limbs()[out_i])); | | |
| 201 | const overflow = math.shr(Limb, bi, 8 - shift); | | |
| 202 | out_i += 1; | | |
| 203 | if (out_i >= out.limbs_len) { | | |
| 204 | if (overflow != 0 or i != 0) { | | |
| 205 | return error.Overflow; | | |
| 206 | } | | |
| 207 | break; | | |
| 208 | } | | |
| 209 | out.limbs()[out_i] = overflow; | | |
| 210 | } | | |
| 211 | switch (endian) { | | |
| 212 | .big => { | | |
| 213 | if (i == 0) break; | | |
| 214 | i -= 1; | | |
| 215 | }, | | |
| 216 | .little => { | | |
| 217 | i += 1; | | |
| 218 | if (i == bytes.len) break; | | |
| 219 | }, | | |
| 220 | } | | |
| 221 | } | 197 | } |
| 222 | return out; | 198 | return out; |
| 223 | } | 199 | } |
| ... | @@ -239,11 +215,7 @@ pub fn Uint(comptime max_bits: comptime_int) type { | ... | @@ -239,11 +215,7 @@ pub fn Uint(comptime max_bits: comptime_int) type { |
| 239 | | 215 | |
| 240 | /// Returns `true` if the integer is zero. | 216 | /// Returns `true` if the integer is zero. |
| 241 | pub fn isZero(x: Self) bool { | 217 | pub fn isZero(x: Self) bool { |
| 242 | var t: Limb = 0; | 218 | return ct.eql(orLimbs(x.limbsConst()), 0); |
| 243 | for (x.limbsConst()) |elem| { | | |
| 244 | t |= elem; | | |
| 245 | } | | |
| 246 | return ct.eql(t, 0); | | |
| 247 | } | 219 | } |
| 248 | | 220 | |
| 249 | /// Returns `true` if the integer is odd. | 221 | /// Returns `true` if the integer is odd. |
| ... | @@ -376,6 +348,47 @@ fn Fe_(comptime bits: comptime_int) type { | ... | @@ -376,6 +348,47 @@ fn Fe_(comptime bits: comptime_int) type { |
| 376 | }; | 348 | }; |
| 377 | } | 349 | } |
| 378 | | 350 | |
| | 351 | // Decodes into zeroed limbs and returns the OR of the bits that didn't fit. |
| | 352 | fn decodeBytes(limbs: []Limb, bytes: []const u8, comptime endian: Endian) Limb { |
| | 353 | var acc: Limb = 0; |
| | 354 | var shift: usize = 0; |
| | 355 | var out_i: usize = 0; |
| | 356 | for (0..bytes.len) |k| { |
| | 357 | const bi = bytes[ |
| | 358 | switch (endian) { |
| | 359 | .big => bytes.len - 1 - k, |
| | 360 | .little => k, |
| | 361 | } |
| | 362 | ]; |
| | 363 | if (out_i >= limbs.len) { |
| | 364 | acc |= bi; |
| | 365 | continue; |
| | 366 | } |
| | 367 | limbs[out_i] |= math.shl(Limb, bi, shift); |
| | 368 | shift += 8; |
| | 369 | if (shift >= t_bits) { |
| | 370 | shift -= t_bits; |
| | 371 | limbs[out_i] = @as(TLimb, @truncate(limbs[out_i])); |
| | 372 | const spill = math.shr(Limb, bi, 8 - shift); |
| | 373 | out_i += 1; |
| | 374 | if (out_i < limbs.len) { |
| | 375 | limbs[out_i] = spill; |
| | 376 | } else { |
| | 377 | acc |= spill; |
| | 378 | } |
| | 379 | } |
| | 380 | } |
| | 381 | return acc; |
| | 382 | } |
| | 383 | |
| | 384 | fn orLimbs(limbs: []const Limb) Limb { |
| | 385 | var t: Limb = 0; |
| | 386 | for (limbs) |limb| { |
| | 387 | t |= limb; |
| | 388 | } |
| | 389 | return t; |
| | 390 | } |
| | 391 | |
| 379 | /// A modulus, defining a finite field. | 392 | /// A modulus, defining a finite field. |
| 380 | /// All operations within the field are performed modulo this modulus, without heap allocations. | 393 | /// All operations within the field are performed modulo this modulus, without heap allocations. |
| 381 | /// `max_bits` represents the number of bits in the maximum value the modulus can be set to. | 394 | /// `max_bits` represents the number of bits in the maximum value the modulus can be set to. |
| ... | @@ -411,6 +424,11 @@ pub fn Modulus(comptime max_bits: comptime_int) type { | ... | @@ -411,6 +424,11 @@ pub fn Modulus(comptime max_bits: comptime_int) type { |
| 411 | return self.limbs_count() * t_bits - self.leading; | 424 | return self.limbs_count() * t_bits - self.leading; |
| 412 | } | 425 | } |
| 413 | | 426 | |
| | 427 | /// Returns the encoded length, in bytes. |
| | 428 | pub fn encodedLen(self: Self) usize { |
| | 429 | return @divCeil(self.bits(), 8); |
| | 430 | } |
| | 431 | |
| 414 | /// Returns the element `1`. | 432 | /// Returns the element `1`. |
| 415 | pub fn one(self: Self) Fe { | 433 | pub fn one(self: Self) Fe { |
| 416 | var fe = self.zero; | 434 | var fe = self.zero; |
| ... | @@ -474,6 +492,11 @@ pub fn Modulus(comptime max_bits: comptime_int) type { | ... | @@ -474,6 +492,11 @@ pub fn Modulus(comptime max_bits: comptime_int) type { |
| 474 | return self.v.toBytes(bytes, endian); | 492 | return self.v.toBytes(bytes, endian); |
| 475 | } | 493 | } |
| 476 | | 494 | |
| | 495 | /// Returns the modulus as an integer |
| | 496 | pub fn toUint(self: Self) FeUint { |
| | 497 | return self.v; |
| | 498 | } |
| | 499 | |
| 477 | /// Rejects field elements that are not in the canonical form. | 500 | /// Rejects field elements that are not in the canonical form. |
| 478 | pub fn rejectNonCanonical(self: Self, fe: Fe) error{NonCanonical}!void { | 501 | pub fn rejectNonCanonical(self: Self, fe: Fe) error{NonCanonical}!void { |
| 479 | if (fe.limbs_count() != self.limbs_count() or ct.limbsCmpGeq(fe.v, self.v)) { | 502 | if (fe.limbs_count() != self.limbs_count() or ct.limbsCmpGeq(fe.v, self.v)) { |
| ... | @@ -1085,3 +1108,100 @@ test ct { | ... | @@ -1085,3 +1108,100 @@ test ct { |
| 1085 | try testCt(ct_protected); | 1108 | try testCt(ct_protected); |
| 1086 | try testCt(ct_unprotected); | 1109 | try testCt(ct_unprotected); |
| 1087 | } | 1110 | } |
| | 1111 | |
| | 1112 | fn expectWellFormedLimbs(x: anytype) !void { |
| | 1113 | for (x.limbsConst()) |limb| { |
| | 1114 | try testing.expect(limb <= math.maxInt(TLimb)); |
| | 1115 | } |
| | 1116 | for (x.limbs_buffer[x.limbs_len..]) |limb| { |
| | 1117 | try testing.expectEqual(0, limb); |
| | 1118 | } |
| | 1119 | } |
| | 1120 | |
| | 1121 | // Big-endian encoding of the largest value a `Uint` can store. |
| | 1122 | fn maxUintBytes(comptime U: type) [@divCeil(U.capacity_bits, 8)]u8 { |
| | 1123 | var buf: [@divCeil(U.capacity_bits, 8)]u8 = @splat(0xff); |
| | 1124 | buf[0] = 0xff >> (8 * buf.len - U.capacity_bits); |
| | 1125 | return buf; |
| | 1126 | } |
| | 1127 | |
| | 1128 | test "modulus creation" { |
| | 1129 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; |
| | 1130 | |
| | 1131 | const M = Modulus(256); |
| | 1132 | try testing.expectError(error.EvenModulus, M.fromPrimitive(u8, 0)); |
| | 1133 | try testing.expectError(error.ModulusTooSmall, M.fromPrimitive(u8, 1)); |
| | 1134 | try testing.expectError(error.EvenModulus, M.fromPrimitive(u8, 2)); |
| | 1135 | for ([_]u65{ 3, 255, (1 << 64) + 1 }) |v| { |
| | 1136 | const m = try M.fromPrimitive(u65, v); |
| | 1137 | try testing.expectEqual(v, try m.toUint().toPrimitive(u65)); |
| | 1138 | try testing.expectEqual(@divCeil(65 - @clz(v), 8), m.encodedLen()); |
| | 1139 | try testing.expect((try M.fromUint(m.toUint())).v.eql(m.v)); |
| | 1140 | } |
| | 1141 | const cap = maxUintBytes(Uint(256)); |
| | 1142 | try testing.expectEqual(cap.len, (try M.fromBytes(&cap, .big)).encodedLen()); |
| | 1143 | } |
| | 1144 | |
| | 1145 | test "Uint serialization" { |
| | 1146 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; |
| | 1147 | |
| | 1148 | const U = Uint(256); |
| | 1149 | const x = try U.fromPrimitive(u128, (1 << t_bits) + 5); |
| | 1150 | try testing.expectError(error.Overflow, Uint(64).fromPrimitive(u256, 1 << 200)); |
| | 1151 | try testing.expectError(error.Overflow, x.toPrimitive(u8)); |
| | 1152 | try testing.expectError(error.Overflow, x.toPrimitive(@Int(.unsigned, t_bits))); |
| | 1153 | try testing.expectEqual((1 << t_bits) + 5, try x.toPrimitive(@Int(.unsigned, t_bits + 1))); |
| | 1154 | const max = try U.fromPrimitive(u128, (1 << t_bits) - 1); |
| | 1155 | try testing.expectEqual((1 << t_bits) - 1, try max.toPrimitive(@Int(.unsigned, t_bits))); |
| | 1156 | try testing.expectError(error.Overflow, max.toPrimitive(@Int(.unsigned, t_bits - 1))); |
| | 1157 | const signed_max = try U.fromPrimitive(u128, math.maxInt(i128)); |
| | 1158 | try testing.expectEqual(math.maxInt(i128), try signed_max.toPrimitive(i128)); |
| | 1159 | try testing.expectError(error.Overflow, (try U.fromPrimitive(u128, 1 << 127)).toPrimitive(i128)); |
| | 1160 | |
| | 1161 | inline for (.{ .big, .little }) |endian| { |
| | 1162 | const zero = try U.fromBytes(&.{}, endian); |
| | 1163 | var empty: [0]u8 = .{}; |
| | 1164 | try zero.toBytes(&empty, endian); |
| | 1165 | try testing.expectError(error.Overflow, x.toBytes(&empty, endian)); |
| | 1166 | var tight: [16]u8 = undefined; |
| | 1167 | try x.toBytes(&tight, endian); |
| | 1168 | try testing.expectEqual((1 << t_bits) + 5, mem.readInt(u128, &tight, endian)); |
| | 1169 | try testing.expect(x.eql(try U.fromBytes(&tight, endian))); |
| | 1170 | var normalized: [16]u8 = undefined; |
| | 1171 | try x.normalize().toBytes(&normalized, endian); |
| | 1172 | try testing.expectEqualSlices(u8, &tight, &normalized); |
| | 1173 | |
| | 1174 | var cap = maxUintBytes(U); |
| | 1175 | if (endian == .little) mem.reverse(u8, &cap); |
| | 1176 | const full = try U.fromBytes(&cap, endian); |
| | 1177 | try expectWellFormedLimbs(full); |
| | 1178 | var out: [cap.len]u8 = undefined; |
| | 1179 | try full.toBytes(&out, endian); |
| | 1180 | try testing.expectEqualSlices(u8, &cap, &out); |
| | 1181 | out[if (endian == .big) 0 else out.len - 1] |= 1 << (U.capacity_bits % 8); |
| | 1182 | try testing.expectError(error.Overflow, U.fromBytes(&out, endian)); |
| | 1183 | var padded = if (endian == .big) [_]u8{0} ++ cap else cap ++ [_]u8{0}; |
| | 1184 | try testing.expect(full.eql(try U.fromBytes(&padded, endian))); |
| | 1185 | padded[if (endian == .big) 0 else padded.len - 1] = 1; |
| | 1186 | try testing.expectError(error.Overflow, U.fromBytes(&padded, endian)); |
| | 1187 | } |
| | 1188 | } |
| | 1189 | |
| | 1190 | test "field element decoding" { |
| | 1191 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; |
| | 1192 | |
| | 1193 | const M = Modulus(256); |
| | 1194 | const mv: u256 = (1 << 190) + 33; |
| | 1195 | const m = try M.fromPrimitive(u256, mv); |
| | 1196 | try testing.expectError(error.NonCanonical, M.Fe.fromPrimitive(u256, m, mv)); |
| | 1197 | try testing.expectError(error.Overflow, M.Fe.fromPrimitive(u256, m, 1 << 255)); |
| | 1198 | |
| | 1199 | inline for (.{ .big, .little }) |endian| { |
| | 1200 | var buf: [32]u8 = undefined; |
| | 1201 | try m.toBytes(&buf, endian); |
| | 1202 | try testing.expectError(error.NonCanonical, M.Fe.fromBytes(m, &buf, endian)); |
| | 1203 | const x = try M.Fe.fromPrimitive(u256, m, mv - 1); |
| | 1204 | try x.toBytes(&buf, endian); |
| | 1205 | try testing.expect(x.eql(try M.Fe.fromBytes(m, &buf, endian))); |
| | 1206 | } |
| | 1207 | } |