From 174d6bf925510be4f66d6efd60fb1b837e65d3fe Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Thu, 29 Feb 2024 21:19:55 +0100 Subject: [PATCH] Add garaga hint (#433) * Add garaga hint * rename hint --------- Co-authored-by: lanaivina <31368580+lana-shanghai@users.noreply.github.com> --- src/hint_processor/builtin_hint_codes.zig | 5 + src/hint_processor/felt_bit_length.zig | 194 ++++++++++++++++++++ src/hint_processor/hint_processor_def.zig | 3 + src/hint_processor/hint_processor_utils.zig | 152 +++++++++++++-- src/hint_processor/hint_utils.zig | 90 ++++++++- src/lib.zig | 1 + 6 files changed, 428 insertions(+), 17 deletions(-) create mode 100644 src/hint_processor/felt_bit_length.zig diff --git a/src/hint_processor/builtin_hint_codes.zig b/src/hint_processor/builtin_hint_codes.zig index 92a6b646..4a880be9 100644 --- a/src/hint_processor/builtin_hint_codes.zig +++ b/src/hint_processor/builtin_hint_codes.zig @@ -1,3 +1,8 @@ +pub const GET_FELT_BIT_LENGTH = + \\x = ids.x + \\ids.bit_length = x.bit_length() +; + pub const ASSERT_NN = "from starkware.cairo.common.math_utils import assert_integer\nassert_integer(ids.a)\nassert 0 <= ids.a % PRIME < range_check_builtin.bound, f'a = {ids.a} is out of range.'"; pub const VERIFY_ECDSA_SIGNATURE = "ecdsa_builtin.add_signature(ids.ecdsa_ptr.address_, (ids.signature_r, ids.signature_s))"; pub const IS_POSITIVE = "from starkware.cairo.common.math_utils import is_positive\nids.is_positive = 1 if is_positive(\n value=ids.value, prime=PRIME, rc_bound=range_check_builtin.bound) else 0"; diff --git a/src/hint_processor/felt_bit_length.zig b/src/hint_processor/felt_bit_length.zig new file mode 100644 index 00000000..d5adbfe2 --- /dev/null +++ b/src/hint_processor/felt_bit_length.zig @@ -0,0 +1,194 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const expectEqual = std.testing.expectEqual; +const expect = std.testing.expect; + +const CairoVM = @import("../vm/core.zig").CairoVM; +const HintReference = @import("./hint_processor_def.zig").HintReference; +const hint_utils = @import("./hint_utils.zig"); +const ApTracking = @import("../vm/types/programjson.zig").ApTracking; +const HintProcessor = @import("hint_processor_def.zig").CairoVMHintProcessor; +const HintData = @import("hint_processor_def.zig").HintData; +const Relocatable = @import("../vm/memory/relocatable.zig").Relocatable; +const MaybeRelocatable = @import("../vm/memory/relocatable.zig").MaybeRelocatable; +const Felt252 = @import("../math/fields/starknet.zig").Felt252; +const hint_codes = @import("builtin_hint_codes.zig"); + +/// Implements a hint that calculates the bit length of a variable and assigns it to another variable. +/// +/// This function retrieves the value of variable `x` from the provided `ids_datas`, calculates its bit length, +/// and then inserts the bit length value into the `ids_datas` under the variable name `bit_length`. +/// +/// # Arguments +/// +/// - `allocator`: An allocator to manage memory allocation. +/// - `vm`: A pointer to the CairoVM instance. +/// - `ids_datas`: A hashmap containing variable names and their associated references. +/// - `ap_tracking`: An ApTracking instance providing access path tracking information. +/// +/// # Errors +/// +/// This function returns an error if there is any issue with retrieving or inserting values into the `ids_datas`. +/// +/// # Implements hint: +/// ```python +/// x = ids.x, +/// ids.bit_length = x.bit_length() +/// ``` +pub fn getFeltBitLength( + allocator: Allocator, + vm: *CairoVM, + ids_datas: std.StringHashMap(HintReference), + ap_tracking: ApTracking, +) !void { + // Retrieve the value of variable `x` from `ids_datas`. + const x = try hint_utils.getIntegerFromVarName("x", vm, ids_datas, ap_tracking); + + // Calculate the bit length of `x`. + // Insert the bit length value into `ids_datas` under the variable name `bit_length`. + try hint_utils.insertValueFromVarName( + allocator, + "bit_length", + MaybeRelocatable.fromInt(usize, x.numBits()), + vm, + ids_datas, + ap_tracking, + ); +} + +test "FeltBitLength: simple test" { + // Initialize a hashmap to store variable references. + var ids_data = std.StringHashMap(HintReference).init(std.testing.allocator); + defer ids_data.deinit(); + + // Store references for variables "x" and "bit_length". + // Variable "x" is located at `fp + 0`, and "bit_length" at `fp + 1`. + try ids_data.put("x", HintReference.initSimple(0)); + try ids_data.put("bit_length", HintReference.initSimple(1)); + + // Initialize the Cairo virtual machine. + var vm = try CairoVM.init(std.testing.allocator, .{}); + defer vm.deinit(); + + // Set the frame pointer to point to the beginning of the stack. + vm.run_context.*.fp.* = .{}; + + // Allocate memory space for variables `ids.x` and `ids.bit_length`. + inline for (0..2) |_| _ = try vm.addMemorySegment(); + + // Set up memory segments in the virtual machine. + // The memory layout is: [0, 0, 0, 0, 0, 0, 0] (total 7 words). + try vm.segments.memory.setUpMemory( + std.testing.allocator, + .{.{ .{ 0, 0 }, .{7} }}, + ); + defer vm.segments.memory.deinitData(std.testing.allocator); + + // Initialize a HintProcessor instance. + const hint_processor: HintProcessor = .{}; + + // Initialize HintData with the GET_FELT_BIT_LENGTH hint code and the `ids_data`. + var hint_data = HintData.init(hint_codes.GET_FELT_BIT_LENGTH, ids_data, .{}); + + // Execute the hint processor with the provided data. + try hint_processor.executeHint(std.testing.allocator, &vm, &hint_data, undefined, undefined); + + // Retrieve the result from the memory location of `ids.bit_length`. + const res = try vm.getFelt(Relocatable.init(0, 1)); + + // Ensure that the result matches the expected value. + try expectEqual(Felt252.fromInt(u8, 3), res); +} + +test "FeltBitLength: range test" { + // Iterate over a range of values from 0 to 251 (inclusive). + for (0..252) |i| { + // Initialize a hashmap to store variable references. + var ids_data = std.StringHashMap(HintReference).init(std.testing.allocator); + defer ids_data.deinit(); + + // Store references for variables "x" and "bit_length". + // Variable "x" is located at `fp + 0`, and "bit_length" at `fp + 1`. + try ids_data.put("x", HintReference.initSimple(0)); + try ids_data.put("bit_length", HintReference.initSimple(1)); + + // Initialize the Cairo virtual machine. + var vm = try CairoVM.init(std.testing.allocator, .{}); + defer vm.deinit(); + + // Set the frame pointer to point to the beginning of the stack. + vm.run_context.*.fp.* = .{}; + + // Allocate memory space for variables `ids.x` and `ids.bit_length`. + inline for (0..2) |_| _ = try vm.addMemorySegment(); + + // Set the value of `ids.x` to 2^i. + try vm.segments.memory.set( + std.testing.allocator, + .{}, + MaybeRelocatable.fromFelt(Felt252.two().pow(i)), + ); + defer vm.segments.memory.deinitData(std.testing.allocator); + + // Initialize a HintProcessor instance. + const hint_processor: HintProcessor = .{}; + + // Initialize HintData with the GET_FELT_BIT_LENGTH hint code and the `ids_data`. + var hint_data = HintData.init(hint_codes.GET_FELT_BIT_LENGTH, ids_data, .{}); + + // Execute the hint processor with the provided data. + try hint_processor.executeHint(std.testing.allocator, &vm, &hint_data, undefined, undefined); + + // Retrieve the result from the memory location of `ids.bit_length`. + const res = try vm.getFelt(Relocatable.init(0, 1)); + + // Ensure that the result matches the expected value. + try expectEqual(Felt252.fromInt(u256, i + 1), res); + } +} + +test "FeltBitLength: wrap around" { + // Initialize a hashmap to store variable references. + var ids_data = std.StringHashMap(HintReference).init(std.testing.allocator); + defer ids_data.deinit(); + + // Store references for variables "x" and "bit_length". + // Variable "x" is located at `fp + 0`, and "bit_length" at `fp + 1`. + try ids_data.put("x", HintReference.initSimple(0)); + try ids_data.put("bit_length", HintReference.initSimple(1)); + + // Initialize the Cairo virtual machine. + var vm = try CairoVM.init(std.testing.allocator, .{}); + defer vm.deinit(); + + // Set the frame pointer to point to the beginning of the stack. + vm.run_context.*.fp.* = .{}; + + // Allocate memory space for variables `ids.x` and `ids.bit_length`. + inline for (0..2) |_| _ = try vm.addMemorySegment(); + + // Set the value of `ids.x` to (Felt252.Modulo - 1) + 1, causing wrap around. + try vm.segments.memory.set( + std.testing.allocator, + .{}, + MaybeRelocatable.fromFelt( + Felt252.fromInt(u256, Felt252.Modulo - 1).add(Felt252.one()), + ), + ); + defer vm.segments.memory.deinitData(std.testing.allocator); + + // Initialize a HintProcessor instance. + const hint_processor: HintProcessor = .{}; + + // Initialize HintData with the GET_FELT_BIT_LENGTH hint code and the `ids_data`. + var hint_data = HintData.init(hint_codes.GET_FELT_BIT_LENGTH, ids_data, .{}); + + // Execute the hint processor with the provided data. + try hint_processor.executeHint(std.testing.allocator, &vm, &hint_data, undefined, undefined); + + // Retrieve the result from the memory location of `ids.bit_length`. + const res = try vm.getFelt(Relocatable.init(0, 1)); + + // Ensure that the result matches the expected value (0). + try expectEqual(Felt252.zero(), res); +} diff --git a/src/hint_processor/hint_processor_def.zig b/src/hint_processor/hint_processor_def.zig index 33e000f0..9afd1dd7 100644 --- a/src/hint_processor/hint_processor_def.zig +++ b/src/hint_processor/hint_processor_def.zig @@ -19,6 +19,7 @@ const Relocatable = @import("../vm/memory/relocatable.zig").Relocatable; const hint_codes = @import("builtin_hint_codes.zig"); const math_hints = @import("math_hints.zig"); const memcpy_hint_utils = @import("memcpy_hint_utils.zig"); +const felt_bit_length = @import("felt_bit_length.zig"); const deserialize_utils = @import("../parser/deserialize_utils.zig"); @@ -197,6 +198,8 @@ pub const CairoVMHintProcessor = struct { try memcpy_hint_utils.exitScope(exec_scopes); } else if (std.mem.eql(u8, hint_codes.MEMCPY_ENTER_SCOPE, hint_data.code)) { try memcpy_hint_utils.memcpyEnterScope(allocator, vm, exec_scopes, hint_data.ids_data, hint_data.ap_tracking); + } else if (std.mem.eql(u8, hint_codes.GET_FELT_BIT_LENGTH, hint_data.code)) { + try felt_bit_length.getFeltBitLength(allocator, vm, hint_data.ids_data, hint_data.ap_tracking); } else {} } diff --git a/src/hint_processor/hint_processor_utils.zig b/src/hint_processor/hint_processor_utils.zig index 44aae932..3284db20 100644 --- a/src/hint_processor/hint_processor_utils.zig +++ b/src/hint_processor/hint_processor_utils.zig @@ -1,4 +1,6 @@ const std = @import("std"); +const expectEqual = std.testing.expectEqual; +const expect = std.testing.expect; const Register = @import("../vm/instructions.zig").Register; const ApTracking = @import("../vm/types/programjson.zig").ApTracking; @@ -25,21 +27,41 @@ pub fn insertValueFromReference( } else return HintError.UnknownIdentifierInternal; } -///Returns the Integer value stored in the given ids variable -/// Returns an internal error, users should map it into a more informative type +/// Retrieves the integer value stored in the given ids variable. +/// +/// This function retrieves the integer value stored in the given ids variable indicated by the provided `hint_reference`. +/// If the value is stored as an immediate, it returns the value directly. +/// Otherwise, it computes the memory address of the variable and retrieves the integer value from memory. +/// +/// # Parameters +/// - `vm`: A pointer to the Cairo virtual machine. +/// - `hint_reference`: The hint reference indicating the variable. +/// - `ap_tracking`: The AP tracking data. +/// +/// # Returns +/// Returns the integer value stored in the variable indicated by the hint reference. +/// If the variable is not found or if there's an error retrieving the value, it returns an error of type `HintError`. pub fn getIntegerFromReference( vm: *CairoVM, hint_reference: HintReference, ap_tracking: ApTracking, ) !Felt252 { - // if the reference register is none, this means it is an immediate value and we - // should return that value. + // If the reference register is none, this means it is an immediate value, and we should return that value. switch (hint_reference.offset1) { .immediate => |int_1| return int_1, else => {}, } - return if (computeAddrFromReference(hint_reference, ap_tracking, vm)) |var_addr| vm.segments.memory.getFelt(var_addr) catch HintError.WrongIdentifierTypeInternal else HintError.UnknownIdentifierInternal; + // std.debug.print( + // "totototot = {any}\n", + // .{computeAddrFromReference(hint_reference, ap_tracking, vm)}, + // ); + + // Compute the memory address of the variable and retrieve the integer value from memory. + return if (computeAddrFromReference(hint_reference, ap_tracking, vm)) |var_addr| + vm.segments.memory.getFelt(var_addr) catch HintError.WrongIdentifierTypeInternal + else + HintError.UnknownIdentifierInternal; } ///Returns the Relocatable value stored in the given ids variable @@ -85,18 +107,59 @@ pub fn getOffsetValueReference( return MaybeRelocatable.fromRelocatable(base_addr.addInt(@as(i64, @intCast(refer[1]))) catch unreachable); } } -///Computes the memory address of the ids variable indicated by the HintReference as a [Relocatable] -pub fn computeAddrFromReference(hint_reference: HintReference, hint_ap_tracking: ApTracking, vm: *CairoVM) ?Relocatable { + +/// Computes the memory address indicated by the provided hint reference. +/// +/// This function takes a hint reference, which is a complex data structure indicating the address +/// of a variable within the Cairo virtual machine's memory. The function computes this address +/// by following the instructions encoded in the hint reference. +/// +/// # Parameters +/// - `hint_reference`: The hint reference indicating the address. +/// - `hint_ap_tracking`: The AP tracking data associated with the hint reference. +/// - `vm`: A pointer to the Cairo virtual machine. +/// +/// # Returns +/// Returns the computed memory address as a `Relocatable` if successful, or `null` if the address +/// could not be computed. +pub fn computeAddrFromReference( + hint_reference: HintReference, + hint_ap_tracking: ApTracking, + vm: *CairoVM, +) ?Relocatable { + // Extract the first offset value from the hint reference. const offset1 = switch (hint_reference.offset1) { - .reference => getOffsetValueReference(vm, hint_reference, hint_ap_tracking, hint_reference.offset1).?.intoRelocatable() catch unreachable, + .reference => if (getOffsetValueReference( + vm, + hint_reference, + hint_ap_tracking, + hint_reference.offset1, + )) |v| + // Convert the offset value to a relocatable address. + v.intoRelocatable() catch return null + else + return null, else => return null, }; + // Compute the memory address based on the second offset value or a constant value. return switch (hint_reference.offset2) { - .reference => offset1.addFelt(getOffsetValueReference(vm, hint_reference, hint_ap_tracking, hint_reference.offset2).?.intoFelt() catch unreachable) catch unreachable, - - .value => |val| offset1.addInt(val) catch unreachable, - + .reference => blk: { + // If the second offset is a reference, it must be resolved. + const value = getOffsetValueReference( + vm, + hint_reference, + hint_ap_tracking, + hint_reference.offset2, + ) orelse return null; + + // Convert the offset value to an unsigned 64-bit integer. + const value_int = value.intoU64() catch return null; + + // Add the offset value to the base address. + break :blk offset1.addUint(value_int) catch return null; + }, + .value => |val| offset1.addInt(val) catch null, else => null, }; } @@ -122,3 +185,68 @@ pub fn getMaybeRelocatableFromReference( else null; } + +test "computeAddrFromReference: no register in reference" { + // Initialize the Cairo virtual machine. + var vm = try CairoVM.init(std.testing.allocator, .{}); + defer vm.deinit(); // Ensure cleanup. + + // Set up memory segments in the virtual machine. + try vm.segments.memory.setUpMemory( + std.testing.allocator, + .{.{ .{ 1, 0 }, .{ 4, 0 } }}, + ); + defer vm.segments.memory.deinitData(std.testing.allocator); // Clean up memory data. + + // Create a hint reference with no register information. + var hint_reference = HintReference.init(0, 0, false, false); + // Set the immediate offset value to 2. + hint_reference.offset1 = .{ .immediate = Felt252.fromInt(u8, 2) }; + + // Ensure that the computed address is null, as no register information is provided. + try expectEqual(null, computeAddrFromReference(hint_reference, .{}, &vm)); +} + +test "computeAddrFromReference: failed to get ids" { + // Initialize the Cairo virtual machine. + var vm = try CairoVM.init(std.testing.allocator, .{}); + defer vm.deinit(); // Ensure cleanup. + + // Set up memory segments in the virtual machine. + try vm.segments.memory.setUpMemory( + std.testing.allocator, + .{.{ .{ 1, 0 }, .{4} }}, + ); + defer vm.segments.memory.deinitData(std.testing.allocator); // Clean up memory data. + + // Create a hint reference with register information pointing to the frame pointer (FP). + var hint_reference = HintReference.init(0, 0, false, false); + // Set the reference offset to point to an unknown location relative to the frame pointer. + hint_reference.offset1 = .{ .reference = .{ .FP, -1, true } }; + + // Ensure that the computed address is null due to failure in retrieving the ids variable. + try expectEqual(null, computeAddrFromReference(hint_reference, .{}, &vm)); +} + +test "getIntegerFromReference: with immediate value" { + // Initialize the Cairo virtual machine. + var vm = try CairoVM.init(std.testing.allocator, .{}); + defer vm.deinit(); // Ensure cleanup. + + // Set up memory segments in the virtual machine. + try vm.segments.memory.setUpMemory( + std.testing.allocator, + .{.{ .{ 1, 0 }, .{0} }}, + ); + defer vm.segments.memory.deinitData(std.testing.allocator); // Clean up memory data. + + // Create a hint reference with an immediate value. + var hint_reference = HintReference.init(0, 0, false, true); + hint_reference.offset1 = .{ .immediate = Felt252.fromInt(u8, 2) }; + + // Assert that the integer value retrieved from the reference is equal to the expected value. + try expectEqual( + Felt252.fromInt(u8, 2), + getIntegerFromReference(&vm, hint_reference, .{}), + ); +} diff --git a/src/hint_processor/hint_utils.zig b/src/hint_processor/hint_utils.zig index d26763a8..cad19566 100644 --- a/src/hint_processor/hint_utils.zig +++ b/src/hint_processor/hint_utils.zig @@ -1,4 +1,7 @@ const std = @import("std"); +const expectEqual = std.testing.expectEqual; +const expect = std.testing.expect; +const expectError = std.testing.expectError; const Register = @import("../vm/instructions.zig").Register; const ApTracking = @import("../vm/types/programjson.zig").ApTracking; @@ -79,7 +82,14 @@ pub fn getAddressFromVarName( ids_data: std.StringHashMap(HintReference), ap_tracking: ApTracking, ) !MaybeRelocatable { - return MaybeRelocatable.fromRelocatable(try getRelocatableFromVarName(var_name, vm, ids_data, ap_tracking)); + return MaybeRelocatable.fromRelocatable( + try getRelocatableFromVarName( + var_name, + vm, + ids_data, + ap_tracking, + ), + ); } //Gets the address, as a Relocatable of the variable given by the ids name @@ -92,18 +102,35 @@ pub fn getRelocatableFromVarName( return if (ids_data.get(var_name)) |x| if (hint_processor_utils.computeAddrFromReference(x, ap_tracking, vm)) |v| v else HintError.UnknownIdentifier else HintError.UnknownIdentifier; } -//Gets the value of a variable name. -//If the value is an MaybeRelocatable::Int(Bigint) return &Bigint -//else raises Err +/// Retrieves the value of a variable by its name. +/// +/// This function retrieves the value of a variable identified by its name. If the value is an +/// integer, it returns the integer value. Otherwise, it raises an error. +/// +/// # Parameters +/// - `var_name`: The name of the variable to retrieve. +/// - `vm`: Pointer to the Cairo virtual machine. +/// - `ids_data`: String hashmap containing variable references. +/// - `ap_tracking`: ApTracking object representing the current activation packet tracking. +/// +/// # Returns +/// Returns the integer value of the variable identified by `var_name`. +/// +/// # Errors +/// Returns an error if the variable value is not an integer or if the variable is not found. pub fn getIntegerFromVarName( var_name: []const u8, vm: *CairoVM, ids_data: std.StringHashMap(HintReference), ap_tracking: ApTracking, ) !Felt252 { + // Retrieve the reference to the variable using its name. const reference = try getReferenceFromVarName(var_name, ids_data); - return hint_processor_utils.getIntegerFromReference(vm, reference, ap_tracking) catch |err| switch (err) { + // Get the integer value from the reference. + return hint_processor_utils.getIntegerFromReference(vm, reference, ap_tracking) catch |err| + // Handle specific errors. + switch (err) { HintError.WrongIdentifierTypeInternal => HintError.IdentifierNotInteger, else => HintError.UnknownIdentifier, }; @@ -138,3 +165,56 @@ pub fn getMaybeRelocatableFromVarName( return hint_processor_utils.getMaybeRelocatableFromReference(vm, reference, ap_tracking) orelse HintError.UnknownIdentifier; } + +test "getIntegerFromVarName: valid" { + // Initializes the Cairo virtual machine. + var vm = try CairoVM.init(std.testing.allocator, .{}); + defer vm.deinit(); // Ensure cleanup. + + // Sets up memory segments in the virtual machine. + try vm.segments.memory.setUpMemory( + std.testing.allocator, + .{.{ .{ 1, 0 }, .{1} }}, + ); + defer vm.segments.memory.deinitData(std.testing.allocator); // Clean up memory data. + + // Creates a hashmap containing variable references. + var ids_data = std.StringHashMap(HintReference).init(std.testing.allocator); + defer ids_data.deinit(); + + // Puts a hint reference named "value" into the hashmap. + try ids_data.put("value", HintReference.initSimple(0)); + + // Calls `getIntegerFromVarName` function with the variable name "value". + try expectEqual( + Felt252.fromInt(u8, 1), + try getIntegerFromVarName("value", &vm, ids_data, .{}), + ); +} + +test "getIntegerFromVarName: invalid" { + // Initializes the Cairo virtual machine. + var vm = try CairoVM.init(std.testing.allocator, .{}); + defer vm.deinit(); // Ensure cleanup. + + // Sets up memory segments in the virtual machine with an invalid configuration. + try vm.segments.memory.setUpMemory( + std.testing.allocator, + .{.{ .{ 1, 0 }, .{ 0, 0 } }}, + ); + defer vm.segments.memory.deinitData(std.testing.allocator); // Clean up memory data. + + // Creates a hashmap containing variable references. + var ids_data = std.StringHashMap(HintReference).init(std.testing.allocator); + defer ids_data.deinit(); + + // Puts a hint reference named "value" into the hashmap. + try ids_data.put("value", HintReference.initSimple(0)); + + // Calls `getIntegerFromVarName` function with the variable name "value". + // Expects the function to return an error of type `HintError.IdentifierNotInteger`. + try expectError( + HintError.IdentifierNotInteger, + getIntegerFromVarName("value", &vm, ids_data, .{}), + ); +} diff --git a/src/lib.zig b/src/lib.zig index 1c5e9c0a..811a99ad 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -44,6 +44,7 @@ pub const hint_processor = struct { pub usingnamespace @import("hint_processor/memcpy_hint_utils.zig"); pub usingnamespace @import("hint_processor/hint_utils.zig"); pub usingnamespace @import("hint_processor/math_hints.zig"); + pub usingnamespace @import("hint_processor/felt_bit_length.zig"); }; pub const parser = struct {