Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 34 additions & 14 deletions src/libfuncs/felt252.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
runtime_bindings::{ExtendedEuclideanWidth, RuntimeBindingsMeta},
MetadataStorage,
},
utils::{felt_to_unsigned, ProgramRegistryExt, PRIME},
utils::{felt_to_unsigned, get_integer_layout, ProgramRegistryExt, PRIME},
};
use cairo_lang_sierra::{
extensions::{
Expand Down Expand Up @@ -128,21 +128,41 @@ pub fn build_binary_operation<'ctx, 'this>(
entry.trunci(result, felt252_ty, location)?
}
Felt252BinaryOperator::Mul => {
let lhs = entry.extui(lhs, i512, location)?;
let rhs = entry.extui(rhs, i512, location)?;
let result = entry.muli(lhs, rhs, location)?;

let prime = entry.const_int_from_type(context, location, PRIME.clone(), i512)?;
let result_mod = entry.append_op_result(arith::remui(result, prime, location))?;
let is_out_of_range =
entry.cmpi(context, CmpiPredicate::Uge, result, prime, location)?;
let layout_i256 = get_integer_layout(256);
let lhs_ptr =
helper
.init_block()
.alloca1(context, location, i256, layout_i256.align())?;
let rhs_ptr =
helper
.init_block()
.alloca1(context, location, i256, layout_i256.align())?;
let dst_ptr =
helper
.init_block()
.alloca1(context, location, i256, layout_i256.align())?;

let lhs_i256 = entry.extui(lhs, i256, location)?;
let rhs_i256 = entry.extui(rhs, i256, location)?;
entry.store(context, location, lhs_ptr, lhs_i256)?;
entry.store(context, location, rhs_ptr, rhs_i256)?;

let result = entry.append_op_result(arith::select(
is_out_of_range,
result_mod,
result,
let runtime_bindings_meta = metadata
.get_mut::<RuntimeBindingsMeta>()
.to_native_assert_error(
"Unable to get the RuntimeBindingsMeta from MetadataStorage",
)?;
runtime_bindings_meta.felt252_mul(
context,
helper.module,
entry,
dst_ptr,
lhs_ptr,
rhs_ptr,
location,
))?;
)?;

let result = entry.load(context, location, dst_ptr, i256)?;
entry.trunci(result, felt252_ty, location)?
}
Felt252BinaryOperator::Div => {
Expand Down
32 changes: 32 additions & 0 deletions src/metadata/runtime_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ enum RuntimeBinding {
U64SquareRoot,
U128SquareRoot,
U256SquareRoot,
Felt252Mul,
CircuitArithOperation,
DictIntoEntries,
QM31Add,
Expand Down Expand Up @@ -87,6 +88,7 @@ impl RuntimeBinding {
Self::U64SquareRoot => "cairo_native__u64_square_root",
Self::U128SquareRoot => "cairo_native__u128_square_root",
Self::U256SquareRoot => "cairo_native__u256_square_root",
Self::Felt252Mul => "cairo_native__felt252_mul",
Self::CircuitArithOperation => "cairo_native__circuit_arith_operation",
Self::DictIntoEntries => "cairo_native__dict_into_entries",
Self::QM31Add => "cairo_native__libfunc__qm31__qm31_add",
Expand Down Expand Up @@ -134,6 +136,7 @@ impl RuntimeBinding {
Self::U64SquareRoot => cairo_native__u64_square_root as *const (),
Self::U128SquareRoot => cairo_native__u128_square_root as *const (),
Self::U256SquareRoot => cairo_native__u256_square_root as *const (),
Self::Felt252Mul => cairo_native__felt252_mul as *const (),
Self::ArenaAlloc => cairo_native__arena_alloc as *const (),
Self::ExtendedEuclideanAlgorithm(_) | Self::CircuitArithOperation => return None,
#[cfg(feature = "with-cheatcode")]
Expand Down Expand Up @@ -349,6 +352,34 @@ impl RuntimeBindingsMeta {
.into())
}

/// Register if necessary, then invoke `cairo_native__felt252_mul`, which
/// computes `(lhs * rhs) mod STARK_PRIME`. The pointers reference 32-byte
/// little-endian felt252 buffers; the result is written to `dst_ptr`.
#[allow(clippy::too_many_arguments)]
pub fn felt252_mul<'c, 'a>(
&mut self,
context: &'c Context,
module: &Module,
block: &'a Block<'c>,
dst_ptr: Value<'c, '_>,
lhs_ptr: Value<'c, '_>,
rhs_ptr: Value<'c, '_>,
location: Location<'c>,
) -> Result<OperationRef<'c, 'a>>
where
'c: 'a,
{
let function =
self.build_function(context, module, block, location, RuntimeBinding::Felt252Mul)?;

Ok(block.append_operation(
OperationBuilder::new("llvm.call", location)
.add_operands(&[function])
.add_operands(&[dst_ptr, lhs_ptr, rhs_ptr])
.build()?,
))
}

/// Builds, if necessary, the circuit operation function, used to perform
/// circuit arithmetic operations.
///
Expand Down Expand Up @@ -973,6 +1004,7 @@ pub fn setup_runtime(find_symbol_ptr: impl Fn(&str) -> Option<*mut c_void>) {
RuntimeBinding::U64SquareRoot,
RuntimeBinding::U128SquareRoot,
RuntimeBinding::U256SquareRoot,
RuntimeBinding::Felt252Mul,
#[cfg(feature = "with-cheatcode")]
RuntimeBinding::VtableCheatcode,
] {
Expand Down
80 changes: 80 additions & 0 deletions src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,57 @@ pub extern "C" fn cairo_native__u256_square_root(lo: u128, hi: u128) -> u128 {
.expect("the square root of a u256 always fits in a u128")
}

/// Compute `(lhs * rhs) mod STARK_PRIME`. Each input buffer is the 32-byte
/// little-endian *canonical* integer; the canonical product is written to `dst`.
///
/// A `Felt` stores its value `v` in Montgomery form: its internal limbs — what
/// we call its *raw* bytes — are `v · R mod p`, with `R = 2^256 mod p`. So:
/// - `from_bytes_le`/`to_bytes_le` convert canonical integer <-> `Felt`, each
/// costing one Montgomery multiplication;
/// - `from_raw`/`to_raw` move the raw (Montgomery-limb) bytes in/out with no
/// conversion at all — they are free.
///
/// The naive `from_bytes_le(a) * from_bytes_le(b)` then `to_bytes_le` is four
/// Montgomery multiplications. Reinterpreting `a`'s canonical bytes as raw
/// Montgomery limbs (`from_raw`) cuts it to two. Tracking the value and the raw
/// bytes of each `Felt` (canonical inputs `a` = `lhs`, `b` = `rhs`):
pub extern "C" fn cairo_native__felt252_mul(dst: &mut [u8; 32], lhs: &[u8; 32], rhs: &[u8; 32]) {
// value = a * R⁻¹ raw bytes = a (free: read a as raw limbs)
let lhs = felt_from_raw_le_bytes(lhs);
// value = b raw bytes = b * R (1 Montgomery mul)
let rhs = Felt::from_bytes_le(rhs);
// value = a * b * R⁻¹ raw bytes = a * b (1 Montgomery mul)
let product = lhs * rhs;
// read the raw bytes (= a * b), the canonical product.
*dst = felt_raw_to_le_bytes(&product);
}

/// Build a `Felt` by interpreting a 32-byte little-endian buffer as the felt's
/// raw internal (Montgomery) representation. The inverse of
/// [`felt_raw_to_le_bytes`]. Zero-cost: no canonical<->Montgomery conversion.
///
/// `Felt::from_raw` takes limbs most-significant-first, while the buffer is
/// little-endian, so the limbs are read least-significant-first then reversed.
fn felt_from_raw_le_bytes(buffer: &[u8; 32]) -> Felt {
let mut limbs = [0u64; 4];
for (i, limb) in limbs.iter_mut().enumerate() {
*limb = u64::from_le_bytes(buffer[i * 8..i * 8 + 8].try_into().unwrap());
}
limbs.reverse();
Felt::from_raw(limbs)
}

/// Little-endian image of a `Felt`'s raw internal (Montgomery) representation.
/// The inverse of [`felt_from_raw_le_bytes`]. Zero-cost.
fn felt_raw_to_le_bytes(value: &Felt) -> [u8; 32] {
let limbs = value.to_raw_reversed(); // least-significant limb first
let mut buffer = [0u8; 32];
for (i, limb) in limbs.iter().enumerate() {
buffer[i * 8..i * 8 + 8].copy_from_slice(&limb.to_le_bytes());
}
buffer
}

/// Allocate `size` bytes with `align` alignment from the per-execution arena.
pub unsafe extern "C" fn cairo_native__arena_alloc(size: u64, align: u64) -> *mut u8 {
EXECUTION_ARENA.with(|arena| {
Expand Down Expand Up @@ -976,6 +1027,35 @@ mod tests {
os::fd::AsRawFd,
};

/// The 2-CIOS `cairo_native__felt252_mul` must equal the canonical field
/// product for every input, including 0, 1, -1, and `PRIME`-adjacent values.
#[test]
fn felt252_mul_matches_field_product() {
let cases = [
Felt::ZERO,
Felt::ONE,
Felt::THREE,
Felt::from(-1),
Felt::from(-2),
Felt::from(1234567890123456789u64),
Felt::from_hex_unchecked(
"0x4d6e41de886ac83938da3456ccf1481182687989ead34d9d35236f0864575a0",
),
Felt::MAX,
];
for &a in &cases {
for &b in &cases {
let mut dst = [0u8; 32];
cairo_native__felt252_mul(&mut dst, &a.to_bytes_le(), &b.to_bytes_le());
assert_eq!(
Felt::from_bytes_le(&dst),
a * b,
"felt252_mul({a:#x}, {b:#x}) gave the wrong product"
);
}
}
}

pub fn felt252_short_str(value: &str) -> Felt {
let values: Vec<_> = value
.chars()
Expand Down
Loading