Skip to content

feat(ca2a): mutual attestation design, and the challenge primitive - #89

Merged
imran-siddique merged 3 commits into
mainfrom
docs/mutual-attestation-design
Aug 10, 2026
Merged

feat(ca2a): mutual attestation design, and the challenge primitive#89
imran-siddique merged 3 commits into
mainfrom
docs/mutual-attestation-design

Conversation

@imran-siddique

Copy link
Copy Markdown
Member

Design only, no code. cA2A is the one asset with no funded competitor, so it is worth getting the protocol change right rather than fast.

What is actually one-directional

Not the primitives. Either peer can bind a channel key into a report and either can appraise one — that is why attestation.md says the design composes into mutual attestation, and why claim C6 drives both directions.

The reference transport is. The callee verifies the caller's delegation chain, which is authorization: it proves the caller holds a credential whose scope covers the request. It says nothing about what the caller is running. A callee cannot tell an enclave from a laptop.

The change

A callee-issued challenge, and a caller offer bound to it. Three properties fall out, each a requirement rather than a consequence:

  • The challenge must come from the callee. A caller that picks its own nonce proves it can produce a report, not that it produced one for this exchange — the same replay the caller's nonce already prevents in the other direction.
  • The callee must appraise before it acts, not before it responds. The payload is sealed to the callee's key and readable the moment it arrives, so appraising afterwards means an unattested caller has already had its work done. This ordering is the whole value of the change and needs a test that fails if the calls are swapped.
  • The caller's key must be used, or it is ceremony. Sealing the response to it makes the callee's answer readable only by the enclave it appraised. An attested key that is appraised and discarded adds a round trip and no property.

The decision I did not make

A challenge is worth nothing unless it is single-use and expiring, and the server keeps no state today.

Gives Costs
Challenge store exactly-once within one process server becomes stateful; a challenge issued by one instance is unknown to the next
Stateless HMAC works across instances, no storage single-use is unachievable; replay is bounded by a window, not eliminated

What it still would not give

Not simultaneous — there is an instant where the caller has committed a sealed payload and the callee has not yet verified it. Not trustworthiness — it establishes what each side is running, and the delegation chain remains what says it is allowed. And it does not make the recorded cross-operator hardware run mutual; that run had the caller appraise a real TDX quote and the callee appraise nothing. This makes a mutual run possible.

🤖 Generated with Claude Code

…t give

The primitives are already symmetric: either peer can bind a channel key into a
report and either can appraise one, which is why attestation.md says the design
composes into mutual attestation and why claim C6 validates both directions. The
reference transport is what is one-directional.

The callee today verifies the caller's delegation chain, which is authorization:
it proves the caller holds a credential whose scope covers the request. It says
nothing about what the caller is running, so a callee cannot tell an enclave from
a laptop.

The change is a callee-issued challenge and a caller offer bound to it. Three
things fall out and each is a requirement rather than a consequence. The
challenge must come from the callee, because a caller that picks its own nonce
proves only that it can produce a report and not that it produced one for this
exchange. The callee must appraise before it acts rather than before it responds,
because the payload is sealed to the callee's key and readable on arrival, so
appraising afterwards means an unattested caller has already had its work done.
And the caller's key must be used or it is ceremony: sealing the response to it
makes the callee's answer readable only by the enclave it appraised.

Two open decisions, both with real costs. A challenge store gives exactly-once
within one process and makes the server stateful; a stateless HMAC challenge
works across instances and cannot be single-use, only bounded by a window.

The document ends with what this still would not give: it is not simultaneous,
there is an instant where the caller has committed and the callee has not
verified; it does not make either peer trustworthy, only established; and it does
not make the recorded cross-operator hardware run mutual, it makes a mutual run
possible.

Proposal only. No code written.
…guarantee stated

Step one of the mutual-attestation design: the callee issues the nonce that the
caller's attestation report must bind. A caller that picks its own nonce proves
it can produce a report, not that it produced one for this exchange.

Stateless, per the decision recorded in the design. A challenge is
v1.<expiry>.<random>.<mac>, the MAC covering the first three parts under a
per-process secret, so any instance verifies what any other issued and nothing
has to be remembered. The cost is real and is written down rather than implied:
single-use is unachievable without state, so the property is
at-most-once-per-window and the window is the TTL. A deployment that needs
exactly-once wants the challenge store this was chosen over.

Two details that are decisions rather than style. The MAC is verified before the
expiry is read, because acting on a timestamp pulled out of an unauthenticated
string means trusting an attacker's arithmetic, and answering "expired" to a
forgery tells the sender their forgery was well-formed. And the secret is
per-process and never persisted, so restarting the callee invalidates
outstanding challenges: a restarted enclave is a different enclave, and a
challenge it never issued should not verify.

16 tests, including that extending a captured challenge by editing its timestamp
is caught by the MAC, and that a forgery reports as forged rather than expired.
@imran-siddique imran-siddique changed the title docs(spec): design for mutual attestation, and what it still would not give feat(ca2a): mutual attestation design, and the challenge primitive Aug 9, 2026
@imran-siddique

Copy link
Copy Markdown
Member Author

Updated: the design decisions are recorded and the challenge primitive is in. Transport wiring follows in a second PR so the security-critical piece gets reviewed on its own.

Decisions taken, all three now in the document:

  1. Stateless HMAC challenge. Works across instances, no storage, and the guarantee is at-most-once-per-window rather than exactly-once. That weaker property is written down rather than implied — a deployment needing exactly-once wants the challenge store this was chosen over.
  2. Record the outcome, requirement configurable. A callee does not demand attestation by default and can be told to.
  3. Seal the response to the caller's attested key, so that key is load-bearing rather than ceremonial.

Two details in challenge.py that are decisions, not style

The MAC is verified before the expiry is read. Pulling a timestamp out of an unauthenticated string and acting on it means trusting an attacker's arithmetic, and answering expired to a forgery tells the sender their forgery was well-formed. A test pins that a forgery reports as forged.

The secret is per-process and never persisted. Restarting the callee invalidates outstanding challenges, which is correct rather than incidental: a restarted enclave is a different enclave, and a challenge it never issued should not verify.

16 tests, including that extending a captured challenge by editing its timestamp is caught by the MAC.

The argument for sealing the response to the caller's attested key was that an
appraised key which is never used is ceremony. Wrong on both halves.

There is no confidential response to seal. serialize_peer_result never echoes
the opened payload, by design; the response carries the provenance record, which
exists to be chained and handed to a verifier. Sealing it would produce a record
only one enclave can read, encrypting the one artifact built to be shareable.

And the key was never ceremonial. It is the vehicle: binding it into a report
under the callee's challenge is what makes the caller's measurement live rather
than replayed. The callee learns what the caller is running, which is the whole
property. The key does its job at appraisal time.

Withdrawn before implementation rather than after, which is the cheap moment.
The other two decisions stand: stateless HMAC challenge, and record the outcome
with the requirement configurable.
@imran-siddique
imran-siddique merged commit c5a5969 into main Aug 10, 2026
11 checks passed
@imran-siddique
imran-siddique deleted the docs/mutual-attestation-design branch August 10, 2026 04:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant