Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ const STORED_BLOCK_HASH_BUFFER = 10;

// Allowed virtual OS program hashes for client-side proving.
const ALLOWED_VIRTUAL_OS_PROGRAM_HASHES_0 = (
0x053f6c9fcfd31d27279ff7d7e422b44623550a732b59fe193354a7316a96daa1
0x01c7be3225dfb33359b3ba9ffbe1542b7da27879b6d89f47b967e512463fd324
);
const ALLOWED_VIRTUAL_OS_PROGRAM_HASHES_1 = (
0x01c7be3225dfb33359b3ba9ffbe1542b7da27879b6d89f47b967e512463fd324
0x00fedbf2b6504b9217026ba2bf9b2a33e8e28ed38e5b0f84eef59075b02174b8
);
const ALLOWED_VIRTUAL_OS_PROGRAM_HASHES_LEN = 2;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
from starkware.cairo.common.cairo_builtins import HashBuiltin
from starkware.cairo.common.ec import StarkCurve
from starkware.cairo.common.hash_state import (
hash_finalize,
hash_init,
hash_update_single,
hash_update_with_hashchain,
)
from starkware.starknet.common.storage import normalize_address
from starkware.cairo.common.math import assert_le_felt
from starkware.cairo.common.math_cmp import is_le_felt
from starkware.starknet.common.storage import ADDR_BOUND, normalize_address
from starkware.starknet.core.os.hash.hash_state_blake import HashState as BlakeHashState
from starkware.starknet.core.os.hash.hash_state_blake import hash_finalize as hash_finalize_blake
from starkware.starknet.core.os.hash.hash_state_blake import hash_init as hash_init_blake
from starkware.starknet.core.os.hash.hash_state_blake import (
hash_update_single as hash_update_single_blake,
)
from starkware.starknet.core.os.hash.hash_state_blake import (
hash_update_with_nested_hash as hash_update_with_nested_hash_blake,
)

const CONTRACT_ADDRESS_PREFIX = 'STARKNET_CONTRACT_ADDRESS';

Expand Down Expand Up @@ -33,3 +45,109 @@ func get_contract_address{hash_ptr: HashBuiltin*, range_check_ptr}(

return (contract_address=contract_address);
}

// Same as `get_contract_address`, but uses Blake2s (the optimized
// `encode_felt252_data_and_calc_blake_hash` encoding) instead of Pedersen.
func get_contract_address_blake{range_check_ptr}(
salt: felt,
class_hash: felt,
constructor_calldata_size: felt,
constructor_calldata: felt*,
deployer_address: felt,
) -> (contract_address: felt) {
let hash_state: BlakeHashState = hash_init_blake();
with hash_state {
hash_update_single_blake(item=CONTRACT_ADDRESS_PREFIX);
hash_update_single_blake(item=deployer_address);
hash_update_single_blake(item=salt);
hash_update_single_blake(item=class_hash);
hash_update_with_nested_hash_blake(
data_ptr=constructor_calldata, data_length=constructor_calldata_size
);
}
let contract_address_before_modulo: felt = hash_finalize_blake(hash_state=hash_state);
let (contract_address) = normalize_address(addr=contract_address_before_modulo);

return (contract_address=contract_address);
}

// A fixed quadratic non-residue in the STARK field: `t = NON_RESIDUE_WITNESS_FACTOR * s * s`
// proves `t` is a non-residue.
const NON_RESIDUE_WITNESS_FACTOR = 3;
// Addresses below this bound (the field prime minus ADDR_BOUND) have a second lift into the
// field: both `address` and `address + ADDR_BOUND` are below the prime.
const ADDRESS_SECOND_LIFT_BOUND = 0x11000000000000000000000000000000000000000000000101;

// Returns `x^3 + ALPHA * x + BETA` — a square iff `x` is a STARK-curve x-coordinate, i.e. iff
// some Pedersen hash output equals `x`.
func curve_cubic(x: felt) -> felt {
return x * x * x + StarkCurve.ALPHA * x + StarkCurve.BETA;
}

