From cf4a3fdc393051c55f36eeb5f987651aac6f1d60 Mon Sep 17 00:00:00 2001 From: Nikita Orlov Date: Mon, 15 Apr 2024 18:46:56 +0200 Subject: [PATCH] Feat/bigint hint (#502) * add math hint * add `getRef(`) in execution scope * add pack and split uint utils * [wip] add bigint utils * update code to use self * use element based iteration * add BASE constant * add bigint hint namespace * use std big int * add tests [wip] * add error * add error codes * add math helper functions * add `BASE` constant * add bigint3 split function * add hint codes * add function `nondetBigInt3()` and more tests * add bigint hint * add hint code * add hint * remove duplicate functions * add tests [wip] * fix test and remove debug logs * add bigint hint to hint processor * use addition shorthand * add missing types to execution scope * use updated `bigintPackDivModHint` * update datatype * update type * update tests * remove debug logs and fix tests * update frame pointer initialisation * deallocate memory * fix test + rework on bigint backend --------- Co-authored-by: Sweta Shaw --- src/hint_processor/bigint.zig | 232 ++++++++ src/hint_processor/builtin_hint_codes.zig | 28 + .../secp/bigint_utils.zig | 521 ++++++++++++++++++ .../secp/secp_utils.zig | 44 ++ src/hint_processor/hint_processor_def.zig | 13 + src/hint_processor/math_hints.zig | 7 + src/hint_processor/uint_utils.zig | 24 +- src/integration_tests.zig | 10 +- src/lib.zig | 1 + src/math/fields/constants.zig | 5 + src/math/fields/fields.zig | 6 +- src/math/fields/helper.zig | 140 ++++- src/vm/error.zig | 6 + src/vm/types/execution_scopes.zig | 6 +- 14 files changed, 1019 insertions(+), 24 deletions(-) create mode 100644 src/hint_processor/bigint.zig create mode 100644 src/hint_processor/builtin_hint_processor/secp/bigint_utils.zig create mode 100644 src/hint_processor/builtin_hint_processor/secp/secp_utils.zig diff --git a/src/hint_processor/bigint.zig b/src/hint_processor/bigint.zig new file mode 100644 index 00000000..dbf974a6 --- /dev/null +++ b/src/hint_processor/bigint.zig @@ -0,0 +1,232 @@ +const hint_utils = @import("hint_utils.zig"); +const std = @import("std"); +const CairoVM = @import("../vm/core.zig").CairoVM; +const HintReference = @import("hint_processor_def.zig").HintReference; +const HintProcessor = @import("hint_processor_def.zig").CairoVMHintProcessor; +const testing_utils = @import("testing_utils.zig"); +const Felt252 = @import("../math/fields/starknet.zig").Felt252; +const hint_codes = @import("builtin_hint_codes.zig"); +const Relocatable = @import("../vm/memory/relocatable.zig").Relocatable; +const MaybeRelocatable = @import("../vm/memory/relocatable.zig").MaybeRelocatable; +const ApTracking = @import("../vm/types/programjson.zig").ApTracking; +const HintData = @import("hint_processor_def.zig").HintData; +const ExecutionScopes = @import("../vm/types/execution_scopes.zig").ExecutionScopes; +const BigInt3 = @import("builtin_hint_processor/secp/bigint_utils.zig").BigInt3; +const BigInt5 = @import("builtin_hint_processor/secp/bigint_utils.zig").BigInt5; +const BigIntN = @import("builtin_hint_processor/secp/bigint_utils.zig").BigIntN; +const Int = @import("std").math.big.int.Managed; +const BASE = @import("../math/fields/constants.zig").BASE; +const field_helper = @import("../math/fields/helper.zig"); +const safeDivBigInt = @import("../math/fields/helper.zig").safeDivBigInt; +const insertValueFromVarName = @import("../hint_processor/hint_utils.zig").insertValueFromVarName; + +/// Implements hint: +/// ```python +/// from starkware.cairo.common.cairo_secp.secp_utils import pack +/// from starkware.cairo.common.math_utils import as_int +/// from starkware.python.math_utils import div_mod, safe_div +/// +/// p = pack(ids.P, PRIME) +/// x = pack(ids.x, PRIME) + as_int(ids.x.d3, PRIME) * ids.BASE ** 3 + as_int(ids.x.d4, PRIME) * ids.BASE ** 4 +/// y = pack(ids.y, PRIME) +/// +/// value = res = div_mod(x, y, p) +/// ``` +pub fn bigintPackDivModHint(allocator: std.mem.Allocator, vm: *CairoVM, exec_scopes: *ExecutionScopes, ids_data: std.StringHashMap(HintReference), ap_tracking: ApTracking) !void { + // Initiate BigInt default element + var p = try (try BigInt3.fromVarName("P", vm, ids_data, ap_tracking)).pack86(allocator); + errdefer p.deinit(); + + var x: Int = blk: { + const x_bigint5 = try BigInt5.fromVarName("x", vm, ids_data, ap_tracking); + + var x_lower = try BigInt3.fromValues([3]Felt252{ + x_bigint5.limbs[0], x_bigint5.limbs[1], x_bigint5.limbs[2], + }).pack86(allocator); + defer x_lower.deinit(); + + var d3 = try x_bigint5.limbs[3].toSignedBigInt(allocator); + defer d3.deinit(); + + var d4 = try x_bigint5.limbs[3].toSignedBigInt(allocator); + defer d4.deinit(); + + var tmp = try Int.init(allocator); + defer tmp.deinit(); + + var base = try Int.initSet(allocator, BASE); + defer base.deinit(); + + var result = try Int.init(allocator); + errdefer result.deinit(); + + try tmp.pow(&base, 3); + + try tmp.mul(&d3, &tmp); + + try result.add(&x_lower, &tmp); + + try tmp.pow(&base, 4); + + try tmp.mul(&d4, &tmp); + + try result.add(&result, &tmp); + + break :blk result; + }; + errdefer x.deinit(); + + var y = try (try BigInt3.fromVarName("y", vm, ids_data, ap_tracking)).pack86(allocator); + errdefer y.deinit(); + + var res = try field_helper.divModBigInt(allocator, &x, &y, &p); + errdefer res.deinit(); + + try exec_scopes.assignOrUpdateVariable("res", .{ + .big_int = res, + }); + + try exec_scopes.assignOrUpdateVariable("value", .{ + .big_int = try res.clone(), + }); + + try exec_scopes.assignOrUpdateVariable("x", .{ + .big_int = x, + }); + + try exec_scopes.assignOrUpdateVariable("y", .{ + .big_int = y, + }); + + try exec_scopes.assignOrUpdateVariable("p", .{ + .big_int = p, + }); +} + +/// Implements hint: +/// ```python +/// k = safe_div(res * y - x, p) +/// value = k if k > 0 else 0 - k +/// ids.flag = 1 if k > 0 else 0 +/// ``` +pub fn bigIntSafeDivHint(allocator: std.mem.Allocator, vm: *CairoVM, exec_scopes: *ExecutionScopes, ids_data: std.StringHashMap(HintReference), apTracking: ApTracking) !void { + const res = try exec_scopes.getValueRef(Int, "res"); + const x = try exec_scopes.getValueRef(Int, "x"); + const y = try exec_scopes.getValueRef(Int, "y"); + const p = try exec_scopes.getValueRef(Int, "p"); + + var tmp = try Int.init(allocator); + errdefer tmp.deinit(); + + var tmp2 = try Int.init(allocator); + errdefer tmp2.deinit(); + + try tmp.mul(res, y); + try tmp.sub(&tmp, x); + try tmp.divFloor(&tmp2, &tmp, p); + + var flag: Felt252 = undefined; + + try tmp2.copy(tmp.toConst()); + if (tmp.isPositive() or tmp.eqlZero()) { + flag = Felt252.one(); + } else { + flag = Felt252.zero(); + tmp2.negate(); + } + + // k == tmp + // result == tmp2 + + try exec_scopes.assignOrUpdateVariable("k", .{ .big_int = tmp }); + try exec_scopes.assignOrUpdateVariable("value", .{ .big_int = tmp2 }); + try insertValueFromVarName(allocator, "flag", MaybeRelocatable.fromFelt(flag), vm, ids_data, apTracking); +} + +test "big int pack div mod hint" { + var vm = try CairoVM.init(std.testing.allocator, .{}); + defer vm.deinit(); + defer vm.segments.memory.deinitData(std.testing.allocator); + var ids_data = try testing_utils.setupIdsNonContinuousIdsData( + std.testing.allocator, + &.{ + .{ "x", 0 }, + .{ "y", 5 }, + .{ "P", 8 }, + }, + ); + + defer ids_data.deinit(); + + vm.run_context.fp.* = 0; + inline for (0..11) |_| _ = try vm.addMemorySegment(); + + // Set up memory segments in the virtual machine. + try vm.segments.memory.setUpMemory(std.testing.allocator, .{ + .{ .{ 1, 0 }, .{0x38a23ca66202c8c2a72277} }, + .{ .{ 1, 1 }, .{0x6730e765376ff17ea8385} }, + .{ .{ 1, 2 }, .{0xca1ad489ab60ea581e6c1} }, + .{ .{ 1, 3 }, .{0} }, + .{ .{ 1, 4 }, .{0} }, + .{ .{ 1, 5 }, .{0x20a4b46d3c5e24cda81f22} }, + .{ .{ 1, 6 }, .{0x967bf895824330d4273d0} }, + .{ .{ 1, 7 }, .{0x541e10c21560da25ada4c} }, + .{ .{ 1, 8 }, .{0x8a03bbfd25e8cd0364141} }, + .{ .{ 1, 9 }, .{0x3ffffffffffaeabb739abd} }, + .{ .{ 1, 10 }, .{0xfffffffffffffffffffff} }, + }); + + const hint_processor = HintProcessor{}; + var hint_data = HintData.init(hint_codes.BIGINT_PACK_DIV_MOD_HINT, ids_data, .{}); + + var exec_scopes = try ExecutionScopes.init(std.testing.allocator); + defer exec_scopes.deinit(); + + try hint_processor.executeHint(std.testing.allocator, &vm, &hint_data, undefined, &exec_scopes); + + try std.testing.expectEqual(109567829260688255124154626727441144629993228404337546799996747905569082729709, try ((try exec_scopes.getValue(Int, "res"))).to(u512)); + try std.testing.expectEqual(109567829260688255124154626727441144629993228404337546799996747905569082729709, try ((try exec_scopes.getValue(Int, "res"))).to(u512)); + try std.testing.expectEqual(38047400353360331012910998489219098987968251547384484838080352663220422975266, try ((try exec_scopes.getValue(Int, "y"))).to(u512)); + try std.testing.expectEqual(91414600319290532004473480113251693728834511388719905794310982800988866814583, try ((try exec_scopes.getValue(Int, "x"))).to(u512)); + try std.testing.expectEqual(115792089237316195423570985008687907852837564279074904382605163141518161494337, try ((try exec_scopes.getValue(Int, "p"))).to(u512)); +} + +test "big int safe div hint" { + var vm = try CairoVM.init(std.testing.allocator, .{}); + + defer vm.deinit(); + + var ids_data = try testing_utils.setupIdsNonContinuousIdsData( + std.testing.allocator, + &.{ + .{ "flag", 0 }, + }, + ); + + defer ids_data.deinit(); + + // Set the frame pointer to point to the beginning of the stack. + vm.run_context.*.fp.* = 0; + _ = try vm.addMemorySegment(); + _ = try vm.addMemorySegment(); + + defer vm.segments.memory.deinitData(std.testing.allocator); + + var exec_scopes = try ExecutionScopes.init(std.testing.allocator); + defer exec_scopes.deinit(); + + try exec_scopes.assignOrUpdateVariable("res", .{ .big_int = try Int.initSet(std.testing.allocator, 109567829260688255124154626727441144629993228404337546799996747905569082729709) }); + try exec_scopes.assignOrUpdateVariable("y", .{ .big_int = try Int.initSet(std.testing.allocator, 38047400353360331012910998489219098987968251547384484838080352663220422975266) }); + try exec_scopes.assignOrUpdateVariable("x", .{ .big_int = try Int.initSet(std.testing.allocator, 91414600319290532004473480113251693728834511388719905794310982800988866814583) }); + try exec_scopes.assignOrUpdateVariable("p", .{ .big_int = try Int.initSet(std.testing.allocator, 115792089237316195423570985008687907852837564279074904382605163141518161494337) }); + + const hint_processor = HintProcessor{}; + var hint_data = HintData.init(hint_codes.BIGINT_SAFE_DIV, ids_data, .{}); + + try hint_processor.executeHint(std.testing.allocator, &vm, &hint_data, undefined, &exec_scopes); + + try std.testing.expectEqual(36002209591245282109880156842267569109802494162594623391338581162816748840003, try ((try exec_scopes.getValue(Int, "k")).to(u512))); + + try std.testing.expectEqual(36002209591245282109880156842267569109802494162594623391338581162816748840003, try ((try exec_scopes.getValue(Int, "value")).to(u512))); + try testing_utils.checkMemory(vm.segments.memory, .{.{ .{ 1, 0 }, .{1} }}); +} diff --git a/src/hint_processor/builtin_hint_codes.zig b/src/hint_processor/builtin_hint_codes.zig index 889cb2c1..0eb68288 100644 --- a/src/hint_processor/builtin_hint_codes.zig +++ b/src/hint_processor/builtin_hint_codes.zig @@ -516,6 +516,23 @@ pub const DICT_SQUASH_COPY_DICT = \\ 'initial_dict': dict(__dict_manager.get_dict(ids.dict_accesses_end)), \\}) ; +pub const BIGINT_PACK_DIV_MOD_HINT = + \\from starkware.cairo.common.cairo_secp.secp_utils import pack + \\from starkware.cairo.common.math_utils import as_int + \\from starkware.python.math_utils import div_mod, safe_div + \\p = pack(ids.P, PRIME) + \\x = pack(ids.x, PRIME) + as_int(ids.x.d3, PRIME) * ids.BASE ** 3 + as_int(ids.x.d4, PRIME) * ids.BASE ** 4 + \\y = pack(ids.y, PRIME) + \\value = res = div_mod(x, y, p) +; + +pub const BIGINT_SAFE_DIV = + \\ k = safe_div(res * y - x, p) + \\ value = k if k > 0 else 0 - k + \\ ids.flag = 1 if k > 0 else 0 +; + +pub const HI_MAX_BIT_LEN = "ids.len_hi = max(ids.scalar_u.d2.bit_length(), ids.scalar_v.d2.bit_length())-1"; pub const BLOCK_PERMUTATION = \\from starkware.cairo.common.keccak_utils.keccak_utils import keccak_func @@ -636,3 +653,14 @@ pub const SPLIT_XX = \\ids.x.low = x & ((1<<128)-1) \\ids.x.high = x >> 128 ; + +pub const NONDET_BIGINT3_V1 = + \\from starkware.cairo.common.cairo_secp.secp_utils import split + \\ + \\segments.write_arg(ids.res.address_, split(value)) +; + +pub const NONDET_BIGINT3_V2 = + \\from starkware.cairo.common.cairo_secp.secp_utils import split + \\segments.write_arg(ids.res.address_, split(value)) +; diff --git a/src/hint_processor/builtin_hint_processor/secp/bigint_utils.zig b/src/hint_processor/builtin_hint_processor/secp/bigint_utils.zig new file mode 100644 index 00000000..1aeee7c6 --- /dev/null +++ b/src/hint_processor/builtin_hint_processor/secp/bigint_utils.zig @@ -0,0 +1,521 @@ +const std = @import("std"); +const Felt252 = @import("../../../math/fields/starknet.zig").Felt252; +const Relocatable = @import("../../../vm/memory/relocatable.zig").Relocatable; +const CairoVM = @import("../../../vm/core.zig").CairoVM; +const HintError = @import("../../../vm/error.zig").HintError; +const hint_utils = @import("../../hint_utils.zig"); +const HintReference = @import("../../hint_processor_def.zig").HintReference; +const ApTracking = @import("../../../vm/types/programjson.zig").ApTracking; +const ExecutionScopes = @import("../../../vm/types/execution_scopes.zig").ExecutionScopes; +const pow2ConstNz = @import("../../math_hints.zig").pow2ConstNz; +const packBigInt = @import("../../uint_utils.zig").pack; +const splitBigInt = @import("../../uint_utils.zig").split; +const HintProcessor = @import("../../hint_processor_def.zig").CairoVMHintProcessor; +const HintData = @import("../../hint_processor_def.zig").HintData; +const testing_utils = @import("../../testing_utils.zig"); +const MemoryError = @import("../../../vm/error.zig").MemoryError; +const MaybeRelocatable = @import("../../../vm/memory/relocatable.zig").MaybeRelocatable; +const Int = @import("std").math.big.int.Managed; +const secp_utils = @import("../secp/secp_utils.zig"); +const bigInt3Split = secp_utils.bigInt3Split; +const BigInt = std.math.big.int.Managed; +const BASE = @import("../../../math//fields/constants.zig").BASE; +const hint_codes = @import("../../builtin_hint_codes.zig"); + +pub const BigInt3 = BigIntN(3); +pub const Uint384 = BigIntN(3); +pub const Uint512 = BigIntN(4); +pub const BigInt5 = BigIntN(5); +pub const Uint768 = BigIntN(6); + +pub fn BigIntN(comptime NUM_LIMBS: usize) type { + return struct { + const Self = @This(); + + limbs: [NUM_LIMBS]Felt252 = undefined, + + pub fn fromBaseAddr(addr: Relocatable, vm: *CairoVM) !Self { + var limbs: [NUM_LIMBS]Felt252 = undefined; + + inline for (0..NUM_LIMBS) |i| { + limbs[i] = try vm.getFelt(try addr.addUint(i)); + } + + return .{ .limbs = limbs }; + } + + pub fn fromVarName(name: []const u8, vm: *CairoVM, ids_data: std.StringHashMap(HintReference), ap_tracking: ApTracking) !Self { + return Self.fromBaseAddr(try hint_utils.getRelocatableFromVarName(name, vm, ids_data, ap_tracking), vm); + } + + pub fn fromValues(limbs: [NUM_LIMBS]Felt252) Self { + return .{ .limbs = limbs }; + } + + pub fn insertFromVarName(self: *Self, allocator: std.mem.allocator, var_name: []const u8, vm: *CairoVM, ids_data: std.StringHashMap(HintReference), ap_tracking: ApTracking) !void { + const addr = try hint_utils.getRelocatableFromVarName(var_name, vm, ids_data, ap_tracking); + inline for (0..NUM_LIMBS) |i| { + try vm.insertInMemory(allocator, addr + i, self.limbs[i]); + } + } + + pub fn pack(self: *const Self, allocator: std.mem.Allocator) !Int { + const result = packBigInt(allocator, NUM_LIMBS, self.limbs, 128); + return result; + } + + pub fn pack86(self: *const Self, allocator: std.mem.Allocator) !Int { + var result = try Int.initSet(allocator, 0); + errdefer result.deinit(); + + inline for (0..3) |i| { + var tmp = try self.limbs[i].toSignedBigInt(allocator); + defer tmp.deinit(); + + try tmp.shiftLeft(&tmp, i * 86); + + try result.add(&result, &tmp); + } + + return result; + } + + pub fn split(self: *Self, num: Int) Self { + const limbs = splitBigInt(std.mem.Allocator, num, self.limbs.len, 128); + return self.fromValues(limbs); + } + + // @TODO: implement from. It is dependent on split function. + pub fn from(self: *Self, value: Int) !Self { + // Assuming we have a `split` function in BigIntN.zig that performs the conversion. + return self.split(value); + } + }; +} + +// Implements hint: +// %{ +// from starkware.cairo.common.cairo_secp.secp_utils import split +// segments.write_arg(ids.res.address_, split(value)) +// %} +/// +pub fn nondetBigInt3(allocator: std.mem.Allocator, vm: *CairoVM, exec_scopes: *ExecutionScopes, ids_data: std.StringHashMap(HintReference), ap_tracking: ApTracking) !void { + const res_reloc = try hint_utils.getRelocatableFromVarName("res", vm, ids_data, ap_tracking); + const value = try exec_scopes.getValueRef(Int, "value"); + + if (!value.isPositive()) return HintError.BigintToUsizeFail; + + var arg = try secp_utils.bigInt3Split(allocator, value); + defer for (0..arg.len) |x| arg[x].deinit(); + + const result: [3]MaybeRelocatable = .{ + MaybeRelocatable.fromInt(u512, try arg[0].to(u512)), + MaybeRelocatable.fromInt(u512, try arg[1].to(u512)), + MaybeRelocatable.fromInt(u512, try arg[2].to(u512)), + }; + + _ = try vm.segments.loadData(allocator, res_reloc, result[0..]); +} + +// Implements hint +// %{ ids.low = (ids.x.d0 + ids.x.d1 * ids.BASE) & ((1 << 128) - 1) %} +pub fn bigintToUint256(vm: *CairoVM, ids_data: std.StringHashMap(HintReference), ap_tracking: ApTracking, constants: std.StringHashMap(Felt252)) !void { + const x_struct = try hint_utils.getRelocatableFromVarName("x", vm, ids_data, ap_tracking); + const d0 = try vm.getFelt(x_struct); + const d1 = try vm.getFelt(x_struct.offset(1)); + const base_86 = constants.get("BASE_86") orelse return HintError.MissingConstant; + const mask = pow2ConstNz(128); + const low = ((d0 + (d1 * base_86)) % mask); + try hint_utils.insertValueFromVarName(std.mem.Allocator, "low", low, vm, ids_data, ap_tracking); +} + +// Implements hint +// %{ ids.len_hi = max(ids.scalar_u.d2.bit_length(), ids.scalar_v.d2.bit_length())-1 %} +pub fn hiMaxBitlen(vm: *CairoVM, allocator: std.mem.Allocator, ids_data: std.StringHashMap(HintReference), ap_tracking: ApTracking) !void { + var scalar_u = try BigInt3.fromVarName("scalar_u", vm, ids_data, ap_tracking); + var scalar_v = try BigInt3.fromVarName("scalar_v", vm, ids_data, ap_tracking); + + // get number of bits in the highest limb + const len_hi_u = scalar_u.limbs[2].numBits(); + const len_hi_v = scalar_v.limbs[2].numBits(); + + const len_hi = @max(len_hi_u, len_hi_v); + + // equal to `len_hi.wrapping_sub(1)` + const res = if (len_hi == 0) Felt252.Max.toInteger() else len_hi - 1; + + try hint_utils.insertValueFromVarName(allocator, "len_hi", MaybeRelocatable.fromInt(u256, res), vm, ids_data, ap_tracking); +} + +// Tests + +test "BigIntUtils: get bigint3 from base addr ok" { + var vm = try CairoVM.init(std.testing.allocator, .{}); + + defer vm.deinit(); + + try vm.segments.memory.setUpMemory(std.testing.allocator, .{ + .{ .{ 0, 0 }, .{1} }, + .{ .{ 0, 1 }, .{2} }, + .{ .{ 0, 2 }, .{3} }, + }); + + defer vm.segments.memory.deinitData(std.testing.allocator); + + const x = try BigInt3.fromBaseAddr(Relocatable{ .segment_index = 0, .offset = 0 }, &vm); + + try std.testing.expectEqual(Felt252.one(), x.limbs[0]); + try std.testing.expectEqual(Felt252.two(), x.limbs[1]); + try std.testing.expectEqual(Felt252.three(), x.limbs[2]); +} + +test "BigIntUtils: get Bigint5 from base addr ok" { + var vm = try CairoVM.init(std.testing.allocator, .{}); + + defer vm.deinit(); + + try vm.segments.memory.setUpMemory(std.testing.allocator, .{ + .{ .{ 0, 0 }, .{1} }, + .{ .{ 0, 1 }, .{2} }, + .{ .{ 0, 2 }, .{3} }, + .{ .{ 0, 3 }, .{4} }, + .{ .{ 0, 4 }, .{5} }, + }); + + defer vm.segments.memory.deinitData(std.testing.allocator); + + const x = try BigInt5.fromBaseAddr(Relocatable{ .segment_index = 0, .offset = 0 }, &vm); + + try std.testing.expectEqual(Felt252.one(), x.limbs[0]); + try std.testing.expectEqual(Felt252.two(), x.limbs[1]); + try std.testing.expectEqual(Felt252.three(), x.limbs[2]); + try std.testing.expectEqual(Felt252.fromInt(u8, 4), x.limbs[3]); + try std.testing.expectEqual(Felt252.fromInt(u8, 5), x.limbs[4]); +} + +test "Get BigInt3 from base address with missing member should fail" { + var vm = try CairoVM.init(std.testing.allocator, .{}); + + defer vm.deinit(); + + try vm.segments.memory.setUpMemory(std.testing.allocator, .{ + .{ .{ 0, 0 }, .{1} }, + .{ .{ 0, 1 }, .{2} }, + }); + + defer vm.segments.memory.deinitData(std.testing.allocator); + + try std.testing.expectError(MemoryError.UnknownMemoryCell, BigInt3.fromBaseAddr(Relocatable{ .segment_index = 0, .offset = 0 }, &vm)); +} + +test "Get BigInt5 from base address with missing member should fail" { + var vm = try CairoVM.init(std.testing.allocator, .{}); + + defer vm.deinit(); + + try vm.segments.memory.setUpMemory(std.testing.allocator, .{ + .{ .{ 0, 0 }, .{1} }, + .{ .{ 0, 1 }, .{2} }, + .{ .{ 0, 2 }, .{3} }, + .{ .{ 0, 3 }, .{4} }, + }); + + defer vm.segments.memory.deinitData(std.testing.allocator); + + try std.testing.expectError(MemoryError.UnknownMemoryCell, BigInt5.fromBaseAddr(Relocatable{ .segment_index = 0, .offset = 0 }, &vm)); +} + +test "BigIntUtils: get bigint3 from var name ok" { + var vm = try CairoVM.init(std.testing.allocator, .{}); + + defer vm.deinit(); + + try vm.segments.memory.setUpMemory(std.testing.allocator, .{ + .{ .{ 1, 0 }, .{1} }, + .{ .{ 1, 1 }, .{2} }, + .{ .{ 1, 2 }, .{3} }, + }); + + vm.run_context.fp.* = 1; + + defer vm.segments.memory.deinitData(std.testing.allocator); + + var ids_data = try testing_utils.setupIdsForTestWithoutMemory(std.testing.allocator, &.{"x"}); + defer ids_data.deinit(); + + const x = try BigInt3.fromVarName("x", &vm, ids_data, .{}); + + try std.testing.expectEqual(Felt252.one(), x.limbs[0]); + try std.testing.expectEqual(Felt252.two(), x.limbs[1]); + try std.testing.expectEqual(Felt252.three(), x.limbs[2]); +} + +test "BigIntUtils: get bigint5 from var name ok" { + var vm = try CairoVM.init(std.testing.allocator, .{}); + + defer vm.deinit(); + + try vm.segments.memory.setUpMemory(std.testing.allocator, .{ + .{ .{ 1, 0 }, .{1} }, + .{ .{ 1, 1 }, .{2} }, + .{ .{ 1, 2 }, .{3} }, + .{ .{ 1, 3 }, .{4} }, + .{ .{ 1, 4 }, .{5} }, + }); + + vm.run_context.fp.* = 1; + + defer vm.segments.memory.deinitData(std.testing.allocator); + + var ids_data = try testing_utils.setupIdsForTestWithoutMemory(std.testing.allocator, &.{"x"}); + defer ids_data.deinit(); + + const x = try BigInt5.fromVarName("x", &vm, ids_data, .{}); + + try std.testing.expectEqual(Felt252.one(), x.limbs[0]); + try std.testing.expectEqual(Felt252.two(), x.limbs[1]); + try std.testing.expectEqual(Felt252.three(), x.limbs[2]); + try std.testing.expectEqual(Felt252.fromInt(u8, 4), x.limbs[3]); + try std.testing.expectEqual(Felt252.fromInt(u8, 5), x.limbs[4]); +} + +test "BigIntUtils: get bigint3 from var name with missing member fail" { + var vm = try CairoVM.init(std.testing.allocator, .{}); + + defer vm.deinit(); + + try vm.segments.memory.setUpMemory(std.testing.allocator, .{ + .{ .{ 1, 0 }, .{1} }, + .{ .{ 1, 1 }, .{2} }, + }); + + defer vm.segments.memory.deinitData(std.testing.allocator); + + vm.run_context.fp.* = 1; + + var ids_data = try testing_utils.setupIdsForTestWithoutMemory(std.testing.allocator, &.{"x"}); + defer ids_data.deinit(); + + try std.testing.expectError(MemoryError.UnknownMemoryCell, BigInt3.fromVarName("x", &vm, ids_data, .{})); +} + +test "BigIntUtils: get bigint5 from var name with missing member should fail" { + var vm = try CairoVM.init(std.testing.allocator, .{}); + + defer vm.deinit(); + + try vm.segments.memory.setUpMemory(std.testing.allocator, .{ + .{ .{ 1, 0 }, .{1} }, + .{ .{ 1, 1 }, .{2} }, + .{ .{ 1, 2 }, .{3} }, + .{ .{ 1, 3 }, .{4} }, + }); + + defer vm.segments.memory.deinitData(std.testing.allocator); + + vm.run_context.fp.* = 1; + + var ids_data = try testing_utils.setupIdsForTestWithoutMemory(std.testing.allocator, &.{"x"}); + defer ids_data.deinit(); + + try std.testing.expectError(MemoryError.UnknownMemoryCell, BigInt5.fromVarName("x", &vm, ids_data, .{})); +} + +test "BigIntUtils: get bigint3 from varname invalid reference should fail" { + var vm = try CairoVM.init(std.testing.allocator, .{}); + + defer vm.deinit(); + + try vm.segments.memory.setUpMemory(std.testing.allocator, .{ + .{ .{ 1, 0 }, .{1} }, + .{ .{ 1, 1 }, .{2} }, + .{ .{ 1, 2 }, .{3} }, + }); + + defer vm.segments.memory.deinitData(std.testing.allocator); + + var ids_data = try testing_utils.setupIdsForTestWithoutMemory(std.testing.allocator, &.{"x"}); + defer ids_data.deinit(); + + try std.testing.expectError(HintError.UnknownIdentifier, BigInt3.fromVarName("x", &vm, ids_data, .{})); +} + +test "BigIntUtils: get bigint5 from varname invalid reference should fail" { + var vm = try CairoVM.init(std.testing.allocator, .{}); + + defer vm.deinit(); + + try vm.segments.memory.setUpMemory(std.testing.allocator, .{ + .{ .{ 1, 0 }, .{1} }, + .{ .{ 1, 1 }, .{2} }, + .{ .{ 1, 2 }, .{3} }, + .{ .{ 1, 3 }, .{4} }, + .{ .{ 1, 4 }, .{5} }, + }); + + defer vm.segments.memory.deinitData(std.testing.allocator); + + var ids_data = try testing_utils.setupIdsForTestWithoutMemory(std.testing.allocator, &.{"x"}); + defer ids_data.deinit(); + + try std.testing.expectError(HintError.UnknownIdentifier, BigInt5.fromVarName("x", &vm, ids_data, .{})); +} + +test "BigIntUtils: Run hiMaxBitlen ok" { + var vm = try CairoVM.init(std.testing.allocator, .{}); + + defer vm.deinit(); + + var ids_data = try testing_utils.setupIdsNonContinuousIdsData( + std.testing.allocator, + &.{ + .{ "scalar_u", 0 }, + .{ "scalar_v", 3 }, + .{ + "len_hi", + 6, + }, + }, + ); + defer ids_data.deinit(); + + try vm.segments.memory.setUpMemory(std.testing.allocator, .{ + .{ .{ 1, 0 }, .{0} }, + .{ .{ 1, 1 }, .{0} }, + .{ .{ 1, 2 }, .{1} }, + .{ .{ 1, 3 }, .{0} }, + .{ .{ 1, 4 }, .{0} }, + .{ .{ 1, 5 }, .{1} }, + }); + defer vm.segments.memory.deinitData(std.testing.allocator); + + vm.run_context.fp.* = 0; + vm.run_context.ap.* = 7; + + //Execute the hint + const hint_code = "ids.len_hi = max(ids.scalar_u.d2.bit_length(), ids.scalar_v.d2.bit_length())-1"; + + try testing_utils.runHint(std.testing.allocator, &vm, ids_data, hint_code, undefined, undefined); + + try testing_utils.checkMemory(vm.segments.memory, .{ + .{ .{ 1, 6 }, .{0} }, + }); +} + +test "BigIntUtils: nondet bigint3 ok" { + var vm = try testing_utils.initVMWithRangeCheck(std.testing.allocator); + defer vm.deinit(); + defer vm.segments.memory.deinitData(std.testing.allocator); + + inline for (0..3) |_| _ = try vm.segments.addSegment(); + + var exec_scopes = try ExecutionScopes.init(std.testing.allocator); + defer exec_scopes.deinit(); + + try exec_scopes.assignOrUpdateVariable("value", .{ .big_int = try Int.initSet(std.testing.allocator, 7737125245533626718119526477371252455336267181195264773712524553362) }); + + vm.run_context.pc.* = Relocatable.init(0, 0); + vm.run_context.ap.* = 6; + vm.run_context.fp.* = 6; + + var ids_data = try testing_utils.setupIdsNonContinuousIdsData( + std.testing.allocator, + &.{ + .{ "res", 5 }, + }, + ); + defer ids_data.deinit(); + + var constants = std.StringHashMap(Felt252).init(std.testing.allocator); + defer constants.deinit(); + + try constants.put(secp_utils.BASE_86, pow2ConstNz(86)); + + try testing_utils.runHint(std.testing.allocator, &vm, ids_data, "from starkware.cairo.common.cairo_secp.secp_utils import split\n\nsegments.write_arg(ids.res.address_, split(value))", &constants, &exec_scopes); + + try testing_utils.checkMemory(vm.segments.memory, .{ + .{ + .{ 1, 11 }, .{773712524553362}, + }, + .{ + .{ 1, 12 }, .{57408430697461422066401280}, + }, + .{ + .{ 1, 13 }, .{1292469707114105}, + }, + }); +} + +test "BigIntUtils: nondet bigint3 split error" { + var vm = try testing_utils.initVMWithRangeCheck(std.testing.allocator); + defer vm.deinit(); + defer vm.segments.memory.deinitData(std.testing.allocator); + + inline for (0..3) |_| _ = try vm.segments.addSegment(); + + var exec_scopes = try ExecutionScopes.init(std.testing.allocator); + defer exec_scopes.deinit(); + + try exec_scopes.assignOrUpdateVariable("value", .{ .big_int = try Int.initSet(std.testing.allocator, -1) }); + + var ids_data = try testing_utils.setupIdsNonContinuousIdsData( + std.testing.allocator, + &.{ + .{ "res", 5 }, + }, + ); + defer ids_data.deinit(); + + try std.testing.expectError(HintError.BigintToUsizeFail, testing_utils.runHint(std.testing.allocator, &vm, ids_data, "from starkware.cairo.common.cairo_secp.secp_utils import split\n\nsegments.write_arg(ids.res.address_, split(value))", undefined, &exec_scopes)); +} + +test "BigIntUtils: nondet bigint3 value not in scope" { + var vm = try testing_utils.initVMWithRangeCheck(std.testing.allocator); + defer vm.deinit(); + defer vm.segments.memory.deinitData(std.testing.allocator); + + inline for (0..3) |_| _ = try vm.segments.addSegment(); + + var exec_scopes = try ExecutionScopes.init(std.testing.allocator); + defer exec_scopes.deinit(); + + vm.run_context.pc.* = Relocatable.init(0, 0); + vm.run_context.ap.* = 6; + vm.run_context.fp.* = 6; + + var ids_data = try testing_utils.setupIdsNonContinuousIdsData( + std.testing.allocator, + &.{ + .{ "res", 5 }, + }, + ); + defer ids_data.deinit(); + + try std.testing.expectError(HintError.VariableNotInScopeError, testing_utils.runHint(std.testing.allocator, &vm, ids_data, "from starkware.cairo.common.cairo_secp.secp_utils import split\n\nsegments.write_arg(ids.res.address_, split(value))", undefined, &exec_scopes)); +} + +test "BigIntUtils: u384 pack86" { + var val = try Uint384.fromValues(.{ + Felt252.fromInt(u8, 10), + Felt252.fromInt(u8, 10), + Felt252.fromInt(u8, 10), + }).pack86(std.testing.allocator); + defer val.deinit(); + + try std.testing.expectEqual( + 59863107065073783529622931521771477038469668772249610, + val.to(u384), + ); + + var val1 = try Uint384.fromValues(.{ + Felt252.fromInt(u128, 773712524553362), + Felt252.fromInt(u128, 57408430697461422066401280), + Felt252.fromInt(u128, 1292469707114105), + }).pack86(std.testing.allocator); + defer val1.deinit(); + + try std.testing.expectEqual( + 7737125245533626718119526477371252455336267181195264773712524553362, + try val1.to(u384), + ); +} diff --git a/src/hint_processor/builtin_hint_processor/secp/secp_utils.zig b/src/hint_processor/builtin_hint_processor/secp/secp_utils.zig new file mode 100644 index 00000000..c9994f1c --- /dev/null +++ b/src/hint_processor/builtin_hint_processor/secp/secp_utils.zig @@ -0,0 +1,44 @@ +const std = @import("std"); +const BASE = @import("../../../math/fields/constants.zig").BASE; +const Int = std.math.big.int.Managed; + +pub const BASE_86 = "starkware.cairo.common.cairo_secp.constants.BASE"; + +const MathError = @import("../../../vm/error.zig").MathError; + +/// Takes a 256-bit integer and returns its canonical representation as: +/// d0 + BASE * d1 + BASE**2 * d2, +/// where BASE = 2**86. +pub fn bigInt3Split(allocator: std.mem.Allocator, integer: *const Int) ![3]Int { + var canonical_repr: [3]Int = undefined; + + // init all data + inline for (0..3) |i| { + canonical_repr[i] = try Int.init(allocator); + errdefer inline for (0..i) |x| canonical_repr[x].deinit(); + } + + errdefer inline for (0..3) |x| canonical_repr[x].deinit(); + + var tmp = try Int.init(allocator); + defer tmp.deinit(); + + var num = try integer.cloneWithDifferentAllocator(allocator); + defer num.deinit(); + + var base_minus_one = try Int.initSet(allocator, BASE - 1); + defer base_minus_one.deinit(); + + inline for (&canonical_repr) |*item| { + try item.*.bitAnd(&num, &base_minus_one); + // shift right got a bug, shift size more than 64 glitching, so we need just make n shifts less than 64 + try num.shiftRight(&num, 43); + try num.shiftRight(&num, 43); + } + + if (!num.eqlZero()) { + return MathError.SecpSplitOutOfRange; + } + + return canonical_repr; +} diff --git a/src/hint_processor/hint_processor_def.zig b/src/hint_processor/hint_processor_def.zig index d6b18da1..10924073 100644 --- a/src/hint_processor/hint_processor_def.zig +++ b/src/hint_processor/hint_processor_def.zig @@ -36,6 +36,9 @@ const set = @import("set.zig"); const pow_utils = @import("pow_utils.zig"); const segments = @import("segments.zig"); +const bigint_utils = @import("../hint_processor/builtin_hint_processor/secp/bigint_utils.zig"); +const bigint = @import("bigint.zig"); + const deserialize_utils = @import("../parser/deserialize_utils.zig"); const testing_utils = @import("testing_utils.zig"); @@ -371,6 +374,16 @@ pub const CairoVMHintProcessor = struct { try squash_dict_utils.squashDictInnerNextKey(allocator, vm, exec_scopes, hint_data.ids_data, hint_data.ap_tracking); } else if (std.mem.eql(u8, hint_codes.SQUASH_DICT, hint_data.code)) { try squash_dict_utils.squashDict(allocator, vm, exec_scopes, hint_data.ids_data, hint_data.ap_tracking); + } else if (std.mem.eql(u8, hint_codes.HI_MAX_BIT_LEN, hint_data.code)) { + try bigint_utils.hiMaxBitlen(vm, allocator, hint_data.ids_data, hint_data.ap_tracking); + } else if (std.mem.eql(u8, hint_codes.NONDET_BIGINT3_V1, hint_data.code)) { + try bigint_utils.nondetBigInt3(allocator, vm, exec_scopes, hint_data.ids_data, hint_data.ap_tracking); + } else if (std.mem.eql(u8, hint_codes.NONDET_BIGINT3_V2, hint_data.code)) { + try bigint_utils.nondetBigInt3(allocator, vm, exec_scopes, hint_data.ids_data, hint_data.ap_tracking); + } else if (std.mem.eql(u8, hint_codes.BIGINT_PACK_DIV_MOD_HINT, hint_data.code)) { + try bigint.bigintPackDivModHint(allocator, vm, exec_scopes, hint_data.ids_data, hint_data.ap_tracking); + } else if (std.mem.eql(u8, hint_codes.BIGINT_SAFE_DIV, hint_data.code)) { + try bigint.bigIntSafeDivHint(allocator, vm, exec_scopes, hint_data.ids_data, hint_data.ap_tracking); } else { std.log.err("not implemented: {s}\n", .{hint_data.code}); return HintError.HintNotImplemented; diff --git a/src/hint_processor/math_hints.zig b/src/hint_processor/math_hints.zig index e9bb8114..f10551fe 100644 --- a/src/hint_processor/math_hints.zig +++ b/src/hint_processor/math_hints.zig @@ -162,6 +162,13 @@ pub fn sqrt( ); } +/// Returns the `n`th (up to the `251`th power) power of 2 as a [`&stark_felt::NonZeroFelt`] +/// in constant time. +/// It silently returns `1` if the input is out of bounds. +pub fn pow2ConstNz(comptime n: u32) Felt252 { + return Felt252.pow2Const(n); +} + // Implements hint: // from starkware.cairo.common.math_utils import assert_integer diff --git a/src/hint_processor/uint_utils.zig b/src/hint_processor/uint_utils.zig index 1dd312f9..107a6439 100644 --- a/src/hint_processor/uint_utils.zig +++ b/src/hint_processor/uint_utils.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Felt252 = @import("../math/fields/starknet.zig").Felt252; -const Int = std.math.big.int.Managed; +const Int = @import("std").math.big.int.Managed; const testing = std.testing; pub fn split(allocator: std.mem.Allocator, num: Int, comptime N: usize, num_bits_shift: usize) ![N]Felt252 { @@ -28,16 +28,22 @@ pub fn split(allocator: std.mem.Allocator, num: Int, comptime N: usize, num_bits pub fn pack(allocator: std.mem.Allocator, comptime N: usize, limbs: [N]Felt252, num_bits_shift: usize) !Int { var result = try Int.init(allocator); + errdefer result.deinit(); + var tmp = try Int.init(allocator); + defer tmp.deinit(); for (0..N) |i| { - var limb_to_uint = try Int.initSet(allocator, limbs[i].toInteger()); - defer limb_to_uint.deinit(); - try limb_to_uint.shiftLeft(&limb_to_uint, num_bits_shift * i); - try result.add(&result, &limb_to_uint); + try tmp.set(limbs[i].toInteger()); + + try tmp.shiftLeft(&tmp, num_bits_shift * i); + + try result.add(&result, &tmp); } + return result; } -test "split64 with uint utils" { + +test "UintUtils: split64 with uint utils" { var num = try Int.initSet(testing.allocator, 850981239023189021389081239089023); defer num.deinit(); const limbs = try split(testing.allocator, num, 2, 64); @@ -45,14 +51,14 @@ test "split64 with uint utils" { try std.testing.expectEqualSlices(Felt252, &[2]Felt252{ Felt252.fromInt(u64, 7249717543555297151), Felt252.fromInt(u64, 46131785404667) }, &limbs); } -test "u384 split128 with uint utils" { +test "UintUtils: u384 split128 with uint utils" { var num = try Int.initSet(testing.allocator, 6805647338418769269267492148635364229100); defer num.deinit(); const limbs = try split(testing.allocator, num, 2, 128); try std.testing.expectEqualSlices(Felt252, &[2]Felt252{ Felt252.fromInt(u128, 340282366920938463463374607431768211436), Felt252.fromInt(u128, 19) }, &limbs); } -test "pack 64 with uint utils" { +test "UintUtils: pack 64 with uint utils" { const limbs = [2]Felt252{ Felt252.fromInt(u64, 7249717543555297151), Felt252.fromInt(u64, 46131785404667) }; var num = try pack(testing.allocator, 2, limbs, 64); defer num.deinit(); @@ -61,7 +67,7 @@ test "pack 64 with uint utils" { try std.testing.expectEqualSlices(usize, expected.limbs, num.limbs); } -test "pack 128 with uint utils" { +test "UintUtils: pack 128 with uint utils" { const limbs = [2]Felt252{ Felt252.fromInt(u128, 59784002098951797857788619418398833806), Felt252.fromInt(u128, 1) }; var num = try pack(testing.allocator, 2, limbs, 128); defer num.deinit(); diff --git a/src/integration_tests.zig b/src/integration_tests.zig index 1f0381e0..70d71c62 100644 --- a/src/integration_tests.zig +++ b/src/integration_tests.zig @@ -23,10 +23,10 @@ pub fn main() void { .{ .pathname = "cairo_programs/assert_le_felt_old_compiled.json", .layout = "all_cairo" }, .{ .pathname = "cairo_programs/assert_nn_compiled.json", .layout = "all_cairo" }, .{ .pathname = "cairo_programs/assert_not_zero_compiled.json", .layout = "all_cairo" }, - // TODO: merge bigint hint + // TODO field utils hint not implemented // .{ .pathname = "cairo_programs/bigint_compiled.json", .layout = "all_cairo" }, .{ .pathname = "cairo_programs/big_struct_compiled.json", .layout = "all_cairo" }, - // TODO: not implemented hint + .{ .pathname = "cairo_programs/bitand_hint_compiled.json", .layout = "all_cairo" }, .{ .pathname = "cairo_programs/bitwise_output_compiled.json", .layout = "all_cairo" }, .{ .pathname = "cairo_programs/bitwise_builtin_test.json", .layout = "all_cairo" }, @@ -84,7 +84,7 @@ pub fn main() void { // TODO: HintNotImplemented error // .{ .pathname = "cairo_programs/fast_ec_add_v3.json", .layout = "all_cairo" }, .{ .pathname = "cairo_programs/fibonacci.json", .layout = "plain" }, - // TODO: HintNotImplemented error uint384 hint + // TODO: HintNotImplemented error secp signature hint // .{ .pathname = "cairo_programs/field_arithmetic.json", .layout = "all_cairo" }, // TODO: merge blake hint // .{ .pathname = "cairo_programs/finalize_blake2s.json", .layout = "all_cairo" }, @@ -98,7 +98,7 @@ pub fn main() void { .{ .pathname = "cairo_programs/function_return_if_print.json", .layout = "all_cairo" }, .{ .pathname = "cairo_programs/function_return_to_variable.json", .layout = "all_cairo" }, .{ .pathname = "cairo_programs/garaga.json", .layout = "all_cairo" }, - // TODO: hint not implemented (BigInt) error + // TODO: hint not implemented fq error // .{ .pathname = "cairo_programs/highest_bitlen.json", .layout = "all_cairo" }, .{ .pathname = "cairo_programs/if_and_prime.json", .layout = "all_cairo" }, .{ .pathname = "cairo_programs/if_in_function.json", .layout = "all_cairo" }, @@ -142,7 +142,7 @@ pub fn main() void { // TODO: hint not implemented ec utils // .{ .pathname = "cairo_programs/n_bit.json", .layout = "all_cairo" }, - // TODO: hint not implemented secp + // TODO: hint not implemented signature secp // .{ .pathname = "cairo_programs/nondet_bigint3_v2.json", .layout = "all_cairo" }, .{ .pathname = "cairo_programs/normalize_address.json", .layout = "all_cairo" }, .{ .pathname = "cairo_programs/not_main.json", .layout = "all_cairo" }, diff --git a/src/lib.zig b/src/lib.zig index 231e09b6..eef643ff 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -71,6 +71,7 @@ pub const hint_processor = struct { pub usingnamespace @import("hint_processor/squash_dict_utils.zig"); pub usingnamespace @import("hint_processor/usort.zig"); pub usingnamespace @import("hint_processor/memset_utils.zig"); + pub usingnamespace @import("hint_processor/builtin_hint_processor/secp/bigint_utils.zig"); pub usingnamespace @import("hint_processor/cairo_keccak_hints.zig"); pub usingnamespace @import("hint_processor/math_utils.zig"); }; diff --git a/src/math/fields/constants.zig b/src/math/fields/constants.zig index 665b1651..bea424f7 100644 --- a/src/math/fields/constants.zig +++ b/src/math/fields/constants.zig @@ -1,2 +1,7 @@ +const Felt252 = @import("starknet.zig").Felt252; +const std = @import("std"); + pub const STARKNET_PRIME = 0x800000000000011000000000000000000000000000000000000000000000001; pub const FELT_BYTE_SIZE = 32; + +pub const BASE = 77371252455336267181195264; diff --git a/src/math/fields/fields.zig b/src/math/fields/fields.zig index 35ee9e49..9cce9f3a 100644 --- a/src/math/fields/fields.zig +++ b/src/math/fields/fields.zig @@ -465,7 +465,7 @@ pub fn Field(comptime F: type, comptime modulo: u256) type { } pub fn modInverse(operand: Self, modulus: Self) !Self { - const ext = extendedGCD(@bitCast(operand.toInteger()), @bitCast(modulus.toInteger())); + const ext = extendedGCD(i256, @bitCast(operand.toInteger()), @bitCast(modulus.toInteger())); if (ext.gcd != 1) { @panic("GCD must be one"); @@ -651,6 +651,10 @@ pub fn Field(comptime F: type, comptime modulo: u256) type { return Self.fromInt(u256, @intCast(-value)).neg(); } + pub fn toSignedBigInt(self: Self, allocator: std.mem.Allocator) !std.math.big.int.Managed { + return std.math.big.int.Managed.initSet(allocator, self.toSignedInt()); + } + // converting felt to abs value with sign, in (- FIELD / 2, FIELD / 2 pub fn toSignedInt(self: Self) i256 { const val = self.toInteger(); diff --git a/src/math/fields/helper.zig b/src/math/fields/helper.zig index 041dc365..8cdaa246 100644 --- a/src/math/fields/helper.zig +++ b/src/math/fields/helper.zig @@ -1,4 +1,6 @@ const std = @import("std"); +const MathError = @import("../../vm/error.zig").MathError; +const Int = std.math.big.int.Managed; ///Returns the integer square root of the nonnegative integer n. ///This is the floor of the exact square root of n. @@ -97,16 +99,70 @@ pub fn tonelliShanks(n: u512, p: u512) struct { u512, u512, bool } { return .{ result, p - result, true }; } -pub fn extendedGCD(self: i256, other: i256) struct { gcd: i256, x: i256, y: i256 } { - var s = [_]i256{ 0, 1 }; - var t = [_]i256{ 1, 0 }; - var r = [_]i256{ other, self }; +pub fn extendedGCDBigInt(allocator: std.mem.Allocator, self: *const Int, other: *const Int) !struct { gcd: Int, x: Int, y: Int } { + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + + var s = [_]Int{ try Int.initSet(arena.allocator(), 0), try Int.initSet(arena.allocator(), 1) }; + var t = [_]Int{ try Int.initSet(arena.allocator(), 1), try Int.initSet(arena.allocator(), 0) }; + var r = [_]Int{ try other.cloneWithDifferentAllocator(arena.allocator()), try self.cloneWithDifferentAllocator(arena.allocator()) }; + + var q_tmp = try Int.init(arena.allocator()); + var r_tmp = try Int.init(arena.allocator()); + + while (!r[0].eqlZero()) { + try q_tmp.divFloor(&r_tmp, &r[1], &r[0]); + + std.mem.swap(Int, &r[0], &r[1]); + std.mem.swap(Int, &s[0], &s[1]); + std.mem.swap(Int, &t[0], &t[1]); + + try r_tmp.mul(&q_tmp, &r[1]); + try r[0].sub(&r[0], &r_tmp); + + try r_tmp.mul(&q_tmp, &s[1]); + try s[0].sub(&s[0], &r_tmp); + + try r_tmp.mul(&q_tmp, &t[1]); + try t[0].sub(&t[0], &r_tmp); + } + + var gcd = try Int.init(allocator); + errdefer gcd.deinit(); + + var x = try Int.init(allocator); + errdefer x.deinit(); + + var y = try Int.init(allocator); + errdefer y.deinit(); + + if (!r[1].isPositive()) { + r[1].negate(); + s[1].negate(); + t[1].negate(); + } + + try gcd.copy(r[1].toConst()); + try x.copy(s[1].toConst()); + try y.copy(t[1].toConst()); + + return .{ + .gcd = gcd, + .x = x, + .y = y, + }; +} + +pub fn extendedGCD(comptime T: type, self: T, other: T) struct { gcd: T, x: T, y: T } { + var s = [_]T{ 0, 1 }; + var t = [_]T{ 1, 0 }; + var r = [_]T{ other, self }; while (r[0] != 0) { const q = @divFloor(r[1], r[0]); - std.mem.swap(i256, &r[0], &r[1]); - std.mem.swap(i256, &s[0], &s[1]); - std.mem.swap(i256, &t[0], &t[1]); + std.mem.swap(T, &r[0], &r[1]); + std.mem.swap(T, &s[0], &s[1]); + std.mem.swap(T, &t[0], &t[1]); r[0] = r[0] - q * r[1]; s[0] = s[0] - q * s[1]; t[0] = t[0] - q * t[1]; @@ -141,3 +197,73 @@ pub fn divRem(comptime T: type, num: T, denominator: T) !struct { T, T } { @rem(num, denominator), }; } + +pub fn divModBigInt(allocator: std.mem.Allocator, n: *const Int, m: *const Int, p: *const Int) !Int { + var tmp = try Int.initSet(allocator, 1); + defer tmp.deinit(); + + var result = try Int.init(allocator); + errdefer result.deinit(); + + var igcdex_result = try extendedGCDBigInt(allocator, m, p); + defer { + igcdex_result.gcd.deinit(); + igcdex_result.x.deinit(); + igcdex_result.y.deinit(); + } + + if (!igcdex_result.gcd.eql(tmp)) { + return MathError.DivModIgcdexNotZero; + } + + try tmp.mul(n, &igcdex_result.x); + try tmp.divFloor(&result, &tmp, p); + + return result; +} + +pub fn divMod(comptime T: type, n: T, m: T, p: T) !T { + const igcdex_result = extendedGCD(T, m, p); + + if (igcdex_result.gcd != 1) { + return MathError.DivModIgcdexNotZero; + } + + return @mod(n * igcdex_result.x, p); +} + +/// Performs integer division between x and y; fails if x is not divisible by y. +pub fn safeDivBigInt(x: i512, y: i512) !i512 { + if (y == 0) { + return MathError.DividedByZero; + } + + const result = try divModFloor(i512, x, y); + + if (result[1] != 0) { + return MathError.SafeDivFailBigInt; + } + + return result[0]; +} + +test "Helper: extendedGCD big" { + const result = extendedGCD(i512, 12452004504504594952858248542859182495912, 20504205040); + + var self = try Int.initSet(std.testing.allocator, 12452004504504594952858248542859182495912); + defer self.deinit(); + + var other = try Int.initSet(std.testing.allocator, 20504205040); + defer other.deinit(); + + var res2 = try extendedGCDBigInt(std.testing.allocator, &self, &other); + defer { + res2.gcd.deinit(); + res2.x.deinit(); + res2.y.deinit(); + } + + try std.testing.expectEqual(result.gcd, try res2.gcd.to(i512)); + try std.testing.expectEqual(result.x, try res2.x.to(i512)); + try std.testing.expectEqual(result.y, try res2.y.to(i512)); +} diff --git a/src/vm/error.zig b/src/vm/error.zig index dabfe6f4..ea429354 100644 --- a/src/vm/error.zig +++ b/src/vm/error.zig @@ -212,6 +212,10 @@ pub const MathError = error{ ByteConversionError, DividedByZero, Felt252ToUsizeConversion, + DivModIgcdexNotZero, + SafeDivFailBigInt, + + SecpSplitOutOfRange, SafeDivFailU32, }; @@ -358,6 +362,8 @@ pub const HintError = error{ /// Occurs when a hint is attempting to be executed that is not yet implemented HintNotImplemented, + + MemoryHasNoValue, }; pub const InsufficientAllocatedCellsError = error{ diff --git a/src/vm/types/execution_scopes.zig b/src/vm/types/execution_scopes.zig index f815af62..407c7480 100644 --- a/src/vm/types/execution_scopes.zig +++ b/src/vm/types/execution_scopes.zig @@ -149,7 +149,7 @@ pub fn Rc(comptime T: type) type { } inline fn innerPtr(self: *const Self) *Inner { - return @fieldParentPtr(Inner, "value", self.value); + return @fieldParentPtr("value", self.value); } /// A single threaded, weak reference to a reference-counted value. @@ -167,7 +167,7 @@ pub fn Rc(comptime T: type) type { /// Creates a new weak reference object from a pointer to it's underlying value, /// without increasing the weak count. pub fn fromValuePtr(value: *T, alloc: std.mem.Allocator) Weak { - return .{ .inner = @fieldParentPtr(Inner, "value", value), .alloc = alloc }; + return .{ .inner = @fieldParentPtr("value", value), .alloc = alloc }; } /// Gets the number of strong references to this value. @@ -259,6 +259,7 @@ pub const HintType = union(enum) { dict_manager: Rc(DictManager), felt_map_of_felt_list: std.AutoHashMap(Felt252, std.ArrayList(Felt252)), felt_list: ArrayList(Felt252), + big_int: std.math.big.int.Managed, pub fn deinit(self: *Self) void { switch (self.*) { @@ -277,6 +278,7 @@ pub const HintType = union(enum) { .maybe_relocatable_map => |*m| m.deinit(), .u64_list => |*a| a.deinit(), .felt_list => |*a| a.deinit(), + .big_int => |*a| a.deinit(), .dict_manager => |d| d.releaseWithFn(DictManager.deinit), else => {}, }