Wrapping source feeds
audience: ai
The oracle’s trust model collapses if any of its
sources is read outside the enclave. This chapter
specifies how each source class from
source-reads is terminated
inside the TDX guest so that the only code that
touches cleartext prices is the code whose
MR_TD the consumers admitted.
The enclave boundary
The oracle process runs in a TDX guest. Its
Measurements cover MR_TD (the guest’s initial
memory image), MR_CONFIG_ID (the guest’s folded
configuration, including the source-catalog digest),
and the owner set
(MR_OWNER, MR_OWNER_CONFIG). Nothing outside
the guest can observe or inject into the
subscriptions below.
┌─────────────────────────────────────────────────┐
│ TDX guest — measured by ORACLE_MEASUREMENTS │
│ │
│ ┌──────────────┐ ┌──────────────────────────┐ │
│ │ CEX clients │ │ on-chain readers │ │
│ │ (TLS-pinned) │ │ (archive / light client) │ │
│ └──────┬───────┘ └───────────┬──────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌────────────────────────────────────────┐ │
│ │ aggregation + publication pipeline │ │
│ └───────────────────┬────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ mosaik Stream commits │ │
│ │ (signed by enclave) │ │
│ └──────────────────────┘ │
└─────────────────────────────────────────────────┘
▲ │
│ host I/O (opaque) │ signed commits
│ ▼
untrusted host consumer subscribers
Host I/O crosses the boundary only as ciphertext or as TLS bytes whose endpoints are pinned inside the enclave. Nothing else is trusted.
CEX spot feeds
Each Source::CexSpot entry is a WebSocket
subscription terminated by a TLS client built into
the oracle image. The client:
- Pins SPKI. The TLS handshake checks the leaf
certificate’s SPKI against
tls_spkifrom source-reads. Any mismatch is a hard-fail; the source transitions toreconnectingand does not recover until a matching handshake. - Ignores the host’s resolver. Hostnames are resolved inside the enclave (DoH to a pinned resolver, or the resolver identity is folded into the Measurements alongside the SPKI pin).
- Rejects downgrade. TLS 1.3 only; any negotiation past a declared cipher-suite set is a hard-fail.
A single shared TlsClient inside the enclave
holds an (exchange, pair) → WebSocket map; each
message arriving is stamped with an enclave-local
timestamp and forwarded to the aggregation
pipeline.
pub struct TlsClient {
config: Arc<rustls::ClientConfig>, // pinned SPKI verifier
}
impl TlsClient {
pub async fn subscribe(
&self,
source: &Source,
) -> Result<BoxStream<'static, RawQuote>, SourceError> {
match source {
Source::CexSpot { endpoint, symbol, tls_spki, .. } => {
let stream = self.connect_ws(endpoint, *tls_spki).await?;
Ok(stream.map(|msg| parse_raw_quote(symbol, msg)).boxed())
}
_ => unreachable!("non-CEX sources handled elsewhere"),
}
}
}
On-chain AMM reads
Source::OnChainAmm entries read AMM pool slots
(Uniswap V3 slot0, Curve get_virtual_price,
etc.) through one of two paths:
- Archive node. The operator runs an archive
node (e.g.
reth --fullorgeth --syncmode snap) whose RPC endpoint is accessible only from the enclave’s network namespace. The client authenticates the RPC session with a mutual TLS pair whose CA cert is folded into the oracle’s Measurements; the archive node’s identity is therefore transitively part of the oracle’s trust model. An operator who runs their archive node honestly delivers honest reads; a dishonest or compromised archive node shows up as quorum disagreement ifquorum > 1. - Light client. The enclave runs an Ethereum consensus light client that verifies every execution-layer read against a sync-committee- signed head. Under honest-majority assumptions on the consensus layer, the reads are trustless. Slower than archive; strictly stronger trust story.
pub struct OnChainReader {
archive: Option<Arc<ArchiveClient>>, // mTLS-pinned
light: Option<Arc<LightClient>>, // consensus-verified
}
impl OnChainReader {
pub async fn read(
&self,
source: &Source,
) -> Result<Quote, SourceError> {
match source {
Source::OnChainAmm { read_mode, .. } => match read_mode {
OnChainReadMode::ArchiveNode => self.read_archive(source).await,
OnChainReadMode::LightClient => self.read_light(source).await,
},
_ => unreachable!(),
}
}
}
The choice between archive and light client is the
oracle operator’s. Most operators run archive in
production (latency) and light in a verifier role
(cross-check); some catalog entries mark a source
quorum: 2 meaning both an archive read and a
light-client read must agree within a tolerance
before the source contributes.
Peer oracle sources
Source::OnChainOracle entries subscribe to another
mosaik oracle exactly the way a consumer does
(binding). The enclave’s
TicketValidator composes the peer oracle’s
Measurements alongside its own; the resulting
streams feed into the aggregation pipeline like any
other source.
The composition is transitive: the oracle’s
Measurements cover a code path that admits another
oracle’s Measurements. A consumer of this oracle
who wants to audit the peer set can do so by reading
SOURCE_CATALOG (its digest is in the content
hash) and verifying each referenced oracle’s
published Measurements.
Source-availability reporting
The aggregation pipeline is supplied with a live per-source availability map:
pub struct Availability {
pub fresh: HashSet<SourceId>,
pub stale: HashSet<SourceId>,
pub reconnecting:HashSet<SourceId>,
}
Availability is updated on every message arrival and every reconnect-timeout. The aggregation policy (aggregation) reads this snapshot each tick to decide whether to publish, skip, or flag.
What the enclave never does
- It never accepts unpinned TLS. A non-matching SPKI is a hard-fail.
- It never leaks raw source quotes outside the guest. Only aggregated ticks leave the enclave, and only through the signed mosaik stream commits in publication.
- It never imports source data through the host OS. File-backed cache, shared memory, and host syscalls other than network I/O are not used. Any cache is in-guest RAM only.
Forward
Chapter 5 (aggregation) specifies what the in-enclave pipeline does with the fresh quotes once they arrive.