| name | crypto-recon-web3-starknet |
| description | Specialist for StarkNet and ZK-based chain cryptography — Pedersen hash, Poseidon hash, Stark curve, Cairo felt252 types, SNARK-based signing, and all StarkNet SDKs. Fundamentally different from all other chains. |
Crypto Recon — StarkNet & ZK Chains Specialist
Why This Needs Its Own Skill
StarkNet uses cryptographic primitives found NOWHERE else in web development:
| Primitive | StarkNet | EVM | Solana |
|---|
| Hash | Pedersen + Poseidon | keccak256 | SHA256 |
| Curve | Stark curve (custom) | secp256k1 | Ed25519 curve |
| Field | felt252 (252-bit) | uint256 | 256-bit |
| Signing | ECDSA on Stark curve | ECDSA on secp256k1 | EdDSA |
| Accounts | Smart contract accounts | EOA | Keypair |
If you see pedersen, poseidon, felt252, stark_curve → this skill applies.
Libraries & Detection
import { Account, Contract, Provider, RpcProvider,
CallData, cairo, num, hash, ec } from 'starknet'
import { shortString, validateAndParseAddress } from 'starknet'
import { WeierstrassSignatureType, stark } from 'starknet'
import { connect, disconnect } from 'starknetkit'
import { useAccount, useConnect } from '@starknet-react/core'
import { InjectedConnector } from 'starknetkit/injected'
import { ArgentMobileConnector } from 'starknetkit/argentMobile'
import { WebWalletConnector } from 'starknetkit/webwallet'
window.starknet
window.starknet_braavos
window.starknet.enable()
window.starknet.account.signMessage(typedData)
1. Stark Curve & felt252
Everything in StarkNet is a field element (felt252 = 252-bit prime field).
import { num, cairo } from 'starknet'
const felt = num.toHex(12345)
const n = num.toBigInt("0x3039")
import { shortString } from 'starknet'
const encoded = shortString.encodeShortString("hello")
const decoded = shortString.decodeShortString(felt)
cairo.uint256(1000n)
cairo.felt(42n)
Key insight: All contract arguments in StarkNet are felt252. Extract how data is converted to felt252 before calling contracts.
2. Pedersen Hash
StarkNet's primary hash function — used in storage keys, commitments, legacy signing.
import { hash } from 'starknet'
hash.pedersen(a, b)
hash.pedersen([a, b, c])
hash.getStorageVarAddress("balance")
hash.getStorageVarAddress("allowance", [addr1, addr2])
import { pedersen } from 'micro-starknet'
pedersen(BigInt(a), BigInt(b))
3. Poseidon Hash (modern StarkNet)
Newer hash replacing Pedersen — used in SNARK-friendly operations, Starknet OS v2+.
import { hash, num } from 'starknet'
hash.poseidonHash(a, b)
hash.poseidonHashMany([a, b, c, d])
4. TypedData Signing (StarkNet's version of EIP-712)
StarkNet has its own typed data standard (SNIP-12), different from EIP-712.
const typedData = {
types: {
StarkNetDomain: [
{ name: "name", type: "felt252" },
{ name: "version", type: "felt252" },
{ name: "chainId", type: "felt252" },
],
Order: [
{ name: "maker", type: "ContractAddress" },
{ name: "amount", type: "u256" },
{ name: "deadline","type": "u64" },
]
},
primaryType: "Order",
domain: {
name: "MyDApp",
version: "1",
chainId: "SN_MAIN"
},
message: { maker: "0x...", amount: { low: 1000n, high: 0n }, deadline: 1748123456n }
}
const signature = await account.signMessage(typedData)
const sig = await window.starknet.account.signMessage(typedData)
For each TypedData found — extract COMPLETELY:
types with all field names and felt252/u256/ContractAddress types
domain (name, version, chainId)
primaryType
- What
message values are built from (trace construction)
How StarkNet TypedData hashing works:
domainHash = pedersen([
h("StarkNetDomain"),
h("name"), h(domain.name),
h("version"), h(domain.version),
h("chainId"), h(domain.chainId)
])
typeHash = pedersen(["Order", h("maker"), h("amount"), h("deadline")])
messageHash = pedersen([typeHash, maker, amount.low, amount.high, deadline])
digest = pedersen([
h("StarkNet Message"),
domainHash,
account_address,
messageHash
])
signature = ecdsa_stark_curve.sign(digest, private_key)
5. Contract Calls & CallData
const contract = new Contract(abi, contractAddress, account)
const abi = [
{
"type": "function",
"name": "transfer",
"inputs": [
{ "name": "recipient", "type": "core::starknet::contract_address::ContractAddress" },
{ "name": "amount", "type": "core::integer::u256" }
],
"outputs": [{ "type": "core::bool" }],
"state_mutability": "external"
}
]
import { CallData } from 'starknet'
const calldata = CallData.compile({ recipient: address, amount: cairo.uint256(1000n) })
await account.execute([
{ contractAddress: tokenAddr, entrypoint: "approve", calldata: [...] },
{ contractAddress: dexAddr, entrypoint: "swap", calldata: [...] },
])
For each contract ABI found in JS — extract fully. Cairo types are different from Solidity:
felt252, u8, u16, u32, u64, u128, u256
bool
ContractAddress (felt252 internally)
ClassHash (felt252 internally)
Array<T>
Span<T>
core::byte_array::ByteArray (long strings)
6. Stark Curve Key Management
import { ec, stark } from 'starknet'
const privateKey = stark.randomAddress()
const publicKey = ec.starkCurve.getPublicKey(privateKey)
const address = hash.computeAddress(publicKey, classHash, salt)
const sig = ec.starkCurve.sign(messageHash, privateKey)
const isValid = ec.starkCurve.verify(sig, messageHash, publicKey)
const address = hash.calculateContractAddressFromHash(salt, classHash, constructorCalldata, deployerAddress)
7. Storage Layout
StarkNet storage uses Pedersen-based slot derivation:
hash.getStorageVarAddress("ERC20_balance", [user_address])
hash.getStorageVarAddress("allowances", [owner, spender])
Analysis Steps
Step 1: Confirm StarkNet
Look for: import from 'starknet', window.starknet, pedersen, felt252, SN_MAIN
Step 2: Find TypedData signing
Search for signMessage, typedData, primaryType, StarkNetDomain
→ Extract the complete TypedData object
Step 3: Find contract ABIs
Search for new Contract(abi,, or ABI arrays with Cairo types (ContractAddress, u256, felt252)
→ Extract ABI verbatim
Step 4: Map CallData construction
How are contract arguments encoded?
→ CallData.compile(...) usage
→ cairo.uint256(n) for big numbers
→ shortString.encodeShortString(str) for strings
Step 5: Identify hash operations
hash.pedersen(a, b) → legacy, pre-v2
hash.poseidonHash(a, b) → modern, v2+
- Mixed usage = hybrid app (older + newer contracts)
Output Format
STARKNET FINDINGS
=================
[SN1] TypedData Signing — HIGH
File: chunks/nft-market.js:5501
Standard: SNIP-12 (StarkNet TypedData)
Wallet: ArgentX (window.starknet.account.signMessage)
ChainId: SN_MAIN
Domain:
name: "MyNFTMarket"
version: "1"
chainId: "SN_MAIN"
Types:
Order: [
{ name: "maker", type: "ContractAddress" },
{ name: "tokenId", type: "u256" },
{ name: "price", type: "u256" },
{ name: "deadline", type: "u64" }
]
Hash flow:
typeHash = pedersen(["Order", h(maker), h(tokenId), h(price), h(deadline)])
msgHash = pedersen([typeHash, maker, tokenId.low, tokenId.high, ...])
digest = pedersen([h("StarkNet Message"), domainHash, account, msgHash])
sig = ecdsa_stark(digest, privateKey) → { r, s }
[SN2] ERC20 Contract ABI — HIGH
File: vendor.js:12401
Address: 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7
Token: ETH on StarkNet
Key methods: transfer(ContractAddress, u256), approve, balanceOf
CallData example:
CallData.compile({ recipient: addr, amount: cairo.uint256(1000000000000000000n) })
→ [recipient_felt, amount_low, amount_high]
Python Reconstruction
from starknet_py.net.account.account import Account
from starknet_py.net.full_node_client import FullNodeClient
from starknet_py.net.models.chains import StarknetChainId
from starknet_py.net.signer.stark_curve_signer import KeyPair
from starknet_py.hash.hash_method import HashMethod
from starknet_py.utils.typed_data import TypedData
client = FullNodeClient(node_url="https://starknet-mainnet.public.blastapi.io")
key_pair = KeyPair.from_private_key(0x...)
account = Account(client=client, address=account_address,
key_pair=key_pair, chain=StarknetChainId.MAINNET)
typed_data = TypedData.from_dict({
"types": { "StarkNetDomain": [...], "Order": [...] },
"primaryType": "Order",
"domain": { "name": "MyDApp", "version": "1", "chainId": "SN_MAIN" },
"message": { ... }
})
signature = account.sign_message(typed_data)
from crypto_cpp_py.cpp_bindings import pedersen_hash
result = pedersen_hash(int(a, 16), int(b, 16))
from poseidon_py.poseidon_hash import poseidon_hash, poseidon_hash_many
result = poseidon_hash(a, b)
result_many = poseidon_hash_many([a, b, c, d])