Skip to main content
The EVM Verifier is a Solidity contract that performs on-chain verification of Molpha payloads on any supported EVM chain. Like every Molpha verifier, it holds a mirror of the registered node set and checks a single aggregated Schnorr signature over a self-contained data payload — so the value the node network signs once is trustlessly verifiable on-chain. It’s the only contract a consumer needs to talk to in order to trust off-chain oracle data.
Solana is the canonical chain. Jobs, staking, deposits, rewards, and the authoritative node registry live on Solana. Each EVM deployment carries only a mirror of the node public-key set (versioned snapshots) plus this lightweight verifier. It stores no job, round, or feed state — everything it needs arrives in calldata and is checked cryptographically.

One signature, every chain

The Molpha signed message deliberately omits chainId. The node network signs (jobId, value, timestamp, …) once, and every EVM verifier checks that exact same message — no re-signing, no chain-specific payload. This is what lets a single signed result be consumed across every supported chain.

What the Verifier does

Node registry

Mirrors up to 256 oracle nodes, storing their secp256k1 public keys in gas-efficient SSTORE2 blobs. Every add/remove creates a new immutable registry version.

Deterministic selection

Derives the per-round signer group from a seed bound to the data payload, so callers cannot grind which nodes are eligible to sign.

Plain-sum aggregation

Reconstructs the coalition public key by summing the keys of the actual signers, then verifies one Schnorr signature against it.

Stateless verification

verify is a view call. The signed payload is self-contained and permissionlessly verifiable on any supported EVM chain.
The full verification pipeline, signer-selection algorithm, and domain separators are shared by every Molpha chain and documented once in Cryptography. On EVM, the final curve check reuses the ecrecover precompile, which recovers precisely the s·G − e·P point the Schnorr check needs.

Core concepts

Registry versions

The node set is append-only and versioned. Each version is an immutable SSTORE2 snapshot of the key set; addNode / removeNode writes a brand-new snapshot, so the registry version increases by one on every mutation. Historical snapshots are never mutated, so a payload signed against an older registry version stays permanently verifiable.
  • Index 0 of each snapshot is the plain-sum aggregate key of all nodes in that version.
  • Indices 1 .. nodeCount hold the individual node keys.
  • A DataUpdate is always verified against the exact registryVersion it was signed for, even when newer versions exist.
The protocol caps a registry at 256 nodes — exactly what a single 256-bit signersBitmap can address.

Redundancy buffer and group size

For each round the contract selects a signer group slightly larger than the strict threshold, so a few nodes can drop out without failing the round:
groupSize = min(signaturesRequired + redundancyBuffer, nodeCount)
redundancyBuffer is a protocol-admin parameter. The actual signers must be a subset of this selected group, and there must be at least signaturesRequired of them.

Signers bitmap

Participation is encoded as a uint256 bitmap: bit i-1 is set iff the 1-based node index i signed. With at most 256 nodes, a single uint256 is always sufficient.

Data structures

These are defined in IVerifier.

DataUpdate — the signed result payload

struct DataUpdate {
    bytes32 jobId;              // chain-agnostic identifier of the data task
    uint32  registryVersion;   // node-set snapshot the signature is bound to
    uint32  signaturesRequired;// minimum signers (threshold) for this update
    bytes32 value;             // the signed result value (encoding is job-defined)
    uint64  canonicalTimestamp;// round timestamp (part of the signed message)
}

SchnorrSignature — the aggregate signature

struct SchnorrSignature {
    bytes32 signature;     // aggregate Schnorr scalar s
    address commitment;    // eth-address of the nonce point R
    uint256 signersBitmap; // bit (i-1) set iff 1-based signer index i participated
}
A gateway response maps directly onto these structs, so the SDK can build calldata from the same fields it returns.

Verifying a data update

function verify(
    DataUpdate calldata dataUpdate,
    SchnorrSignature calldata schnorrData
) external view returns (bool isVerified);
verify is a view function. It returns true for a valid signature and false for a cryptographically invalid one. Structural / policy violations revert (bad registry version, empty registry, zeroed inputs, too few signers, or a signer outside the selection set). Integrators should treat a revert and a false return as equivalent “not verified” outcomes. The checks run in order:
  1. The registryVersion exists and has at least one node.
  2. Structural guards: non-zero signaturesRequired, signersBitmap, signature, commitment.
  3. popcount(signersBitmap) ≥ signaturesRequired.
  4. Signers ⊆ the deterministically derived selection set.
  5. The aggregate Schnorr signature over the canonical message.
verify returns a boolean for a bad signature rather than reverting. Consumer contracts must check the return value (e.g. require(verifier.verify(...))), or they will silently accept unverified data.

Consumer integration

See Verify on EVM for the full consumer-contract walkthrough and integration checklist. Because the Verifier is stateless with respect to feeds, your contract owns any freshness, replay, or value-bounds policyverify only attests that the node network signed (jobId, value, timestamp, …) against a given registry version. A successful verify proves at least signaturesRequired keys from the mirrored registryVersion signed the exact DataUpdate, and that every signer is inside the deterministic selection set. It does not prove freshness, prevent replay, or independently prove that the payload corresponds to genuine Solana job state. Those guarantees — and the protocol’s admin powers — are covered in the Security Model.