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
66 changes: 29 additions & 37 deletions src/libfuncs/felt252.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
use super::LibfuncHelper;
use crate::{
error::{panic::ToNativeAssertError, Result},
metadata::{
runtime_bindings::{ExtendedEuclideanWidth, RuntimeBindingsMeta},
MetadataStorage,
},
metadata::{runtime_bindings::RuntimeBindingsMeta, MetadataStorage},
utils::{felt_to_unsigned, get_integer_layout, ProgramRegistryExt, PRIME},
};
use cairo_lang_sierra::{
Expand Down Expand Up @@ -72,7 +69,6 @@ pub fn build_binary_operation<'ctx, 'this>(
&info.branch_signatures()[0].vars[0].ty,
)?;
let i256 = IntegerType::new(context, 256).into();
let i512 = IntegerType::new(context, 512).into();

let (op, lhs, rhs) = match info {
Felt252BinaryOperationConcrete::WithVar(operation) => {
Expand Down Expand Up @@ -166,49 +162,45 @@ pub fn build_binary_operation<'ctx, 'this>(
entry.trunci(result, felt252_ty, location)?
}
Felt252BinaryOperator::Div => {
// The field division (a modular inverse followed by a multiply) is
// delegated to a runtime binding. Lowering it inline produces an
// extended-euclidean loop plus an i512 multiply and remainder that
// the LLVM backend legalizes into huge sequences of limb
// operations, making codegen of division-heavy contracts
// pathologically slow.
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 runtime_bindings_meta = metadata
.get_mut::<RuntimeBindingsMeta>()
.to_native_assert_error(
"Unable to get the RuntimeBindingsMeta from MetadataStorage",
)?;

let prime = entry.const_int_from_type(context, location, PRIME.clone(), felt252_ty)?;

// Find 1 / rhs.
let euclidean_result = runtime_bindings_meta.extended_euclidean_algorithm(
runtime_bindings_meta.felt252_div(
context,
helper.module,
entry,
[dst_ptr, lhs_ptr, rhs_ptr],
location,
[rhs, prime],
ExtendedEuclideanWidth::U252,
)?;

// Here we omit checking if inverse is actually the inverse,
// satisfying gcd(a,b) == 1, because we are using a prime as the
// modulus. This ensures that for any value of a, included in the
// field, gcd(a,b) == 1.
let prime = entry.const_int_from_type(context, location, PRIME.clone(), i512)?;
let inverse = {
let inverse =
entry.extract_value(context, location, euclidean_result, felt252_ty, 1)?;
entry.extui(inverse, i512, location)?
};

// Peform lhs * (1 / rhs)
let lhs = entry.extui(lhs, i512, location)?;
let result = entry.muli(lhs, inverse, location)?;
// Apply modulo and convert result to felt252
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 result = entry.append_op_result(arith::select(
is_out_of_range,
result_mod,
result,
location,
))?;

let result = entry.load(context, location, dst_ptr, i256)?;
entry.trunci(result, felt252_ty, location)?
}
};
Expand Down
32 changes: 29 additions & 3 deletions src/metadata/runtime_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ enum RuntimeBinding {
U128SquareRoot,
U256SquareRoot,
Felt252Mul,
Felt252Div,
CircuitArithOperation,
DictIntoEntries,
QM31Add,
Expand Down Expand Up @@ -89,6 +90,7 @@ impl RuntimeBinding {
Self::U128SquareRoot => "cairo_native__u128_square_root",
Self::U256SquareRoot => "cairo_native__u256_square_root",
Self::Felt252Mul => "cairo_native__felt252_mul",
Self::Felt252Div => "cairo_native__felt252_div",
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 @@ -137,6 +139,7 @@ impl RuntimeBinding {
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::Felt252Div => cairo_native__felt252_div as *const (),
Self::ArenaAlloc => cairo_native__arena_alloc as *const (),
Self::ExtendedEuclideanAlgorithm(_) | Self::CircuitArithOperation => return None,
#[cfg(feature = "with-cheatcode")]
Expand All @@ -150,7 +153,6 @@ impl RuntimeBinding {
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
pub enum ExtendedEuclideanWidth {
U31,
U252,
U256,
U384,
}
Expand All @@ -159,7 +161,6 @@ impl ExtendedEuclideanWidth {
const fn symbol(self) -> &'static str {
match self {
Self::U31 => "cairo_native__u31_extended_euclidean_algorithm",
Self::U252 => "cairo_native__u252_extended_euclidean_algorithm",
Self::U256 => "cairo_native__u256_extended_euclidean_algorithm",
Self::U384 => "cairo_native__u384_extended_euclidean_algorithm",
}
Expand All @@ -168,7 +169,6 @@ impl ExtendedEuclideanWidth {
const fn bits(self) -> u32 {
match self {
Self::U31 => 31,
Self::U252 => 252,
Self::U256 => 256,
Self::U384 => 384,
}
Expand Down Expand Up @@ -380,6 +380,31 @@ impl RuntimeBindingsMeta {
))
}

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

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 @@ -1005,6 +1030,7 @@ pub fn setup_runtime(find_symbol_ptr: impl Fn(&str) -> Option<*mut c_void>) {
RuntimeBinding::U128SquareRoot,
RuntimeBinding::U256SquareRoot,
RuntimeBinding::Felt252Mul,
RuntimeBinding::Felt252Div,
#[cfg(feature = "with-cheatcode")]
RuntimeBinding::VtableCheatcode,
] {
Expand Down
56 changes: 55 additions & 1 deletion src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use num_traits::{ToPrimitive, Zero};
use starknet_curve::curve_params::BETA;
use starknet_types_core::{
curve::{AffinePoint, ProjectivePoint},
felt::Felt,
felt::{Felt, NonZeroFelt},
hash::StarkHash,
qm31::QM31,
};
Expand Down Expand Up @@ -104,6 +104,28 @@ pub extern "C" fn cairo_native__felt252_mul(dst: &mut [u8; 32], lhs: &[u8; 32],
*dst = felt_raw_to_le_bytes(&product);
}

/// Compute `lhs / rhs` in the STARK field, i.e. `lhs * rhs⁻¹ mod STARK_PRIME`,
/// and store the canonical little-endian result in `dst`.
///
/// Mirrors the raw-representation trick in [`cairo_native__felt252_mul`]: the
/// `from_raw`/`to_raw` conversions are free, so only `rhs` pays a Montgomery
/// multiplication. `field_div` flips `rhs`'s exponent, so the `R` factors land
/// exactly as in the multiply case:
/// - `from_raw(canonical(a))` has value `a * R⁻¹` (free),
/// - dividing by `from_bytes_le(b)` (value `b`) gives value `a * b⁻¹ * R⁻¹`,
/// - whose raw representation is exactly the canonical `a / b`, read by `to_raw`.
///
/// `rhs` originates from a `NonZero<felt252>`, so it is guaranteed nonzero and
/// `field_div`'s Montgomery inverse never fails.
pub extern "C" fn cairo_native__felt252_div(dst: &mut [u8; 32], lhs: &[u8; 32], rhs: &[u8; 32]) {
// value = a * R⁻¹ (free: reinterpret canonical bytes as raw limbs).
let lhs = felt_from_raw_le_bytes(lhs);
// value = b (one Montgomery multiplication).
let rhs = NonZeroFelt::from_felt_unchecked(Felt::from_bytes_le(rhs));
// value = a * b⁻¹ * R⁻¹, so its raw representation is the canonical `a / b`.
*dst = felt_raw_to_le_bytes(&lhs.field_div(&rhs));
}

/// 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.
Expand Down Expand Up @@ -1056,6 +1078,38 @@ mod tests {
}
}

/// The raw-representation `cairo_native__felt252_div` must equal the
/// canonical field quotient for every nonzero divisor.
#[test]
fn felt252_div_matches_field_quotient() {
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 {
if b == Felt::ZERO {
continue;
}
let mut dst = [0u8; 32];
cairo_native__felt252_div(&mut dst, &a.to_bytes_le(), &b.to_bytes_le());
assert_eq!(
Felt::from_bytes_le(&dst),
a.field_div(&NonZeroFelt::from_felt_unchecked(b)),
"felt252_div({a:#x}, {b:#x}) gave the wrong quotient"
);
}
}
}

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