This commit is contained in:
lz_db
2025-11-16 12:31:03 +08:00
commit 0fab423a18
1451 changed files with 743213 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
from typing import Sequence
from ..constants import CONTRACT_ADDRESS_PREFIX, L2_ADDRESS_UPPER_BOUND
from .utils import (
HEX_PREFIX,
_starknet_keccak,
compute_hash_on_elements,
encode_uint,
get_bytes_length,
)
def compute_address(
*,
class_hash: int,
constructor_calldata: Sequence[int],
salt: int,
deployer_address: int = 0,
) -> int:
"""
Computes the contract address in the Starknet network - a unique identifier of the contract.
:param class_hash: class hash of the contract
:param constructor_calldata: calldata for the contract constructor
:param salt: salt used to calculate contract address
:param deployer_address: address of the deployer (if not provided default 0 is used)
:return: Contract's address
"""
constructor_calldata_hash = compute_hash_on_elements(data=constructor_calldata)
raw_address = compute_hash_on_elements(
data=[
CONTRACT_ADDRESS_PREFIX,
deployer_address,
salt,
class_hash,
constructor_calldata_hash,
],
)
return raw_address % L2_ADDRESS_UPPER_BOUND
def get_checksum_address(address: str) -> str:
"""
Outputs formatted checksum address.
Follows implementation of starknet.js. It is not compatible with EIP55 as it treats hex string as encoded number,
instead of encoding it as ASCII string.
:param address: Address to encode
:return: Checksum address
"""
if not address.lower().startswith(HEX_PREFIX):
raise ValueError(f"{address} is not a valid hexadecimal address.")
int_address = int(address, 16)
string_address = address[2:].zfill(64)
address_in_bytes = encode_uint(int_address, get_bytes_length(int_address))
address_hash = _starknet_keccak(address_in_bytes)
result = "".join(
(
char.upper()
if char.isalpha() and (address_hash >> 256 - 4 * i - 1) & 1
else char
)
for i, char in enumerate(string_address)
)
return f"{HEX_PREFIX}{result}"
def is_checksum_address(address: str) -> bool:
"""
Checks if provided string is in a checksum address format.
"""
return get_checksum_address(address) == address

View File

@@ -0,0 +1,111 @@
# File is copied from
# https://github.com/starkware-libs/cairo-lang/blob/v0.13.1/src/starkware/starknet/core/os/contract_class/compiled_class_hash_objects.py
import dataclasses
import itertools
from abc import ABC, abstractmethod
from typing import Any, List, Union
from poseidon_py.poseidon_hash import poseidon_hash_many
class BytecodeSegmentStructure(ABC):
"""
Represents the structure of the bytecode to allow loading it partially into the OS memory.
See the documentation of the OS function `bytecode_hash_node` in `compiled_class.cairo`
for more details.
"""
@abstractmethod
def hash(self) -> int:
"""
Computes the hash of the node.
"""
def bytecode_with_skipped_segments(self):
"""
Returns the bytecode of the node.
Skipped segments are replaced with [-1, -2, -2, -2, ...].
"""
res: List[int] = []
self.add_bytecode_with_skipped_segments(res)
return res
@abstractmethod
def add_bytecode_with_skipped_segments(self, data: List[int]):
"""
Same as bytecode_with_skipped_segments, but appends the result to the given list.
"""
@dataclasses.dataclass
class BytecodeLeaf(BytecodeSegmentStructure):
"""
Represents a leaf in the bytecode segment tree.
"""
data: List[int]
def hash(self) -> int:
return poseidon_hash_many(self.data)
def add_bytecode_with_skipped_segments(self, data: List[int]):
data.extend(self.data)
@dataclasses.dataclass
class BytecodeSegmentedNode(BytecodeSegmentStructure):
"""
Represents an internal node in the bytecode segment tree.
Each child can be loaded into memory or skipped.
"""
segments: List["BytecodeSegment"]
def hash(self) -> int:
return (
poseidon_hash_many(
itertools.chain( # pyright: ignore
*[
(node.segment_length, node.inner_structure.hash())
for node in self.segments
]
)
)
+ 1
)
def add_bytecode_with_skipped_segments(self, data: List[int]):
for segment in self.segments:
if segment.is_used:
segment.inner_structure.add_bytecode_with_skipped_segments(data)
else:
data.append(-1)
data.extend(-2 for _ in range(segment.segment_length - 1))
@dataclasses.dataclass
class BytecodeSegment:
"""
Represents a child of BytecodeSegmentedNode.
"""
# The length of the segment.
segment_length: int
# Should the segment (or part of it) be loaded to memory.
# In other words, is the segment used during the execution.
# Note that if is_used is False, the entire segment is not loaded to memory.
# If is_used is True, it is possible that part of the segment will be skipped (according
# to the "is_used" field of the child segments).
is_used: bool
# The inner structure of the segment.
inner_structure: BytecodeSegmentStructure
def __post_init__(self):
assert (
self.segment_length > 0
), f"Invalid segment length: {self.segment_length}."
# Represents a nested list of integers. E.g., [1, [2, [3], 4], 5, 6].
NestedIntList = Union[int, List[Any]]

