Verifiable Inference
On-chain AI inference is only useful if consumers can trust the results. Our approach to verifiable inference gives developers a choice: Citrate implements three verification tiers that provide different tradeoffs between speed, cost, and cryptographic guarantee strength. Every inference result on the network carries an attestation, and the verification tier determines how that attestation is validated.
Why Verification Matters
In a decentralized network, the model operator executing your inference request is an untrusted third party. Without verification, an operator could return fabricated results, serve a cheaper model than advertised, or tamper with outputs to manipulate downstream contract logic. Verifiable inference closes this trust gap by providing mathematical or economic guarantees that the result is authentic.
The three tiers form a spectrum from fastest/cheapest to slowest/most-secure. Most applications will use Signature verification for routine operations and escalate to Optimistic or ZK-SNARK for high-value decisions.
Tier 1: Signature Verification
Signature verification is the fastest and cheapest tier. The model operator signs the inference output with their registered attestation key, and the consumer verifies the signature on-chain.
How it works:
- The operator executes inference and produces output bytes
- The operator signs
keccak256(requestId || output)with their attestation private key - The signature is included with the fulfilled result
- The calling contract (or the InferenceEngine) verifies the signature against the operator's registered public key
// On-chain signature verification
interface IAttestationVerifier {
function verifySignature(
bytes32 requestId,
bytes calldata output,
bytes calldata signature,
address attester
) external view returns (bool valid);
}
// Usage in your contract
contract MyApp {
IAttestationVerifier constant verifier = IAttestationVerifier(address(0x0104));
function handleResult(
bytes32 requestId,
bytes calldata output,
bytes calldata signature,
address operator
) external {
require(
verifier.verifySignature(requestId, output, signature, operator),
"Invalid attestation"
);
// Process the verified output
}
}
Tradeoffs:
| Property | Value |
|---|---|
| Latency | ~0 additional (inline with result) |
| Gas cost | ~15,000 gas for verification |
| Security model | Trust the operator's key; slashing for misbehavior |
| Best for | Low-value queries, high-frequency requests, non-critical outputs |
The security relies on economic incentives: operators stake SALT as collateral, and if a signature is proven to attest to an incorrect result, the operator's stake is slashed. This makes cheating economically irrational for operators with meaningful stake.
Tier 2: Optimistic Verification
Optimistic verification adds a challenge window during which any network participant can dispute the result. If no dispute is raised within the window, the result is accepted as valid. If a dispute is raised, the network re-executes the inference and compares outputs.
How it works:
- The operator executes inference and submits the result with a signature attestation
- The result enters a challenge window (configurable, default 10 blocks / ~10 seconds)
- During the window, any staked challenger can submit a dispute by calling
dispute(requestId) - If disputed, the network selects three independent operators to re-execute the inference
- If the majority of re-executions disagree with the original result, the original operator is slashed
- If no dispute is raised, the result finalizes after the window closes
// Request inference with optimistic verification
function requestWithOptimisticVerification(bytes32 modelId, bytes calldata input)
external payable
{
IInferenceEngine engine = IInferenceEngine(address(0x0101));
// The verification tier is encoded in the request options
bytes memory options = abi.encode(
uint8(2), // Verification tier: 2 = Optimistic
uint256(10), // Challenge window: 10 blocks
uint256(100_000) // Callback gas limit
);
engine.requestInference{value: msg.value}(
modelId,
input,
address(this),
this.onVerifiedResult.selector,
100_000
);
}
Tradeoffs:
| Property | Value |
|---|---|
| Latency | Challenge window (10-50 blocks, configurable) |
| Gas cost | ~15,000 base + ~50,000 if disputed |
| Security model | Economic security; disputes are expensive to raise frivolously |
| Best for | Medium-value decisions, DeFi integrations, governance inputs |
Challengers must stake SALT to raise a dispute, which they forfeit if the original result is confirmed correct. This prevents spam disputes while ensuring legitimate challenges are economically viable.
Tier 3: ZK-SNARK Verification
ZK-SNARK verification provides the highest security guarantee: a cryptographic proof that the inference was executed correctly on the declared model with the given inputs. No trust assumptions, no challenge windows, no economic games.
How it works:
- The operator executes inference inside a zkVM (zero-knowledge virtual machine)
- The zkVM produces a SNARK proof alongside the output
- The proof attests that a specific computation (the model) was applied to specific inputs to produce the output
- The on-chain verifier checks the proof in constant time regardless of computation complexity
// ZK-SNARK verification on-chain
interface IZKVerifier {
function verifyProof(
bytes32 requestId,
bytes calldata output,
bytes calldata proof,
bytes32 verificationKey
) external view returns (bool valid);
}
// Request ZK-verified inference
contract HighValueApp {
IInferenceEngine constant engine = IInferenceEngine(address(0x0101));
function requestZKInference(bytes32 modelId, bytes calldata input)
external payable
{
// ZK verification is specified through higher payment
// The network routes to ZK-capable operators
engine.requestInference{value: msg.value}(
modelId,
input,
address(this),
this.onZKVerifiedResult.selector,
200_000
);
}
function onZKVerifiedResult(bytes32 requestId, bytes calldata output) external {
// This callback only fires if the ZK proof verified successfully
// Process with full cryptographic confidence
}
}
Tradeoffs:
| Property | Value |
|---|---|
| Latency | 30 seconds to 5 minutes (proof generation) |
| Gas cost | ~300,000 gas for proof verification |
| Security model | Cryptographic; no trust assumptions |
| Best for | High-value financial decisions, governance votes, regulatory compliance |
ZK-SNARK proofs are computationally expensive to generate, so inference fees for this tier are significantly higher. Currently, ZK verification is supported for models under 1 billion parameters. Larger models use a hybrid approach where critical layers are proven and others use optimistic verification.
Choosing the Right Tier
Select your verification tier based on the value at risk and your latency tolerance:
| Scenario | Recommended Tier | Rationale |
|---|---|---|
| Chat responses, content generation | Signature | Low stakes, high frequency |
| DeFi price feeds, risk scores | Optimistic | Medium stakes, acceptable delay |
| Loan approvals, large trades | ZK-SNARK | High value at risk |
| Governance proposal analysis | Optimistic or ZK | Depends on proposal value |
| Real-time gaming, UX interactions | Signature | Latency-critical |
You can also mix tiers within a single application. We built the system this way intentionally -- for example, a DeFi protocol might use Signature verification for displaying indicative prices and ZK-SNARK verification for executing actual trades.
Further Reading
- AI Precompiles -- the AttestationVerifier precompile
- Using AI Precompiles -- calling precompiles from Solidity
- Paraconsistent Consensus -- how the network handles conflicting attestations
- Finality Checkpoints -- how finalized results interact with verification