Skip to main content
Source: molpha/evm-verifier · Solidity 0.8.31 · Cancun · Apache-2.0 The EVM 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 EVM deployment mirrors the node public-key set as versioned SSTORE2 snapshots and exposes verify as a view function. The signed message intentionally omits chainId, so one gateway payload verifies on every supported EVM chain.

Quick start

  1. Pin a deployed verifier address from Deployments.
  2. Map a gateway DataUpdateResult with buildEvmVerifierArgs (or build the structs yourself).
  3. Call verify and require the boolean — a bad signature returns false, it does not revert.
  4. Enforce your own freshness / replay / feedId checks before using value.
Full consumer walkthrough: Verify on EVM.

What the contract does (and does not)

Shared crypto (selection, message hash, Schnorr identity): Cryptography.

Install / depend on the contracts

As a Foundry dependency (example remapping):
Key paths in the repo:

Calldata: structs

Defined in IVerifier.

DataUpdate

SchnorrSignature

Gateway → on-chain mapping


verify

Pipeline (in order)

  1. registryVersion < registryPointers.length (else InvalidRegistryVersion).
  2. Snapshot has at least one node (else NoNodes).
  3. Structural guards: non-zero signaturesRequired, signersBitmap, signature, commitment; signature < Q (else custom errors).
  4. popcount(signersBitmap) >= signaturesRequired (else NotEnoughSignatures).
  5. Derive selection set; require signersBitmap ⊆ selectionBitmap (else SignerNotSelected).
  6. Sum signers’ public keys in ascending index order; verify Schnorr over the canonical message.
  7. Return true / false for crypto validity — do not treat false as a revert.
Always require(verifier.verify(...)). A cryptographically invalid signature returns false without reverting. Structural / policy failures revert with IVerifier errors.

Signed message

No chainId. Same digest verifies on every EVM deployment.

Selection seed and group size

NodeGroupBitmapLib expands the seed with keccak256(seed ‖ keccak256("MOLPHA_SELECTION_DERIVE") ‖ counter), samples without replacement (bias-rejecting uint32 limbs), and uses the complement path when groupSize > nodeCount / 2.

Bitmap convention

Bit i - 1 ↔ 1-based registry index i. Index 0 in each snapshot is the running aggregate key, not a signer. Node indices can change after removeNode (swap-and-pop); resolve live indices with getNodeIndex(address).

Consumer checklist

Do these in your contract (or off-chain client) around every verify call:
  1. Require the boolean return value.
  2. Authorize feedId (and optionally signaturesRequired) for your product.
  3. Freshness — compare canonicalTimestamp to block.timestamp (or your clock).
  4. Replay — track last timestamp / digest if the same valid payload must not settle twice.
  5. Decode value with your feed’s encoding; treat it as opaque bytes32 until then.
  6. Pass the exact registryVersion used when the nodes signed (historical versions stay valid).

Registry & admin API

Only protocolAdmin can mutate the registry or redundancy buffer.

Proof-of-possession (registration only)

PoP is verified at addNode and is not part of verify. Nodes register per deployment (address is in the digest).

Reads

Registry version 0 is the empty constructor snapshot. See Registry Versions.

Errors

verify / admin paths revert with these IVerifier errors when inputs are malformed or policy fails. Invalid crypto after passing guards returns false instead.

Gas (reference)

Measured with FOUNDRY_PROFILE=gas forge test -vv --match-contract VerifierGasTest (Solidity 0.8.31, Cancun, IR, 1M optimizer runs). Fixed 128-node registry, redundancy buffer 2. Total = execution + calldata + 21 000 base. Cost scales mainly with the number of keys summed for the coalition, not total registry size (selection + key reads dominate).

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.
  • Report vulnerabilities per SECURITY.md — not public GitHub issues.
  • Protocol trust assumptions: Security Model.