// Increments `candidate` (expected ~1 step) until no lift of it is a STARK-curve x-coordinate,
// so no Pedersen derivation can produce the returned address and funded-but-undeployed Blake
// addresses cannot be front-run through the Pedersen deploy paths.
//
// Each skipped candidate is proven reachable with an on-curve square-root witness; the returned
// candidate is proven unreachable with non-residue witnesses (`witness^2 = t / 3`, sound since 3
// is a non-residue). The hint only supplies witnesses — the escape logic itself is fully
// verified.
//
// Note: the Rust derivation also wraps around ADDR_BOUND and skips addresses < 2. Both cases
// require a Blake output within ~2^-240 of the bound edges, which is cryptographically
// unreachable; hitting one would make the transaction unprovable rather than diverge.
func escape_pedersen_image{range_check_ptr}(candidate: felt) -> (contract_address: felt) {
alloc_locals;
local is_reachable;
local reachable_lift;
local witness;
local second_witness;
%{ EscapePedersenImageWitness %}
assert is_reachable * (is_reachable - 1) = 0;
assert reachable_lift * (reachable_lift - 1) = 0;

if (is_reachable != 0) {
// The candidate is in the Pedersen image: verify with an on-curve square-root witness
// for one of its lifts, then move to the next candidate.
if (reachable_lift != 0) {
// A second lift exists only below ADDRESS_SECOND_LIFT_BOUND.
assert_le_felt(candidate, ADDRESS_SECOND_LIFT_BOUND - 1);
let t_lift = curve_cubic(x=candidate + ADDR_BOUND);
assert witness * witness = t_lift;
return escape_pedersen_image(candidate=candidate + 1);
}
let t = curve_cubic(x=candidate);
assert witness * witness = t;
return escape_pedersen_image(candidate=candidate + 1);
}

// The candidate escapes the Pedersen image: every lift of it is off-curve.
let t = curve_cubic(x=candidate);
assert witness * witness = t / NON_RESIDUE_WITNESS_FACTOR;
let candidate_has_second_lift = is_le_felt(candidate, ADDRESS_SECOND_LIFT_BOUND - 1);
if (candidate_has_second_lift != 0) {
let t_lift = curve_cubic(x=candidate + ADDR_BOUND);
assert second_witness * second_witness = t_lift / NON_RESIDUE_WITNESS_FACTOR;
return (contract_address=candidate);
}
return (contract_address=candidate);
}

