Skip to content

Commit 44a1128

Browse files
starknet_os_flow_tests,blockifier_test_utils: add secp256r1 zero-x-point OS flow test
Add an OS flow test exercising the secp256r1 affine point with x == 0 (which exists on secp256r1 but not secp256k1) through three txs in one flow: add(x==0, generator), mul(x==0, 3), and a mul whose scalar-mul precompute table reaches that point as an intermediate (2 * P/2). Each tx checks the EC result in the contract, exercising the OS handling of the x == 0 point end-to-end. Adds test_add_secp256r1_checked and test_mul_point_secp256r1_checked entry points to the cairo1 test feature contract. Growing TestContract shifts two size-dependent expectations, updated here: the bouncer migration-gas expects and the V2 compiled-class-hash estimation margin (350 -> 400; the estimate's per-entry-point/segment overhead slightly under-counts, proportional to size). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 15a107d commit 44a1128

4 files changed

Lines changed: 116 additions & 8 deletions

File tree

crates/blockifier/src/bouncer_test.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -843,11 +843,11 @@ fn class_hash_migration_data_from_state(
843843

844844
if should_migrate {
845845
expect![[r#"
846-
107086865
846+
111498104
847847
"#]]
848848
.assert_debug_eq(&migration_sierra_gas.0);
849849
expect![[r#"
850-
231505645
850+
241253787
851851
"#]]
852852
.assert_debug_eq(&migration_proving_gas.0);
853853
} else {

crates/blockifier_test_utils/resources/feature_contracts/cairo1/test_contract.cairo

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ mod TestContract {
2424
use starknet::info::v2::{ExecutionInfo, ResourceBounds, TxInfo as TxInfoV2};
2525
use starknet::info::v3::TxInfo as TxInfoV3;
2626
use starknet::info::{BlockInfo, SyscallResultTrait, get_contract_address};
27-
use starknet::secp256_trait::{Signature, is_valid_signature};
28-
use starknet::secp256r1::{Secp256r1Impl, Secp256r1Point};
27+
use starknet::secp256_trait::{Secp256Trait, Signature, is_valid_signature};
28+
use starknet::secp256r1::{
29+
Secp256r1Impl, Secp256r1Point, secp256r1_add_syscall, secp256r1_get_xy_syscall,
30+
secp256r1_mul_syscall, secp256r1_new_syscall,
31+
};
2932
use starknet::storage_access::{
3033
storage_address_from_base_and_offset, storage_base_address_from_felt252,
3134
};
@@ -974,6 +977,34 @@ mod TestContract {
974977
starknet::secp256r1::secp256r1_mul_syscall(p: generator, :scalar).unwrap_syscall();
975978
}
976979

980+
// Adds (x, y) to the generator and asserts the result equals (expected_x, expected_y).
981+
#[external(v0)]
982+
fn test_add_secp256r1_checked(
983+
ref self: ContractState, x: u256, y: u256, expected_x: u256, expected_y: u256,
984+
) {
985+
let p0 = secp256r1_new_syscall(x, y).unwrap_syscall().unwrap();
986+
let generator = Secp256Trait::get_generator_point();
987+
let sum = secp256r1_add_syscall(p0, p1: generator).unwrap_syscall();
988+
let (rx, ry) = secp256r1_get_xy_syscall(sum).unwrap_syscall();
989+
assert(rx == expected_x && ry == expected_y, 'unexpected add result');
990+
}
991+
992+
// Multiplies (x, y) by scalar and asserts the result equals (expected_x, expected_y).
993+
#[external(v0)]
994+
fn test_mul_point_secp256r1_checked(
995+
ref self: ContractState,
996+
x: u256,
997+
y: u256,
998+
scalar: u256,
999+
expected_x: u256,
1000+
expected_y: u256,
1001+
) {
1002+
let p = secp256r1_new_syscall(x, y).unwrap_syscall().unwrap();
1003+
let prod = secp256r1_mul_syscall(:p, :scalar).unwrap_syscall();
1004+
let (rx, ry) = secp256r1_get_xy_syscall(prod).unwrap_syscall();
1005+
assert(rx == expected_x && ry == expected_y, 'unexpected mul result');
1006+
}
1007+
9771008
/// Returns a golden valid message hash and its signature, for testing.
9781009
fn get_message_and_secp256r1_signature() -> (u256, Signature, u256, u256, EthAddress) {
9791010
let msg_hash = 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855;

crates/starknet_os/src/hints/hint_implementation/compiled_class/compiled_class_test.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,12 @@ const EXPECTED_BUILTIN_USAGE_PARTIAL_CONTRACT_V2_HASH: expect_test::Expect =
7777
expect!["range_check_builtin: 581"];
7878
const EXPECTED_N_STEPS_PARTIAL_CONTRACT_V2_HASH: Expect = expect!["23616"];
7979
// Allowed margin between estimated and actual execution resources.
80-
// Larger than strictly needed (max observed margin is ~325 on `test_contract`) so adding new
80+
// Larger than strictly needed (max observed margin is ~370 on `test_contract`) so adding new
8181
// feature contracts doesn't immediately bust the budget. The residual gap comes from non-Blake
82-
// constants in `casm_hash_estimation` (entry-point / segment overheads); the Blake step formula
83-
// itself is exact for every input checked in `blake2s_test`.
84-
const ALLOWED_MARGIN_BLAKE_N_STEPS: usize = 350;
82+
// constants in `casm_hash_estimation` (entry-point / segment overheads that slightly under-count,
83+
// proportional to the number of entry points and segments); the Blake step formula itself is exact
84+
// for every input checked in `blake2s_test`.
85+
const ALLOWED_MARGIN_BLAKE_N_STEPS: usize = 400;
8586
const ALLOWED_MARGIN_BLAKE_OPCODE_COUNT: usize = 4;
8687

8788
const CLASS_HASH_WITH_SEGMENTATION: &str =

crates/starknet_os_flow_tests/src/tests.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1721,6 +1721,82 @@ async fn test_new_syscalls_flow(#[case] use_kzg_da: bool, #[case] n_blocks_in_mu
17211721
));
17221722
}
17231723

1724+
/// Runs three secp256r1 operations involving the affine point with x == 0 (which exists on
1725+
/// secp256r1 but not secp256k1) as txs in a single flow, then runs the OS. Each contract entry
1726+
/// point asserts the correct result, so this exercises the OS handling of the x == 0 point
1727+
/// end-to-end (it previously mishandled the point; see the per-tx comments). The u256 (low, high)
1728+
/// limb pairs are the inputs followed by the expected result.
1729+
#[tokio::test]
1730+
async fn test_secp256r1_zero_x_point_handling() {
1731+
let test_contract = FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Casm));
1732+
let (mut test_builder, [main_contract_address]) = TestBuilder::create_standard([(
1733+
test_contract,
1734+
default_test_contract_constructor_calldata(),
1735+
)])
1736+
.await;
1737+
1738+
// add(x == 0 point, generator).
1739+
test_builder.add_funded_account_invoke(invoke_tx_args! {
1740+
calldata: create_calldata(
1741+
main_contract_address,
1742+
"test_add_secp256r1_checked",
1743+
&[
1744+
Felt::ZERO,
1745+
Felt::ZERO,
1746+
Felt::from_hex_unchecked("0x541c2af31dae871728bf856a174f93f4"),
1747+
Felt::from_hex_unchecked("0x66485c780e2f83d72433bd5d84a06bb6"),
1748+
Felt::from_hex_unchecked("0x309d479ae02982a3a0c135a210379e6f"),
1749+
Felt::from_hex_unchecked("0x00486efab89170d45f6160cbc7d034a9"),
1750+
Felt::from_hex_unchecked("0xbe0766825f92a0794540ee7970f48bb9"),
1751+
Felt::from_hex_unchecked("0x651969b753803a6019cec6e6877a0ff8"),
1752+
],
1753+
),
1754+
});
1755+
1756+
// mul(x == 0 point, 3).
1757+
test_builder.add_funded_account_invoke(invoke_tx_args! {
1758+
calldata: create_calldata(
1759+
main_contract_address,
1760+
"test_mul_point_secp256r1_checked",
1761+
&[
1762+
Felt::ZERO,
1763+
Felt::ZERO,
1764+
Felt::from_hex_unchecked("0x541c2af31dae871728bf856a174f93f4"),
1765+
Felt::from_hex_unchecked("0x66485c780e2f83d72433bd5d84a06bb6"),
1766+
Felt::from(3_u8),
1767+
Felt::ZERO,
1768+
Felt::from_hex_unchecked("0x1e1338620020b5febb703b78a52557b1"),
1769+
Felt::from_hex_unchecked("0x4edb2f8a9b1b9d31dc704c71e17cd2d5"),
1770+
Felt::from_hex_unchecked("0x149ce1aa9aee2f11be92df6e405c55b8"),
1771+
Felt::from_hex_unchecked("0x9f6c246d01e73176c7318a8b17bd3ca2"),
1772+
],
1773+
),
1774+
});
1775+
1776+
// mul(P/2, 3), where 2 * (P/2) == the x == 0 point: it appears mid-computation as the
1777+
// scalar-mul precompute entry table[2].
1778+
test_builder.add_funded_account_invoke(invoke_tx_args! {
1779+
calldata: create_calldata(
1780+
main_contract_address,
1781+
"test_mul_point_secp256r1_checked",
1782+
&[
1783+
Felt::from_hex_unchecked("0x278e28febff3b05632eeff09011c5579"),
1784+
Felt::from_hex_unchecked("0x81bfb55b010b1bdf08b8d9d8590087aa"),
1785+
Felt::from_hex_unchecked("0x50799b354b0fb1e77eb75eba8bff3d58"),
1786+
Felt::from_hex_unchecked("0x8cd2f199d9815d7585073034eb76c93d"),
1787+
Felt::from(3_u8),
1788+
Felt::ZERO,
1789+
Felt::from_hex_unchecked("0x3987510e0f01f0675cab69d0ccb480b7"),
1790+
Felt::from_hex_unchecked("0x6f370ba949025de60e38bfaec452e3d5"),
1791+
Felt::from_hex_unchecked("0x5df6f10c46fb2a67036c5251f9e5c9af"),
1792+
Felt::from_hex_unchecked("0xd62ff00ad9d9eaa09da9ba13a1c26049"),
1793+
],
1794+
),
1795+
});
1796+
1797+
test_builder.build_and_run().await.perform_default_validations();
1798+
}
1799+
17241800
/// Runs the same syscall several times from various call depths. E.g.,
17251801
/// 1. sha256()
17261802
/// 2. call_contract(sha256)

0 commit comments

Comments
 (0)