Secure Attestations
Secure attestations are stake-backed signatures over a message hash that contracts can verify against the active validator set header.
Concretely:
- The validator set for epoch
eis fixed by a committed header (operators, keys, weights, threshold). - Operators in that set sign a message hash.
- An aggregator combines these signatures into an aggregate proof.
- A Settlement contract on the destination chain verifies the proof against the header for epoch
e.
Message Contents (typical)
The protocol doesn’t hardcode a single message struct, but in practice you want something like:
networkId– Relay network identifiersubnetworkId– optional, for partitioned logicepochorvalsetId– which validator set header must be usedpayloadType– enum or tag (bridge, checkpoint, oracle, etc.)payloadHash– hash of the actual data your app cares aboutdstChainId– EVM chain where this will be verifieddstAppordstContract– target contract / app identifierexpiry– timestamp or block after which the attestation is invalidnonce– monotonically increasing per channel / app
You encode this structure, compute messageHash = keccak256(encodedMessage), and only messageHash is signed by operators.
Aggregation and Verification
The flow is:
-
Build the message
Middleware or the app constructs the message struct, fills
epoch,dstChainId,dstApp,nonce,expiry, etc., and computesmessageHash. -
Operators sign
Each operator in the current validator set for
epochsignsmessageHashwith its registered key (BLS or ECDSA, depending on your Relay config). -
Aggregate off-chain
An aggregator collects signatures and:
- for the Simple path:
- aggregates BLS signatures into
sigmaAgg - builds a participant bitmap / list
- aggregates BLS signatures into
- for the ZK path:
- uses the individual signatures and weights to generate a zk proof that “signers’ voting power ≥ threshold for header H and messageHash M”
- for the Simple path:
-
Submit to Settlement
On the destination chain, the aggregator (or any relayer) calls something like:
settlement.verifyAndConsume(message, epoch, sigmaAgg, participants)for Simple, orsettlement.verifyAndConsume(message, epoch, zkProof)for ZK.
-
On-chain check
Settlement:
- loads the validator set header for
epoch - reconstructs keys and weights from that header
- runs either:
- SimpleVerifier: check BLS aggregate against the participants and ensure their summed voting power ≥ threshold
- ZKVerifier: check the proof that encodes both signature validity and power ≥ threshold
If verification passes, your app logic (bridge, rollup, oracle, etc.) is allowed to execute.
- loads the validator set header for
Safety Properties
Secure attestations are tied down in a few specific ways:
-
Bound to a validator set The message includes
epoch(or a valset ID). Settlement only verifies against the header stored for that epoch. An attestation for epochecannot be validated against the header for epoche+1. -
Bound to destination
dstChainIdanddstAppare part of the signed payload. The same hash cannot be replayed on a different chain or different contract because the signature is over the full encoded message. -
Replay protection Your app (or Settlement integration) tracks:
nonce: reject messages with a nonce ≤ lastSeenNonce for that channel / appexpiry: reject messages whose expiry is in the past
-
Slashable misbehavior
If operators sign:
- two different payloads for the same
(networkId, subnetworkId, epoch, nonce) - or obviously invalid content (e.g. violates your protocol’s invariants),
that evidence can be fed into your Network’s middleware, which then submits a slashing request to the relevant vaults in Symbiotic. The economic backing for those keys is what makes the attestation “secure”.
- two different payloads for the same
So in short: a secure attestation is a message bound to a specific epoch, network, and destination, proven on-chain to have signatures from enough stake-weighted operators in that epoch’s validator set.
