atelier — TDX co-building
audience: contributors
Proposed source: crates/atelier/.
The block-assembly organism. Every atelier committee member
runs a reproducible TDX image whose MR_TD is pinned in the
lattice’s atelier::Config. Members subscribe to
unseal::UnsealedPool and offer::AuctionOutcome, simulate
bundles inside the enclave, and commit a single Candidates
entry per slot signed under a BLS aggregate signature across
the committee.
Crate layout
atelier::proto— wire types for block templates, hints, candidate-block payload, aggregate signature. No I/O.atelier::core— pure simulation + ordering. Inputs: unsealed transactions, auction winner, parent state root. Output: deterministic canonical tx list + gas estimate.atelier::node— mosaik integration.AtelierMachine, role event loops, TDX ticket validator, chain-RPC bridge.
The chain-RPC bridge is the one place atelier reaches outside
the lattice universe: simulation needs parent state. The
bridge is a sealed in-enclave HTTP client; the RPC endpoint is
pinned in atelier::Config so that the bridge’s target is part
of the fingerprint (and so that a rogue operator cannot swap
it out of an attested image).
Public facade:
atelier::Atelier::<Block>::read(&network, &Config) -> Reader<Block>— read-side for proposers, sequencers, analytics agents.atelier::Atelier::<Block>::verify(&Block, &Config) -> bool— pure verification of the BLS aggregate signature against the pinned committee roster.atelier::Config.
Public surface
Candidates collection
declare::collection! {
pub Candidates<B> = Vec<Candidate<B>>,
derive_id: ATELIER_ROOT.derive("candidates"),
consumer require_ticket: LATTICE_READ_TICKET,
writer require_ticket: AtelierMember,
}
pub struct Candidate<B: BlockDatum> {
pub slot: u64,
pub parent_hash: [u8; 32],
pub header: B::Header,
pub body: Vec<B::Tx>,
pub hints_applied: Vec<HintId>,
pub builder_sig: BlsAggSig,
pub committee_roster: Vec<BlsPub>,
}
B: BlockDatum is the lattice’s chain-specific block schema
(e.g. L1Post4844, OpStackBedrock).
Hint<Template> stream
declare::stream! {
pub Hint<T: HintDatum> = HintBundle<T>,
derive_id: ATELIER_ROOT.derive("hint"),
producer require_ticket: CoBuilder,
consumer require_ticket: AtelierMember,
}
pub struct HintBundle<T: HintDatum> {
pub slot: u64,
pub co_builder: CoBuilderId,
pub template: T,
pub signature: Secp256k1Sig,
}
Hints are how co-builder operators (Phase 2) contribute
partial block templates. In a Phase 1 single-operator lattice
the Hint stream has no external writers; the committee’s
own members may still emit hints internally for intra-member
proposal exchange.
Internal plumbing
Derived private network keyed off
ATELIER_ROOT.derive("private"). Two streams:
Simulations— pairwise exchange of simulation outputs for cross-member agreement. Not on the public universe because it carries pre-commit tentative block bodies at high frequency.RosterGossip— BLS roster synchronization used during committee churn.
State machine
pub enum Command {
OpenSlot { slot: u64, parent: [u8; 32] },
AcceptHint(HintAccept),
SubmitSimulation(SimOutput),
SealCandidate(SealCommit),
MarkDone(u64),
}
pub enum Query {
OpenSlots,
HintsFor(u64),
SimulationAgreement(u64),
CandidateFor(u64),
}
Apply semantics
OpenSlot. Leader-issued when the proposer slot boundary is observed on-chain. Initializes per-slot state.AcceptHint. Committee member proposes a hint for inclusion. Validation: hint’sslotmatches an open slot;co_builderACL ticket matches; signature verifies.SubmitSimulation. Each committee member runs the simulation of(UnsealedPool[S] + AuctionOutcome[S] + hints)inside its TDX enclave and commits the resulting output hash. The state machine cross-checks member outputs: if a majority agrees on a single hash, that hash wins; divergence increments a counter the operator watches.SealCandidate. Issued once a majority agrees. Apply handler:- Reconstructs the candidate block body from the deterministic ordering function using the agreed inputs.
- Aggregates each member’s BLS signature over the body
hash (signatures arrive via the private
Simulationsstream alongside the simulation output). - Constructs
Candidate<B>withbuilder_sig = BlsAggregate(valid_member_sigs)andcommittee_roster = members_who_signed. - Appends to
Candidates.
MarkDone. GC after tally commitsRefunds[S].
Invariants
- One candidate per slot.
SealCandidaterejects ifCandidateFor(S)is already populated. - Candidate body is a deterministic function of committed
inputs. Two committee members re-applying the same
sequence arrive at byte-identical
Candidate<B>.body. - Minority divergence cannot commit. A minority of TDX
images that simulate differently cannot force their
output into a
Candidate; apply requires majority agreement on the simulation hash. - Chain RPC is inside the TCB. The RPC endpoint URL is pinned in the fingerprint; a compromised RPC is a compromised atelier.
Signature versioning
fn signature(&self) -> UniqueId {
let tag = format!(
"atelier.v{WIRE_VERSION}.schema={}.mrtd={:x}.rpc={}.committee={}",
self.config.block_schema.tag(),
blake3(self.config.mrtd_acl.sorted_concat()),
self.config.chain_rpc_hash(),
self.config.committee_size,
);
UniqueId::from(tag.as_str())
}
Adding an MR_TD to the acl (onboarding a co-builder) changes the signature — this is intentional. See Rotations and upgrades — Co-building.
Committee key ceremony
At bring-up time every member generates a BLS12-381 keypair
inside its TDX image. The public keys are collected via the
operator’s driver and pinned as the committee_roster_pubkeys
in atelier::Config. No DKG is required — atelier’s
cryptography is aggregate signature, not threshold secret,
so keys are independent.
Rotation is per-member: generate new BLS key, publish, update
atelier::Config.committee_roster_pubkeys, retire + replace
the lattice. Rotating one key changes the fingerprint.
ACL composition
impl Config {
pub fn member_validator(&self) -> Validators {
let mut v = Validators::stacked()
.with(JwtIssuer::from(self.operator_jwt_key));
for mrtd in &self.mrtd_acl {
v = v.with(Tdx::new().require_mrtd(*mrtd));
}
v
}
pub fn co_builder_validator(&self) -> Validators {
Validators::stacked().with(JwtIssuer::from(self.co_builder_jwt_key))
}
}
mrtd_acl is a set (not a single value) to support co-building:
two operators contributing atelier members each publish their
own MR_TD; both land in the acl; admission succeeds for either.
Adding an MR_TD is a fingerprint change; see
phase2-spec.
Trust shape
TDX attestation + majority-honest. See threat-model.md — atelier.
Open questions
- Bundle simulation determinism across TDX images.
Simulation at byte-identical output requires a deterministic
EVM implementation. The reference atelier ships a vetted
revm build inside the image; divergence-in-practice between
images is what the
simulation_divergence_totalmetric surfaces. What level of determinism is actually achievable on shared chain RPC is an open research question. - Pre-confirmation support. Flashblocks-style
pre-confirmations (fast-confirm a partial ordering before
the full slot) would need atelier to commit partial
Candidatesentries per sub-slot. Not in v1; likely a v2 extension that adds a second collection keyed by(slot, sub_slot). - Revert protection. Integrator-level revert protection
requires atelier to simulate each bundle in a sandbox and
exclude any that revert. The logic is in
atelier::core; the question is whether to expose revert-filtered bundles in the canonical ordering or only at integrator request. Open.
Cross-references
- The six organisms
- offer — upstream.
- unseal — upstream.
- relay — downstream.
- cryptography.md — atelier
- threat-model.md — atelier
- Operator runbook