Skip to content

Commit da9e014

Browse files
authored
feature(sierra): Added BoundedIntGuarantee and verify libfunc. (#9777)
1 parent 4e919aa commit da9e014

9 files changed

Lines changed: 221 additions & 24 deletions

File tree

crates/cairo-lang-sierra-ap-change/src/core_libfunc_ap_change.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,11 @@ pub fn core_libfunc_ap_change<InfoProvider: InvocationApChangeInfoProvider>(
433433
BoundedIntConcreteLibfunc::WrapNonZero(_) => {
434434
vec![ApChange::Known(0)]
435435
}
436+
BoundedIntConcreteLibfunc::GuaranteeVerify(libfunc) => {
437+
let ap_change = if libfunc.range.lower.is_zero() { 0 } else { 1 }
438+
+ if &libfunc.range.upper - 1 == u128::MAX.into() { 0 } else { 1 };
439+
vec![ApChange::Known(ap_change)]
440+
}
436441
},
437442
Circuit(CircuitConcreteLibfunc::TryIntoCircuitModulus(_)) => {
438443
vec![ApChange::Known(1), ApChange::Known(1)]

crates/cairo-lang-sierra-gas/src/core_libfunc_cost_base.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,12 @@ pub fn core_libfunc_cost(
554554
BoundedIntConcreteLibfunc::WrapNonZero(_) => {
555555
vec![ConstCost::steps(0).into()]
556556
}
557+
BoundedIntConcreteLibfunc::GuaranteeVerify(libfunc) => {
558+
let steps = 2
559+
+ if libfunc.range.lower.is_zero() { 0 } else { 1 }
560+
+ if &libfunc.range.upper - 1 == u128::MAX.into() { 0 } else { 1 };
561+
vec![ConstCost { steps, holes: 0, range_checks: 2, range_checks96: 0 }.into()]
562+
}
557563
},
558564
Circuit(libfunc) => match libfunc {
559565
CircuitConcreteLibfunc::AddInput(_) => {

crates/cairo-lang-sierra-to-casm/src/invocations/casts.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ fn add_downcast_overflow_both(
204204
}
205205

206206
/// Validates that `value` is smaller than `bound`.
207-
fn validate_lt(casm_builder: &mut CasmBuilder, range_check: Var, value: Var, bound: &BigInt) {
207+
pub fn validate_lt(casm_builder: &mut CasmBuilder, range_check: Var, value: Var, bound: &BigInt) {
208208
casm_build_extend! {casm_builder,
209209
// value < bound <=> value + (2**128 - bound) < 2**128.
210210
const pos_shift = (BigInt::from(u128::MAX) + 1 - bound) as BigInt;
@@ -215,7 +215,7 @@ fn validate_lt(casm_builder: &mut CasmBuilder, range_check: Var, value: Var, bou
215215

216216
/// Validates that `value` is greater or equal to `bound`.
217217
/// If `bound` is zero, only range checks without an additional calculation.
218-
fn validate_ge(casm_builder: &mut CasmBuilder, range_check: Var, value: Var, bound: &BigInt) {
218+
pub fn validate_ge(casm_builder: &mut CasmBuilder, range_check: Var, value: Var, bound: &BigInt) {
219219
casm_build_extend! {casm_builder,
220220
// value >= bound <=> value - bound >= 0.
221221
const bound = bound.clone();

crates/cairo-lang-sierra-to-casm/src/invocations/int/bounded.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ use std::ops::Shl;
33
use cairo_lang_casm::builder::CasmBuilder;
44
use cairo_lang_casm::casm_build_extend;
55
use cairo_lang_sierra::extensions::bounded_int::{
6-
BoundedIntConcreteLibfunc, BoundedIntDivRemAlgorithm,
6+
BoundedIntConcreteLibfunc, BoundedIntDivRemAlgorithm, BoundedIntGuaranteeVerifyConcreteLibfunc,
77
};
88
use cairo_lang_sierra::extensions::felt252::Felt252BinaryOperator;
99
use cairo_lang_sierra::extensions::gas::CostTokenType;
1010
use cairo_lang_sierra::extensions::utils::Range;
1111
use num_bigint::BigInt;
1212
use num_traits::One;
1313

14+
use crate::invocations::casts::{validate_ge, validate_lt};
1415
use crate::invocations::felt252::build_felt252_op_with_var;
1516
use crate::invocations::misc::{build_identity, build_is_zero};
1617
use crate::invocations::{
@@ -45,6 +46,9 @@ pub fn build(
4546
}
4647
BoundedIntConcreteLibfunc::IsZero(_) => build_is_zero(builder),
4748
BoundedIntConcreteLibfunc::WrapNonZero(_) => build_identity(builder),
49+
BoundedIntConcreteLibfunc::GuaranteeVerify(libfunc) => {
50+
build_guarantee_verify(builder, libfunc)
51+
}
4852
}
4953
}
5054

@@ -246,3 +250,33 @@ fn build_trim(
246250
Default::default(),
247251
))
248252
}
253+
254+
/// Build verification for a BoundedIntGuarantee.
255+
/// Performs range checks to verify the value is within the specified bounds.
256+
fn build_guarantee_verify(
257+
builder: CompiledInvocationBuilder<'_>,
258+
libfunc: &BoundedIntGuaranteeVerifyConcreteLibfunc,
259+
) -> Result<CompiledInvocation, InvocationError> {
260+
let [range_check, value] = builder.try_get_single_cells()?;
261+
262+
let mut casm_builder = CasmBuilder::with_capacity(4, 2);
263+
add_input_variables! {casm_builder,
264+
buffer(2) range_check;
265+
deref value;
266+
};
267+
casm_build_extend!(casm_builder, let orig_range_check = range_check;);
268+
validate_ge(&mut casm_builder, range_check, value, &libfunc.range.lower);
269+
validate_lt(&mut casm_builder, range_check, value, &libfunc.range.upper);
270+
Ok(builder.build_from_casm_builder(
271+
casm_builder,
272+
[("Fallthrough", &[&[range_check]], None)],
273+
CostValidationInfo {
274+
builtin_infos: vec![BuiltinInfo {
275+
cost_token_ty: CostTokenType::RangeCheck,
276+
start: orig_range_check,
277+
end: range_check,
278+
}],
279+
extra_costs: None,
280+
},
281+
))
282+
}

crates/cairo-lang-sierra-type-size/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ pub fn get_type_size_map(
9090
| CoreTypeConcrete::SegmentArena(_)
9191
| CoreTypeConcrete::Bytes31(_)
9292
| CoreTypeConcrete::BoundedInt(_)
93+
| CoreTypeConcrete::BoundedIntGuarantee(_)
9394
| CoreTypeConcrete::QM31(_) => 1,
9495
// Size 2.
9596
CoreTypeConcrete::Array(_)

crates/cairo-lang-sierra/src/extensions/core.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use super::array::{ArrayLibfunc, ArrayType};
33
use super::bitwise::BitwiseType;
44
use super::blake::{Blake2sState, BlakeLibfunc};
55
use super::boolean::BoolLibfunc;
6-
use super::bounded_int::{BoundedIntLibfunc, BoundedIntType};
6+
use super::bounded_int::{BoundedIntGuaranteeType, BoundedIntLibfunc, BoundedIntType};
77
use super::branch_align::BranchAlignLibfunc;
88
use super::bytes31::{Bytes31Libfunc, Bytes31Type};
99
use super::casts::CastLibfunc;
@@ -103,6 +103,7 @@ define_type_hierarchy! {
103103
Snapshot(SnapshotType),
104104
Bytes31(Bytes31Type),
105105
BoundedInt(BoundedIntType),
106+
BoundedIntGuarantee(BoundedIntGuaranteeType),
106107
QM31(QM31Type),
107108
}, CoreTypeConcrete
108109
}

crates/cairo-lang-sierra/src/extensions/modules/bounded_int.rs

Lines changed: 128 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::ops::Shl;
33
use cairo_lang_utils::require;
44
use itertools::Itertools;
55
use num_bigint::{BigInt, ToBigInt};
6-
use num_traits::{One, Signed, Zero};
6+
use num_traits::{One, Signed, ToPrimitive, Zero};
77
use starknet_types_core::felt::Felt as Felt252;
88

99
use super::non_zero::{NonZeroType, nonzero_ty};
@@ -37,30 +37,34 @@ impl NamedType for BoundedIntType {
3737
_context: &dyn TypeSpecializationContext,
3838
args: &[GenericArg],
3939
) -> Result<Self::Concrete, SpecializationError> {
40-
let (min, max) = match args {
41-
[GenericArg::Value(min), GenericArg::Value(max)] => (min.clone(), max.clone()),
42-
[_, _] => return Err(SpecializationError::UnsupportedGenericArg),
43-
_ => return Err(SpecializationError::WrongNumberOfGenericArgs),
44-
};
45-
46-
let prime: BigInt = Felt252::prime().into();
47-
if !(-&prime < min && min <= max && max < prime && &max - &min < prime) {
48-
return Err(SpecializationError::UnsupportedGenericArg);
49-
}
40+
specialize_bounded_int_type(Self::ID, args, true, true)
41+
}
42+
}
5043

51-
let long_id = Self::concrete_type_long_id(args);
52-
let ty_info = TypeInfo {
53-
long_id,
54-
zero_sized: false,
55-
storable: true,
56-
droppable: true,
57-
duplicatable: true,
58-
};
44+
/// Type for BoundedIntGuarantee.
45+
/// A guarantee that a value is within the specified bounds.
46+
/// Unlike BoundedInt, this type is non-duplicatable and non-droppable.
47+
/// It must be verified via `bounded_int_guarantee_verify` to be consumed.
48+
/// Currently only supports u32 bounds (0 to 2^32-1).
49+
#[derive(Default)]
50+
pub struct BoundedIntGuaranteeType {}
51+
impl NamedType for BoundedIntGuaranteeType {
52+
type Concrete = BoundedIntConcreteType;
5953

60-
Ok(Self::Concrete { info: ty_info, range: Range::closed(min, max) })
54+
const ID: GenericTypeId = GenericTypeId::new_inline("BoundedIntGuarantee");
55+
fn specialize(
56+
&self,
57+
_context: &dyn TypeSpecializationContext,
58+
args: &[GenericArg],
59+
) -> Result<Self::Concrete, SpecializationError> {
60+
let (min, max) = extract_bounds(args)?;
61+
// Currently only u32 guarantees are supported.
62+
require(is_u32_range(min, max)).ok_or(SpecializationError::UnsupportedGenericArg)?;
63+
specialize_bounded_int_type(Self::ID, args, false, false)
6164
}
6265
}
6366

67+
/// Shared concrete type for BoundedInt and BoundedIntGuarantee.
6468
pub struct BoundedIntConcreteType {
6569
pub info: TypeInfo,
6670
/// The range bounds for a value of this type.
@@ -83,6 +87,7 @@ define_libfunc_hierarchy! {
8387
TrimMax(BoundedIntTrimLibfunc<true>),
8488
IsZero(BoundedIntIsZeroLibfunc),
8589
WrapNonZero(BoundedIntWrapNonZeroLibfunc),
90+
GuaranteeVerify(BoundedIntGuaranteeVerifyLibfunc),
8691
}, BoundedIntConcreteLibfunc
8792
}
8893

@@ -544,6 +549,63 @@ impl SignatureOnlyGenericLibfunc for BoundedIntWrapNonZeroLibfunc {
544549
}
545550
}
546551

552+
/// Libfunc for verifying a BoundedIntGuarantee.
553+
/// Consumes the guarantee and returns the underlying value as a BoundedInt.
554+
/// Generic args: [min, max] - the bounds of the guarantee.
555+
/// Currently only supports u32 bounds (0 to 2^32-1).
556+
#[derive(Default)]
557+
pub struct BoundedIntGuaranteeVerifyLibfunc {}
558+
impl NamedLibfunc for BoundedIntGuaranteeVerifyLibfunc {
559+
type Concrete = BoundedIntGuaranteeVerifyConcreteLibfunc;
560+
561+
const STR_ID: &'static str = "bounded_int_guarantee_verify";
562+
563+
fn specialize_signature(
564+
&self,
565+
context: &dyn SignatureSpecializationContext,
566+
args: &[GenericArg],
567+
) -> Result<LibfuncSignature, SpecializationError> {
568+
let (min, max) = extract_bounds(args)?;
569+
// Currently only u32 guarantees are supported.
570+
require(is_u32_range(min, max)).ok_or(SpecializationError::UnsupportedGenericArg)?;
571+
572+
let range_check_ty = context.get_concrete_type(RangeCheckType::ID, &[])?;
573+
let guarantee_ty = bounded_int_guarantee_ty(context, min.clone(), max.clone())?;
574+
575+
Ok(LibfuncSignature::new_non_branch_ex(
576+
vec![
577+
ParamSignature::new(range_check_ty.clone()).with_allow_add_const(),
578+
ParamSignature::new(guarantee_ty),
579+
],
580+
vec![OutputVarInfo::new_builtin(range_check_ty)],
581+
SierraApChange::Known { new_vars_only: false },
582+
))
583+
}
584+
585+
fn specialize(
586+
&self,
587+
context: &dyn SpecializationContext,
588+
args: &[GenericArg],
589+
) -> Result<Self::Concrete, SpecializationError> {
590+
let (min, max) = extract_bounds(args)?;
591+
// Currently only u32 guarantees are supported.
592+
require(is_u32_range(min, max)).ok_or(SpecializationError::UnsupportedGenericArg)?;
593+
594+
let range = Range::closed(min.clone(), max.clone());
595+
Ok(Self::Concrete { range, signature: self.specialize_signature(context, args)? })
596+
}
597+
}
598+
599+
pub struct BoundedIntGuaranteeVerifyConcreteLibfunc {
600+
pub range: Range,
601+
signature: LibfuncSignature,
602+
}
603+
impl SignatureBasedConcreteLibfunc for BoundedIntGuaranteeVerifyConcreteLibfunc {
604+
fn signature(&self) -> &LibfuncSignature {
605+
&self.signature
606+
}
607+
}
608+
547609
/// Returns the concrete type for a BoundedInt<min, max>.
548610
pub fn bounded_int_ty(
549611
context: &dyn SignatureSpecializationContext,
@@ -552,3 +614,49 @@ pub fn bounded_int_ty(
552614
) -> Result<ConcreteTypeId, SpecializationError> {
553615
context.get_concrete_type(BoundedIntType::ID, &[GenericArg::Value(min), GenericArg::Value(max)])
554616
}
617+
618+
/// Returns the concrete type for a BoundedIntGuarantee<min, max>.
619+
pub fn bounded_int_guarantee_ty(
620+
context: &dyn SignatureSpecializationContext,
621+
min: BigInt,
622+
max: BigInt,
623+
) -> Result<ConcreteTypeId, SpecializationError> {
624+
context.get_concrete_type(
625+
BoundedIntGuaranteeType::ID,
626+
&[GenericArg::Value(min), GenericArg::Value(max)],
627+
)
628+
}
629+
630+
/// Extracts min and max values from generic args.
631+
fn extract_bounds(args: &[GenericArg]) -> Result<(&BigInt, &BigInt), SpecializationError> {
632+
match args {
633+
[GenericArg::Value(min), GenericArg::Value(max)] => Ok((min, max)),
634+
[_, _] => Err(SpecializationError::UnsupportedGenericArg),
635+
_ => Err(SpecializationError::WrongNumberOfGenericArgs),
636+
}
637+
}
638+
639+
/// Checks if the given bounds match the u32 range.
640+
fn is_u32_range(min: &BigInt, max: &BigInt) -> bool {
641+
min.is_zero() && max.to_u32() == Some(u32::MAX)
642+
}
643+
644+
/// Helper to specialize bounded int types.
645+
fn specialize_bounded_int_type(
646+
generic_id: GenericTypeId,
647+
args: &[GenericArg],
648+
droppable: bool,
649+
duplicatable: bool,
650+
) -> Result<BoundedIntConcreteType, SpecializationError> {
651+
let (min, max) = extract_bounds(args)?;
652+
653+
let prime: BigInt = Felt252::prime().into();
654+
if !(-&prime < *min && min <= max && max < &prime && max - min < prime) {
655+
return Err(SpecializationError::UnsupportedGenericArg);
656+
}
657+
658+
let long_id = crate::program::ConcreteTypeLongId { generic_id, generic_args: args.to_vec() };
659+
let ty_info = TypeInfo { long_id, zero_sized: false, storable: true, droppable, duplicatable };
660+
661+
Ok(BoundedIntConcreteType { info: ty_info, range: Range::closed(min.clone(), max.clone()) })
662+
}

crates/cairo-lang-starknet-classes/src/allowed_libfuncs_lists/all.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"bounded_int_add",
2525
"bounded_int_constrain",
2626
"bounded_int_div_rem",
27+
"bounded_int_guarantee_verify",
2728
"bounded_int_is_zero",
2829
"bounded_int_mul",
2930
"bounded_int_sub",

tests/e2e_test_data/libfuncs/bounded_int

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,3 +1089,44 @@ test::foo@F0([0]: u8) -> (core::internal::OptionRev::<test::BoundedInt::<0, 254>
10891089

10901090
//! > function_costs
10911091
test::foo: SmallOrderedMap({Const: 400})
1092+
1093+
//! > ==========================================================================
1094+
1095+
//! > bounded_int_guarantee_verify libfunc for u32.
1096+
1097+
//! > test_runner_name
1098+
SmallE2ETestRunner
1099+
1100+
//! > cairo_code
1101+
extern type BoundedIntGuarantee<const MIN: felt252, const MAX: felt252>;
1102+
extern fn bounded_int_guarantee_verify<const MIN: felt252, const MAX: felt252>(
1103+
value: BoundedIntGuarantee<MIN, MAX>,
1104+
) implicits(RangeCheck) nopanic;
1105+
1106+
fn foo(value: BoundedIntGuarantee<0, 0xffffffff>) {
1107+
bounded_int_guarantee_verify(value)
1108+
}
1109+
1110+
//! > casm
1111+
[fp + -3] = [[fp + -4] + 0];
1112+
[ap + 0] = [fp + -3] + 340282366920938463463374607427473244160, ap++;
1113+
[ap + -1] = [[fp + -4] + 1];
1114+
[ap + 0] = [fp + -4] + 2, ap++;
1115+
ret;
1116+
1117+
//! > function_costs
1118+
test::foo: SmallOrderedMap({Const: 540})
1119+
1120+
//! > sierra_code
1121+
type RangeCheck = RangeCheck [storable: true, drop: false, dup: false, zero_sized: false];
1122+
type BoundedIntGuarantee<0, 4294967295> = BoundedIntGuarantee<0, 4294967295> [storable: true, drop: false, dup: false, zero_sized: false];
1123+
1124+
libfunc bounded_int_guarantee_verify<0, 4294967295> = bounded_int_guarantee_verify<0, 4294967295>;
1125+
libfunc store_temp<RangeCheck> = store_temp<RangeCheck>;
1126+
1127+
F0:
1128+
bounded_int_guarantee_verify<0, 4294967295>([0], [1]) -> ([2]);
1129+
store_temp<RangeCheck>([2]) -> ([2]);
1130+
return([2]);
1131+
1132+
test::foo@F0([0]: RangeCheck, [1]: BoundedIntGuarantee<0, 4294967295>) -> (RangeCheck);

0 commit comments

Comments
 (0)