| ... | ... | @@ -55,14 +55,18 @@ pub const RepresentationError = error{UnexpectedRepresentation}; |
| 55 | 55 | pub const Error = OverflowError || InvalidModulusError || NullExponentError || FieldElementError || RepresentationError; |
| 56 | 56 | |
| 57 | 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 | 59 | /// Unless side-channels mitigations are explicitly disabled, operations are designed to be constant-time. |
| 59 | 60 | pub fn Uint(comptime max_bits: comptime_int) type { |
| 60 | 61 | comptime assert(@bitSizeOf(Limb) % 8 == 0); // Limb size must be a multiple of 8 |
| 62 | comptime assert(max_bits > 0); |
| 61 | 63 | |
| 62 | 64 | return struct { |
| 63 | 65 | const Self = @This(); |
| 64 | 66 | const max_limbs_count = @divCeil(max_bits, t_bits); |
| 65 | 67 | |
| 68 | pub const capacity_bits = max_limbs_count * t_bits; |
| 69 | |
| 66 | 70 | limbs_buffer: [max_limbs_count]Limb, |
| 67 | 71 | /// The number of active limbs. |
| 68 | 72 | limbs_len: usize, |
| ... | ... | @@ -154,15 +158,15 @@ pub fn Uint(comptime max_bits: comptime_int) type { |
| 154 | 158 | var remaining_bits = t_bits; |
| 155 | 159 | var limb = self.limbsConst()[i]; |
| 156 | 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 | 162 | const consumed = 8 - shift; |
| 159 | | limb >>= @as(u4, @truncate(consumed)); |
| 163 | limb >>= @truncate(consumed); |
| 160 | 164 | remaining_bits -= consumed; |
| 161 | 165 | shift = 0; |
| 162 | 166 | switch (endian) { |
| 163 | 167 | .big => { |
| 164 | 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 | 170 | return error.Overflow; |
| 167 | 171 | } |
| 168 | 172 | return; |
| ... | ... | @@ -172,7 +176,7 @@ pub fn Uint(comptime max_bits: comptime_int) type { |
| 172 | 176 | .little => { |
| 173 | 177 | out_i += 1; |
| 174 | 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 | 180 | return error.Overflow; |
| 177 | 181 | } |
| 178 | 182 | return; |
| ... | ... | @@ -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 | 188 | shift = remaining_bits; |
| 185 | 189 | } |
| 186 | 190 | } |
| 187 | 191 | |
| 188 | 192 | /// Creates a new big integer from a byte array. |
| 189 | 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 | 194 | var out = Self.zero; |
| 193 | | var out_i: usize = 0; |
| 194 | | var i: usize = switch (endian) { |
| 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 | | } |
| 195 | if (decodeBytes(out.limbs(), bytes, endian) != 0) { |
| 196 | return error.Overflow; |
| 225 | 197 | } |
| 226 | 198 | return out; |
| 227 | 199 | } |
| ... | ... | @@ -243,11 +215,7 @@ pub fn Uint(comptime max_bits: comptime_int) type { |
| 243 | 215 | |
| 244 | 216 | /// Returns `true` if the integer is zero. |
| 245 | 217 | pub fn isZero(x: Self) bool { |
| 246 | | var t: Limb = 0; |
| 247 | | for (x.limbsConst()) |elem| { |
| 248 | | t |= elem; |
| 249 | | } |
| 250 | | return ct.eql(t, 0); |
| 218 | return ct.eql(orLimbs(x.limbsConst()), 0); |
| 251 | 219 | } |
| 252 | 220 | |
| 253 | 221 | /// Returns `true` if the integer is odd. |
| ... | ... | @@ -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 | 392 | /// A modulus, defining a finite field. |
| 384 | 393 | /// All operations within the field are performed modulo this modulus, without heap allocations. |
| 385 | 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 | 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 | 1136 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; |
| 1095 | 1137 | |
| 1096 | 1138 | const U = Uint(256); |
| ... | ... | @@ -1105,4 +1147,51 @@ test "Uint serialization and predicates" { |
| 1105 | 1147 | const signed_max = try U.fromPrimitive(u128, math.maxInt(i128)); |
| 1106 | 1148 | try testing.expectEqual(math.maxInt(i128), try signed_max.toPrimitive(i128)); |
| 1107 | 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 | } |