Verify a signature
Recover the attester, check the registry state, and check freshness — off chain in TypeScript or on chain for about 30k gas.
Argued in full in the whitepaper at §7.2.
Verification is a pure function of the payload, the signature and registry state. No oracle call, no external dependency.
On chain#
function verify(Attestation calldata a, bytes calldata sig)
external view returns (bool valid, address attester)
{
bytes32 digest = _hashTypedDataV4(_structHash(a));
attester = ECDSA.recover(digest, sig);
valid = registeredAttester[attester]
&& block.timestamp <= a.validUntil
&& bondOf[attester] >= minBond
&& !revoked[digest];
}About 30k gas — a deliberate target. A deposit guard that costs more than the deposit it protects will not be adopted, so verification had to be cheap enough to sit in the hot path rather than in a periodic job.
Note what the check covers beyond the signature itself:
registeredAttester— the key is one the registry knowsvalidUntil— the attestation is not stalebondOf >= minBond— the attester is still economically backed!revoked— the attestation was not withdrawn early
Off chain#
import { verifyTypedData } from "viem";
const domain = {
name: "Cleaton",
version: "1",
chainId: a.chainId,
verifyingContract: REGISTRY,
} as const;
const types = {
Attestation: [
{ name: "pool", type: "address" },
{ name: "chainId", type: "uint256" },
{ name: "accountingId", type: "bytes32" },
{ name: "refLiquidity", type: "uint256" },
{ name: "horizonDays", type: "uint32" },
{ name: "confidenceBps", type: "uint16" },
{ name: "thresholdBps", type: "uint16" },
{ name: "observedAt", type: "uint64" },
{ name: "validUntil", type: "uint64" },
{ name: "modelHash", type: "bytes32" },
{ name: "featureRoot", type: "bytes32" },
{ name: "flags", type: "uint8" },
{ name: "nonce", type: "uint64" },
],
} as const;
const recovered = await verifyTypedData({
address: a.attester, domain, types,
primaryType: "Attestation", message: a, signature: a.signature,
});
// Freshness is a separate check. A valid signature over a stale attestation
// is still a valid signature.
const fresh = a.validUntil > Math.floor(Date.now() / 1000);
if (!recovered || !fresh) throw new Error("reject");