From 6269ca2cc86a16a1409939d13fe18a3aeacce373 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Wed, 22 Oct 2025 14:34:57 -0300 Subject: [PATCH 01/22] move felt divition to a mlir function --- .cargo/config.toml | 2 + src/libfuncs/circuit.rs | 1 + src/libfuncs/felt252.rs | 134 ++++++++----------------------- src/metadata/runtime_bindings.rs | 27 +++---- 4 files changed, 51 insertions(+), 113 deletions(-) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000..a5f75cb66 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[install] +root = "binaries/" diff --git a/src/libfuncs/circuit.rs b/src/libfuncs/circuit.rs index 901ff11ae..d36b386b8 100644 --- a/src/libfuncs/circuit.rs +++ b/src/libfuncs/circuit.rs @@ -608,6 +608,7 @@ fn build_gate_evaluation<'ctx, 'this>( location, rhs_value, circuit_modulus_u768, + u768_type )?; // Extract the values from the result struct let gcd = diff --git a/src/libfuncs/felt252.rs b/src/libfuncs/felt252.rs index af79646f1..97e8d24cf 100644 --- a/src/libfuncs/felt252.rs +++ b/src/libfuncs/felt252.rs @@ -2,8 +2,8 @@ use super::LibfuncHelper; use crate::{ - error::Result, - metadata::MetadataStorage, + error::{panic::ToNativeAssertError, Result}, + metadata::{runtime_bindings::RuntimeBindingsMeta, MetadataStorage}, utils::{ProgramRegistryExt, PRIME}, }; use cairo_lang_sierra::{ @@ -19,11 +19,8 @@ use cairo_lang_sierra::{ program_registry::ProgramRegistry, }; use melior::{ - dialect::{ - arith::{self, CmpiPredicate}, - cf, - }, - helpers::{ArithBlockExt, BuiltinBlockExt}, + dialect::arith::{self, CmpiPredicate}, + helpers::{ArithBlockExt, BuiltinBlockExt, LlvmBlockExt}, ir::{r#type::IntegerType, Block, BlockLike, Location, Value, ValueLike}, Context, }; @@ -149,80 +146,35 @@ pub fn build_binary_operation<'ctx, 'this>( entry.trunci(result, felt252_ty, location)? } Felt252BinaryOperator::Div => { - // The extended euclidean algorithm calculates the greatest common divisor of two integers, - // as well as the bezout coefficients x and y such that for inputs a and b, ax+by=gcd(a,b) - // We use this in felt division to find the modular inverse of a given number - // If a is the number we're trying to find the inverse of, we can do - // ax+y*PRIME=gcd(a,PRIME)=1 => ax = 1 (mod PRIME) - // Hence for input a, we return x - // The input MUST be non-zero - // See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm - let start_block = helper.append_block(Block::new(&[(i512, location)])); - let loop_block = helper.append_block(Block::new(&[ - (i512, location), - (i512, location), - (i512, location), - (i512, location), - ])); - let negative_check_block = helper.append_block(Block::new(&[])); - // Block containing final result - let inverse_result_block = helper.append_block(Block::new(&[(i512, location)])); - // Egcd works by calculating a series of remainders, each the remainder of dividing the previous two - // For the initial setup, r0 = PRIME, r1 = a - // This order is chosen because if we reverse them, then the first iteration will just swap them - let prev_remainder = - start_block.const_int_from_type(context, location, PRIME.clone(), i512)?; - let remainder = start_block.arg(0)?; - // Similarly we'll calculate another series which starts 0,1,... and from which we will retrieve the modular inverse of a - let prev_inverse = start_block.const_int_from_type(context, location, 0, i512)?; - let inverse = start_block.const_int_from_type(context, location, 1, i512)?; - start_block.append_operation(cf::br( - loop_block, - &[prev_remainder, remainder, prev_inverse, inverse], - location, - )); - - //---Loop body--- - // Arguments are rem_(i-1), rem, inv_(i-1), inv - let prev_remainder = loop_block.arg(0)?; - let remainder = loop_block.arg(1)?; - let prev_inverse = loop_block.arg(2)?; - let inverse = loop_block.arg(3)?; - - // First calculate q = rem_(i-1)/rem_i, rounded down - let quotient = - loop_block.append_op_result(arith::divui(prev_remainder, remainder, location))?; - // Then r_(i+1) = r_(i-1) - q * r_i, and inv_(i+1) = inv_(i-1) - q * inv_i - let rem_times_quo = loop_block.muli(remainder, quotient, location)?; - let inv_times_quo = loop_block.muli(inverse, quotient, location)?; - let next_remainder = loop_block.append_op_result(arith::subi( - prev_remainder, - rem_times_quo, - location, - ))?; - let next_inverse = - loop_block.append_op_result(arith::subi(prev_inverse, inv_times_quo, location))?; - - // If r_(i+1) is 0, then inv_i is the inverse - let zero = loop_block.const_int_from_type(context, location, 0, i512)?; - let next_remainder_eq_zero = - loop_block.cmpi(context, CmpiPredicate::Eq, next_remainder, zero, location)?; - loop_block.append_operation(cf::cond_br( + let runtime_bindings_meta = metadata + .get_mut::() + .to_native_assert_error( + "Unable to get the RuntimeBindingsMeta from MetadataStorage", + )?; + + let prime = entry.const_int_from_type(context, location, PRIME.clone(), i512)?; + let lhs = entry.extui(lhs, i512, location)?; + let rhs = entry.extui(rhs, i512, location)?; + + // Find 1 / rhs. + let euclidean_result = runtime_bindings_meta.extended_euclidean_algorithm( context, - next_remainder_eq_zero, - negative_check_block, - loop_block, - &[], - &[remainder, next_remainder, inverse, next_inverse], + helper.module, + entry, location, - )); + rhs, + prime, + i512 + )?; + + let inverse = entry.extract_value(context, location, euclidean_result, i512, 1)?; // egcd sometimes returns a negative number for the inverse, // in such cases we must simply wrap it around back into [0, PRIME) // this suffices because |inv_i| <= divfloor(PRIME,2) - let zero = negative_check_block.const_int_from_type(context, location, 0, i512)?; + let zero = entry.const_int_from_type(context, location, 0, i512)?; - let is_negative = negative_check_block + let is_negative = entry .append_operation(arith::cmpi( context, CmpiPredicate::Slt, @@ -233,46 +185,30 @@ pub fn build_binary_operation<'ctx, 'this>( .result(0)? .into(); // if the inverse is < 0, add PRIME - let prime = - negative_check_block.const_int_from_type(context, location, PRIME.clone(), i512)?; - let wrapped_inverse = negative_check_block.addi(inverse, prime, location)?; - let inverse = negative_check_block.append_op_result(arith::select( + let wrapped_inverse = entry.addi(inverse, prime, location)?; + let inverse = entry.append_op_result(arith::select( is_negative, wrapped_inverse, inverse, location, ))?; - negative_check_block.append_operation(cf::br( - inverse_result_block, - &[inverse], - location, - )); - // Div Logic Start - // Fetch operands - let lhs = entry.extui(lhs, i512, location)?; - let rhs = entry.extui(rhs, i512, location)?; - // Calculate inverse of rhs, callling the inverse implementation's starting block - entry.append_operation(cf::br(start_block, &[rhs], location)); - // Fetch the inverse result from the result block - let inverse = inverse_result_block.arg(0)?; - // Peform lhs * (1/ rhs) - let result = inverse_result_block.muli(lhs, inverse, location)?; + // Peform lhs * (1 / rhs) + let result = entry.muli(lhs, inverse, location)?; // Apply modulo and convert result to felt252 - let result_mod = - inverse_result_block.append_op_result(arith::remui(result, prime, location))?; + let result_mod = entry.append_op_result(arith::remui(result, prime, location))?; let is_out_of_range = - inverse_result_block.cmpi(context, CmpiPredicate::Uge, result, prime, location)?; + entry.cmpi(context, CmpiPredicate::Uge, result, prime, location)?; - let result = inverse_result_block.append_op_result(arith::select( + let result = entry.append_op_result(arith::select( is_out_of_range, result_mod, result, location, ))?; - let result = inverse_result_block.trunci(result, felt252_ty, location)?; + let result = entry.trunci(result, felt252_ty, location)?; - return helper.br(inverse_result_block, 0, &[result], location); + return helper.br(entry, 0, &[result], location); } }; diff --git a/src/metadata/runtime_bindings.rs b/src/metadata/runtime_bindings.rs index 8f539c881..d1cab09a2 100644 --- a/src/metadata/runtime_bindings.rs +++ b/src/metadata/runtime_bindings.rs @@ -210,6 +210,7 @@ impl RuntimeBindingsMeta { location: Location<'c>, a: Value<'c, '_>, b: Value<'c, '_>, + integer_type: Type<'c> ) -> Result> where 'c: 'a, @@ -219,9 +220,8 @@ impl RuntimeBindingsMeta { .active_map .insert(RuntimeBinding::ExtendedEuclideanAlgorithm) { - build_egcd_function(module, context, location, func_symbol)?; + build_egcd_function(module, context, location, func_symbol, integer_type)?; } - let integer_type: Type = IntegerType::new(context, 384 * 2).into(); // The struct returned by the function that contains both of the results let return_type = llvm::r#type::r#struct(context, &[integer_type, integer_type], false); Ok(block @@ -825,14 +825,24 @@ fn build_egcd_function<'ctx>( context: &'ctx Context, location: Location<'ctx>, func_symbol: &str, + integer_type: Type, ) -> Result<()> { - let integer_type: Type = IntegerType::new(context, 384 * 2).into(); let region = Region::new(); let entry_block = region.append_block(Block::new(&[ (integer_type, location), (integer_type, location), ])); + let loop_block = region.append_block(Block::new(&[ + (integer_type, location), + (integer_type, location), + (integer_type, location), + (integer_type, location), + ])); + let end_block = region.append_block(Block::new(&[ + (integer_type, location), + (integer_type, location), + ])); let a = entry_block.arg(0)?; let b = entry_block.arg(1)?; @@ -847,17 +857,6 @@ fn build_egcd_function<'ctx>( let prev_inverse = entry_block.const_int_from_type(context, location, 0, integer_type)?; let inverse = entry_block.const_int_from_type(context, location, 1, integer_type)?; - let loop_block = region.append_block(Block::new(&[ - (integer_type, location), - (integer_type, location), - (integer_type, location), - (integer_type, location), - ])); - let end_block = region.append_block(Block::new(&[ - (integer_type, location), - (integer_type, location), - ])); - entry_block.append_operation(cf::br( &loop_block, &[prev_remainder, remainder, prev_inverse, inverse], From ed5d1a1346a1abdb68a8bb65a493612a1c62377a Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Wed, 22 Oct 2025 15:07:14 -0300 Subject: [PATCH 02/22] add two implementations (circuits and felt252) --- .cargo/config.toml | 2 - src/libfuncs/circuit.rs | 18 ++++----- src/libfuncs/felt252.rs | 3 +- src/metadata/runtime_bindings.rs | 64 ++++++++++++++++++++++++++++---- 4 files changed, 66 insertions(+), 21 deletions(-) delete mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index a5f75cb66..000000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[install] -root = "binaries/" diff --git a/src/libfuncs/circuit.rs b/src/libfuncs/circuit.rs index d36b386b8..988ed3124 100644 --- a/src/libfuncs/circuit.rs +++ b/src/libfuncs/circuit.rs @@ -601,15 +601,15 @@ fn build_gate_evaluation<'ctx, 'this>( let circuit_modulus_u768 = block.extui(circuit_modulus, u768_type, location)?; // Apply egcd to find gcd and inverse - let euclidean_result = runtime_bindings_meta.extended_euclidean_algorithm( - context, - helper.module, - block, - location, - rhs_value, - circuit_modulus_u768, - u768_type - )?; + let euclidean_result = runtime_bindings_meta + .circuit_extended_euclidean_algorithm( + context, + helper.module, + block, + location, + rhs_value, + circuit_modulus_u768, + )?; // Extract the values from the result struct let gcd = block.extract_value(context, location, euclidean_result, u768_type, 0)?; diff --git a/src/libfuncs/felt252.rs b/src/libfuncs/felt252.rs index 97e8d24cf..0a11bb029 100644 --- a/src/libfuncs/felt252.rs +++ b/src/libfuncs/felt252.rs @@ -157,14 +157,13 @@ pub fn build_binary_operation<'ctx, 'this>( let rhs = entry.extui(rhs, i512, location)?; // Find 1 / rhs. - let euclidean_result = runtime_bindings_meta.extended_euclidean_algorithm( + let euclidean_result = runtime_bindings_meta.felt252_extended_euclidean_algorithm( context, helper.module, entry, location, rhs, prime, - i512 )?; let inverse = entry.extract_value(context, location, euclidean_result, i512, 1)?; diff --git a/src/metadata/runtime_bindings.rs b/src/metadata/runtime_bindings.rs index d1cab09a2..403395385 100644 --- a/src/metadata/runtime_bindings.rs +++ b/src/metadata/runtime_bindings.rs @@ -46,7 +46,8 @@ enum RuntimeBinding { DictDup, GetCostsBuiltin, DebugPrint, - ExtendedEuclideanAlgorithm, + Felt252ExtendedEuclideanAlgorithm, + CircuitExtendedEuclideanAlgorithm, CircuitArithOperation, #[cfg(feature = "with-cheatcode")] VtableCheatcode, @@ -72,8 +73,11 @@ impl RuntimeBinding { RuntimeBinding::DictDrop => "cairo_native__dict_drop", RuntimeBinding::DictDup => "cairo_native__dict_dup", RuntimeBinding::GetCostsBuiltin => "cairo_native__get_costs_builtin", - RuntimeBinding::ExtendedEuclideanAlgorithm => { - "cairo_native__extended_euclidean_algorithm" + RuntimeBinding::Felt252ExtendedEuclideanAlgorithm => { + "cairo_native__felt252_extended_euclidean_algorithm" + } + RuntimeBinding::CircuitExtendedEuclideanAlgorithm => { + "cairo_native__circuit_extended_euclidean_algorithm" } RuntimeBinding::CircuitArithOperation => "cairo_native__circuit_arith_operation", #[cfg(feature = "with-cheatcode")] @@ -124,7 +128,8 @@ impl RuntimeBinding { RuntimeBinding::GetCostsBuiltin => { crate::runtime::cairo_native__get_costs_builtin as *const () } - RuntimeBinding::ExtendedEuclideanAlgorithm => return None, + RuntimeBinding::Felt252ExtendedEuclideanAlgorithm + | RuntimeBinding::CircuitExtendedEuclideanAlgorithm => return None, RuntimeBinding::CircuitArithOperation => return None, #[cfg(feature = "with-cheatcode")] RuntimeBinding::VtableCheatcode => { @@ -202,7 +207,50 @@ impl RuntimeBindingsMeta { /// After checking, calls the MLIR function with arguments `a` and `b` which are the initial remainders /// used in the algorithm and returns a `Value` containing a struct where the first element is the /// greatest common divisor of `a` and `b` and the second element is the bezout coefficient x. - pub fn extended_euclidean_algorithm<'c, 'a>( + /// + /// This implementation is only for felt252, which uses u252 integers. + pub fn felt252_extended_euclidean_algorithm<'c, 'a>( + &mut self, + context: &'c Context, + module: &Module, + block: &'a Block<'c>, + location: Location<'c>, + a: Value<'c, '_>, + b: Value<'c, '_>, + ) -> Result> + where + 'c: 'a, + { + let integer_type = IntegerType::new(context, 512).into(); + let func_symbol = RuntimeBinding::Felt252ExtendedEuclideanAlgorithm.symbol(); + if self + .active_map + .insert(RuntimeBinding::Felt252ExtendedEuclideanAlgorithm) + { + build_egcd_function(module, context, location, func_symbol, integer_type)?; + } + // The struct returned by the function that contains both of the results + let return_type = llvm::r#type::r#struct(context, &[integer_type, integer_type], false); + Ok(block + .append_operation( + OperationBuilder::new("llvm.call", location) + .add_attributes(&[( + Identifier::new(context, "callee"), + FlatSymbolRefAttribute::new(context, func_symbol).into(), + )]) + .add_operands(&[a, b]) + .add_results(&[return_type]) + .build()?, + ) + .result(0)? + .into()) + } + + /// Similar to [felt252_extended_euclidean_algorithm](Self::felt252_extended_euclidean_algorithm). + /// + /// The difference with the other is that this function is meant to be used + /// with circuits, which use u384 integers. + pub fn circuit_extended_euclidean_algorithm<'c, 'a>( &mut self, context: &'c Context, module: &Module, @@ -210,15 +258,15 @@ impl RuntimeBindingsMeta { location: Location<'c>, a: Value<'c, '_>, b: Value<'c, '_>, - integer_type: Type<'c> ) -> Result> where 'c: 'a, { - let func_symbol = RuntimeBinding::ExtendedEuclideanAlgorithm.symbol(); + let integer_type = IntegerType::new(context, 512).into(); + let func_symbol = RuntimeBinding::CircuitExtendedEuclideanAlgorithm.symbol(); if self .active_map - .insert(RuntimeBinding::ExtendedEuclideanAlgorithm) + .insert(RuntimeBinding::CircuitExtendedEuclideanAlgorithm) { build_egcd_function(module, context, location, func_symbol, integer_type)?; } From 4bf16327bc14dd6e9e3c258db4f8a9c963958a92 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Wed, 22 Oct 2025 16:38:00 -0300 Subject: [PATCH 03/22] fmt --- src/metadata/runtime_bindings.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata/runtime_bindings.rs b/src/metadata/runtime_bindings.rs index 403395385..261921d00 100644 --- a/src/metadata/runtime_bindings.rs +++ b/src/metadata/runtime_bindings.rs @@ -247,7 +247,7 @@ impl RuntimeBindingsMeta { } /// Similar to [felt252_extended_euclidean_algorithm](Self::felt252_extended_euclidean_algorithm). - /// + /// /// The difference with the other is that this function is meant to be used /// with circuits, which use u384 integers. pub fn circuit_extended_euclidean_algorithm<'c, 'a>( From 12aebda1331ed4584bb30d2cd09673ff9a5e05c9 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Wed, 22 Oct 2025 17:03:53 -0300 Subject: [PATCH 04/22] fix euclidean algorithm for circuits --- src/metadata/runtime_bindings.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata/runtime_bindings.rs b/src/metadata/runtime_bindings.rs index 261921d00..79ab0ed01 100644 --- a/src/metadata/runtime_bindings.rs +++ b/src/metadata/runtime_bindings.rs @@ -262,7 +262,7 @@ impl RuntimeBindingsMeta { where 'c: 'a, { - let integer_type = IntegerType::new(context, 512).into(); + let integer_type = IntegerType::new(context, 768).into(); let func_symbol = RuntimeBinding::CircuitExtendedEuclideanAlgorithm.symbol(); if self .active_map From 8c56617889ed518f64561c6dfdc50e570f10da5d Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Thu, 23 Oct 2025 11:57:11 -0300 Subject: [PATCH 05/22] add negative check the build_egcd_function --- src/libfuncs/circuit.rs | 20 -------------------- src/libfuncs/felt252.rs | 26 +------------------------- src/metadata/runtime_bindings.rs | 32 ++++++++++++++++++++++++++------ 3 files changed, 27 insertions(+), 51 deletions(-) diff --git a/src/libfuncs/circuit.rs b/src/libfuncs/circuit.rs index 988ed3124..a3c655703 100644 --- a/src/libfuncs/circuit.rs +++ b/src/libfuncs/circuit.rs @@ -637,26 +637,6 @@ fn build_gate_evaluation<'ctx, 'this>( )); block = has_inverse_block; - // if the inverse is negative, then add modulus - let zero = block.const_int_from_type(context, location, 0, u768_type)?; - let is_negative = block - .append_operation(arith::cmpi( - context, - CmpiPredicate::Slt, - inverse, - zero, - location, - )) - .result(0)? - .into(); - let wrapped_inverse = block.addi(inverse, circuit_modulus_u768, location)?; - let inverse = block.append_op_result(arith::select( - is_negative, - wrapped_inverse, - inverse, - location, - ))?; - // Truncate back let inverse = block.trunci(inverse, u384_type, location)?; diff --git a/src/libfuncs/felt252.rs b/src/libfuncs/felt252.rs index 0a11bb029..8b972b7b2 100644 --- a/src/libfuncs/felt252.rs +++ b/src/libfuncs/felt252.rs @@ -21,7 +21,7 @@ use cairo_lang_sierra::{ use melior::{ dialect::arith::{self, CmpiPredicate}, helpers::{ArithBlockExt, BuiltinBlockExt, LlvmBlockExt}, - ir::{r#type::IntegerType, Block, BlockLike, Location, Value, ValueLike}, + ir::{r#type::IntegerType, Block, Location, Value, ValueLike}, Context, }; use num_bigint::{BigInt, Sign}; @@ -168,30 +168,6 @@ pub fn build_binary_operation<'ctx, 'this>( let inverse = entry.extract_value(context, location, euclidean_result, i512, 1)?; - // egcd sometimes returns a negative number for the inverse, - // in such cases we must simply wrap it around back into [0, PRIME) - // this suffices because |inv_i| <= divfloor(PRIME,2) - let zero = entry.const_int_from_type(context, location, 0, i512)?; - - let is_negative = entry - .append_operation(arith::cmpi( - context, - CmpiPredicate::Slt, - inverse, - zero, - location, - )) - .result(0)? - .into(); - // if the inverse is < 0, add PRIME - let wrapped_inverse = entry.addi(inverse, prime, location)?; - let inverse = entry.append_op_result(arith::select( - is_negative, - wrapped_inverse, - inverse, - location, - ))?; - // Peform lhs * (1 / rhs) let result = entry.muli(lhs, inverse, location)?; // Apply modulo and convert result to felt252 diff --git a/src/metadata/runtime_bindings.rs b/src/metadata/runtime_bindings.rs index 79ab0ed01..a2d9f7966 100644 --- a/src/metadata/runtime_bindings.rs +++ b/src/metadata/runtime_bindings.rs @@ -947,17 +947,37 @@ fn build_egcd_function<'ctx>( location, )); + let gcd = end_block.arg(0)?; + let inverse = end_block.arg(1)?; + + // EGDC sometimes returns a negative number for the inverse, + // in such cases we must simply wrap it around back into [0, MODULUS) + // this suffices because |inv_i| <= divfloor(MODULUS,2) + let zero = end_block.const_int_from_type(context, location, 0, integer_type)?; + let is_negative = end_block + .append_operation(arith::cmpi( + context, + CmpiPredicate::Slt, + inverse, + zero, + location, + )) + .result(0)? + .into(); + let wrapped_inverse = end_block.addi(inverse, b, location)?; + let inverse = end_block.append_op_result(arith::select( + is_negative, + wrapped_inverse, + inverse, + location, + ))?; + // Create the struct that will contain the results let results = end_block.append_op_result(llvm::undef( llvm::r#type::r#struct(context, &[integer_type, integer_type], false), location, ))?; - let results = end_block.insert_values( - context, - location, - results, - &[end_block.arg(0)?, end_block.arg(1)?], - )?; + let results = end_block.insert_values(context, location, results, &[gcd, inverse])?; end_block.append_operation(llvm::r#return(Some(results), location)); let func_name = StringAttribute::new(context, func_symbol); From 0ce2bb504fc80137984ebc7deb29c9e122f1dd3a Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Fri, 24 Oct 2025 15:55:12 -0300 Subject: [PATCH 06/22] better docs --- src/metadata/runtime_bindings.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/metadata/runtime_bindings.rs b/src/metadata/runtime_bindings.rs index a2d9f7966..b2f6aa63f 100644 --- a/src/metadata/runtime_bindings.rs +++ b/src/metadata/runtime_bindings.rs @@ -868,6 +868,9 @@ pub fn setup_runtime(find_symbol_ptr: impl Fn(&str) -> Option<*mut c_void>) { /// /// This function declares a MLIR function that given two numbers a and b, returns a MLIR struct with gcd(a, b) /// and the bezout coefficient x. The declaration is done in the body of the module. +/// +/// The primary use of this function is to find the modular multiplicative inverse of a value. To so, it is expected +/// the a represents the value to be inverted and b the modulus of the field field. fn build_egcd_function<'ctx>( module: &Module, context: &'ctx Context, @@ -892,13 +895,13 @@ fn build_egcd_function<'ctx>( (integer_type, location), ])); - let a = entry_block.arg(0)?; - let b = entry_block.arg(1)?; + let rhs = entry_block.arg(0)?; + let prime_modulus = entry_block.arg(1)?; // The egcd algorithm works by calculating a series of remainders `rem`, being each `rem_i` the remainder of dividing `rem_{i-1}` with `rem_{i-2}` // For the initial setup, rem_0 = b, rem_1 = a. // This order is chosen because if we reverse them, then the first iteration will just swap them - let remainder = a; - let prev_remainder = b; + let remainder = rhs; + let prev_remainder = prime_modulus; // Similarly we'll calculate another series which starts 0,1,... and from which we // will retrieve the modular inverse of a @@ -964,7 +967,7 @@ fn build_egcd_function<'ctx>( )) .result(0)? .into(); - let wrapped_inverse = end_block.addi(inverse, b, location)?; + let wrapped_inverse = end_block.addi(inverse, prime_modulus, location)?; let inverse = end_block.append_op_result(arith::select( is_negative, wrapped_inverse, From 9088bdffab4cdd296d4e1083c39200ad0680c4ab Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Fri, 24 Oct 2025 16:02:19 -0300 Subject: [PATCH 07/22] better function naming --- src/libfuncs/circuit.rs | 2 +- src/libfuncs/felt252.rs | 2 +- src/metadata/runtime_bindings.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libfuncs/circuit.rs b/src/libfuncs/circuit.rs index a3c655703..f84bd17b2 100644 --- a/src/libfuncs/circuit.rs +++ b/src/libfuncs/circuit.rs @@ -602,7 +602,7 @@ fn build_gate_evaluation<'ctx, 'this>( // Apply egcd to find gcd and inverse let euclidean_result = runtime_bindings_meta - .circuit_extended_euclidean_algorithm( + .u384_extended_euclidean_algorithm( context, helper.module, block, diff --git a/src/libfuncs/felt252.rs b/src/libfuncs/felt252.rs index 8b972b7b2..dfc07c613 100644 --- a/src/libfuncs/felt252.rs +++ b/src/libfuncs/felt252.rs @@ -157,7 +157,7 @@ pub fn build_binary_operation<'ctx, 'this>( let rhs = entry.extui(rhs, i512, location)?; // Find 1 / rhs. - let euclidean_result = runtime_bindings_meta.felt252_extended_euclidean_algorithm( + let euclidean_result = runtime_bindings_meta.u252_extended_euclidean_algorithm( context, helper.module, entry, diff --git a/src/metadata/runtime_bindings.rs b/src/metadata/runtime_bindings.rs index b2f6aa63f..7715206d4 100644 --- a/src/metadata/runtime_bindings.rs +++ b/src/metadata/runtime_bindings.rs @@ -209,7 +209,7 @@ impl RuntimeBindingsMeta { /// greatest common divisor of `a` and `b` and the second element is the bezout coefficient x. /// /// This implementation is only for felt252, which uses u252 integers. - pub fn felt252_extended_euclidean_algorithm<'c, 'a>( + pub fn u252_extended_euclidean_algorithm<'c, 'a>( &mut self, context: &'c Context, module: &Module, @@ -250,7 +250,7 @@ impl RuntimeBindingsMeta { /// /// The difference with the other is that this function is meant to be used /// with circuits, which use u384 integers. - pub fn circuit_extended_euclidean_algorithm<'c, 'a>( + pub fn u384_extended_euclidean_algorithm<'c, 'a>( &mut self, context: &'c Context, module: &Module, From a93ff671f9917d6802805aba78c0523b5fdaf70b Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Fri, 24 Oct 2025 16:10:02 -0300 Subject: [PATCH 08/22] change enum variants name as well --- src/metadata/runtime_bindings.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/metadata/runtime_bindings.rs b/src/metadata/runtime_bindings.rs index 7715206d4..7a9ffdbef 100644 --- a/src/metadata/runtime_bindings.rs +++ b/src/metadata/runtime_bindings.rs @@ -46,8 +46,8 @@ enum RuntimeBinding { DictDup, GetCostsBuiltin, DebugPrint, - Felt252ExtendedEuclideanAlgorithm, - CircuitExtendedEuclideanAlgorithm, + U252ExtendedEuclideanAlgorithm, + U384ExtendedEuclideanAlgorithm, CircuitArithOperation, #[cfg(feature = "with-cheatcode")] VtableCheatcode, @@ -73,11 +73,11 @@ impl RuntimeBinding { RuntimeBinding::DictDrop => "cairo_native__dict_drop", RuntimeBinding::DictDup => "cairo_native__dict_dup", RuntimeBinding::GetCostsBuiltin => "cairo_native__get_costs_builtin", - RuntimeBinding::Felt252ExtendedEuclideanAlgorithm => { - "cairo_native__felt252_extended_euclidean_algorithm" + RuntimeBinding::U252ExtendedEuclideanAlgorithm => { + "cairo_native__u252_extended_euclidean_algorithm" } - RuntimeBinding::CircuitExtendedEuclideanAlgorithm => { - "cairo_native__circuit_extended_euclidean_algorithm" + RuntimeBinding::U384ExtendedEuclideanAlgorithm => { + "cairo_native__u384_extended_euclidean_algorithm" } RuntimeBinding::CircuitArithOperation => "cairo_native__circuit_arith_operation", #[cfg(feature = "with-cheatcode")] @@ -128,8 +128,8 @@ impl RuntimeBinding { RuntimeBinding::GetCostsBuiltin => { crate::runtime::cairo_native__get_costs_builtin as *const () } - RuntimeBinding::Felt252ExtendedEuclideanAlgorithm - | RuntimeBinding::CircuitExtendedEuclideanAlgorithm => return None, + RuntimeBinding::U252ExtendedEuclideanAlgorithm + | RuntimeBinding::U384ExtendedEuclideanAlgorithm => return None, RuntimeBinding::CircuitArithOperation => return None, #[cfg(feature = "with-cheatcode")] RuntimeBinding::VtableCheatcode => { @@ -222,10 +222,10 @@ impl RuntimeBindingsMeta { 'c: 'a, { let integer_type = IntegerType::new(context, 512).into(); - let func_symbol = RuntimeBinding::Felt252ExtendedEuclideanAlgorithm.symbol(); + let func_symbol = RuntimeBinding::U252ExtendedEuclideanAlgorithm.symbol(); if self .active_map - .insert(RuntimeBinding::Felt252ExtendedEuclideanAlgorithm) + .insert(RuntimeBinding::U252ExtendedEuclideanAlgorithm) { build_egcd_function(module, context, location, func_symbol, integer_type)?; } @@ -263,10 +263,10 @@ impl RuntimeBindingsMeta { 'c: 'a, { let integer_type = IntegerType::new(context, 768).into(); - let func_symbol = RuntimeBinding::CircuitExtendedEuclideanAlgorithm.symbol(); + let func_symbol = RuntimeBinding::U384ExtendedEuclideanAlgorithm.symbol(); if self .active_map - .insert(RuntimeBinding::CircuitExtendedEuclideanAlgorithm) + .insert(RuntimeBinding::U384ExtendedEuclideanAlgorithm) { build_egcd_function(module, context, location, func_symbol, integer_type)?; } From 2ef63742f52ceaaa6966fa8ccf1860ede7619097 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Fri, 19 Dec 2025 10:52:37 -0300 Subject: [PATCH 09/22] fix secp256_get_point_from_x and secp256_new implementation --- debug_utils/sierra-emu/src/vm/starknet.rs | 4 ++-- debug_utils/sierra-emu/tests/corelib.rs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/debug_utils/sierra-emu/src/vm/starknet.rs b/debug_utils/sierra-emu/src/vm/starknet.rs index 4c435335c..8d0646110 100644 --- a/debug_utils/sierra-emu/src/vm/starknet.rs +++ b/debug_utils/sierra-emu/src/vm/starknet.rs @@ -174,7 +174,7 @@ fn eval_secp256_new( None => Value::Enum { self_ty: enum_ty.clone(), index: 1, - payload: Box::new(Value::Unit), + payload: Box::new(Value::Struct(vec![])), }, }; @@ -328,7 +328,7 @@ fn eval_secp256_get_point_from_x( None => Value::Enum { self_ty: enum_ty.clone(), index: 1, - payload: Box::new(Value::Unit), + payload: Box::new(Value::Struct(vec![])), }, }; diff --git a/debug_utils/sierra-emu/tests/corelib.rs b/debug_utils/sierra-emu/tests/corelib.rs index 1ec60741c..cda19064d 100644 --- a/debug_utils/sierra-emu/tests/corelib.rs +++ b/debug_utils/sierra-emu/tests/corelib.rs @@ -67,7 +67,6 @@ fn test_corelib() { "core::test::hash_test::test_blake2s", "core::test::testing_test::test_get_unspent_gas", "core::test::qm31_test::", - "core::test::secp256k1_test::test_verify_eth_signature_invalid_signature", ]; let compiled = compile_tests( From 7fc8ef39d523dce46009ac5fa945870bc0299dec Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Fri, 19 Dec 2025 18:31:12 -0300 Subject: [PATCH 10/22] use i252 for division --- src/libfuncs/felt252.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libfuncs/felt252.rs b/src/libfuncs/felt252.rs index dfc07c613..f66facf4b 100644 --- a/src/libfuncs/felt252.rs +++ b/src/libfuncs/felt252.rs @@ -152,7 +152,7 @@ pub fn build_binary_operation<'ctx, 'this>( "Unable to get the RuntimeBindingsMeta from MetadataStorage", )?; - let prime = entry.const_int_from_type(context, location, PRIME.clone(), i512)?; + let prime = entry.const_int_from_type(context, location, PRIME.clone(), felt252_ty)?; let lhs = entry.extui(lhs, i512, location)?; let rhs = entry.extui(rhs, i512, location)?; From 0ff9e236829f3ceb7eb2590588434d55977941a9 Mon Sep 17 00:00:00 2001 From: Franco Giachetta Date: Fri, 19 Dec 2025 18:31:53 -0300 Subject: [PATCH 11/22] Fix typo Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> --- src/metadata/runtime_bindings.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metadata/runtime_bindings.rs b/src/metadata/runtime_bindings.rs index ce1e05308..447bb03da 100644 --- a/src/metadata/runtime_bindings.rs +++ b/src/metadata/runtime_bindings.rs @@ -1011,7 +1011,7 @@ fn build_egcd_function<'ctx>( let gcd = end_block.arg(0)?; let inverse = end_block.arg(1)?; - // EGDC sometimes returns a negative number for the inverse, + // EGCD sometimes returns a negative number for the inverse, // in such cases we must simply wrap it around back into [0, MODULUS) // this suffices because |inv_i| <= divfloor(MODULUS,2) let zero = end_block.const_int_from_type(context, location, 0, integer_type)?; From 0b57df7e72eb898da0c44e7317fa03a0254fdcb7 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Fri, 19 Dec 2025 18:35:00 -0300 Subject: [PATCH 12/22] fix comment on build_egcd_function --- src/metadata/runtime_bindings.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/metadata/runtime_bindings.rs b/src/metadata/runtime_bindings.rs index ce1e05308..cecbc4772 100644 --- a/src/metadata/runtime_bindings.rs +++ b/src/metadata/runtime_bindings.rs @@ -867,12 +867,6 @@ pub fn setup_runtime(find_symbol_ptr: impl Fn(&str) -> Option<*mut c_void>) { /// (gcd) of two integers `a` and `b`, as well as the Bézout coefficients `x` /// and `y` such that `ax + by = gcd(a,b)`. If `gcd(a,b) = 1`, then `x` is the /// modular multiplicative inverse of `a` modulo `b`. -/// -/// This function declares a MLIR function that given two numbers a and b, returns a MLIR struct with gcd(a, b) -/// and the bezout coefficient x. The declaration is done in the body of the module. -/// -/// The primary use of this function is to find the modular multiplicative inverse of a value. To so, it is expected -/// the a represents the value to be inverted and b the modulus of the field field. fn build_egcd_function<'ctx>( module: &Module, context: &'ctx Context, From 95c1ba0a46dba7a7c5178b5b2c729ff7eecb56e4 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Fri, 19 Dec 2025 18:38:39 -0300 Subject: [PATCH 13/22] fmt --- src/metadata/runtime_bindings.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/metadata/runtime_bindings.rs b/src/metadata/runtime_bindings.rs index 5ba119e81..89a46ec4a 100644 --- a/src/metadata/runtime_bindings.rs +++ b/src/metadata/runtime_bindings.rs @@ -1033,12 +1033,7 @@ fn build_egcd_function<'ctx>( llvm::r#type::r#struct(context, &[integer_type, integer_type], false), location, ))?; - let results = end_block.insert_values( - context, - location, - results, - &[gcd, inverse], - )?; + let results = end_block.insert_values(context, location, results, &[gcd, inverse])?; end_block.append_operation(llvm::r#return(Some(results), location)); } From 2dcfb1e62f6139ff74a258a0eec2fe584ded44e9 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Fri, 19 Dec 2025 18:51:54 -0300 Subject: [PATCH 14/22] bring back ignored test --- tests/tests/circuit.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/tests/circuit.rs b/tests/tests/circuit.rs index 5afbc49d5..fdb337065 100644 --- a/tests/tests/circuit.rs +++ b/tests/tests/circuit.rs @@ -1911,7 +1911,6 @@ fn test_circuit_add_ec_points_g2() { // NOTE: Since Cairo 2.14.0-dev.1, the BIG_CIRCUIT program takes forever to // compile to Sierra. Enable this test once fixed. #[test] -#[ignore] fn test_circuit_clear_cofactor_bls12_381() { let program = &BIG_CIRCUIT; From f38c1e03d7820ea6ce578ba1407cb1075fcdd055 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Mon, 22 Dec 2025 10:45:23 -0300 Subject: [PATCH 15/22] fix --- src/libfuncs/circuit.rs | 3 -- src/libfuncs/felt252.rs | 15 ++------- src/metadata/runtime_bindings.rs | 55 ++++++++++++++++---------------- 3 files changed, 31 insertions(+), 42 deletions(-) diff --git a/src/libfuncs/circuit.rs b/src/libfuncs/circuit.rs index 0857fc5cc..ce647cb69 100644 --- a/src/libfuncs/circuit.rs +++ b/src/libfuncs/circuit.rs @@ -636,9 +636,6 @@ fn build_gate_evaluation<'ctx, 'this>( )); block = has_inverse_block; - // Truncate back - let inverse = block.trunci(inverse, u384_type, location)?; - gates[gate_offset.lhs] = Some(inverse); } // The imposibility to solve this mul gate offset would render the circuit unsolvable diff --git a/src/libfuncs/felt252.rs b/src/libfuncs/felt252.rs index f66facf4b..22a984eae 100644 --- a/src/libfuncs/felt252.rs +++ b/src/libfuncs/felt252.rs @@ -153,8 +153,6 @@ pub fn build_binary_operation<'ctx, 'this>( )?; let prime = entry.const_int_from_type(context, location, PRIME.clone(), felt252_ty)?; - let lhs = entry.extui(lhs, i512, location)?; - let rhs = entry.extui(rhs, i512, location)?; // Find 1 / rhs. let euclidean_result = runtime_bindings_meta.u252_extended_euclidean_algorithm( @@ -166,7 +164,8 @@ pub fn build_binary_operation<'ctx, 'this>( prime, )?; - let inverse = entry.extract_value(context, location, euclidean_result, i512, 1)?; + let inverse = + entry.extract_value(context, location, euclidean_result, felt252_ty, 1)?; // Peform lhs * (1 / rhs) let result = entry.muli(lhs, inverse, location)?; @@ -175,15 +174,7 @@ pub fn build_binary_operation<'ctx, 'this>( 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.trunci(result, felt252_ty, location)?; - - return helper.br(entry, 0, &[result], location); + entry.append_op_result(arith::select(is_out_of_range, result_mod, result, location))? } }; diff --git a/src/metadata/runtime_bindings.rs b/src/metadata/runtime_bindings.rs index 89a46ec4a..51c841639 100644 --- a/src/metadata/runtime_bindings.rs +++ b/src/metadata/runtime_bindings.rs @@ -946,8 +946,8 @@ fn build_egcd_function<'ctx>( (integer_type, location), // old_s ])); - let modulus = entry_block.arg(1)?; - let rhs = entry_block.arg(0)?; + let a = entry_block.arg(0)?; + let modulus = entry_block.arg(1)?; // b // Jump to loop block from entry block, with initial values. // - old_r = b @@ -957,8 +957,8 @@ fn build_egcd_function<'ctx>( entry_block.append_operation(cf::br( &loop_block, &[ - rhs, modulus, + a, entry_block.const_int_from_type(context, location, 0, integer_type)?, entry_block.const_int_from_type(context, location, 1, integer_type)?, ], @@ -1002,33 +1002,34 @@ fn build_egcd_function<'ctx>( )); } - let gcd = end_block.arg(0)?; - let inverse = end_block.arg(1)?; - - // EGCD sometimes returns a negative number for the inverse, - // in such cases we must simply wrap it around back into [0, MODULUS) - // this suffices because |inv_i| <= divfloor(MODULUS,2) - let zero = end_block.const_int_from_type(context, location, 0, integer_type)?; - let is_negative = end_block - .append_operation(arith::cmpi( - context, - CmpiPredicate::Slt, + + // END BLOCK + { + let gcd = end_block.arg(0)?; + let inverse = end_block.arg(1)?; + + // EGCD sometimes returns a negative number for the inverse, + // in such cases we must simply wrap it around back into [0, MODULUS) + // this suffices because |inv_i| <= divfloor(MODULUS,2) + let zero = end_block.const_int_from_type(context, location, 0, integer_type)?; + let is_negative = end_block + .append_operation(arith::cmpi( + context, + CmpiPredicate::Slt, + inverse, + zero, + location, + )) + .result(0)? + .into(); + let wrapped_inverse = end_block.addi(inverse, modulus, location)?; + let inverse = end_block.append_op_result(arith::select( + is_negative, + wrapped_inverse, inverse, - zero, location, - )) - .result(0)? - .into(); - let wrapped_inverse = end_block.addi(inverse, modulus, location)?; - let inverse = end_block.append_op_result(arith::select( - is_negative, - wrapped_inverse, - inverse, - location, - ))?; + ))?; - // END BLOCK - { let results = end_block.append_op_result(llvm::undef( llvm::r#type::r#struct(context, &[integer_type, integer_type], false), location, From ac77e7b0daffaf96ec81a4cd7cd12e52e26de0b9 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Mon, 22 Dec 2025 16:59:50 -0300 Subject: [PATCH 16/22] make felt division use 512 bits --- src/libfuncs/felt252.rs | 14 +++++++++----- src/metadata/runtime_bindings.rs | 33 +++++++++++++++++--------------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/libfuncs/felt252.rs b/src/libfuncs/felt252.rs index 22a984eae..3398a25bf 100644 --- a/src/libfuncs/felt252.rs +++ b/src/libfuncs/felt252.rs @@ -151,11 +151,13 @@ pub fn build_binary_operation<'ctx, 'this>( .to_native_assert_error( "Unable to get the RuntimeBindingsMeta from MetadataStorage", )?; - - let prime = entry.const_int_from_type(context, location, PRIME.clone(), felt252_ty)?; + + let lhs = entry.extui(lhs, i512, location)?; + let rhs = entry.extui(rhs, i512, location)?; + let prime = entry.const_int_from_type(context, location, PRIME.clone(), i512)?; // Find 1 / rhs. - let euclidean_result = runtime_bindings_meta.u252_extended_euclidean_algorithm( + let euclidean_result = runtime_bindings_meta.u512_extended_euclidean_algorithm( context, helper.module, entry, @@ -165,7 +167,7 @@ pub fn build_binary_operation<'ctx, 'this>( )?; let inverse = - entry.extract_value(context, location, euclidean_result, felt252_ty, 1)?; + entry.extract_value(context, location, euclidean_result, i512, 1)?; // Peform lhs * (1 / rhs) let result = entry.muli(lhs, inverse, location)?; @@ -174,7 +176,9 @@ pub fn build_binary_operation<'ctx, 'this>( let is_out_of_range = entry.cmpi(context, CmpiPredicate::Uge, result, prime, location)?; - entry.append_op_result(arith::select(is_out_of_range, result_mod, result, location))? + let result = entry.append_op_result(arith::select(is_out_of_range, result_mod, result, location))?; + + entry.trunci(result, felt252_ty, location)? } }; diff --git a/src/metadata/runtime_bindings.rs b/src/metadata/runtime_bindings.rs index 51c841639..6ca7c2213 100644 --- a/src/metadata/runtime_bindings.rs +++ b/src/metadata/runtime_bindings.rs @@ -46,7 +46,7 @@ enum RuntimeBinding { DictDup, GetCostsBuiltin, DebugPrint, - U252ExtendedEuclideanAlgorithm, + U512ExtendedEuclideanAlgorithm, U384ExtendedEuclideanAlgorithm, CircuitArithOperation, #[cfg(feature = "with-cheatcode")] @@ -73,8 +73,8 @@ impl RuntimeBinding { RuntimeBinding::DictDrop => "cairo_native__dict_drop", RuntimeBinding::DictDup => "cairo_native__dict_dup", RuntimeBinding::GetCostsBuiltin => "cairo_native__get_costs_builtin", - RuntimeBinding::U252ExtendedEuclideanAlgorithm => { - "cairo_native__u252_extended_euclidean_algorithm" + RuntimeBinding::U512ExtendedEuclideanAlgorithm => { + "cairo_native__u512_extended_euclidean_algorithm" } RuntimeBinding::U384ExtendedEuclideanAlgorithm => { "cairo_native__u384_extended_euclidean_algorithm" @@ -128,7 +128,7 @@ impl RuntimeBinding { RuntimeBinding::GetCostsBuiltin => { crate::runtime::cairo_native__get_costs_builtin as *const () } - RuntimeBinding::U252ExtendedEuclideanAlgorithm + RuntimeBinding::U512ExtendedEuclideanAlgorithm | RuntimeBinding::U384ExtendedEuclideanAlgorithm => return None, RuntimeBinding::CircuitArithOperation => return None, #[cfg(feature = "with-cheatcode")] @@ -209,7 +209,7 @@ impl RuntimeBindingsMeta { /// greatest common divisor of `a` and `b` and the second element is the bezout coefficient x. /// /// This implementation is only for felt252, which uses u252 integers. - pub fn u252_extended_euclidean_algorithm<'c, 'a>( + pub fn u512_extended_euclidean_algorithm<'c, 'a>( &mut self, context: &'c Context, module: &Module, @@ -221,11 +221,11 @@ impl RuntimeBindingsMeta { where 'c: 'a, { - let integer_type = IntegerType::new(context, 252).into(); - let func_symbol = RuntimeBinding::U252ExtendedEuclideanAlgorithm.symbol(); + let integer_type = IntegerType::new(context, 512).into(); + let func_symbol = RuntimeBinding::U512ExtendedEuclideanAlgorithm.symbol(); if self .active_map - .insert(RuntimeBinding::U252ExtendedEuclideanAlgorithm) + .insert(RuntimeBinding::U512ExtendedEuclideanAlgorithm) { build_egcd_function(module, context, location, func_symbol, integer_type)?; } @@ -946,8 +946,7 @@ fn build_egcd_function<'ctx>( (integer_type, location), // old_s ])); - let a = entry_block.arg(0)?; - let modulus = entry_block.arg(1)?; // b + let modulus = entry_block.arg(1)?; // Jump to loop block from entry block, with initial values. // - old_r = b @@ -957,8 +956,8 @@ fn build_egcd_function<'ctx>( entry_block.append_operation(cf::br( &loop_block, &[ - modulus, - a, + modulus, // b + entry_block.arg(0)?, entry_block.const_int_from_type(context, location, 0, integer_type)?, entry_block.const_int_from_type(context, location, 1, integer_type)?, ], @@ -1002,12 +1001,11 @@ fn build_egcd_function<'ctx>( )); } - // END BLOCK { let gcd = end_block.arg(0)?; let inverse = end_block.arg(1)?; - + // EGCD sometimes returns a negative number for the inverse, // in such cases we must simply wrap it around back into [0, MODULUS) // this suffices because |inv_i| <= divfloor(MODULUS,2) @@ -1034,7 +1032,12 @@ fn build_egcd_function<'ctx>( llvm::r#type::r#struct(context, &[integer_type, integer_type], false), location, ))?; - let results = end_block.insert_values(context, location, results, &[gcd, inverse])?; + let results = end_block.insert_values( + context, + location, + results, + &[gcd, inverse], + )?; end_block.append_operation(llvm::r#return(Some(results), location)); } From 189b1c871bd41568851b8b4faf19eb9465019ae9 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Mon, 22 Dec 2025 17:30:05 -0300 Subject: [PATCH 17/22] only extend while multiplying --- src/libfuncs/felt252.rs | 25 ++++++++++++++++--------- src/metadata/runtime_bindings.rs | 23 +++++++++-------------- tests/tests/circuit.rs | 1 + 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/libfuncs/felt252.rs b/src/libfuncs/felt252.rs index 3398a25bf..ec45cde63 100644 --- a/src/libfuncs/felt252.rs +++ b/src/libfuncs/felt252.rs @@ -151,13 +151,11 @@ pub fn build_binary_operation<'ctx, 'this>( .to_native_assert_error( "Unable to get the RuntimeBindingsMeta from MetadataStorage", )?; - - let lhs = entry.extui(lhs, i512, location)?; - let rhs = entry.extui(rhs, i512, location)?; - let prime = entry.const_int_from_type(context, location, PRIME.clone(), i512)?; + + let prime = entry.const_int_from_type(context, location, PRIME.clone(), felt252_ty)?; // Find 1 / rhs. - let euclidean_result = runtime_bindings_meta.u512_extended_euclidean_algorithm( + let euclidean_result = runtime_bindings_meta.u252_extended_euclidean_algorithm( context, helper.module, entry, @@ -166,17 +164,26 @@ pub fn build_binary_operation<'ctx, 'this>( prime, )?; - let inverse = - entry.extract_value(context, location, euclidean_result, i512, 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.append_op_result(arith::select( + is_out_of_range, + result_mod, + result, + location, + ))?; entry.trunci(result, felt252_ty, location)? } diff --git a/src/metadata/runtime_bindings.rs b/src/metadata/runtime_bindings.rs index 6ca7c2213..7c136600a 100644 --- a/src/metadata/runtime_bindings.rs +++ b/src/metadata/runtime_bindings.rs @@ -46,7 +46,7 @@ enum RuntimeBinding { DictDup, GetCostsBuiltin, DebugPrint, - U512ExtendedEuclideanAlgorithm, + U252ExtendedEuclideanAlgorithm, U384ExtendedEuclideanAlgorithm, CircuitArithOperation, #[cfg(feature = "with-cheatcode")] @@ -73,8 +73,8 @@ impl RuntimeBinding { RuntimeBinding::DictDrop => "cairo_native__dict_drop", RuntimeBinding::DictDup => "cairo_native__dict_dup", RuntimeBinding::GetCostsBuiltin => "cairo_native__get_costs_builtin", - RuntimeBinding::U512ExtendedEuclideanAlgorithm => { - "cairo_native__u512_extended_euclidean_algorithm" + RuntimeBinding::U252ExtendedEuclideanAlgorithm => { + "cairo_native__u252_extended_euclidean_algorithm" } RuntimeBinding::U384ExtendedEuclideanAlgorithm => { "cairo_native__u384_extended_euclidean_algorithm" @@ -128,7 +128,7 @@ impl RuntimeBinding { RuntimeBinding::GetCostsBuiltin => { crate::runtime::cairo_native__get_costs_builtin as *const () } - RuntimeBinding::U512ExtendedEuclideanAlgorithm + RuntimeBinding::U252ExtendedEuclideanAlgorithm | RuntimeBinding::U384ExtendedEuclideanAlgorithm => return None, RuntimeBinding::CircuitArithOperation => return None, #[cfg(feature = "with-cheatcode")] @@ -209,7 +209,7 @@ impl RuntimeBindingsMeta { /// greatest common divisor of `a` and `b` and the second element is the bezout coefficient x. /// /// This implementation is only for felt252, which uses u252 integers. - pub fn u512_extended_euclidean_algorithm<'c, 'a>( + pub fn u252_extended_euclidean_algorithm<'c, 'a>( &mut self, context: &'c Context, module: &Module, @@ -221,11 +221,11 @@ impl RuntimeBindingsMeta { where 'c: 'a, { - let integer_type = IntegerType::new(context, 512).into(); - let func_symbol = RuntimeBinding::U512ExtendedEuclideanAlgorithm.symbol(); + let integer_type = IntegerType::new(context, 252).into(); + let func_symbol = RuntimeBinding::U252ExtendedEuclideanAlgorithm.symbol(); if self .active_map - .insert(RuntimeBinding::U512ExtendedEuclideanAlgorithm) + .insert(RuntimeBinding::U252ExtendedEuclideanAlgorithm) { build_egcd_function(module, context, location, func_symbol, integer_type)?; } @@ -1032,12 +1032,7 @@ fn build_egcd_function<'ctx>( llvm::r#type::r#struct(context, &[integer_type, integer_type], false), location, ))?; - let results = end_block.insert_values( - context, - location, - results, - &[gcd, inverse], - )?; + let results = end_block.insert_values(context, location, results, &[gcd, inverse])?; end_block.append_operation(llvm::r#return(Some(results), location)); } diff --git a/tests/tests/circuit.rs b/tests/tests/circuit.rs index fdb337065..5afbc49d5 100644 --- a/tests/tests/circuit.rs +++ b/tests/tests/circuit.rs @@ -1911,6 +1911,7 @@ fn test_circuit_add_ec_points_g2() { // NOTE: Since Cairo 2.14.0-dev.1, the BIG_CIRCUIT program takes forever to // compile to Sierra. Enable this test once fixed. #[test] +#[ignore] fn test_circuit_clear_cofactor_bls12_381() { let program = &BIG_CIRCUIT; From e6c5a8a0bbc2e85fc120823e89bda9e8c6fc18b6 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Tue, 23 Dec 2025 12:21:23 -0300 Subject: [PATCH 18/22] update build_egcd_function comment --- src/metadata/runtime_bindings.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/metadata/runtime_bindings.rs b/src/metadata/runtime_bindings.rs index 7c136600a..25e1f75c0 100644 --- a/src/metadata/runtime_bindings.rs +++ b/src/metadata/runtime_bindings.rs @@ -867,6 +867,10 @@ pub fn setup_runtime(find_symbol_ptr: impl Fn(&str) -> Option<*mut c_void>) { /// (gcd) of two integers `a` and `b`, as well as the Bézout coefficients `x` /// and `y` such that `ax + by = gcd(a,b)`. If `gcd(a,b) = 1`, then `x` is the /// modular multiplicative inverse of `a` modulo `b`. +/// +/// This function declares a MLIR function that given integers `a` +/// and `b`, returns a MLIR struct with `gcd(a,b)` and the Bézout coefficient +/// `x`. The declaration is done in the body of the module. fn build_egcd_function<'ctx>( module: &Module, context: &'ctx Context, From ca85eb045936fce201b7c8fa1c6af148220e9b03 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Tue, 23 Dec 2025 17:30:43 -0300 Subject: [PATCH 19/22] add more docs --- src/libfuncs/felt252.rs | 4 ++++ src/metadata/runtime_bindings.rs | 26 +++++++++++++++----------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/libfuncs/felt252.rs b/src/libfuncs/felt252.rs index ec45cde63..ad3ddfeff 100644 --- a/src/libfuncs/felt252.rs +++ b/src/libfuncs/felt252.rs @@ -164,6 +164,10 @@ pub fn build_binary_operation<'ctx, 'this>( prime, )?; + // 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 = diff --git a/src/metadata/runtime_bindings.rs b/src/metadata/runtime_bindings.rs index 25e1f75c0..c4c781556 100644 --- a/src/metadata/runtime_bindings.rs +++ b/src/metadata/runtime_bindings.rs @@ -870,7 +870,9 @@ pub fn setup_runtime(find_symbol_ptr: impl Fn(&str) -> Option<*mut c_void>) { /// /// This function declares a MLIR function that given integers `a` /// and `b`, returns a MLIR struct with `gcd(a,b)` and the Bézout coefficient -/// `x`. The declaration is done in the body of the module. +/// `x`. This is not a pure implementation of the extended euclidean algorithm, +/// the Bézout coefficient `x` is calculated such that x modulo b. The +/// declaration is done in the body of the module. fn build_egcd_function<'ctx>( module: &Module, context: &'ctx Context, @@ -1008,27 +1010,29 @@ fn build_egcd_function<'ctx>( // END BLOCK { let gcd = end_block.arg(0)?; - let inverse = end_block.arg(1)?; + let beuzout_coeff = end_block.arg(1)?; - // EGCD sometimes returns a negative number for the inverse, - // in such cases we must simply wrap it around back into [0, MODULUS) - // this suffices because |inv_i| <= divfloor(MODULUS,2) + // A pure implementation of EGCD would return the gcd and Bézout + // coefficient as they are now. However, since we want to return the + // Bézout coefficient modulo b, we still need to check if it is + // negative. In such case, we must simply wrap it around back into + // [0, MODULUS) this suffices because |inv_i| <= divfloor(MODULUS,2). let zero = end_block.const_int_from_type(context, location, 0, integer_type)?; let is_negative = end_block .append_operation(arith::cmpi( context, CmpiPredicate::Slt, - inverse, + beuzout_coeff, zero, location, )) .result(0)? .into(); - let wrapped_inverse = end_block.addi(inverse, modulus, location)?; - let inverse = end_block.append_op_result(arith::select( + let wrapped_beuzout_coeff = end_block.addi(beuzout_coeff, modulus, location)?; + let beuzout_coeff = end_block.append_op_result(arith::select( is_negative, - wrapped_inverse, - inverse, + wrapped_beuzout_coeff, + beuzout_coeff, location, ))?; @@ -1036,7 +1040,7 @@ fn build_egcd_function<'ctx>( llvm::r#type::r#struct(context, &[integer_type, integer_type], false), location, ))?; - let results = end_block.insert_values(context, location, results, &[gcd, inverse])?; + let results = end_block.insert_values(context, location, results, &[gcd, beuzout_coeff])?; end_block.append_operation(llvm::r#return(Some(results), location)); } From cf02f60680fcfb593e7d2077833148408df2a8e9 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Mon, 5 Jan 2026 15:02:25 -0300 Subject: [PATCH 20/22] improve comment on build_egcd_function --- src/metadata/runtime_bindings.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/metadata/runtime_bindings.rs b/src/metadata/runtime_bindings.rs index c4c781556..4a4aef038 100644 --- a/src/metadata/runtime_bindings.rs +++ b/src/metadata/runtime_bindings.rs @@ -870,9 +870,9 @@ pub fn setup_runtime(find_symbol_ptr: impl Fn(&str) -> Option<*mut c_void>) { /// /// This function declares a MLIR function that given integers `a` /// and `b`, returns a MLIR struct with `gcd(a,b)` and the Bézout coefficient -/// `x`. This is not a pure implementation of the extended euclidean algorithm, -/// the Bézout coefficient `x` is calculated such that x modulo b. The -/// declaration is done in the body of the module. +/// `x`. Tipically, the EGCD algorithm returns the Bézout coefficient as is. +/// However, because we actually want to calculate the inverse of a modulo b, +/// we wrap the x coefficient around b if negative (x % b). fn build_egcd_function<'ctx>( module: &Module, context: &'ctx Context, From d916944ac80a2a197a7ac0ca7e5c4b2bb41bc77b Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Mon, 5 Jan 2026 15:08:05 -0300 Subject: [PATCH 21/22] improve comment in egcd --- src/metadata/runtime_bindings.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/metadata/runtime_bindings.rs b/src/metadata/runtime_bindings.rs index 4a4aef038..959ab54eb 100644 --- a/src/metadata/runtime_bindings.rs +++ b/src/metadata/runtime_bindings.rs @@ -1016,7 +1016,8 @@ fn build_egcd_function<'ctx>( // coefficient as they are now. However, since we want to return the // Bézout coefficient modulo b, we still need to check if it is // negative. In such case, we must simply wrap it around back into - // [0, MODULUS) this suffices because |inv_i| <= divfloor(MODULUS,2). + // [0, MODULUS). This is fine because, in such case, + // |beuzout_coeff| <= divfloor(MODULUS,2). let zero = end_block.const_int_from_type(context, location, 0, integer_type)?; let is_negative = end_block .append_operation(arith::cmpi( From 0608a4e79beaca8c5df11057bbf95cd8fa30eaf6 Mon Sep 17 00:00:00 2001 From: FrancoGiachetta Date: Mon, 5 Jan 2026 15:08:05 -0300 Subject: [PATCH 22/22] improve comment in egcd --- src/metadata/runtime_bindings.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/metadata/runtime_bindings.rs b/src/metadata/runtime_bindings.rs index 4a4aef038..6c1e69560 100644 --- a/src/metadata/runtime_bindings.rs +++ b/src/metadata/runtime_bindings.rs @@ -1016,7 +1016,8 @@ fn build_egcd_function<'ctx>( // coefficient as they are now. However, since we want to return the // Bézout coefficient modulo b, we still need to check if it is // negative. In such case, we must simply wrap it around back into - // [0, MODULUS) this suffices because |inv_i| <= divfloor(MODULUS,2). + // [0, MODULUS). This is fine because, in such case, + // |beuzout_coeff| <= divfloor(MODULUS,2). let zero = end_block.const_int_from_type(context, location, 0, integer_type)?; let is_negative = end_block .append_operation(arith::cmpi(