#![allow(non_snake_case)]
use crate::{
starknet::{ArrayAbi, Felt252Abi},
types::{ec_point::NUM_FELTS as EC_POINT_NUM_FELTS, ec_state::NUM_FELTS as EC_STATE_NUM_FELTS},
utils::{blake_utils, felt_from_slot, BuiltinCosts},
};
use bumpalo::Bump;
use cairo_lang_sierra_gas::core_libfunc_cost::{
DICT_SQUASH_REPEATED_ACCESS_COST, DICT_SQUASH_UNIQUE_KEY_COST,
};
use itertools::Itertools;
use lambdaworks_math::field::fields::mersenne31::extensions::Degree4ExtensionField;
use lazy_static::lazy_static;
use num_bigint::BigUint;
use num_traits::{ToPrimitive, Zero};
use starknet_curve::curve_params::BETA;
use starknet_types_core::{
curve::{AffinePoint, ProjectivePoint},
felt::{Felt, NonZeroFelt},
hash::StarkHash,
qm31::QM31,
};
use std::{
alloc::Layout,
cell::{Cell, RefCell},
collections::{hash_map::Entry, HashMap},
ffi::{c_int, c_void},
fs::File,
io::Write,
mem::ManuallyDrop,
os::fd::FromRawFd,
ptr::{self, null_mut},
};
use std::{ops::Mul, vec::IntoIter};
thread_local! {
pub(crate) static EXECUTION_ARENA: RefCell<Bump> = RefCell::new(Bump::new());
pub(crate) static DICT_REGISTRY: RefCell<Vec<*mut FeltDict>> = const { RefCell::new(Vec::new()) };
}
pub extern "C" fn cairo_native__u8_square_root(value: u8) -> u8 {
value.isqrt()
}
pub extern "C" fn cairo_native__u16_square_root(value: u16) -> u8 {
value.isqrt() as u8
}
pub extern "C" fn cairo_native__u32_square_root(value: u32) -> u16 {
value.isqrt() as u16
}
pub extern "C" fn cairo_native__u64_square_root(value: u64) -> u32 {
value.isqrt() as u32
}
pub extern "C" fn cairo_native__u128_square_root(value: u128) -> u64 {
value.isqrt() as u64
}
pub extern "C" fn cairo_native__u256_square_root(lo: u128, hi: u128) -> u128 {
let value = (BigUint::from(hi) << 128u32) + BigUint::from(lo);
value
.sqrt()
.to_u128()
.expect("the square root of a u256 always fits in a u128")
}
pub extern "C" fn cairo_native__felt252_mul(dst: &mut [u8; 32], lhs: &[u8; 32], rhs: &[u8; 32]) {
let lhs = felt_from_raw_le_bytes(lhs);
let rhs = Felt::from_bytes_le(rhs);
let product = lhs * rhs;
*dst = felt_raw_to_le_bytes(&product);
}
pub extern "C" fn cairo_native__felt252_div(dst: &mut [u8; 32], lhs: &[u8; 32], rhs: &[u8; 32]) {
let lhs = felt_from_raw_le_bytes(lhs);
let rhs = NonZeroFelt::from_felt_unchecked(Felt::from_bytes_le(rhs));
*dst = felt_raw_to_le_bytes(&lhs.field_div(&rhs));
}
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)
}
fn felt_raw_to_le_bytes(value: &Felt) -> [u8; 32] {
let limbs = value.to_raw_reversed(); 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
}
pub unsafe extern "C" fn cairo_native__arena_alloc(size: u64, align: u64) -> *mut u8 {
EXECUTION_ARENA.with(|arena| {
let layout = Layout::from_size_align(size as usize, align as usize)
.expect("cairo_native__arena_alloc: invalid layout");
arena.borrow_mut().alloc_layout(layout).as_ptr()
})
}
lazy_static! {
pub static ref HALF_PRIME: Felt = Felt::from_dec_str(
"1809251394333065606848661391547535052811553607665798349986546028067936010240"
)
.unwrap();
pub static ref DICT_GAS_REFUND_PER_ACCESS: u64 =
(DICT_SQUASH_UNIQUE_KEY_COST.cost() - DICT_SQUASH_REPEATED_ACCESS_COST.cost()) as u64;
}
pub unsafe extern "C" fn cairo_native__libfunc__debug__print(
target_fd: i32,
data: *const [u8; 32],
len: u32,
) -> i32 {
let mut target = ManuallyDrop::new(File::from_raw_fd(target_fd));
let mut items = Vec::with_capacity(len as usize);
for i in 0..len as usize {
let mut data = *data.add(i);
data[31] &= 0x0F;
let value = Felt::from_bytes_le(&data);
items.push(value);
}
let value = format_for_debug(items.into_iter());
if write!(target, "{}", value).is_err() {
return 1;
};
0
}
pub unsafe extern "C" fn cairo_native__libfunc__pedersen(
dst: &mut [u8; 32],
lhs: &[u8; 32],
rhs: &[u8; 32],
) {
let mut lhs = *lhs;
let mut rhs = *rhs;
lhs[31] &= 0x0F; rhs[31] &= 0x0F;
let lhs = Felt::from_bytes_le(&lhs);
let rhs = Felt::from_bytes_le(&rhs);
let res = starknet_types_core::hash::Pedersen::hash(&lhs, &rhs);
*dst = res.to_bytes_le();
}
pub unsafe extern "C" fn cairo_native__libfunc__hades_permutation(
op0: &mut [u8; 32],
op1: &mut [u8; 32],
op2: &mut [u8; 32],
) {
op0[31] &= 0x0F; op1[31] &= 0x0F; op2[31] &= 0x0F;
let mut state = [
Felt::from_bytes_le(op0),
Felt::from_bytes_le(op1),
Felt::from_bytes_le(op2),
];
starknet_types_core::hash::Poseidon::hades_permutation(&mut state);
*op0 = state[0].to_bytes_le();
*op1 = state[1].to_bytes_le();
*op2 = state[2].to_bytes_le();
}
pub unsafe extern "C" fn cairo_native__libfunc__blake_compress(
out_state: &mut [u32; 8],
state: &[u32; 8],
message: &[u32; 16],
count_bytes: u32,
finalize: bool,
) {
let new_state = blake_utils::blake2s_compress(
state,
message,
count_bytes,
0,
if finalize { 0xFFFFFFFF } else { 0 },
0,
);
*out_state = new_state;
BLAKE_CALL_COUNT.with(|c| c.set(c.get() + 1));
}
#[derive(Debug)]
pub struct FeltDict {
pub mappings: HashMap<Felt, usize>,
pub layout: Layout,
pub elements: *mut (),
pub count: u64,
}
pub unsafe extern "C" fn cairo_native__dict_new(size: u64, align: u64) -> *mut FeltDict {
let dict_ptr = EXECUTION_ARENA.with(|arena| {
let layout = Layout::new::<FeltDict>();
arena.borrow_mut().alloc_layout(layout).as_ptr() as *mut FeltDict
});
dict_ptr.write(FeltDict {
mappings: HashMap::default(),
layout: Layout::from_size_align_unchecked(size as usize, align as usize),
elements: ptr::null_mut(),
count: 0,
});
DICT_REGISTRY.with(|reg| reg.borrow_mut().push(dict_ptr));
dict_ptr
}
pub unsafe extern "C" fn cairo_native__dict_get(
dict_ptr: *mut FeltDict,
key: &[u8; 32],
value_ptr: *mut *mut c_void,
) -> c_int {
let dict = &mut *dict_ptr;
let num_mappings = dict.mappings.len();
let has_capacity = num_mappings != dict.mappings.capacity();
let (is_present, index) = match dict.mappings.entry(Felt::from_bytes_le(key)) {
Entry::Occupied(entry) => (true, *entry.get()),
Entry::Vacant(entry) => {
entry.insert(num_mappings);
(false, num_mappings)
}
};
if !has_capacity && !is_present {
let elem_stride = dict.layout.pad_to_align().size();
let old_size = elem_stride * num_mappings;
let new_size = elem_stride * dict.mappings.capacity();
dict.elements = EXECUTION_ARENA.with(|arena| {
let layout = Layout::from_size_align_unchecked(new_size, dict.layout.align());
let new_ptr = arena
.borrow_mut()
.alloc_layout(layout)
.as_ptr()
.cast::<()>();
if !dict.elements.is_null() && old_size > 0 {
std::ptr::copy_nonoverlapping(
dict.elements.cast::<u8>(),
new_ptr.cast::<u8>(),
old_size,
);
}
new_ptr
});
}
*value_ptr = dict
.elements
.byte_add(dict.layout.pad_to_align().size() * index)
.cast();
dict.count += 1;
is_present as c_int
}
unsafe fn create_dict_entries_array(dict: &mut FeltDict) -> ArrayAbi<c_void> {
let len = dict.mappings.len();
if len == 0 {
return ArrayAbi {
ptr: null_mut(),
since: 0,
until: 0,
capacity: 0,
};
}
let tuple_layout = Layout::new::<Felt252Abi>()
.extend(dict.layout)
.expect("Should be posible to extend Felt252Abi layout")
.0
.extend(dict.layout)
.expect("Should be able to extend with the last tuple element")
.0;
let tuple_stride = tuple_layout.pad_to_align().size();
let data_ptr = cairo_native__arena_alloc(
(tuple_stride * dict.mappings.len()) as u64,
tuple_layout.align() as u64,
);
let mut work_ptr = data_ptr;
let key_size = Layout::new::<Felt252Abi>().pad_to_align().size();
let element_size = dict.layout.pad_to_align().size();
for (key, elem_index) in dict.mappings.iter().sorted() {
let key_ptr = work_ptr as *mut [u8; 32];
let default_value_ptr = work_ptr.byte_add(key_size);
let final_value_ptr = default_value_ptr.byte_add(element_size);
work_ptr = work_ptr.byte_add(tuple_stride);
*key_ptr = key.to_bytes_le();
default_value_ptr.write_bytes(0, element_size);
let value = dict.elements.byte_add(element_size * elem_index) as *mut u8;
final_value_ptr.copy_from_nonoverlapping(value, element_size);
}
ArrayAbi {
ptr: data_ptr.cast(),
since: 0,
until: len as u32,
capacity: len as u32,
}
}
pub unsafe extern "C" fn cairo_native__dict_into_entries(
dict_ptr: *mut FeltDict,
array_ptr: *mut ArrayAbi<c_void>,
) {
let dict = &mut *dict_ptr;
let arr = create_dict_entries_array(dict);
*array_ptr = arr;
}
pub unsafe extern "C" fn cairo_native__dict_squash(
dict_ptr: *const FeltDict,
range_check_ptr: &mut u64,
gas_ptr: &mut u64,
) {
let dict = &*dict_ptr;
*gas_ptr +=
(dict.count.saturating_sub(dict.mappings.len() as u64)) * *DICT_GAS_REFUND_PER_ACCESS;
*range_check_ptr += 2;
let u128_max = Felt::from(u128::MAX);
let no_big_keys = dict.mappings.keys().all(|key| *key <= u128_max);
let number_of_keys = dict.mappings.len() as u64;
if no_big_keys {
*range_check_ptr += number_of_keys;
}
*range_check_ptr += 2 * number_of_keys;
if !no_big_keys && number_of_keys > 1 {
*range_check_ptr += 4 * (number_of_keys - 1);
}
*range_check_ptr += dict.count.saturating_sub(dict.mappings.len() as u64);
}
pub unsafe extern "C" fn cairo_native__libfunc__ec__ec_point_from_x_nz(
point_ptr: &mut [[u8; 32]; EC_POINT_NUM_FELTS],
) -> bool {
let x = felt_from_slot(&point_ptr[0]);
let x2 = x * x;
let x3 = x2 * x;
let alpha_x_plus_beta = x + BETA;
let rhs = x3 + alpha_x_plus_beta;
let y = rhs
.sqrt()
.unwrap_or_else(|| (Felt::THREE * rhs).sqrt().unwrap());
let y = y.min(-y);
match AffinePoint::new(x, y) {
Ok(point) => {
point_ptr[1] = point.y().to_bytes_le();
true
}
Err(_) => false,
}
}
pub unsafe extern "C" fn cairo_native__libfunc__ec__ec_point_try_new_nz(
point_ptr: &mut [[u8; 32]; EC_POINT_NUM_FELTS],
) -> bool {
let x = felt_from_slot(&point_ptr[0]);
let y = felt_from_slot(&point_ptr[1]);
match AffinePoint::new(x, y) {
Ok(point) => {
point_ptr[0] = point.x().to_bytes_le();
point_ptr[1] = point.y().to_bytes_le();
true
}
Err(_) => false,
}
}
fn ec_read_state(state_ptr: &[[u8; 32]; EC_STATE_NUM_FELTS]) -> ProjectivePoint {
ProjectivePoint::new_unchecked(
felt_from_slot(&state_ptr[0]),
felt_from_slot(&state_ptr[1]),
felt_from_slot(&state_ptr[2]),
)
}
fn ec_write_state(state_ptr: &mut [[u8; 32]; EC_STATE_NUM_FELTS], state: &ProjectivePoint) {
state_ptr[0] = state.x().to_bytes_le();
state_ptr[1] = state.y().to_bytes_le();
state_ptr[2] = state.z().to_bytes_le();
}
pub unsafe extern "C" fn cairo_native__libfunc__ec__ec_state_add(
state_ptr: &mut [[u8; 32]; EC_STATE_NUM_FELTS],
point_ptr: &[[u8; 32]; EC_POINT_NUM_FELTS],
) {
let mut state = ec_read_state(state_ptr);
let point = ProjectivePoint::from_affine_unchecked(
felt_from_slot(&point_ptr[0]),
felt_from_slot(&point_ptr[1]),
);
state += &point;
ec_write_state(state_ptr, &state);
}
pub unsafe extern "C" fn cairo_native__libfunc__ec__ec_state_add_mul(
state_ptr: &mut [[u8; 32]; EC_STATE_NUM_FELTS],
scalar_ptr: &[u8; 32],
point_ptr: &[[u8; 32]; EC_POINT_NUM_FELTS],
) {
let mut state = ec_read_state(state_ptr);
let point = ProjectivePoint::from_affine_unchecked(
felt_from_slot(&point_ptr[0]),
felt_from_slot(&point_ptr[1]),
);
state += &point.mul(felt_from_slot(scalar_ptr));
ec_write_state(state_ptr, &state);
}
pub unsafe extern "C" fn cairo_native__libfunc__ec__ec_state_try_finalize_nz(
point_ptr: &mut [[u8; 32]; EC_POINT_NUM_FELTS],
state_ptr: &[[u8; 32]; EC_STATE_NUM_FELTS],
) -> bool {
match ec_read_state(state_ptr).to_affine() {
Ok(point) => {
point_ptr[0] = point.x().to_bytes_le();
point_ptr[1] = point.y().to_bytes_le();
true
}
Err(_) => {
*point_ptr = [[0u8; 32]; EC_POINT_NUM_FELTS];
false
}
}
}
pub unsafe extern "C" fn cairo_native__libfunc__qm31__qm31_add(
lhs: &[u32; 4],
rhs: &[u32; 4],
res: &mut [u32; 4],
) {
let lhs = QM31(Degree4ExtensionField::const_from_coefficients(
lhs[0], lhs[1], lhs[2], lhs[3],
));
let rhs = QM31(Degree4ExtensionField::const_from_coefficients(
rhs[0], rhs[1], rhs[2], rhs[3],
));
*res = qm31_to_representative_coefficients(lhs + rhs);
}
pub unsafe extern "C" fn cairo_native__libfunc__qm31__qm31_sub(
lhs: &[u32; 4],
rhs: &[u32; 4],
res: &mut [u32; 4],
) {
let lhs = QM31(Degree4ExtensionField::const_from_coefficients(
lhs[0], lhs[1], lhs[2], lhs[3],
));
let rhs = QM31(Degree4ExtensionField::const_from_coefficients(
rhs[0], rhs[1], rhs[2], rhs[3],
));
*res = qm31_to_representative_coefficients(lhs - rhs);
}
pub unsafe extern "C" fn cairo_native__libfunc__qm31__qm31_mul(
lhs: &[u32; 4],
rhs: &[u32; 4],
res: &mut [u32; 4],
) {
let lhs = QM31(Degree4ExtensionField::const_from_coefficients(
lhs[0], lhs[1], lhs[2], lhs[3],
));
let rhs = QM31(Degree4ExtensionField::const_from_coefficients(
rhs[0], rhs[1], rhs[2], rhs[3],
));
*res = qm31_to_representative_coefficients(lhs * rhs);
}
pub unsafe extern "C" fn cairo_native__libfunc__qm31__qm31_div(
lhs: &[u32; 4],
rhs: &[u32; 4],
res: &mut [u32; 4],
) {
let lhs = QM31(Degree4ExtensionField::const_from_coefficients(
lhs[0], lhs[1], lhs[2], lhs[3],
));
let rhs = QM31(Degree4ExtensionField::const_from_coefficients(
rhs[0], rhs[1], rhs[2], rhs[3],
));
*res = qm31_to_representative_coefficients((lhs / rhs).expect("rhs should not be a QM31 0"));
}
thread_local! {
pub(crate) static BUILTIN_COSTS: Cell<BuiltinCosts> = const {
Cell::new(BuiltinCosts {
r#const: 0,
pedersen: 0,
bitwise: 0,
ecop: 0,
poseidon: 0,
add_mod: 0,
mul_mod: 0,
blake: 0,
})
};
pub(crate) static BLAKE_CALL_COUNT: Cell<u64> = const { Cell::new(0) };
}
pub fn qm31_to_representative_coefficients(qm31: QM31) -> [u32; 4] {
let [a, b] = qm31.0.value();
let [c1, c2] = a.value();
let [c3, c4] = b.value();
[
c1.representative(),
c2.representative(),
c3.representative(),
c4.representative(),
]
}
pub extern "C" fn cairo_native__get_costs_builtin() -> *const [u64; 8] {
BUILTIN_COSTS.with(|x| x.as_ptr()) as *const [u64; 8]
}
fn format_for_debug(mut felts: IntoIter<Felt>) -> String {
let mut items = Vec::new();
while let Some(item) = format_next_item(&mut felts) {
items.push(item);
}
if let [item] = &items[..] {
if item.is_string {
return item.item.clone();
}
}
items
.into_iter()
.map(|item| {
if item.is_string {
format!("{}\n", item.item)
} else {
format!("[DEBUG]\t{}\n", item.item)
}
})
.join("")
}
pub struct FormattedItem {
item: String,
is_string: bool,
}
impl FormattedItem {
#[must_use]
pub fn get(self) -> String {
self.item
}
#[must_use]
pub fn quote_if_string(self) -> String {
if self.is_string {
format!("\"{}\"", self.item)
} else {
self.item
}
}
}
pub const BYTE_ARRAY_MAGIC: &str =
"46a6158a16a947e5916b2a2ca68501a45e93d7110e81aa2d6438b1c57c879a3";
pub const BYTES_IN_WORD: usize = 31;
pub fn format_next_item<T>(values: &mut T) -> Option<FormattedItem>
where
T: Iterator<Item = Felt> + Clone,
{
let first_felt = values.next()?;
if first_felt == Felt::from_hex(BYTE_ARRAY_MAGIC).unwrap() {
if let Some(string) = try_format_string(values) {
return Some(FormattedItem {
item: string,
is_string: true,
});
}
}
Some(FormattedItem {
item: format_short_string(&first_felt),
is_string: false,
})
}
fn format_short_string(value: &Felt) -> String {
let hex_value = value.to_biguint();
match as_cairo_short_string(value) {
Some(as_string) => format!("{hex_value:#x} ('{as_string}')"),
None => format!("{hex_value:#x}"),
}
}
fn try_format_string<T>(values: &mut T) -> Option<String>
where
T: Iterator<Item = Felt> + Clone,
{
let mut cloned_values_iter = values.clone();
let num_full_words = cloned_values_iter.next()?.to_usize()?;
let full_words = cloned_values_iter
.by_ref()
.take(num_full_words)
.collect_vec();
let pending_word = cloned_values_iter.next()?;
let pending_word_len = cloned_values_iter.next()?.to_usize()?;
let full_words_string = full_words
.into_iter()
.map(|word| as_cairo_short_string_ex(&word, BYTES_IN_WORD))
.collect::<Option<Vec<String>>>()?
.join("");
let pending_word_string = as_cairo_short_string_ex(&pending_word, pending_word_len)?;
*values = cloned_values_iter;
Some(format!("{full_words_string}{pending_word_string}"))
}
#[must_use]
pub fn as_cairo_short_string(value: &Felt) -> Option<String> {
let mut as_string = String::default();
let mut is_end = false;
for byte in value.to_biguint().to_bytes_be() {
if byte == 0 {
is_end = true;
} else if is_end {
return None;
} else if byte.is_ascii_graphic() || byte.is_ascii_whitespace() {
as_string.push(byte as char);
} else {
return None;
}
}
Some(as_string)
}
#[must_use]
pub fn as_cairo_short_string_ex(value: &Felt, length: usize) -> Option<String> {
if length == 0 {
return if value.is_zero() {
Some(String::new())
} else {
None
};
}
if length > 31 {
return None;
}
let bytes = value.to_biguint().to_bytes_be();
let bytes_len = bytes.len();
if bytes_len > length {
return None;
}
let mut as_string = String::new();
for byte in bytes {
if byte == 0 {
as_string.push_str(r"\0");
} else if byte.is_ascii_graphic() || byte.is_ascii_whitespace() {
as_string.push(byte as char);
} else {
as_string.push_str(format!(r"\x{:02x}", byte).as_str());
}
}
let missing_nulls = length - bytes_len;
as_string.insert_str(0, &r"\0".repeat(missing_nulls));
Some(as_string)
}
#[cfg(test)]
mod tests {
use super::*;
use std::{
env, fs,
io::{Read, Seek},
os::fd::AsRawFd,
};
#[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"
);
}
}
}
#[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()
.filter_map(|c| c.is_ascii().then_some(c as u8))
.collect();
assert!(values.len() < 32);
Felt::from_bytes_be_slice(&values)
}
#[test]
fn test_debug_print() {
let dir = env::temp_dir();
fs::remove_file(dir.join("print.txt")).ok();
let mut file = File::create_new(dir.join("print.txt")).unwrap();
{
let fd = file.as_raw_fd();
let data = felt252_short_str("hello world");
let data = data.to_bytes_le();
unsafe { cairo_native__libfunc__debug__print(fd, &data, 1) };
}
file.seek(std::io::SeekFrom::Start(0)).unwrap();
let mut result = String::new();
file.read_to_string(&mut result).unwrap();
assert_eq!(
result,
"[DEBUG]\t0x68656c6c6f20776f726c64 ('hello world')\n"
);
}
#[test]
fn test_pederesen() {
let mut dst = [0; 32];
let lhs = Felt::from(1).to_bytes_le();
let rhs = Felt::from(3).to_bytes_le();
unsafe {
cairo_native__libfunc__pedersen(&mut dst, &lhs, &rhs);
}
assert_eq!(
dst,
[
84, 98, 174, 134, 3, 124, 237, 179, 166, 110, 159, 98, 170, 35, 83, 237, 130, 154,
236, 0, 205, 134, 200, 185, 39, 92, 0, 228, 132, 217, 130, 5
]
)
}
#[test]
fn test_hades_permutation() {
let mut op0 = Felt::from(1).to_bytes_le();
let mut op1 = Felt::from(1).to_bytes_le();
let mut op2 = Felt::from(1).to_bytes_le();
unsafe {
cairo_native__libfunc__hades_permutation(&mut op0, &mut op1, &mut op2);
}
assert_eq!(
Felt::from_bytes_le(&op0),
Felt::from_hex("0x4ebdde1149fcacbb41e4fc342432a48c97994fd045f432ad234ae9279269779")
.unwrap()
);
assert_eq!(
Felt::from_bytes_le(&op1),
Felt::from_hex("0x7f4cec57dd08b69414f7de7dffa230fc90fa3993673c422408af05831e0cc98")
.unwrap()
);
assert_eq!(
Felt::from_bytes_le(&op2),
Felt::from_hex("0x5b5d00fd09caade43caffe70527fa84d5d9cd51e22c2ce115693ecbb5854d6a")
.unwrap()
);
}
#[test]
fn test_dict() {
let dict =
unsafe { cairo_native__dict_new(size_of::<u64>() as u64, align_of::<u64>() as u64) };
let key = Felt::ONE.to_bytes_le();
let mut ptr = ptr::null_mut::<u64>();
assert_eq!(
unsafe { cairo_native__dict_get(dict, &key, (&raw mut ptr).cast()) },
0,
);
assert!(!ptr.is_null());
unsafe { *ptr = 24 };
assert_eq!(
unsafe { cairo_native__dict_get(dict, &key, (&raw mut ptr).cast()) },
1,
);
assert!(!ptr.is_null());
assert_eq!(unsafe { *ptr }, 24);
unsafe { *ptr = 42 };
let mut range_check = 0;
let mut gas = 0;
unsafe { cairo_native__dict_squash(dict, &mut range_check, &mut gas) };
assert_eq!(gas, 4050);
assert_eq!(
unsafe { cairo_native__dict_get(dict, &key, (&raw mut ptr).cast()) },
1,
);
assert!(!ptr.is_null());
assert_eq!(unsafe { *ptr }, 42);
}
#[test]
fn test_ec__ec_point() {
let mut point = [Felt::ZERO.to_bytes_le(), Felt::ZERO.to_bytes_le()];
let result = unsafe { cairo_native__libfunc__ec__ec_point_try_new_nz(&mut point) };
assert!(!result);
let point = AffinePoint::generator();
let mut point = [point.x().to_bytes_le(), point.y().to_bytes_le()];
let result = unsafe { cairo_native__libfunc__ec__ec_point_try_new_nz(&mut point) };
assert!(result);
}
fn ec_state_identity() -> [[u8; 32]; EC_STATE_NUM_FELTS] {
[
Felt::ZERO.to_bytes_le(),
Felt::ONE.to_bytes_le(),
Felt::ZERO.to_bytes_le(),
]
}
fn ec_state_of(x: Felt, y: Felt) -> [[u8; 32]; EC_STATE_NUM_FELTS] {
[x.to_bytes_le(), y.to_bytes_le(), Felt::ONE.to_bytes_le()]
}
fn ec_state_to_affine(state: &[[u8; 32]; EC_STATE_NUM_FELTS]) -> Option<(Felt, Felt)> {
ec_read_state(state)
.to_affine()
.ok()
.map(|point| (point.x(), point.y()))
}
#[test]
fn test_ec__ec_state_add__from_zero() {
let g = AffinePoint::generator();
let mut state = ec_state_identity();
let point = [g.x().to_bytes_le(), g.y().to_bytes_le()];
unsafe { cairo_native__libfunc__ec__ec_state_add(&mut state, &point) };
assert_eq!(ec_state_to_affine(&state), Some((g.x(), g.y())));
}
#[test]
fn test_ec__ec_state_add__to_zero() {
let g = AffinePoint::generator();
let mut state = ec_state_of(g.x(), g.y());
let point = [g.x().to_bytes_le(), (-g.y()).to_bytes_le()];
unsafe { cairo_native__libfunc__ec__ec_state_add(&mut state, &point) };
assert_eq!(Felt::from_bytes_le(&state[2]), Felt::ZERO);
assert_eq!(ec_state_to_affine(&state), None);
}
#[test]
fn test_ec__ec_state_add__through_zero() {
let g = AffinePoint::generator();
let r = AffinePoint::new(
Felt::from(1234),
Felt::from_dec_str(
"1301976514684871091717790968549291947487646995000837413367950573852273027507",
)
.unwrap(),
)
.unwrap();
let mut state = ec_state_of(g.x(), g.y());
unsafe {
cairo_native__libfunc__ec__ec_state_add(
&mut state,
&[g.x().to_bytes_le(), (-g.y()).to_bytes_le()],
);
cairo_native__libfunc__ec__ec_state_add(
&mut state,
&[r.x().to_bytes_le(), r.y().to_bytes_le()],
);
};
assert_eq!(ec_state_to_affine(&state), Some((r.x(), r.y())));
}
#[test]
fn test_ec__ec_state_add__shared_y() {
let y = Felt::from_hex_unchecked(
"0x27ff039852193be63e77b8e6adc0b6fbe76e54ff6ad53510fdc370d58446a68",
);
let qx = Felt::from_hex_unchecked(
"0x1bbf97b3eb870eda30823a6f4099695e934127b9a37183c475fa4e64db939d3",
);
let mut state = ec_state_of(Felt::ONE, y);
unsafe {
cairo_native__libfunc__ec__ec_state_add(
&mut state,
&[qx.to_bytes_le(), y.to_bytes_le()],
)
};
assert_eq!(ec_state_to_affine(&state), Some((-(Felt::ONE + qx), -y)));
}
#[test]
fn test_ec__ec_state_add_mul__from_zero() {
let g = AffinePoint::generator();
let mut state = ec_state_identity();
let scalar = Felt::ONE.to_bytes_le();
let point = [g.x().to_bytes_le(), g.y().to_bytes_le()];
unsafe { cairo_native__libfunc__ec__ec_state_add_mul(&mut state, &scalar, &point) };
assert_eq!(ec_state_to_affine(&state), Some((g.x(), g.y())));
}
#[test]
fn test_ec__ec_state_add_mul__to_zero() {
let g = AffinePoint::generator();
let mut state = ec_state_of(g.x(), -g.y());
let scalar = Felt::ONE.to_bytes_le();
let point = [g.x().to_bytes_le(), g.y().to_bytes_le()];
unsafe { cairo_native__libfunc__ec__ec_state_add_mul(&mut state, &scalar, &point) };
assert_eq!(Felt::from_bytes_le(&state[2]), Felt::ZERO);
assert_eq!(ec_state_to_affine(&state), None);
}
#[test]
fn test_ec__ec_state_finalize__zero() {
let state = ec_state_identity();
let mut point = [[0u8; 32]; EC_POINT_NUM_FELTS];
let result =
unsafe { cairo_native__libfunc__ec__ec_state_try_finalize_nz(&mut point, &state) };
assert!(!result);
assert_eq!(point, [[0u8; 32]; EC_POINT_NUM_FELTS]);
}
#[test]
fn test_ec__ec_state_finalize__non_zero() {
let g = AffinePoint::generator();
let state = ec_state_of(g.x(), g.y());
let mut point = [[0u8; 32]; EC_POINT_NUM_FELTS];
let result =
unsafe { cairo_native__libfunc__ec__ec_state_try_finalize_nz(&mut point, &state) };
assert!(result);
assert_eq!(point[0], g.x().to_bytes_le());
assert_eq!(point[1], g.y().to_bytes_le());
}
#[test]
fn test_ec__ec_state_finalize__non_canonical() {
let g = AffinePoint::generator();
let state = [
(g.x() * Felt::TWO).to_bytes_le(),
(g.y() * Felt::TWO).to_bytes_le(),
Felt::TWO.to_bytes_le(),
];
let mut point = [[0u8; 32]; EC_POINT_NUM_FELTS];
let result =
unsafe { cairo_native__libfunc__ec__ec_state_try_finalize_nz(&mut point, &state) };
assert!(result);
assert_eq!(point[0], g.x().to_bytes_le());
assert_eq!(point[1], g.y().to_bytes_le());
}
#[test]
fn test_ec__ec_point_add() {
let x = Felt::from_dec_str(
"874739451078007766457464989774322083649278607533249481151382481072868806602",
)
.unwrap();
let y = Felt::from_dec_str(
"152666792071518830868575557812948353041420400780739481342941381225525861407",
)
.unwrap();
let mut state = ec_state_of(x, y);
let point = [x.to_bytes_le(), y.to_bytes_le()];
unsafe {
cairo_native__libfunc__ec__ec_state_add(&mut state, &point);
};
assert_eq!(
ec_state_to_affine(&state),
Some((
Felt::from_dec_str(
"3324833730090626974525872402899302150520188025637965566623476530814354734325",
)
.unwrap(),
Felt::from_dec_str(
"3147007486456030910661996439995670279305852583596209647900952752170983517249",
)
.unwrap()
))
);
}
}