Skip to content
Open
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
63 changes: 63 additions & 0 deletions src/libfuncs/felt252.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,69 @@ pub mod test {
Felt::from_dec_str(val).unwrap()
}

/// `felt252_const<C>` accepts any integer literal, including values outside `[0, PRIME)`.
/// Those must be canonicalized so that bit-pattern comparisons (`felt252_is_zero`) agree
/// with the field semantics the VM uses.
#[test]
fn felt252_const_non_canonical_is_zero() {
use crate::{context::NativeContext, executor::JitNativeExecutor, utils::PRIME, OptLevel};
use cairo_lang_sierra::ProgramParser;
use num_bigint::BigInt;

let prime = BigInt::from(PRIME.clone());
let cases: [(BigInt, u32); 6] = [
(BigInt::from(0), 111),
(BigInt::from(5), 222),
(prime.clone(), 111),
(&prime + 5, 222),
(-&prime, 111),
(-&prime * 2 - 7, 222),
];

for (c, expected) in cases {
let program = ProgramParser::new()
.parse(&format!(
r#"
type felt252 = felt252;
type NonZero<felt252> = NonZero<felt252>;

libfunc felt252_const<{c}> = felt252_const<{c}>;
libfunc felt252_const<111> = felt252_const<111>;
libfunc felt252_const<222> = felt252_const<222>;
libfunc felt252_is_zero = felt252_is_zero;
libfunc drop<NonZero<felt252>> = drop<NonZero<felt252>>;
libfunc branch_align = branch_align;

felt252_const<{c}>() -> ([0]);
felt252_is_zero([0]) {{ fallthrough() 5([1]) }};
branch_align() -> ();
felt252_const<111>() -> ([2]);
return([2]);
branch_align() -> ();
drop<NonZero<felt252>>([1]) -> ();
felt252_const<222>() -> ([3]);
return([3]);

[0]@0() -> (felt252);
"#
))
.unwrap();

let context = NativeContext::new();
let module = context.compile(&program, false, None, None).unwrap();
let executor = JitNativeExecutor::from_native_module(module, OptLevel::None).unwrap();
let result = executor
.invoke_dynamic(&program.funcs[0].id, &[], None)
.unwrap();

assert_eq!(
result.return_value,
Value::Felt252(Felt::from(expected)),
"felt252_const<{c}> -> felt252_is_zero took the wrong branch"
);
}
}

#[test]
fn felt252_add() {
let program = &get_compiled_program("programs/libfuncs/felt252_add");
Expand Down
34 changes: 30 additions & 4 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,13 +242,18 @@ pub fn find_function_id<'a>(program: &'a Program, function_name: &str) -> Option
.map(|func| &func.id)
}

/// Normalize a signed BigInt felt value to its unsigned field representation.
/// Normalize a signed BigInt felt value to its canonical unsigned field representation
/// in `[0, PRIME)`.
///
/// Negative values are mapped to `PRIME - |value|`.
/// The value is reduced modulo `PRIME`, so out-of-range literals (e.g. `PRIME` itself or
/// large negatives, which Sierra permits in `felt252_const<C>`) produce the same bit pattern
/// as their canonical equivalents. This matters because `felt252_is_zero` and friends compare
/// the raw i252 bits.
pub fn felt_to_unsigned(value: &BigInt) -> BigUint {
let reduced = value.magnitude() % &*PRIME;
match value.sign() {
Sign::Minus => &*PRIME - value.magnitude(),
_ => value.magnitude().clone(),
Sign::Minus if reduced != BigUint::ZERO => &*PRIME - reduced,
_ => reduced,
}
}

Expand Down Expand Up @@ -444,6 +449,27 @@ mod tests {
program::{FunctionSignature, GenFunction, Program, StatementIdx},
};

// ==============================
// == TESTS: felt_to_unsigned
// ==============================
#[test]
fn test_felt_to_unsigned_canonicalizes() {
use super::{felt_to_unsigned, PRIME};
use num_bigint::{BigInt, BigUint};

let prime = BigInt::from(PRIME.clone());
assert_eq!(felt_to_unsigned(&BigInt::from(0)), BigUint::ZERO);
assert_eq!(felt_to_unsigned(&BigInt::from(5)), BigUint::from(5u8));
assert_eq!(felt_to_unsigned(&BigInt::from(-1)), &*PRIME - 1u8);
// Values >= PRIME wrap around.
assert_eq!(felt_to_unsigned(&prime), BigUint::ZERO);
assert_eq!(felt_to_unsigned(&(&prime + 5)), BigUint::from(5u8));
// Large negatives wrap around too; -PRIME is zero, not PRIME.
assert_eq!(felt_to_unsigned(&(-&prime)), BigUint::ZERO);
assert_eq!(felt_to_unsigned(&(-&prime - 1)), &*PRIME - 1u8);
assert_eq!(felt_to_unsigned(&(-&prime * 2 + 3)), BigUint::from(3u8));
}

// ==============================
// == TESTS: get_integer_layout
// ==============================
Expand Down
Loading