feat!: rotate IdentityJwksRoots through the Notary Service - #5
Open
xgreenx wants to merge 5 commits into
Open
Conversation
IdentityJwksRoots no longer verifies a Merkle transcript through the legacy Notary contract: a rotation is the ordinary notarized session every other consumer submits -- the ceremony section 9.1 record of the JWKS reading plus the notary's EIP-191 signature over it -- verified by the Notary Service, which charges the Notary Fee for it. The keeper follows the contract. * proof: `ProofSource::obtain` hands back the notary's `NotarizedSession` as it came off the wire (`notarize_jwks` for MPC-TLS, `build_session` for the mock). The record's bytes are what the signature covers, so nothing re-encodes them on the way to the chain. * chain: `rotate_calldata` encodes `rotate(attestedData, proof)`; `submit_rotation` reads `quoteRotation()` right before sending and attaches it as value -- the contract forwards it whole and the service refuses anything but the exact fee, so a fee changed between decision and submission would otherwise waste an MPC-TLS session on a `WrongValue` revert. Trust is read from `trustedHashExpiresAt` only. * config: one JWKS contract per network. `GoogleOidcVerifier` is archived with the wallet product, so `google_oidc_verifier`, the `ContractKind`/`Target` pair and the `[contracts]` mirror of the chain-configurations schema go; a network resolves to its `identity_jwks_roots` address or fails to load, which is what the legacy eden-testnet fixture now does (kept verbatim). * e2e: NotaryService (fee 1000 wei) + IdentityJwksRoots from the embedded artifacts, Google's real pretty-printed body as the fixture (chunked by the mock, so the on-chain de-chunker runs), dry-run -> rotation -> steady state, and the fee lands in the service exactly once. Pins: notary at the head of feat/jwks-ceremony-attestation and the libid-rs crates at the head of fix/origin-form-request-target (the commit that puts `GET /oauth2/v3/certs HTTP/1.1` on the wire, the line the contract pins), both revs until the stack is tagged; the notary's `[patch]` of tlsnotary/tlsn is repeated here because a `[patch]` is read from the root manifest only. libid-contracts = "0.7.0" is NOT PUBLISHED YET: the crate embeds forge output that is not in git, so it cannot be a git dependency. Verified against a local checkout of that release candidate through an uncommitted `[patch.crates-io]`; the Cargo.lock entry for libid-contracts therefore has no source or checksum, and `cargo build --locked` (the Dockerfile) fails until the release is on crates.io and `cargo update -p libid-contracts` records it. Assisted-by: Claude Fable 5.1 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
libid-contracts 0.7.0 renames IdentityJwksRoots to GoogleJwtRoots and moves it beside the verifier it serves: the list is the google/v1 Platform Verifier's trusted signing keys and nothing else, so "identity" said nothing about it. The keeper follows the contract, on every surface that spelled the old name: * config: the inline key and the `[identity]` mirror of a chain-configurations network file are `google_jwt_roots`; `ResolvedNetwork.google_jwt_roots`; the load error and the address error name the new key. * chain: the binding is `libid_contracts::bindings::ceremony::GoogleJwtRoots` (out of `identity`), calldata and the ABI round-trip test with it. * run/proof/lib docs, log labels and the status table; the crate description says what the keeper keeps: Google's JWT signing keys, trusted on chain. * e2e: deploys the `GoogleJwtRoots` artifact and configures it under the new key; the eden-testnet fixture's comment names the new key (the file is otherwise verbatim and still resolves to no contract). Breaking for keeper.toml and for referenced network files: a `identity_jwks_roots` key is now unknown and refused at load, which is the right failure for a contract that no longer exists under that name. Verified against a scratchpad copy of libid-contracts/rust/contracts at feat/jwks-roots-notary-service (4d97ac3) with the version bumped to 0.7.0 through an uncommitted `[patch.crates-io]`, removed before this commit; Cargo.lock is untouched. Assisted-by: Claude Fable 5.1 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
The contract installs 2048-bit moduli only; a key of any other size reverts InvalidModulusLength. The keeper hashed whatever decoded, so such a key would read as untrusted and send a rotation that reverts, every tick, for as long as Google published it. It is skipped with a warning now, and the live keys keep rotating; a set with no acceptable key at all is still an error. Found by the key-insertion audit of GoogleJwtRoots (2026-09-03). Assisted-by: Claude Fable 5.1 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
The contract keeps the latest reading's key set and the one before it, and nothing per kid: no modulusOfKid, no currentRoots. The end-to-end test asserts the reading became the current generation whole, the kid hash the keeper computed for a storage key nothing keys by any more is gone, and the comments say lifetime where they said TTL. Assisted-by: Claude Fable 5.1 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
libid-contracts v0.7.0 is published, so the lock entry that was resolved under a local path patch gains its registry source and checksum. Nothing else moves: the manifest already asked for 0.7.0, and the notary and libid-rs pins stay on their revs until those stacks release. Verified against the published crate: nightly fmt, cargo build --locked, clippy -D warnings, cargo test --all (18 unit, 4 integration incl. the Anvil end-to-end rotation). Assisted-by: Claude Fable 5.1 Signed-off-by: xgreenx <xgreenx9999@gmail.com>
xgreenx
marked this pull request as ready for review
September 8, 2026 18:40
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The keeper obtains the section 9.1 record of a whole-session JWKS reading from the notary and submits
GoogleJwtRoots.rotate(attestedData, proof)withquoteRotation()attached as value. TheGoogleOidcVerifiertarget is gone with the archived login stack; a network namesgoogle_jwt_rootsonly (a config still namingidentity_jwks_rootsorgoogle_oidc_verifierfails to load).The end-to-end test deploys a real
NotaryService(fee 1000 wei) andGoogleJwtRootson Anvil, serves Google's real key set, and drives dry-run → rotation → steady state; the mock prover frames the body chunked, as Google does, so the on-chain de-chunker is exercised. It asserts the fee reached the service exactly once.Builds only after the notary stack lands
libid-contracts = "0.7.0"is published (libid-org/libid-contracts v0.7.0, 2026-09-08). The lock entry here was resolved under a local path patch and carries no registry source, which is what the Rust job fails on; build: take libid-contracts 0.7.0 from crates.io, and follow the notary and libid-rs to their heads #6, stacked on this branch, adds thesourceandchecksumlines and is verified against the published crate (fmt,cargo build --locked, clippy,cargo test --allincl. the Anvil end-to-end rotation). Merge build: take libid-contracts 0.7.0 from crates.io, and follow the notary and libid-rs to their heads #6 into this branch and the job goes green.notaryis pinned to a rev on libid-org/notaryfeat/jwks-ceremony-attestation(notary#7) andlibid-crypto/libid-signerto the same libid-rs rev the notary pins. build: take libid-contracts 0.7.0 from crates.io, and follow the notary and libid-rs to their heads #6 moves both to their current heads: notary#8 (stacked on feat(jwks): the JWKS prover and its mock live here, on libid-rs #7) and libid-rscc40b0c, the head offeat/ceremony-constructions. Both return to tags once those stacks merge and release. The notary's[patch]oftlsnotary/tlsnis repeated here because[patch]does not propagate to dependents.Follow-up outside this PR: chain-configurations
libid-deploymust deployGoogleJwtRoots(keygoogle_jwt_roots) wired toNotaryServiceon 0.7.0; its eden-testnet file records no[identity]section, so a keeper pointed at it fails config load by design (the test fixture keeps that file verbatim and asserts the error).