Skip to content

Commit

Permalink
feat(vm): implement deduce_memory_cell (#70)
Browse files Browse the repository at this point in the history
* Use Allocator instead of *const Allocator

* add ArrayList(BuiltinRunner) in CairoVM

* implement deduce_memory_cell

* add deduce_memory_cell pedersen builtin valid and clean up

* fix deduce_memory_cell test

* rename Error to BitwiseError

* change bitwise to builtin in VM core

* clean up

* migrate VM core unit tests to dedicated isolate file

* clean up and add documentation

* fix conflicts

* fix test name
  • Loading branch information
tcoratger authored Oct 28, 2023
1 parent cc65725 commit a81c149
Show file tree
Hide file tree
Showing 23 changed files with 1,995 additions and 1,135 deletions.
1 change: 1 addition & 0 deletions src/lib.zig
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub const vm = struct {
pub usingnamespace @import("vm/core.zig");
pub usingnamespace @import("vm/core_test.zig");
pub usingnamespace @import("vm/config.zig");
pub usingnamespace @import("vm/error.zig");
pub usingnamespace @import("vm/instructions.zig");
Expand Down
14 changes: 14 additions & 0 deletions src/math/crypto/signatures.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const std = @import("std");
const Felt252 = @import("../fields/starknet.zig");

const ArrayList = std.ArrayList;

/// Stark ECDSA signature
pub const Signature = struct {
const Self = @This();

/// The `r` value of a signature
r: ArrayList(Felt252),
/// The `s` value of a signature
s: ArrayList(Felt252),
};
30 changes: 17 additions & 13 deletions src/vm/builtins/bitwise/bitwise.zig
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ const Felt252 = @import("../../../math/fields/starknet.zig").Felt252;
// *****************************************************************************

// Error type to represent different error conditions during bitwise builtin.
pub const Error = error{ InvalidBitwiseIndex, UnsupportedNumberOfBits, InvalidAddressForBitwise };
pub const BitwiseError = error{
InvalidBitwiseIndex,
UnsupportedNumberOfBits,
InvalidAddressForBitwise,
};

/// Each bitwise operation consists of 5 cells (two inputs and three outputs - and, or, xor).
// comment credit to: https://github.com/starkware-libs/cairo-lang/blob//src/starkware/cairo/lang/builtins/bitwise/instance_def.py#L4
Expand All @@ -32,17 +36,17 @@ const BITWISE_INPUT_CELLS_PER_INSTANCE = 2;
/// - memory: The cairo memory where addresses are looked up
/// # Returns
/// The felt as an integer.
fn getValue(address: Relocatable, memory: *Memory) Error!u256 {
fn getValue(address: Relocatable, memory: *Memory) BitwiseError!u256 {
var value = memory.get(address) catch {
return Error.InvalidAddressForBitwise;
return BitwiseError.InvalidAddressForBitwise;
};

var felt = value.tryIntoFelt() catch {
return Error.InvalidAddressForBitwise;
return BitwiseError.InvalidAddressForBitwise;
};

if (felt.toInteger() > std.math.pow(u256, 2, BITWISE_TOTAL_N_BITS)) {
return Error.UnsupportedNumberOfBits;
return BitwiseError.UnsupportedNumberOfBits;
}

return felt.toInteger();
Expand All @@ -54,16 +58,16 @@ fn getValue(address: Relocatable, memory: *Memory) Error!u256 {
/// - memory: The cairo memory where addresses are looked up
/// # Returns
/// The deduced value as a `MaybeRelocatable`
pub fn deduce(address: Relocatable, memory: *Memory) Error!MaybeRelocatable {
pub fn deduce(address: Relocatable, memory: *Memory) BitwiseError!MaybeRelocatable {
const index = address.offset % CELLS_PER_BITWISE;

if (index < BITWISE_INPUT_CELLS_PER_INSTANCE) {
return Error.InvalidBitwiseIndex;
return BitwiseError.InvalidBitwiseIndex;
}

// calculate offset
const x_offset = address.subUint(index) catch {
return Error.InvalidBitwiseIndex;
return BitwiseError.InvalidBitwiseIndex;
};
const y_offset = try x_offset.addUint(1);

Expand All @@ -74,7 +78,7 @@ pub fn deduce(address: Relocatable, memory: *Memory) Error!MaybeRelocatable {
2 => x & y, // and
3 => x ^ y, // xor
4 => x | y, // or
else => return Error.InvalidBitwiseIndex,
else => return BitwiseError.InvalidBitwiseIndex,
};

return MaybeRelocatable{ .felt = Felt252.fromInteger(res) };
Expand All @@ -97,7 +101,7 @@ test "deduce when address.offset less than BITWISE_INPUT_CELLS_PER_INSTANCE" {
var address = Relocatable.new(0, 5);

// then
try expectError(Error.InvalidBitwiseIndex, deduce(address, mem));
try expectError(BitwiseError.InvalidBitwiseIndex, deduce(address, mem));
}

test "deduce when address points to nothing in memory" {
Expand All @@ -111,7 +115,7 @@ test "deduce when address points to nothing in memory" {
var address = Relocatable.new(0, 3);

// then
try expectError(Error.InvalidAddressForBitwise, deduce(address, mem));
try expectError(BitwiseError.InvalidAddressForBitwise, deduce(address, mem));
}

test "deduce when address points to relocatable variant of MaybeRelocatable " {
Expand All @@ -127,7 +131,7 @@ test "deduce when address points to relocatable variant of MaybeRelocatable " {
try mem.set(Relocatable.new(0, 5), newFromRelocatable(address));

// then
try expectError(Error.InvalidAddressForBitwise, deduce(address, mem));
try expectError(BitwiseError.InvalidAddressForBitwise, deduce(address, mem));
}

test "deduce when address points to felt greater than BITWISE_TOTAL_N_BITS" {
Expand All @@ -147,7 +151,7 @@ test "deduce when address points to felt greater than BITWISE_TOTAL_N_BITS" {
), fromU256(number));

// then
try expectError(Error.UnsupportedNumberOfBits, deduce(address, mem));
try expectError(BitwiseError.UnsupportedNumberOfBits, deduce(address, mem));
}

// happy path tests graciously ported from https://github.com/lambdaclass/cairo-vm_in_go/blob/main/pkg/builtins/bitwise_test.go#L13
Expand Down
61 changes: 61 additions & 0 deletions src/vm/builtins/builtin_runner/bitwise.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const bitwise_instance_def = @import("../../types/bitwise_instance_def.zig");

/// Bitwise built-in runner
pub const BitwiseBuiltinRunner = struct {
const Self = @This();

/// Ratio
ratio: ?u32,
/// Base
base: usize,
/// Number of cells per instance
cells_per_instance: u32,
/// Number of input cells
n_input_cells: u32,
/// Built-in bitwise instance
bitwise_builtin: bitwise_instance_def.BitwiseInstanceDef,
/// Stop pointer
stop_ptr: ?usize,
/// Included boolean flag
included: bool,
/// Number of instance per component
instances_per_component: u32,

/// Create a new BitwiseBuiltinRunner instance.
///
/// This function initializes a new `BitwiseBuiltinRunner` instance with the provided
/// `instance_def` and `included` values.
///
/// # Arguments
///
/// - `instance_def`: A pointer to the `BitwiseInstanceDef` for this runner.
/// - `included`: A boolean flag indicating whether this runner is included.
///
/// # Returns
///
/// A new `BitwiseBuiltinRunner` instance.
pub fn new(
instance_def: *bitwise_instance_def.BitwiseInstanceDef,
included: bool,
) Self {
return .{
.ratio = instance_def.ratio,
.base = 0,
.cell_per_instance = bitwise_instance_def.CELLS_PER_BITWISE,
.n_input_cells = bitwise_instance_def.INPUT_CELLS_PER_BITWISE,
.bitwise_builtin = instance_def,
.stop_ptr = null,
.included = included,
.instances_per_component = 1,
};
}

/// Get the base value of this runner.
///
/// # Returns
///
/// The base value as a `usize`.
pub fn get_base(self: *const Self) usize {
return self.base;
}
};
54 changes: 54 additions & 0 deletions src/vm/builtins/builtin_runner/builtin_runner.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const BitwiseBuiltinRunner = @import("./bitwise.zig").BitwiseBuiltinRunner;
const EcOpBuiltinRunner = @import("./ec_op.zig").EcOpBuiltinRunner;
const HashBuiltinRunner = @import("./hash.zig").HashBuiltinRunner;
const KeccakBuiltinRunner = @import("./keccak.zig").KeccakBuiltinRunner;
const OutputBuiltinRunner = @import("./output.zig").OutputBuiltinRunner;
const PoseidonBuiltinRunner = @import("./poseidon.zig").PoseidonBuiltinRunner;
const RangeCheckBuiltinRunner = @import("./range_check.zig").RangeCheckBuiltinRunner;
const SegmentArenaBuiltinRunner = @import("./segment_arena.zig").SegmentArenaBuiltinRunner;
const SignatureBuiltinRunner = @import("./signature.zig").SignatureBuiltinRunner;

/// Built-in runner
pub const BuiltinRunner = union(enum) {
const Self = @This();

/// Bitwise built-in runner for bitwise operations.
Bitwise: BitwiseBuiltinRunner,
/// EC Operation built-in runner for elliptic curve operations.
EcOp: EcOpBuiltinRunner,
/// Hash built-in runner for hash operations.
Hash: HashBuiltinRunner,
/// Output built-in runner for output operations.
Output: OutputBuiltinRunner,
/// Range Check built-in runner for range check operations.
RangeCheck: RangeCheckBuiltinRunner,
/// Keccak built-in runner for Keccak operations.
Keccak: KeccakBuiltinRunner,
/// Signature built-in runner for signature operations.
Signature: SignatureBuiltinRunner,
/// Poseidon built-in runner for Poseidon operations.
Poseidon: PoseidonBuiltinRunner,
/// Segment Arena built-in runner for segment arena operations.
SegmentArena: SegmentArenaBuiltinRunner,

/// Get the base value of the built-in runner.
///
/// This function returns the base value specific to the type of built-in runner.
///
/// # Returns
///
/// The base value as a `usize`.
pub fn base(self: *const Self) usize {
return switch (self.*) {
.Bitwise => |*bitwise| bitwise.get_base(),
.EcOp => |*ec| ec.get_base(),
.Hash => |*hash| hash.get_base(),
.Output => |*output| output.get_base(),
.RangeCheck => |*range_check| range_check.get_base(),
.Keccak => |*keccak| keccak.get_base(),
.Signature => |*signature| signature.get_base(),
.Poseidon => |*poseidon| poseidon.get_base(),
.SegmentArena => |*segment_arena| segment_arena.get_base(),
};
}
};
72 changes: 72 additions & 0 deletions src/vm/builtins/builtin_runner/ec_op.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
const std = @import("std");
const ec_op_instance_def = @import("../../types/ec_op_instance_def.zig");
const relocatable = @import("../../memory/relocatable.zig");
const Felt252 = @import("../../../math/fields/starknet.zig").Felt252;

const AutoHashMap = std.AutoHashMap;
const Allocator = std.mem.Allocator;

/// EC Operation built-in runner
pub const EcOpBuiltinRunner = struct {
const Self = @This();

/// Ratio
ratio: ?u32,
/// Base
base: usize,
/// Number of cells per instance
cells_per_instance: u32,
/// Number of input cells
n_input_cells: u32,
/// Built-in EC Operation instance
ec_op_builtin: ec_op_instance_def.EcOpInstanceDef,
/// Stop pointer
stop_ptr: ?usize,
/// Included boolean flag
included: bool,
/// Number of instance per component
instances_per_component: u32,
/// Cache
cache: AutoHashMap(relocatable.Relocatable, Felt252),

/// Create a new ECOpBuiltinRunner instance.
///
/// This function initializes a new `EcOpBuiltinRunner` instance with the provided
/// `allocator`, `instance_def`, and `included` values.
///
/// # Arguments
///
/// - `allocator`: An allocator for initializing the cache.
/// - `instance_def`: A pointer to the `EcOpInstanceDef` for this runner.
/// - `included`: A boolean flag indicating whether this runner is included.
///
/// # Returns
///
/// A new `EcOpBuiltinRunner` instance.
pub fn new(
allocator: Allocator,
instance_def: *ec_op_instance_def.EcOpInstanceDef,
included: bool,
) Self {
return .{
.ratio = instance_def.ratio,
.base = 0,
.n_input_cells = ec_op_instance_def.INPUT_CELLS_PER_EC_OP,
.cell_per_instance = ec_op_instance_def.CELLS_PER_EC_OP,
.ec_op_builtin = instance_def,
.stop_ptr = null,
.included = included,
.instances_per_component = 1,
.cache = AutoHashMap(relocatable.Relocatable, Felt252).init(allocator),
};
}

/// Get the base value of this runner.
///
/// # Returns
///
/// The base value as a `usize`.
pub fn get_base(self: *const Self) usize {
return self.base;
}
};
67 changes: 67 additions & 0 deletions src/vm/builtins/builtin_runner/hash.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const std = @import("std");
const pedersen_instance_def = @import("../../types/pedersen_instance_def.zig");

const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;

/// Hash built-in runner
pub const HashBuiltinRunner = struct {
const Self = @This();

/// Base
base: usize,
/// Ratio
ratio: ?u32,
/// Number of cells per instance
cells_per_instance: u32,
/// Number of input cells
n_input_cells: u32,
/// Stop pointer
stop_ptr: ?usize,
/// Included boolean flag
included: bool,
/// Number of instance per component
instances_per_component: u32,
/// Vector for verified addresses
verified_addresses: ArrayList(bool),

/// Create a new HashBuiltinRunner instance.
///
/// This function initializes a new `HashBuiltinRunner` instance with the provided
/// `allocator`, `ratio`, and `included` values.
///
/// # Arguments
///
/// - `allocator`: An allocator for initializing the `verified_addresses` list.
/// - `ratio`: An optional 32-bit unsigned integer representing the ratio.
/// - `included`: A boolean flag indicating whether this runner is included.
///
/// # Returns
///
/// A new `HashBuiltinRunner` instance.
pub fn new(
allocator: Allocator,
ratio: ?u32,
included: bool,
) Self {
return .{
.base = 0,
.ratio = ratio,
.cells_per_instance = pedersen_instance_def.CELLS_PER_HASH,
.n_input_cells = pedersen_instance_def.INPUT_CELLS_PER_HASH,
.stop_ptr = null,
.included = included,
.instances_per_component = 1,
.verified_addresses = ArrayList(bool).init(allocator),
};
}

/// Get the base value of this runner.
///
/// # Returns
///
/// The base value as a `usize`.
pub fn get_base(self: *const Self) usize {
return self.base;
}
};
Loading

0 comments on commit a81c149

Please sign in to comment.