Skip to content

feat: decouple the TAKT from the AAT and gate verification behind one entrypoint - #884

Open
kilianglas wants to merge 9 commits into
mainfrom
kilianglas/aat-unnest-takt
Open

feat: decouple the TAKT from the AAT and gate verification behind one entrypoint#884
kilianglas wants to merge 9 commits into
mainfrom
kilianglas/aat-unnest-takt

Conversation

@kilianglas

@kilianglas kilianglas commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Aligns the WIP-106 attestation library with the current spec and makes it consumable from another Noir package.

The TAKT is fully decoupled from the AAT

  • The circuit no longer reserializes the TAKT: serialize_takt, compress_bjj_point, FIELD_HALF and its serialization KAT are deleted. 50742 → 49113 ACIR (−3.2%).
  • The Rust AuthenticatorAssertionToken::new now validates AAT claims independently; it no longer parses a TAKT or retains its attested key. Pairing and key-consistency checks belong to the carrying/verifying layer.
  • -80000 renamed signalcdh.
  • New aat_known_answer_sig_structure_and_signature KAT. The TAKT had one, the AAT did not, which is how the Noir fixture could silently drift from the Rust encoder. It caught a bug on first run: a 159-byte payload takes the 1-byte CBOR length, not the 2-byte form the old 417-byte payload needed.

One public entrypoint

verify_takt(...)   // pub(crate)
verify_aat(...)    // pub(crate)
verify_attestation(aat, takt, trust_anchor_key_x, _y, now)   // pub, at the crate root

The composite feeds verify_aat the key read from the TAKT it just verified, so the binding holds by construction.
Struct fields are now pub: they were module-private, so no external package could construct either token.

Rust exp validation

The AAT constructor range-checked aud, but neither token constructor constrained exp to the fixed-width range. An out-of-range value encodes at a different CBOR width and silently breaks the fixed offsets the circuits depend on.

Callers MUST

  • expose trust_anchor_key_x/_y and now as public inputs — a private now lets the prover pick a time at which any token is fresh; a private anchor key lets it sign its own TAKT;
  • bind aat.nonce and aat.cdh to the surrounding proof;
  • expose whichever of sec_flags and authenticator_meta the RP needs.

None of these are enforceable from inside the library. Documented with a worked main in crates/proof/noir/README.md.

@kilianglas
kilianglas requested a review from a team as a code owner August 4, 2026 12:46
Base automatically changed from kilianglas/aat-beta11-toolchain to wip-106-poc August 4, 2026 20:57
@paolodamico

Copy link
Copy Markdown
Collaborator

@codex review

@paolodamico

Copy link
Copy Markdown
Collaborator

cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 89db6b7. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Moves the Trust Anchor Key Token (TAKT) out of the AAT payload and into the AAT COSE_Sign1 unprotected header (takt), aligning the Rust encoder, Noir circuits, and test vectors around the new binding model (TAKT self-authentication + shared assertion_key equality). Also updates the claim set to rename -80000 from signal to cdh and adds/adjusts KATs to pin the exact signed Sig_structure bytes.

Changes:

  • Rust: remove nested TAKT (submods.takt) from the AAT payload; carry TAKT bytes in the unprotected header; rename claim signalcdh; enforce fixed-width [2^16, 2^32) exp range.
  • Noir: shrink AAT_SIG_STRUCTURE_LEN and update aat_sig_structure; remove TAKT reserialization; enforce TAKT exp CBOR-uint32 constraint in verify_takt.
  • Tests/docs: add cross-implementation AAT KAT; update deterministic CBOR map order tests and Noir README guidance.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
crates/proof/src/authenticator_attestation/tests.rs Updates AAT payload assertions (no submods), checks takt in unprotected header, adds AAT Sig_structure/signature KAT, adds exp range tests.
crates/proof/src/authenticator_attestation/mod.rs Implements new on-wire layout (TAKT in unprotected header), renames claim to cdh, and adds exp fixed-width validation.
crates/proof/noir/README.md Documents the new binding model and TAKT placement for Noir developers.
crates/proof/noir/authenticator-attestation/src/takt.nr Moves the exp CBOR-uint32 constraint into verify_takt and removes TAKT serialization/KAT.
crates/proof/noir/authenticator-attestation/src/aat.nr Updates Sig_structure template and constants to match the smaller payload and claim set (no nested TAKT, cdh claim).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

.unprotected
.rest
.iter()
.find(|(label, _)| *label == coset::Label::Text("takt".to_string()))
Comment on lines +203 to +205
/// Validates that `exp` fits the fixed 4-byte CBOR uint encoding mandated by
/// WIP-106 (`2^16 <= exp < 2^32`, dates between 1971 and 2106), so the claim
/// offsets the circuits rely on stay constant.
Comment on lines 27 to 29
@@ -24,14 +29,15 @@ pub struct AuthenticatorAssertionToken {
exp: Field,
Comment on lines +32 to +36
// WIP-106 fixes `exp` to a 4-byte CBOR uint. The circuit no longer
// serializes the TAKT (it rides in the AAT's unprotected header), so this
// is the only place that constraint is still enforced -- the Rust
// constructor only range-checks the CWT numeric date. Must precede the
// freshness check so an out-of-range `exp` reports the encoding error.
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

Reviewed commit: 89db6b7704

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

paolodamico
paolodamico previously approved these changes Aug 5, 2026
Comment thread crates/proof/noir/README.md Outdated
- `authenticator-attestation/` — WIP-106 Authenticator Attestation library
(Trust Anchor Key Token + Authenticator Assertion Token verification).

The TAKT is **not** nested in the AAT. It travels in the AAT's `COSE_Sign1`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, let's remove the line breaks and delegate to IDEs to render this in the screen

Comment thread crates/proof/noir/README.md Outdated
Comment thread crates/proof/noir/README.md Outdated
/// proof that calls this function.
pub fn verify_takt(input: TrustAnchorKeyToken, now: Field) {
// CBOR uint32 encoding of `exp` is checked later in `serialize_takt`.
// WIP-106 fixes `exp` to a 4-byte CBOR uint. The circuit no longer

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd remove the comment, it's not super informative. furthermore, the last part is a bit misleading because constraint failures aren't surfaced to rust

@kilianglas kilianglas Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, didn't cleanup claude comments yet.

assert_eq!(submods[0].1, Value::Bytes(takt));
// The TAKT is NOT in the payload: it rides in the unprotected header, so it
// is outside the AAT signature (WIP-106 AAT section 6).
assert!(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like a very specific assertion to cover a previous design, I'd drop it, there's multiple things that could be in here that shouldn't. or perhaps a more generic test that ensures only the expected keys are present

@kilianglas kilianglas changed the title feat: un-nest the TAKT from the AAT feat: un-nest the TAKT and gate verification behind one entrypoint Aug 5, 2026
Base automatically changed from wip-106-poc to main August 6, 2026 08:07
@kilianglas
kilianglas dismissed paolodamico’s stale review August 6, 2026 08:07

The base branch was changed.

@kilianglas
kilianglas force-pushed the kilianglas/aat-unnest-takt branch from c5f92a9 to cce9ee0 Compare August 6, 2026 08:20
@kilianglas kilianglas changed the title feat: un-nest the TAKT and gate verification behind one entrypoint feat: decouple the TAKT from the AAT and gate verification behind one entrypoint Aug 7, 2026
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.

3 participants