View File

@@ -0,0 +1,16 @@
from ..constants import (
DEFAULT_ENTRY_POINT_NAME,
DEFAULT_ENTRY_POINT_SELECTOR,
DEFAULT_L1_ENTRY_POINT_NAME,
)
from ..hash.utils import _starknet_keccak
def get_selector_from_name(func_name: str) -> int:
"""
Returns the selector of a contract's function name.
"""
if func_name in [DEFAULT_ENTRY_POINT_NAME, DEFAULT_L1_ENTRY_POINT_NAME]:
return DEFAULT_ENTRY_POINT_SELECTOR
return _starknet_keccak(data=func_name.encode("ascii"))

View File

@@ -0,0 +1,12 @@
from functools import reduce
from constants import ADDR_BOUND
from hash.utils import _starknet_keccak, pedersen_hash
def get_storage_var_address(var_name: str, *args: int) -> int:
"""
Returns the storage address of a Starknet storage variable given its name and arguments.
"""
res = _starknet_keccak(var_name.encode("ascii"))
return reduce(pedersen_hash, args, res) % ADDR_BOUND

View File

@@ -0,0 +1,78 @@
import functools
from typing import List, Optional, Sequence
from ... import keccak
from ..common import int_from_bytes
from ..constants import EC_ORDER
from ...starkware.crypto.signature import (
ECSignature,
private_to_stark_key,
sign
# verify
)
from ...starkware.crypto.fast_pedersen_hash import (
pedersen_hash
)
MASK_250 = 2**250 - 1
HEX_PREFIX = "0x"
def _starknet_keccak(data: bytes) -> int:
"""
A variant of eth-keccak that computes a value that fits in a Starknet field element.
"""
return int_from_bytes(keccak.SHA3(data)) & MASK_250
# def pedersen_hash(left: int, right: int) -> int:
# """
# One of two hash functions (along with _starknet_keccak) used throughout Starknet.
# """
# return cpp_hash(left, right)
def compute_hash_on_elements(data: Sequence) -> int:
"""
Computes a hash chain over the data, in the following order:
h(h(h(h(0, data[0]), data[1]), ...), data[n-1]), n).
The hash is initialized with 0 and ends with the data length appended.
The length is appended in order to avoid collisions of the following kind:
H([x,y,z]) = h(h(x,y),z) = H([w, z]) where w = h(x,y).
"""
return functools.reduce(pedersen_hash, [*data, len(data)], 0)
def message_signature(
msg_hash: int, priv_key: int, seed: Optional[int] = 32
) -> ECSignature:
"""
Signs the message with private key.
"""
return sign(msg_hash, priv_key, seed)
# def verify_message_signature(
# msg_hash: int, signature: List[int], public_key: int
# ) -> bool:
# """
# Verifies ECDSA signature of a given message hash with a given public key.
# Returns true if public_key signs the message.
# """
# sig_r, sig_s = signature
# # sig_w = pow(sig_s, -1, EC_ORDER)
# return verify(msg_hash=msg_hash, r=sig_r, s=sig_s, public_key=public_key)
def encode_uint(value: int, bytes_length: int = 32) -> bytes:
return value.to_bytes(bytes_length, byteorder="big")
def encode_uint_list(data: List[int]) -> bytes:
return b"".join(encode_uint(x) for x in data)
def get_bytes_length(value: int) -> int:
return (value.bit_length() + 7) // 8