Repository navigation
docs(specs): on-chain verification path, two-attestation circuits, notary fee - #11
Merged
Merged
Conversation
xgreenx
force-pushed
the
docs/ceremony-round3
branch
14 times, most recently
from
August 19, 2026 22:38
9ed0ff8 to
1ea2aca
Compare
Rewrites the ceremony specs around how the system actually works, from the protocol owner's description. 1. The circuit connects two attestations; it does not verify a proof. Section 6 described GitHub as a server-produced "Token Proof" that a "Final Identity Circuit" verifies in-circuit — recursive proof verification, which the design does not use. GitHub now matches X: two notarized attestations, both authenticated by the Notary Service, one hidden bearer linking them. Each circuit exposes exactly three public inputs: the Authorization Digest and the two bearer commitments. Everything else — client identifier, evidence time, endpoints, identity fields — is revealed attestation bytes the Platform Verifier reads. This fixes a defect that made the design unimplementable: REQ-PLAT-43E forbade the bearer commitment as a public input, but that commitment is what ties the circuit to the two attestations. Circuits no longer verify response schema. `token_type` and the granted scope are gone from the proof statement: nothing on chain acts on them, so the runtime may check them locally. 2. Verification is four roles. Consumer -> Proof Verifier -> Platform Verifier -> Notary Service. The Proof Verifier owns the Supported Version Set, recomputes and records the Authorization Digest, and returns the authenticated operation domain, Authorized Transaction Data, client identifier, `userId`, handle bytes, and `metadataObservedAt`. The Platform Verifier owns every platform constant and every platform check. The Consumer owns only domain ownership, decoding, authorization, storage, and handle normalization. The Notary Service answers for its signature, the transcript commitment, and whether revealed bytes match that transcript — nothing profile-specific. Range layout against a profile is the Platform Verifier's, since a notary holds no profile. It charges a fixed Notary Fee in the Consumer Chain's native asset; the fee may not vary by content or principal, the current fee is readable, value above it returns to the Consumer, and the Proof Verifier quotes the whole path. 3. Client identifier is uniform and authenticated. Every platform returns its exact bytes. Google reaches that with no circuit change: the submission carries `aud` and the Platform Verifier requires SHA256 of those bytes to equal the circuit's audience public input. A Platform Verifier must reject bytes its evidence does not authenticate. Also: PKCE_V1 loses its version — a change to that construction is a change to the proof statement, which bumps `platformVerifierVersion` anyway; PKCE_DOMAIN keeps domain separation at zero cost, since 64- and 96-byte preimages both occupy two SHA-256 blocks. X and GitHub gain the `state`-to-ceremony match the Google profile always had. Vectors recomputed and independently verified. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: xgreenx <xgreenx9999@gmail.com>
xgreenx
force-pushed
the
docs/ceremony-round3
branch
from
August 19, 2026 23:13
1ea2aca to
9a4ef65
Compare
Two rounds of review against the verification path landed in PR #11 turned up defects in what that PR already decided. This corrects them; it adds no new format or algorithm. Digest binding is now per platform. Google carries the Authorization Digest as a public proof input; X and GitHub bind it through the revealed code_verifier and carry no digest input at all. One requirement asserting both was two requirements. A profile's Attestation Count is an exact number of zero or more, and the fee quotation counts attestations rather than assuming one. A path with nothing to verify carries no value. The Platform Verifier is now required to verify the proof against the artifact its selected version pins. Nothing in the suite said so. The Proof Verifier forwards the digest it recomputed to the Platform Verifier, which no longer recomputes it from a caller-supplied Chain ID. Endpoint authority is stated as what the notary authenticated and the verifier compares, not as a revealed range the transcript never carries. Both token responses gain revealed anchors around the committed access_token, and both identity sessions gain their response ranges. The Authorization-header rules are scoped off GitHub's body client_secret, grant_type is checked on chain, and the commitment wording of the bearer requires independent blinders across the two sessions. Also: REQ-COMMON-18 no longer makes google/v1 ineligible; the unexercisable comparisons of REQ-COMMON-06C are exercised; the stale scope-enforcement and replay claims are gone; the suite states that no full grammar is enforced; libid.md no longer credits the circuit with Platform Verifier work; and Security Considerations no longer lists pkceNonce as withheld. REQ-COMMON-41..46, REQ-PLAT-54..60 and TEST-COMMON-20..23 are new here. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
…rifier's Verification of the previous commit turned up six more defects of the same kind it was fixing. Both token reveal tables listed the attestation timestamp as a revealed range. It is not one: it is the attestation's own signed creation time, which is why REQ-COMMON-25 can forbid inferring it from a response header. This is the correction already applied one row below it for the endpoint authority, missed on the row above. The GitHub table therefore had seven revealable rows, not eight, and REQ-PLAT-43D and TEST-PLAT-14 said eight. REQ-COMMON-06C had the Proof Verifier observe a Chain ID under ASM-CHAIN-02, an assumption written about the Consumer. At the same time REQ-COMMON-01C still obliged the Consumer to source a Chain ID that nothing consumes, since digest recomputation moved to the Proof Verifier. Both now name the Proof Verifier, and so does TEST-COMMON-02A. REQ-COMMON-21 had the Notary Service authenticate the server identity of "the session it verifies". It authenticates what it observes; the verifying side holds no transcript. REQ-COMMON-18A required every hidden range to be bounded by revealed anchor bytes, which no leading or trailing hidden range can satisfy. The signed transcript length of REQ-COMMON-36 is the anchor at a transcript end. The identity profiles said so locally; the rule now says it once. libid.md's trust-root list omitted the Platform Verifier, which is the role that verifies the proof and binds the digest. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
…ount Round-6 review. Four defects plus the runtime half of a fifth. The Canonical Runtime constructs the Authorization Digest, but the previous commit made only the Proof Verifier's Chain ID source normative. Both now take it from the same Chain Profile value, and neither from caller input. A value the two source differently produces a recomputation that never matches. Attestation Count was a number stated beside the list of attestations each profile already names, with nothing requiring the two to agree. The profile now fixes the list; the count is its size and is never stated separately. Field-checking ownership was contradictory. Common prose credited the Proving Circuit with checking the fields a profile needs, while X and GitHub assign revealed-field reads to the Platform Verifier, and REQ-COMMON-05D claimed the Platform Verifier checks every required field though redirect_uri and redirect-code equality are runtime-only. REQ-COMMON-19E now assigns each field's extraction to exactly one of the three roles, by where the bytes are: hidden to the circuit, revealed to the verifier, browser-local to the runtime. A comparison on an already-extracted value is not an extraction and may sit elsewhere, which is what the Google audience does. Concurrent version support did not deliver the no-stranding property its own Necessity claimed, because nothing stopped governance retiring the outgoing version the moment its replacement activated. It is retained until every ceremony that could have begun under it passes proofValidUntil, with an explicit emergency removal that is allowed to strand unsafe work. GitHub's `/user` layout revealed "the structural byte after" the id integer without saying which byte, though REQ-COMMON-19D delegates exactly that to the profile. It is `,` or `}`. Both are pinned rather than the comma alone, because JSON member order does not guarantee which one closes the field. Chain IDs stay a hash of whatever identifier a chain gives itself, with no namespacing requirement: uniqueness is the Chain Profile author's to ensure. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
…rlap The single-extraction rule of the previous commit contradicted both TLSNotary profiles. REQ-PLAT-31 and REQ-PLAT-51 have the Platform Verifier extract the identity fields; REQ-PLAT-31A and REQ-PLAT-51A have the Canonical Runtime derive them from the same bytes. That runtime derivation is not redundancy — it is what lets the browser show the user the identity the proof will bind, so forbidding it would reopen the gap where the display names one account and the proof binds another. REQ-COMMON-19E now fixes one source and one algorithm per field and names one role's extraction authoritative: the Proving Circuit where the bytes stay hidden, the Platform Verifier where they are revealed, the Canonical Runtime only where nothing on chain reads the value. The runtime may repeat an extraction another role owns, over the same bytes by the same algorithm, and no proof statement or chain component may depend on that repeat. Both profiles now say which of their two is the authoritative one. The version-retention rule is withdrawn. Requiring an outgoing version to survive until every ceremony that "could have begun" under it expires is unobservable and never terminates, since nothing stops new ceremonies beginning under it. Governance decides when a version leaves the Supported Version Set, this specification fixes no minimum overlap, and a stranded ceremony is recoverable because its holder can run the ceremony again under a supported version. The Necessity no longer claims a no-stranding property the rule never delivered. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
xgreenx
marked this pull request as ready for review
August 20, 2026 09:01
Wondertan
added a commit
that referenced
this pull request
Aug 20, 2026
…tary fee (#11) Define the four-role verification path, two-attestation X and GitHub ceremonies, Platform Verifier dispatch, and fixed Notary Service fees. Close the subsequent verification findings around digest binding, proof and attestation ownership, Chain ID sourcing, field extraction, transcript ranges, and version overlap without changing the resulting specification tree. Co-authored-by: Green Baneling <XgreenX9999@gmail.com> Assisted-by: GPT-5 Signed-off-by: Wondertan <hlibwondertan@gmail.com>
Wondertan
added a commit
that referenced
this pull request
Aug 20, 2026
…tary fee (#11) Define the four-role verification path, two-attestation X and GitHub ceremonies, Platform Verifier dispatch, and fixed Notary Service fees. Close the subsequent verification findings around digest binding, proof and attestation ownership, Chain ID sourcing, field extraction, transcript ranges, and version overlap without changing the resulting specification tree. Co-authored-by: Green Baneling <XgreenX9999@gmail.com> Assisted-by: GPT-5 Signed-off-by: Green Baneling <XgreenX9999@gmail.com> Signed-off-by: Wondertan <hlibwondertan@gmail.com>
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.
Draft follow-up into #3. Two commits: the round-3 design changes, then the corrections that two later review rounds found in them.
The concrete type definitions that came out of the same discussion — the attestation byte layout and the handle-normalization algorithm — are not here. They are stacked in #12 so this PR stays reviewable as decisions rather than formats.
Commit 1 — the round-3 design changes
1. The circuit connects two attestations — it does not verify a proof
§6 described GitHub as a server-produced "Token Proof" that a "Final Identity Circuit" verifies in-circuit. That is recursive SNARK verification, and it is not the design. GitHub now matches X exactly: two notarized attestations, both authenticated on chain, one hidden bearer linking them.
Both platforms' circuits expose exactly two public inputs — the two bearer commitments. Client identifier, evidence timestamp, endpoint triples and identity fields are revealed attestation bytes the Platform Verifier reads directly.
This also fixes a defect that made the design unimplementable: REQ-PLAT-43E forbade the bearer commitment as a public input, but that commitment is exactly what ties the circuit to the two attestations the chain verified (it is what the shipped
dyaka-noir-tokencircuit exposes, for the same reason). The bearer stays private; its commitments must be public, and the Platform Verifier must match them against the submitted attestations.What the ZK proves is now stated plainly: the two TLSNotary sessions use independent blinders, so the same bearer hashes to two different commitments and the chain cannot compare them. Only the circuit can assert one hidden bearer opens both without publishing a live credential.
2. Verification is a four-role on-chain path (new common §5.1)
The Proof Verifier dispatches on (identity platform, Platform Verifier Version), supports several versions concurrently, and returns the authenticated operation domain, Authorized Transaction Data, and client identifier. The operation domain rides in the submission and is authenticated by digest recomputation — name a domain the ceremony did not commit and the digest will not match — so the Consumer can check the domain is its own before selecting a handler.
Naming note: the dispatcher is called Proof Verifier, not "Prover", because the spec already uses prover and Proving Circuit for the party that produces proofs.
3. The Notary Service verifies on chain and charges a fee
It takes attested data and a notary signature and returns one accept-or-reject decision. It charges a fixed fee per verification, paid in the Consumer Chain's native asset over the path the Chain Profile fixes. Added guards: the fee may not vary by content or principal (a per-principal fee is selective censorship of a permissionless service), the current fee is readable before a submission is built, and the Proof Verifier quotes the whole path so a Consumer need not know its topology. A submission whose delivered value does not equal the quotation is rejected — there is no refund path, because there is nothing to refund.
Also
aud) and as a revealed attestation range for X and GitHub — one path per platform, no duplicate representation.libid.mdrecords the Proof Verifier as the most concentrated trust root.Commit 2 — the round-4 and round-5 review findings
Adds no new format or algorithm; corrects what commit 1 decided.
Digest binding is per platform. Google carries the Authorization Digest as a public proof input; X and GitHub bind it through the revealed
code_verifierand carry no digest input at all. One requirement asserting both was two requirements (REQ-COMMON-02A/02B/02C).Nobody was required to verify the proof. No
MUSTbound any role to check the proof against the artifact its selected version pins. The Platform Verifier now is.Attestation Count is an exact number of zero or more, and the fee quotation counts attestations rather than assuming one. Google's profile verifies no attestation and pays nothing; a path with nothing to verify carries no value.
The Proof Verifier forwards the digest it recomputed to the Platform Verifier, which no longer recomputes it from a caller-supplied Chain ID.
Endpoint authority is stated as what the notary authenticated and the verifier compares against its pinned constant — not as a revealed range, which the transcript never carries (it appears only in a prover-composed
Hostheader).Evidence coverage gaps: both token responses gain revealed anchors around the committed
access_token, and both identity sessions gain their response ranges.Also: the Authorization-header rules are scoped off GitHub's body
client_secret;grant_typeis checked on chain; the bearer commitment wording requires independent blinders across the two sessions; REQ-COMMON-18 no longer makesgoogle/v1ineligible; REQ-COMMON-06C's comparisons are exercisable; the suite states that no full request grammar is enforced;libid.mdno longer credits the circuit with Platform Verifier work; and Security Considerations no longer listspkceNonceas withheld.Dropped from commit 1
The Authorization Digest is no longer recorded at the Proof Verifier, and the requirement that operation domains be globally unique across Consumers is gone. A verifier cannot enforce uniqueness over a value each Consumer chooses for itself. Replay protection belongs in the Consumer's own domain.
Commit 3 — six defects the verification pass found in commit 2
Both token reveal tables listed the attestation timestamp as a revealed range. It is not one — it is the attestation's own signed creation time, which is why REQ-COMMON-25 can forbid inferring it from a response header. This is the same correction commit 2 applied one row below it for the endpoint authority, missed on the row above. The GitHub table therefore has seven revealable rows, not eight, and REQ-PLAT-43D and TEST-PLAT-14 said eight.
The Chain ID now belongs to the Proof Verifier. REQ-COMMON-06C had it observe one "under ASM-CHAIN-02," an assumption written about the Consumer; meanwhile REQ-COMMON-01C still obliged the Consumer to source a Chain ID that nothing consumes, since digest recomputation moved to the Proof Verifier. Both rules and TEST-COMMON-02A now name the Proof Verifier.
REQ-COMMON-21 had the Notary Service authenticate the server identity of "the session it verifies." It authenticates what it observes; the verifying side holds no transcript.
REQ-COMMON-18A required every hidden range to be bounded by revealed anchor bytes, which no leading or trailing hidden range can satisfy. The signed transcript length of REQ-COMMON-36 is the anchor at a transcript end — the identity profiles said so locally, the rule now says it once.
libid.md's trust-root list omitted the Platform Verifier, the role that verifies the proof and binds the digest.Commit 4 — round-6 review
Field-extraction ownership. Common prose credited the Proving Circuit with checking the fields a profile needs, while X and GitHub assign revealed-field reads to the Platform Verifier, and REQ-COMMON-05D claimed the Platform Verifier checks every required field though
redirect_uriand redirect-code equality are runtime-only. New REQ-COMMON-19E assigns each field's extraction to exactly one of the three roles, by where the bytes are: hidden to the circuit, revealed to the verifier, browser-local to the runtime. A comparison on an already-extracted value is explicitly not an extraction and may sit elsewhere — otherwisegoogle/v1, which extractsaudin circuit and compares it on chain, would be ineligible under its own rule.Version retention — withdrawn in commit 5. See below.
Attestation Count is derived. It was a number stated beside the attestation list each profile already names, with nothing forcing agreement. The profile fixes the list; the count is its size. Keeping the term (rather than deleting it) leaves the ~15 downstream references and #12's REQ-COMMON-52 working unchanged.
The Canonical Runtime's Chain ID. Commit 3 made only the Proof Verifier's source normative, but the runtime constructs the digest. Both now read the same Chain Profile value, neither from caller input.
GitHub's integer terminator. The
/userlayout revealed "the structural byte after" theidinteger without saying which, though REQ-COMMON-19D delegates exactly that to the profile. Pinned as,or}— both rather than the comma alone, since JSON member order does not guarantee which one closes the field.Not changed: Chain IDs stay a hash of whatever identifier a chain gives itself, with no
namespace:referencerequirement. Uniqueness is the Chain Profile author's to ensure per chain.Commit 5 — round-7 review
One authoritative extraction, and a permitted mirror. Commit 4's single-extraction rule contradicted both TLSNotary profiles: REQ-PLAT-31 and REQ-PLAT-51 have the Platform Verifier extract the identity fields, while REQ-PLAT-31A and REQ-PLAT-51A have the Canonical Runtime derive them from the same bytes. That derivation is not redundancy — it is what lets the browser show the user the identity the proof will bind, so forbidding it would reopen display-A/proof-B.
REQ-COMMON-19E now fixes one source and one algorithm per field and names one role's extraction authoritative: the Proving Circuit where the bytes stay hidden, the Platform Verifier where they are revealed, the Canonical Runtime only where nothing on chain reads the value. The runtime may repeat an extraction another role owns, over the same bytes by the same algorithm, and nothing on chain may depend on that repeat. Both profiles now state which of their two is authoritative.
Version retention withdrawn. Requiring an outgoing version to survive until every ceremony that "could have begun" under it expires is unobservable and never terminates — nothing stops new ceremonies beginning under it, so the set is never exhausted. Governance decides when a version leaves the Supported Version Set, this specification fixes no minimum overlap, and a stranded ceremony is recoverable because its holder can simply run the ceremony again under a supported version. The Necessity no longer claims a no-stranding property the rule never delivered.
Verification
cast keccakand@noble/hashes, each sanity-checked against published keccak vectors): all correct, including theoperationDomain-first preimage.exerciseslist that hid four requirements from the conformance graph, and three requirements whose keyword had no actor.Not changed, deliberately
ASM-PROV-07 stays a soundness dependency. I previously argued that revealing every range plus charset constraints closes the duplicate-field hole structurally; adversarial review refuted that with byte-level counterexamples — an honestly separated duplicate (
client_id=GOOD&client_id=EVIL) satisfies every charset and tiling premise, as do percent-encoded field names, query/body parameter merging, and acharset=utf-7body. Only a full-transcript template pin would replace the assumption, and TLS terminating at a CDN edge limits even that. The prose that overstated this is softened.🤖 Generated with Claude Code