molpha/molpha-starknet · Cairo / Scarb 2.18+ · snforge 0.61+
The Starknet Verifier is a stateless on-chain check: given a DataUpdate and an aggregate Schnorr signature, it answers one question — did enough selected Molpha nodes sign this exact payload under the stated registry version? It does not store feeds, rounds, or consumed updates. Your contract owns freshness, replay, feed authorization, and value decoding.
Solana is the canonical chain. Feeds, staking, and the authoritative node registry live on Solana. Each Starknet deployment mirrors the node public-key set as versioned per-
(version, index) coordinate maps and exposes verify as a view entrypoint. The signed message intentionally omits chainId, so one gateway payload verifies on Starknet and every supported EVM chain unchanged.Quick start
- Pin a deployed verifier address from Deployments.
- Map a gateway
DataUpdateResultwithbuildStarknetVerifierArgs(or build the structs yourself). - Call
verifyand require the boolean — a bad signature returnsfalse, it does not revert. - Enforce your own freshness / replay /
feed_idchecks before usingvalue.
What the contract does (and does not)
Shared crypto (selection, message hash, Schnorr identity): Cryptography. On Starknet the final curve check computes
s·G + (Q−e)·P directly and compares its Ethereum-style address to the commitment — mathematically identical to the EVM ecrecover path.
Install / depend on the contracts
Calldata: structs
Defined ininterface.cairo.
DataUpdate
SchnorrSignature
Gateway → on-chain mapping
verify
Pipeline (in order)
data_update.registry_version <=latest stored version (else'Invalid registry version').- Snapshot has at least one node (else
'No nodes'). - Structural guards: non-zero
signatures_required,signers_bitmap,signature,commitment. popcount(signers_bitmap) >= signatures_required(else'Not enough signatures').- Derive selection set; require
signers_bitmap ⊆ selection_bitmap(else'Signer not selected'). - Sum signers’ public keys in ascending index order; verify Schnorr over the canonical message.
- Return
true/falsefor crypto validity — do not treatfalseas a revert.
Signed message
abi.encodePacked layout. No chainId. Same digest verifies on every EVM and Starknet deployment.
Selection seed and group size
node_group_bitmap expands the seed with keccak256(seed ‖ keccak256("MOLPHA_SELECTION_DERIVE") ‖ counter), samples without replacement (bias-rejecting u32 limbs), and uses the complement path when group_size > node_count / 2.
Bitmap convention
Biti - 1 ↔ 1-based registry index i. Index 0 in each snapshot is the running aggregate key, not a signer. Node indices can change after remove_node (swap-and-pop); resolve live indices with get_node_index(node).
Consumer checklist
Do these in your contract (or off-chain client) around everyverify call:
- Require the boolean return value.
- Authorize
feed_id(and optionallysignatures_required) for your product. - Freshness — compare
canonical_timestampto your clock / block time. - Replay — track last timestamp / digest if the same valid payload must not settle twice.
- Decode
valuewith your feed’s encoding; treat it as opaqueu256until then. - Pass the exact
registry_versionused when the nodes signed (historical versions stay valid).
Registry & admin API
Onlyprotocol_admin can mutate the registry or redundancy buffer.
Proof-of-possession (registration only)
add_node and is not part of verify. The EVM verifier hashes a 20-byte address(this) instead; nodes register per deployment with a chain-specific PoP. Cross-chain verify payloads are unaffected.
Reads
Registry version
0 is the empty constructor snapshot. See Registry Versions.
Storage model
Unlike EVM SSTORE2 blobs, Cairo stores each key askey_x / key_y maps keyed by packed (version, index). Every add_node / remove_node copies the prior node set forward into a new immutable version — same permanence guarantee as EVM.
Errors
verify / admin paths panic with these short strings when inputs are malformed or policy fails. Invalid crypto after passing guards returns false instead.
Gas (reference)
Measured withscarb run bench / snforge test benchmarks --gas-report (snforge 0.61). Baseline fixture: 10 nodes, redundancy_buffer=2, signatures_required=3, 5 signers.
Signer-scaling for
verify (18-node registry, redundancy buffer 2):
Cost is not strictly linear in signer count:
node_group_bitmap::derive switches algorithms when group_size > n/2 or = n. Aggregation + Schnorr still dominate.
Deploy & operate nodes
Deploy to Starknet Sepolia withscripts/deploy_testnet.sh (requires a configured sncast account and PROTOCOL_ADMIN in .env). Default redundancy buffer is 2 (REDUNDANCY_BUFFER).
scripts/add_node.sh — signs a secp256k1 PoP and invokes add_node. Prefer passing public PoP material in production; never commit private keys.
Security surface
- Treat every deployment as a verification component, not a full oracle product. Review registry ops, consumer freshness/replay, and feed authorization before production use.
- Cross-chain parity: unmodified EVM-produced signatures are accepted in
parity.cairo/e2e.cairotests. - Protocol trust assumptions: Security Model.