Skip to content

Commit 4969f33

Browse files
authored
Fix off-by-one comparisons in split_int, assert_250_bit, and sqrt hints (#2348)
* Fix off-by-one comparisons in split_int, assert_250_bit, and sqrt hints * Add changelog entry for hint comparison fix
1 parent 485d074 commit 4969f33

2 files changed

Lines changed: 67 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Both branches support Stwo prover opcodes (Blake2s, QM31) since v2.0.0.
1212

1313
#### Upcoming Changes
1414

15+
* fix: Fix off-by-one comparisons in `split_int`, `assert_250_bit`, and `sqrt` hints [#2348](https://github.com/lambdaclass/cairo-vm/pull/2348)
16+
1517
#### [3.2.0] - 2026-3-3
1618

1719
* fix: Change extended_resource_counter entry from u32 to usize [#2349](https://github.com/lambdaclass/cairo-vm/pull/2349)

vm/src/hint_processor/builtin_hint_processor/math_utils.rs

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ pub fn split_int(
341341
let output = get_ptr_from_var_name("output", vm, ids_data, ap_tracking)?;
342342
//Main Logic
343343
let res = value.mod_floor(base);
344-
if &res > bound {
344+
if &res >= bound {
345345
return Err(HintError::SplitIntLimbOutOfRange(Box::new(res)));
346346
}
347347
vm.insert_value(output, res).map_err(HintError::Memory)
@@ -422,8 +422,8 @@ pub fn sqrt(
422422
ap_tracking: &ApTracking,
423423
) -> Result<(), HintError> {
424424
let mod_value = get_integer_from_var_name("value", vm, ids_data, ap_tracking)?;
425-
//This is equal to mod_value > Felt252::from(2).pow(250)
426-
if mod_value > pow2_const(250) {
425+
//This is equal to mod_value >= Felt252::from(2).pow(250)
426+
if mod_value >= pow2_const(250) {
427427
return Err(HintError::ValueOutside250BitRange(Box::new(mod_value)));
428428
//This is equal to mod_value > bigint!(2).pow(250)
429429
}
@@ -538,7 +538,7 @@ pub fn assert_250_bit(
538538
ap_tracking,
539539
)?));
540540
//Main logic
541-
if &value > upper_bound {
541+
if &value >= upper_bound {
542542
return Err(HintError::ValueOutside250BitRange(Box::new(value)));
543543
}
544544
let (high, low) = value.div_rem(&shift.try_into().map_err(|_| MathError::DividedByZero)?);
@@ -1441,6 +1441,23 @@ mod tests {
14411441
);
14421442
}
14431443

1444+
#[test]
1445+
fn run_split_int_equal_to_bound() {
1446+
let hint_code = "memory[ids.output] = res = (int(ids.value) % PRIME) % ids.base\nassert res < ids.bound, f'split_int(): Limb {res} is out of range.'";
1447+
let mut vm = vm!();
1448+
//Initialize fp
1449+
vm.run_context.fp = 4;
1450+
//Insert ids into memory: value=7, base=10, bound=7 => res = 7 % 10 = 7, 7 == bound should fail
1451+
vm.segments = segments![((1, 0), (2, 0)), ((1, 1), 7), ((1, 2), 10), ((1, 3), 7)];
1452+
add_segments!(vm, 2);
1453+
let ids_data = ids_data!["output", "value", "base", "bound"];
1454+
//Execute the hint
1455+
assert_matches!(
1456+
run_hint!(vm, ids_data, hint_code),
1457+
Err(HintError::SplitIntLimbOutOfRange(bx)) if *bx == Felt252::from(7)
1458+
);
1459+
}
1460+
14441461
#[test]
14451462
fn run_is_positive_hint_true() {
14461463
let hint_code =
@@ -1539,6 +1556,29 @@ mod tests {
15391556
check_memory![vm.segments.memory, ((1, 1), 9)];
15401557
}
15411558

1559+
#[test]
1560+
fn run_sqrt_invalid_equal_to_2_pow_250() {
1561+
let hint_code = "from starkware.python.math_utils import isqrt\nvalue = ids.value % PRIME\nassert value < 2 ** 250, f\"value={value} is outside of the range [0, 2**250).\"\nassert 2 ** 250 < PRIME\nids.root = isqrt(value)";
1562+
let mut vm = vm!();
1563+
//Initialize fp
1564+
vm.run_context.fp = 2;
1565+
//Insert ids.value = 2^250 into memory (exactly at the bound, should fail)
1566+
vm.segments = segments![(
1567+
(1, 0),
1568+
(
1569+
"1809251394333065553493296640760748560207343510400633813116524750123642650624",
1570+
10
1571+
)
1572+
)];
1573+
//Create ids
1574+
let ids_data = ids_data!["value", "root"];
1575+
//Execute the hint
1576+
assert_matches!(
1577+
run_hint!(vm, ids_data, hint_code),
1578+
Err(HintError::ValueOutside250BitRange(bx)) if *bx == pow2_const(250)
1579+
);
1580+
}
1581+
15421582
#[test]
15431583
fn run_sqrt_invalid_negative_number() {
15441584
let hint_code = "from starkware.python.math_utils import isqrt\nvalue = ids.value % PRIME\nassert value < 2 ** 250, f\"value={value} is outside of the range [0, 2**250).\"\nassert 2 ** 250 < PRIME\nids.root = isqrt(value)";
@@ -1845,6 +1885,27 @@ mod tests {
18451885
);
18461886
}
18471887

1888+
#[test]
1889+
fn run_assert_250_bit_equal_to_upper_bound() {
1890+
let hint_code = hint_code::ASSERT_250_BITS;
1891+
let constants = HashMap::from([
1892+
("UPPER_BOUND".to_string(), Felt252::from(15)),
1893+
("SHIFT".to_string(), Felt252::from(5)),
1894+
]);
1895+
let mut vm = vm!();
1896+
//Initialize fp
1897+
vm.run_context.fp = 3;
1898+
//Insert ids into memory: value == UPPER_BOUND (15), should fail
1899+
vm.segments = segments![((1, 0), 15)];
1900+
//Create ids
1901+
let ids_data = ids_data!["value", "high", "low"];
1902+
//Execute the hint
1903+
assert_matches!(
1904+
run_hint!(vm, ids_data, hint_code, &mut exec_scopes_ref!(), &constants),
1905+
Err(HintError::ValueOutside250BitRange(bx)) if *bx == Felt252::from(15)
1906+
);
1907+
}
1908+
18481909
#[test]
18491910
fn run_is_250_bits_valid() {
18501911
let hint_code = "ids.is_250 = 1 if ids.addr < 2**250 else 0";

0 commit comments

Comments
 (0)