// The deploy-account v4 / deploy_v2 contract address derivation: the Blake2s hash of the
// deployment arguments, escaped out of the Pedersen image.
func get_contract_address_blake_escaped{range_check_ptr}(
salt: felt,
class_hash: felt,
constructor_calldata_size: felt,
constructor_calldata: felt*,
deployer_address: felt,
) -> (contract_address: felt) {
let (raw_address) = get_contract_address_blake(
salt=salt,
class_hash=class_hash,
constructor_calldata_size=constructor_calldata_size,
constructor_calldata=constructor_calldata,
deployer_address=deployer_address,
);
return escape_pedersen_image(candidate=raw_address);
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func fill_deprecated_tx_info(tx_info: TxInfo*, dst: DeprecatedTxInfo*) {

// Verifies that the given (non-deprecated) `TxInfo` object is consistent with its version, in the
// sense that deprecated transactions (version < 3) have all new fields set to zero and
// non-deprecated transactions (version = 3) have old fields set to zero.
// non-deprecated transactions (version >= 3) have old fields set to zero.
func assert_deprecated_tx_fields_consistency(tx_info: TxInfo*) {
tempvar version = tx_info.version;
if (version * (version - 1) * (version - 2) == 0) {
Expand All @@ -50,8 +50,10 @@ func assert_deprecated_tx_fields_consistency(tx_info: TxInfo*) {
assert tx_info.account_deployment_data_start = nullptr;
assert tx_info.account_deployment_data_end = nullptr;
} else {
// Version 4 exists only for deploy-account transactions (Blake2 address derivation);
// the version itself is validated by the per-type transaction-hash functions.
with_attr error_message("Invalid transaction version: {version}.") {
assert version = 3;
assert (version - 3) * (version - 4) = 0;
}
assert tx_info.max_fee = 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ from starkware.starknet.core.os.constants import (
VALIDATE_MAX_SIERRA_GAS,
VALIDATED,
)
from starkware.starknet.core.os.contract_address.contract_address import get_contract_address
from starkware.starknet.core.os.contract_address.contract_address import (
get_contract_address,
get_contract_address_blake_escaped,
)
from starkware.starknet.core.os.contract_class.contract_class import (
ContractClassComponentHashes,
finalize_class_hash,
Expand Down Expand Up @@ -88,8 +91,9 @@ func compute_max_possible_fee(tx_info: TxInfo*) -> felt {
tempvar resource_bounds: ResourceBounds* = tx_info.resource_bounds_start;
let n_resource_bounds = (tx_info.resource_bounds_end - resource_bounds) / ResourceBounds.SIZE;

// Only V3 transactions with all resource bounds are supported.
assert tx_info.version = 3;
// Only V3-shaped transactions (V3, and deploy-account V4) with all resource bounds are
// supported.
assert (tx_info.version - 3) * (tx_info.version - 4) = 0;
assert n_resource_bounds = 3;

tempvar l1_gas_bounds: ResourceBounds = resource_bounds[L1_GAS_INDEX];
Expand Down Expand Up @@ -168,7 +172,7 @@ func charge_fee{
//
// The account transaction should be passed in the hint variable 'tx'.
func get_account_tx_common_fields(
block_context: BlockContext*, tx_hash_prefix: felt, sender_address: felt
block_context: BlockContext*, tx_hash_prefix: felt, sender_address: felt, version: felt
) -> CommonTxFields* {
alloc_locals;
local resource_bounds: ResourceBounds*;
Expand All @@ -182,7 +186,7 @@ func get_account_tx_common_fields(
%{ LoadTxNonceAccount %}
tempvar common_tx_fields = new CommonTxFields(
tx_hash_prefix=tx_hash_prefix,
version=3,
version=version,
sender_address=sender_address,
chain_id=block_context.os_global_context.starknet_os_config.chain_id,
nonce=nonce,
Expand Down Expand Up @@ -272,6 +276,7 @@ func execute_invoke_function_transaction{
block_context=block_context,
tx_hash_prefix=INVOKE_HASH_PREFIX,
sender_address=sender_address,
version=3,
);
local account_deployment_data_size;
local account_deployment_data: felt*;
Expand Down Expand Up @@ -518,32 +523,69 @@ func consume_l1_to_l2_message{outputs: OsCarriedOutputs*}(
return ();
}

// Derives the deploy-account contract address for the given transaction version: Pedersen for
// v1/v3, Blake2 with the Pedersen-image escape for v4. The deployer address is always zero for
// deploy-account transactions. The Pedersen path advances the pedersen builtin pointer; the
// Blake path leaves it unchanged.
func get_contract_address_for_version{range_check_ptr, builtin_ptrs: BuiltinPointers*}(
tx_version: felt,
salt: felt,
class_hash: felt,
constructor_calldata_size: felt,
constructor_calldata: felt*,
) -> (contract_address: felt) {
alloc_locals;
if (tx_version == 4) {
let (contract_address) = get_contract_address_blake_escaped(
salt=salt,
class_hash=class_hash,
constructor_calldata_size=constructor_calldata_size,
constructor_calldata=constructor_calldata,
deployer_address=0,
);
return (contract_address=contract_address);
}

let hash_ptr = builtin_ptrs.selectable.pedersen;
with hash_ptr {
let (contract_address) = get_contract_address(
salt=salt,
class_hash=class_hash,
constructor_calldata_size=constructor_calldata_size,
constructor_calldata=constructor_calldata,
deployer_address=0,
);
}
update_pedersen_in_builtin_ptrs(pedersen_ptr=hash_ptr);
return (contract_address=contract_address);
}

// Prepares a constructor execution context based on the 'tx' hint variable.
// Leaves 'execution_info.tx_info' and 'deprecated_tx_info' empty - should be filled later on.
// TODO(Yoni, 1/1/2026): consider removing this function (used only once).
func prepare_constructor_execution_context{range_check_ptr, builtin_ptrs: BuiltinPointers*}(
block_info: BlockInfo*
) -> (constructor_execution_context: ExecutionContext*, salt: felt) {
) -> (constructor_execution_context: ExecutionContext*, salt: felt, tx_version: felt) {
alloc_locals;

local contract_address_salt;
local class_hash;
local constructor_calldata_size;
local constructor_calldata: felt*;
local tx_version;
%{ PrepareConstructorExecution %}
assert_nn_le(constructor_calldata_size, SIERRA_ARRAY_LEN_BOUND - 1);

let hash_ptr = builtin_ptrs.selectable.pedersen;
with hash_ptr {
let (contract_address) = get_contract_address(
salt=contract_address_salt,
class_hash=class_hash,
constructor_calldata_size=constructor_calldata_size,
constructor_calldata=constructor_calldata,
deployer_address=0,
);
}
update_pedersen_in_builtin_ptrs(pedersen_ptr=hash_ptr);
// The hinted version is bound by the transaction-hash assertion performed by the caller:
// both the version felt and the derived contract address are part of the committed hash
// preimage, so lying about either fails the proof.
let (contract_address) = get_contract_address_for_version(
tx_version=tx_version,
salt=contract_address_salt,
class_hash=class_hash,
constructor_calldata_size=constructor_calldata_size,
constructor_calldata=constructor_calldata,
);

let (tx_info_ptr: TxInfo*) = alloc();
let (deprecated_tx_info_ptr: DeprecatedTxInfo*) = alloc();
Expand All @@ -563,7 +605,9 @@ func prepare_constructor_execution_context{range_check_ptr, builtin_ptrs: Builti
);

return (
constructor_execution_context=constructor_execution_context, salt=contract_address_salt
constructor_execution_context=constructor_execution_context,
salt=contract_address_salt,
tx_version=tx_version,
);
}

Expand All @@ -578,7 +622,7 @@ func execute_deploy_account_transaction{

// Calculate address and prepare constructor execution context.
let (
local constructor_execution_context: ExecutionContext*, local salt
local constructor_execution_context: ExecutionContext*, local salt, local tx_version
) = prepare_constructor_execution_context(block_info=block_context.block_info_for_validate);
local constructor_execution_info: ExecutionInfo* = constructor_execution_context.execution_info;
local sender_address = constructor_execution_info.contract_address;
Expand All @@ -596,11 +640,14 @@ func execute_deploy_account_transaction{

// Guess transaction fields.
// Compute transaction hash and prepare transaction info.
// The version validation is done in `compute_deploy_account_transaction_hash()`.
// The version validation is done in `compute_deploy_account_transaction_hash()`; the hinted
// version also selected the address derivation above, and both are bound by the
// transaction-hash assertion below.
let common_tx_fields = get_account_tx_common_fields(
block_context=block_context,
tx_hash_prefix=DEPLOY_ACCOUNT_HASH_PREFIX,
sender_address=sender_address,
version=tx_version,
);
let poseidon_ptr = builtin_ptrs.selectable.poseidon;
with poseidon_ptr {
Expand Down Expand Up @@ -717,6 +764,7 @@ func execute_declare_transaction{
block_context=block_context,
tx_hash_prefix=DECLARE_HASH_PREFIX,
sender_address=sender_address,
version=3,
);

let poseidon_ptr = builtin_ptrs.selectable.poseidon;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,11 @@ func compute_deploy_account_transaction_hash{range_check_ptr, poseidon_ptr: Pose
) -> felt {
alloc_locals;

// v3 and v4 share the same preimage layout; the chained version felt (and the derived
// contract address) distinguish the hashes. v4 derives its address with Blake2 plus the
// Pedersen-image escape.
with_attr error_message("Invalid transaction version: {version}.") {
assert common_fields.version = 3;
assert (common_fields.version - 3) * (common_fields.version - 4) = 0;
}

let hash_state: PoseidonHashState = poseidon_hash_init();
Expand Down
4 changes: 2 additions & 2 deletions crates/apollo_starknet_os_program/src/program_hash.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"os": "0x6826aaa1594a6718a232f3951f07c42a23628a98028dcb8dffb9169ab940a33",
"virtual_os": "0x1c7be3225dfb33359b3ba9ffbe1542b7da27879b6d89f47b967e512463fd324",
"os": "0x75ad601aae1fb3c8b239e68f4f4f33168d9b268dca5551987a5b7e6b3b7c067",
"virtual_os": "0xfedbf2b6504b9217026ba2bf9b2a33e8e28ed38e5b0f84eef59075b02174b8",
"aggregator": "0x3e4ce8340259e374200ed856e597a0c0b268d1021119d33573d7c759c5320f9",
"aggregator_with_prefix": "0x2526c12112a2d7f57f7ae74af7be1fe1b2766e4c34b0c88ceda16b70ed2d6c2"
}
4 changes: 2 additions & 2 deletions crates/apollo_starknet_os_program/src/virtual_os_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ fn test_virtual_os_swapped_files() {
#[test]
fn test_program_bytecode_lengths() {
expect![[r#"
16370
16564
"#]]
.assert_debug_eq(&OS_PROGRAM.data_len());
expect![[r#"
11426
11438
"#]]
.assert_debug_eq(&VIRTUAL_OS_PROGRAM.data_len());
}
Loading
Loading