| ... | @@ -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, |
| ... | @@ -154,15 +158,15 @@ pub fn Uint(comptime max_bits: comptime_int) type { | ... | @@ -154,15 +158,15 @@ pub fn Uint(comptime max_bits: comptime_int) type { |
| 154 | var remaining_bits = t_bits; | 158 | var remaining_bits = t_bits; |
| 155 | var limb = self.limbsConst()[i]; | 159 | var limb = self.limbsConst()[i]; |
| 156 | while (remaining_bits >= 8) { | 160 | while (remaining_bits >= 8) { |
| 157 | bytes[out_i] |= math.shl(u8, @as(u8, @truncate(limb)), shift); | 161 | bytes[out_i] |= math.shl(u8, @truncate(limb), shift); |
| 158 | const consumed = 8 - shift; | 162 | const consumed = 8 - shift; |
| 159 | limb >>= @as(u4, @truncate(consumed)); | 163 | limb >>= @truncate(consumed); |
| 160 | remaining_bits -= consumed; | 164 | remaining_bits -= consumed; |
| 161 | shift = 0; | 165 | shift = 0; |
| 162 | switch (endian) { | 166 | switch (endian) { |
| 163 | .big => { | 167 | .big => { |
| 164 | if (out_i == 0) { | 168 | if (out_i == 0) { |
| 165 | if (i != self.limbs_len - 1 or limb != 0) { | 169 | if (limb | orLimbs(self.limbsConst()[i + 1 ..]) != 0) { |
| 166 | return error.Overflow; | 170 | return error.Overflow; |
| 167 | } | 171 | } |
| 168 | return; | 172 | return; |
| ... | @@ -172,7 +176,7 @@ pub fn Uint(comptime max_bits: comptime_int) type { | ... | @@ -172,7 +176,7 @@ pub fn Uint(comptime max_bits: comptime_int) type { |
| 172 | .little => { | 176 | .little => { |
| 173 | out_i += 1; | 177 | out_i += 1; |
| 174 | if (out_i == bytes.len) { | 178 | if (out_i == bytes.len) { |
| 175 | if (i != self.limbs_len - 1 or limb != 0) { | 179 | if (limb | orLimbs(self.limbsConst()[i + 1 ..]) != 0) { |
| 176 | return error.Overflow; | 180 | return error.Overflow; |
| 177 | } | 181 | } |
| 178 | return; | 182 | return; |
| ... | @@ -180,48 +184,16 @@ pub fn Uint(comptime max_bits: comptime_int) type { | ... | @@ -180,48 +184,16 @@ pub fn Uint(comptime max_bits: comptime_int) type { |
| 180 | }, | 184 | }, |
| 181 | } | 185 | } |
| 182 | } | 186 | } |
| 183 | bytes[out_i] |= @as(u8, @truncate(limb)); | 187 | bytes[out_i] |= @truncate(limb); |
| 184 | shift = remaining_bits; | 188 | shift = remaining_bits; |
| 185 | } | 189 | } |
| 186 | } | 190 | } |
| 187 | | 191 | |
| 188 | /// Creates a new big integer from a byte array. | 192 | /// Creates a new big integer from a byte array. |
| 189 | pub fn fromBytes(bytes: []const u8, comptime endian: Endian) OverflowError!Self { | 193 | pub fn fromBytes(bytes: []const u8, comptime endian: Endian) OverflowError!Self { |
| 190 | if (bytes.len == 0) return Self.zero; | | |
| 191 | var shift: usize = 0; | | |
| 192 | var out = Self.zero; | 194 | var out = Self.zero; |
| 193 | var out_i: usize = 0; | 195 | if (decodeBytes(out.limbs(), bytes, endian) != 0) { |
| 194 | var i: usize = switch (endian) { | 196 | return error.Overflow; |
| 195 | .big => bytes.len - 1, | | |
| 196 | .little => 0, | | |
| 197 | }; | | |
| 198 | while (true) { | | |
| 199 | const bi = bytes[i]; | | |
| 200 | out.limbs()[out_i] |= math.shl(Limb, bi, shift); | | |
| 201 | shift += 8; | | |
| 202 | if (shift >= t_bits) { | | |
| 203 | shift -= t_bits; | | |
| 204 | out.limbs()[out_i] = @as(TLimb, @truncate(out.limbs()[out_i])); | | |
| 205 | const overflow = math.shr(Limb, bi, 8 - shift); | | |
| 206 | out_i += 1; | | |
| 207 | if (out_i >= out.limbs_len) { | | |
| 208 | if (overflow != 0 or i != 0) { | | |
| 209 | return error.Overflow; | | |
| 210 | } | | |
| 211 | break; | | |
| 212 | } | | |
| 213 | out.limbs()[out_i] = overflow; | | |
| 214 | } | | |
| 215 | switch (endian) { | | |
| 216 | .big => { | | |
| 217 | if (i == 0) break; | | |
| 218 | i -= 1; | | |
| 219 | }, | | |
| 220 | .little => { | | |
| 221 | i += 1; | | |
| 222 | if (i == bytes.len) break; | | |
| 223 | }, | | |
| 224 | } | | |
| 225 | } | 197 | } |
| 226 | return out; | 198 | return out; |
| 227 | } | 199 | } |
| ... | @@ -243,11 +215,7 @@ pub fn Uint(comptime max_bits: comptime_int) type { | ... | @@ -243,11 +215,7 @@ pub fn Uint(comptime max_bits: comptime_int) type { |
| 243 | | 215 | |
| 244 | /// Returns `true` if the integer is zero. | 216 | /// Returns `true` if the integer is zero. |
| 245 | pub fn isZero(x: Self) bool { | 217 | pub fn isZero(x: Self) bool { |
| 246 | var t: Limb = 0; | 218 | return ct.eql(orLimbs(x.limbsConst()), 0); |
| 247 | for (x.limbsConst()) |elem| { | | |
| 248 | t |= elem; | | |
| 249 | } | | |
| 250 | return ct.eql(t, 0); | | |
| 251 | } | 219 | } |
| 252 | | 220 | |
| 253 | /// Returns `true` if the integer is odd. | 221 | /// Returns `true` if the integer is odd. |
| ... | @@ -380,6 +348,47 @@ fn Fe_(comptime bits: comptime_int) type { | ... | @@ -380,6 +348,47 @@ fn Fe_(comptime bits: comptime_int) type { |
| 380 | }; | 348 | }; |
| 381 | } | 349 | } |
| 382 | | 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 | |
| 383 | /// A modulus, defining a finite field. | 392 | /// A modulus, defining a finite field. |
| 384 | /// 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. |
| 385 | /// `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. |
| ... | @@ -1090,7 +1099,40 @@ test ct { | ... | @@ -1090,7 +1099,40 @@ test ct { |
| 1090 | try testCt(ct_unprotected); | 1099 | try testCt(ct_unprotected); |
| 1091 | } | 1100 | } |
| 1092 | | 1101 | |
| 1093 | test "Uint serialization and predicates" { | 1102 | fn expectWellFormedLimbs(x: anytype) !void { |
| | 1103 | for (x.limbsConst()) |limb| { |
| | 1104 | try testing.expect(limb <= math.maxInt(TLimb)); |
| | 1105 | } |
| | 1106 | for (x.limbs_buffer[x.limbs_len..]) |limb| { |
| | 1107 | try testing.expectEqual(0, limb); |
| | 1108 | } |
| | 1109 | } |
| | 1110 | |
| | 1111 | // Big-endian encoding of the largest value a `Uint` can store. |
| | 1112 | fn maxUintBytes(comptime U: type) [@divCeil(U.capacity_bits, 8)]u8 { |
| | 1113 | var buf: [@divCeil(U.capacity_bits, 8)]u8 = @splat(0xff); |
| | 1114 | buf[0] = 0xff >> (8 * buf.len - U.capacity_bits); |
| | 1115 | return buf; |
| | 1116 | } |
| | 1117 | |
| | 1118 | test "modulus creation" { |
| | 1119 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; |
| | 1120 | |
| | 1121 | const M = Modulus(256); |
| | 1122 | try testing.expectError(error.EvenModulus, M.fromPrimitive(u8, 0)); |
| | 1123 | try testing.expectError(error.ModulusTooSmall, M.fromPrimitive(u8, 1)); |
| | 1124 | try testing.expectError(error.EvenModulus, M.fromPrimitive(u8, 2)); |
| | 1125 | for ([_]u65{ 3, 255, (1 << 64) + 1 }) |v| { |
| | 1126 | const m = try M.fromPrimitive(u65, v); |
| | 1127 | try testing.expectEqual(v, try m.toUint().toPrimitive(u65)); |
| | 1128 | try testing.expectEqual(@divCeil(65 - @clz(v), 8), m.encodedLen()); |
| | 1129 | try testing.expect((try M.fromUint(m.toUint())).v.eql(m.v)); |
| | 1130 | } |
| | 1131 | const cap = maxUintBytes(Uint(256)); |
| | 1132 | try testing.expectEqual(cap.len, (try M.fromBytes(&cap, .big)).encodedLen()); |
| | 1133 | } |
| | 1134 | |
| | 1135 | test "Uint serialization" { |
| 1094 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; | 1136 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; |
| 1095 | | 1137 | |
| 1096 | const U = Uint(256); | 1138 | const U = Uint(256); |
| ... | @@ -1105,4 +1147,51 @@ test "Uint serialization and predicates" { | ... | @@ -1105,4 +1147,51 @@ test "Uint serialization and predicates" { |
| 1105 | const signed_max = try U.fromPrimitive(u128, math.maxInt(i128)); | 1147 | const signed_max = try U.fromPrimitive(u128, math.maxInt(i128)); |
| 1106 | try testing.expectEqual(math.maxInt(i128), try signed_max.toPrimitive(i128)); | 1148 | try testing.expectEqual(math.maxInt(i128), try signed_max.toPrimitive(i128)); |
| 1107 | try testing.expectError(error.Overflow, (try U.fromPrimitive(u128, 1 << 127)).toPrimitive(i128)); | 1149 | try testing.expectError(error.Overflow, (try U.fromPrimitive(u128, 1 << 127)).toPrimitive(i128)); |
| | 1150 | |
| | 1151 | inline for (.{ .big, .little }) |endian| { |
| | 1152 | const zero = try U.fromBytes(&.{}, endian); |
| | 1153 | var empty: [0]u8 = .{}; |
| | 1154 | try zero.toBytes(&empty, endian); |
| | 1155 | try testing.expectError(error.Overflow, x.toBytes(&empty, endian)); |
| | 1156 | var tight: [16]u8 = undefined; |
| | 1157 | try x.toBytes(&tight, endian); |
| | 1158 | try testing.expectEqual((1 << t_bits) + 5, mem.readInt(u128, &tight, endian)); |
| | 1159 | try testing.expect(x.eql(try U.fromBytes(&tight, endian))); |
| | 1160 | var normalized: [16]u8 = undefined; |
| | 1161 | try x.normalize().toBytes(&normalized, endian); |
| | 1162 | try testing.expectEqualSlices(u8, &tight, &normalized); |
| | 1163 | |
| | 1164 | var cap = maxUintBytes(U); |
| | 1165 | if (endian == .little) mem.reverse(u8, &cap); |
| | 1166 | const full = try U.fromBytes(&cap, endian); |
| | 1167 | try expectWellFormedLimbs(full); |
| | 1168 | var out: [cap.len]u8 = undefined; |
| | 1169 | try full.toBytes(&out, endian); |
| | 1170 | try testing.expectEqualSlices(u8, &cap, &out); |
| | 1171 | out[if (endian == .big) 0 else out.len - 1] |= 1 << (U.capacity_bits % 8); |
| | 1172 | try testing.expectError(error.Overflow, U.fromBytes(&out, endian)); |
| | 1173 | var padded = if (endian == .big) [_]u8{0} ++ cap else cap ++ [_]u8{0}; |
| | 1174 | try testing.expect(full.eql(try U.fromBytes(&padded, endian))); |
| | 1175 | padded[if (endian == .big) 0 else padded.len - 1] = 1; |
| | 1176 | try testing.expectError(error.Overflow, U.fromBytes(&padded, endian)); |
| | 1177 | } |
| | 1178 | } |
| | 1179 | |
| | 1180 | test "field element decoding" { |
| | 1181 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; |
| | 1182 | |
| | 1183 | const M = Modulus(256); |
| | 1184 | const mv: u256 = (1 << 190) + 33; |
| | 1185 | const m = try M.fromPrimitive(u256, mv); |
| | 1186 | try testing.expectError(error.NonCanonical, M.Fe.fromPrimitive(u256, m, mv)); |
| | 1187 | try testing.expectError(error.Overflow, M.Fe.fromPrimitive(u256, m, 1 << 255)); |
| | 1188 | |
| | 1189 | inline for (.{ .big, .little }) |endian| { |
| | 1190 | var buf: [32]u8 = undefined; |
| | 1191 | try m.toBytes(&buf, endian); |
| | 1192 | try testing.expectError(error.NonCanonical, M.Fe.fromBytes(m, &buf, endian)); |
| | 1193 | const x = try M.Fe.fromPrimitive(u256, m, mv - 1); |
| | 1194 | try x.toBytes(&buf, endian); |
| | 1195 | try testing.expect(x.eql(try M.Fe.fromBytes(m, &buf, endian))); |
| | 1196 | } |
| 1108 | } | 1197 | } |