Skip to content
Open
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
67 changes: 56 additions & 11 deletions crates/starknet_api/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,38 +326,83 @@ impl TryFrom<StarkHash> for ContractAddress {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AddressDerivationHash {
Pedersen,
/// Blake2, escaped out of the Pedersen image; see [escape_pedersen_image].
Blake2,
}

/// The STARK curve is `y^2 = x^3 + STARK_CURVE_ALPHA * x + STARK_CURVE_BETA`.
const STARK_CURVE_ALPHA: Felt = Felt::ONE;
const STARK_CURVE_BETA: Felt =
Felt::from_hex_unchecked("0x6f21413efbe40de150e596d72f7a8c5609ad26c15c915c1f4cdfcb99cee9e89");
/// Addresses below this bound (the field prime minus L2_ADDRESS_UPPER_BOUND) have a second lift
/// into the field: both `address` and `address + L2_ADDRESS_UPPER_BOUND` are below the prime.
const ADDRESS_SECOND_LIFT_BOUND: Felt =
Felt::from_hex_unchecked("0x11000000000000000000000000000000000000000000000101");

/// Returns whether `value` is the x-coordinate of a STARK curve point, i.e. whether
/// `value^3 + STARK_CURVE_ALPHA * value + STARK_CURVE_BETA` is a square in the field.
pub fn is_stark_curve_x_coordinate(value: &Felt) -> bool {
let value = *value;
(value * value * value + STARK_CURVE_ALPHA * value + STARK_CURVE_BETA).sqrt().is_some()
}

/// Returns whether some Pedersen hash output reduces (mod L2_ADDRESS_UPPER_BOUND) to `address`.
/// A Pedersen output is the x-coordinate of a STARK curve point, so `address` is reachable iff
/// one of its lifts into the field is a curve x-coordinate.
pub fn is_pedersen_reachable_address(address: &Felt) -> bool {
is_stark_curve_x_coordinate(address)
|| (*address < ADDRESS_SECOND_LIFT_BOUND
&& is_stark_curve_x_coordinate(&(address + Felt::from(&*L2_ADDRESS_UPPER_BOUND))))
}

/// Increments `raw_address` (wrapping mod L2_ADDRESS_UPPER_BOUND, skipping the reserved 0x0/0x1)
/// until no Pedersen derivation can reach it, so a funded-but-undeployed Blake2 address cannot be
/// front-run through the Pedersen deploy paths. Expected ~1 increment; each step is a square-root
/// check, never a re-hash.
fn escape_pedersen_image(raw_address: Felt) -> Felt {
let address_upper_bound = Felt::from(&*L2_ADDRESS_UPPER_BOUND);
let mut address = raw_address;
while address < Felt::TWO || is_pedersen_reachable_address(&address) {
address += Felt::ONE;
if address == address_upper_bound {
address = Felt::ZERO;
}
}
address
}

pub fn calculate_contract_address(
salt: ContractAddressSalt,
class_hash: ClassHash,
constructor_calldata: &Calldata,
deployer_address: ContractAddress,
address_derivation_hash: AddressDerivationHash,
) -> Result<ContractAddress, StarknetApiError> {
match address_derivation_hash {
let address = match address_derivation_hash {
AddressDerivationHash::Pedersen => calculate_contract_address_inner::<Pedersen>(
salt,
class_hash,
constructor_calldata,
deployer_address,
),
AddressDerivationHash::Blake2 => calculate_contract_address_inner::<Blake2Felt252>(
salt,
class_hash,
constructor_calldata,
deployer_address,
),
}
)?,
AddressDerivationHash::Blake2 => {
escape_pedersen_image(calculate_contract_address_inner::<Blake2Felt252>(
salt,
class_hash,
constructor_calldata,
deployer_address,
)?)
}
};
ContractAddress::try_from(address)
}

fn calculate_contract_address_inner<H: CoreStarkHash>(
salt: ContractAddressSalt,
class_hash: ClassHash,
constructor_calldata: &Calldata,
deployer_address: ContractAddress,
) -> Result<ContractAddress, StarknetApiError> {
) -> Result<Felt, StarknetApiError> {
let constructor_calldata_hash = H::hash_array(&constructor_calldata.0);
let contract_address_prefix = format!("0x{}", hex::encode(CONTRACT_ADDRESS_PREFIX));
let address = H::hash_array(&[
Expand All @@ -371,7 +416,7 @@ fn calculate_contract_address_inner<H: CoreStarkHash>(
]);
let (_, address) = address.div_rem(&L2_ADDRESS_UPPER_BOUND);

ContractAddress::try_from(address)
Ok(address)
}

/// The hash of a ContractClass.
Expand Down
41 changes: 41 additions & 0 deletions crates/starknet_api/src/core_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::core::{
ascii_as_felt,
calculate_contract_address,
felt_to_u128,
is_pedersen_reachable_address,
AddressDerivationHash,
ChainId,
ContractAddress,
Expand Down Expand Up @@ -85,6 +86,46 @@ fn test_calculate_contract_address() {
assert_eq!(actual_address, expected_address);
}

#[rstest]
#[case::block_hash_table_address(felt!("0x1"), true)]
#[case::two(felt!("0x2"), true)]
#[case::five(felt!("0x5"), false)]
#[case::large(felt!("0x1234567890abcdef"), true)]
fn test_is_pedersen_reachable_address(#[case] address: Felt, #[case] expected_reachable: bool) {
assert_eq!(is_pedersen_reachable_address(&address), expected_reachable);
}

// Frozen vectors for the Blake2 derivation with the escape rule, cross-checked against an
// independent python implementation. Deployer = 0, class_hash = 0x4242,
// constructor_calldata = [42, 2^63, 1337]; the cases span 0 to 7 escape increments.
#[rstest]
#[case::zero_increments(777, "0x781e95f4b806dfe5b550756620c77a108d974a5b5d1198b1d45901ac1f89e9f")]
#[case::one_increment(771, "0x566c3e328f3fd5a311267250cadc3c1c4de799db54180fcf862fe90b622571d")]
#[case::two_increments(776, "0x1cd7f5c31ef1b147b816048b025a6cc345e7e023aa8ed97222a883e31dc8435")]
#[case::three_increments(775, "0x4f7ba32369d7f68c42a7619242a52a5be9e803459f5afae1772d9377b161c4c")]
#[case::seven_increments(774, "0x47d0c1ff356a1d540cd9f2efa122b60168b1007ef0603af0857bc6100e4b8e8")]
fn test_blake_contract_address_escapes_pedersen_image(
#[case] salt: u16,
#[case] expected_address: &str,
) {
let constructor_calldata =
Calldata(vec![Felt::from(42_u8), Felt::from(1_u64 << 63), Felt::from(1337_u16)].into());

let actual_address = calculate_contract_address(
ContractAddressSalt(Felt::from(salt)),
class_hash!("0x4242"),
&constructor_calldata,
ContractAddress::default(),
AddressDerivationHash::Blake2,
)
.unwrap();

let expected_address =
ContractAddress::try_from(Felt::from_hex_unchecked(expected_address)).unwrap();
assert_eq!(actual_address, expected_address);
assert!(!is_pedersen_reachable_address(actual_address.0.key()));
}

#[test]
fn eth_address_serde() {
let eth_address = EthAddress::try_from(felt!("0x001")).unwrap();
Expand Down
Loading