Identity-platform ceremonies
Part of the libID protocol specification.
1. Scope
Section titled “1. Scope”This document is the normative owner of each platform’s OAuth profile, authenticated identity fields, evidence composition, proof-validity rule, token exchange, and platform-specific failure behavior. The common ceremony rules own the Authorization Digest, serialization, PKCE, transcript extraction, client binding, and evidence time. The Consumer’s protocol owns transaction dispatch and authorization. CCDP owns browsing contexts, redirect transport, and document handoff. Its Application and browser participants together implement the Canonical Runtime. Callback captures and clears the return; Prover owns complete platform-return parsing, canonical evidence decoding, request bindings, and commitment/opening correlation. Application validates the delivered identity and platform proof under the selected profile, including Google’s public-input consistency checks, and wraps it with retained authorization fields; it does not repeat Prover’s evidence extraction or perform cryptographic verification. Ledger verification means the Proof Verifier, Platform Verifier, and Notary Service checks before Consumer acceptance, not browser generation or a locally accepted result.
Google returns a signed OIDC ID Token directly to the redirect fragment. X and GitHub use the OAuth authorization-code flow and notarized transcripts of authenticated platform API responses.
Terms are imported from common ceremony rules §2.
2. Ceremony profiles
Section titled “2. Ceremony profiles”Each platform ceremony has an independently versioned immutable profile. Its Platform Ceremony Version is carried in its Authorization Digest and in the Submission Payload. Each platform section defines its own launch version. A version covers the digest, OAuth construction, platform-specific proof statement, and protocol parameter values, not any Consumer Chain’s verifier implementation. A Consumer Chain routes on its own Verifier Version (common §5.1).
A profile is selected by the pair (identityPlatform, platformCeremonyVersion). Each platform section defines its exact canonical
lowercase ASCII identityPlatform; the version is a separate integer and is
never appended to that string.
Each profile also fixes the attestation list of common REQ-COMMON-41 and the
digest-binding method of common REQ-COMMON-02C. Google verifies no attestation
and binds the digest as a public proof input; X and GitHub each verify two
attestations — a token or token-exchange session and an identity session — and
bind the digest through the revealed code_verifier of common
REQ-COMMON-15A.
- REQ-PLAT-01: The Canonical Runtime MUST select and retain one exact profile for the live ceremony. The Canonical Runtime MUST NOT substitute another profile after authorization starts. Necessity: changing profiles mid-ceremony would produce evidence the selected verifier cannot check.
- REQ-PLAT-01A (upholds SP-BIND-01): Each Platform Ceremony Version MUST identify one exact proof statement and set of semantic public inputs independently of any Platform Verifier artifact or Notary Service deployment. Before accepting that platform and version pair, the Verifier Governance Process MUST select a conforming Platform Verifier artifact and, for a TLSNotary profile, a compatible Notary Service. A version identifies its statement within one set of canonical Platform Verifier addresses. Within that set, a changed statement MUST take a new version. A new set of canonical addresses MAY restate an existing version with a different statement; the previous set is then retired. The restated statement MUST NOT accept a proof or Submission Payload made for the previous one, since the digest binds no Platform Verifier address (common §12). Necessity: chain artifacts implement the versioned ceremony boundary; they do not define it.
- REQ-PLAT-01B:
The Canonical Runtime MUST identify a profile with the exact
(identityPlatform, platformCeremonyVersion)pair its platform section defines. It MUST NOT append the version toidentityPlatformor accept a presentation alias in its place. Necessity: every component that selects a profile must do so on one canonical pair. A Consumer Chain’s Verifier Version selects an implementation of a profile, not the profile. - REQ-PLAT-02: The Application MUST treat a profile as ineligible unless its Bridge configuration enables that platform, the Distribution version list advertises that platform/version pair, and the Application implements it, under REQ-BRIDGE-03 and REQ-DIST-06. Necessity: independently deployed participants must support the selected profile.
- REQ-PLAT-03 (upholds SP-CLIENT-01):
The Prover MUST derive the local identity fields exclusively from the
Platform Profile’s canonical sources in the evidence it returns.
Those fields are not an authority decision; only the Consumer’s
acceptance of the resulting Submission is. For X and GitHub, the Prover
MUST read the identity fields from the exact identity-response bytes its
attestation commits at the ranges the Platform Verifier frames, which are
the bytes the proof opens.
The delivered handle remains raw; a local folded display value follows
REQ-PLAT-08C and does not replace it. The Prover MUST take
metadataObservedAtfrom the profile’s evidence-time source in §2.2, not from a detached identity value. The Prover MUST reject a caller-supplied or detached value used as an alternative source foruserId, handle, ormetadataObservedAt.
This is a data-source invariant, not a browser-flow requirement. It defines
the identity fields returned to callers and used by any composition-owned UI;
it does not create a ceremony-owned confirmation page. CCDP’s separate
IdentityProof.identity record transports Prover’s derivation alongside the
proof; it is not another evidence source. Application validates that delivery’s
structure and selected platform/client binding without repeating the evidence
extraction. Common REQ-COMMON-19E still binds the derivation to the exact evidence
used in the resulting Submission.
2.1 Canonical platform user identifiers
Section titled “2.1 Canonical platform user identifiers”| Identity platform | Authenticated source | Canonical userId | Mutable handle |
|---|---|---|---|
signed ID-Token sub | the sub, as signed | signed email | |
| X | /2/users/me.data.id JSON string | decimal digits, as sent | username |
| GitHub | /user.id JSON integer token | decimal digits, as sent | login |
An identity’s id, or account identifier, is the authenticated value in
the second column: Google’s sub, and the X and GitHub identifiers.
userId is the id unchanged.
-
REQ-PLAT-05A (upholds SP-BIND-01): The Implementation MUST derive an identity’s id node as
SHA256(tag || id), whereidis the account identifier’s exact bytes andtagis the platform’s user-id tag below. An id is never folded or otherwise transformed. Necessity: the id node is the key the Consumer binds the identity under, so every role must derive the same one, and the account identifier never reaches the Consumer Chain. -
REQ-PLAT-05B (upholds SP-BIND-01): The Implementation MUST derive an identity’s handle node as
SHA256(tag || fold(handle)), wherehandleis the handle’s exact authenticated bytes,foldmapsA-Ztoa-zand changes no other byte, andtagis the platform’s handle tag below. Necessity: one handle in two letter cases is one account on every launch platform, so it must have one key. -
REQ-PLAT-05C (upholds SP-BIND-01): The tags are these raw ASCII strings, hashed with no separator and no length prefix before the value:
Identity platform User-id tag Handle tag Google libid.google.user-idlibid.google.handleX libid.x.user-idlibid.x.handleGitHub libid.github.user-idlibid.github.handleA tag MUST NOT be a prefix of another tag, and none of the six above is. Necessity: a node hashes
tag || valuewith no separator, so distinct tags alone do not keep nodes apart: were one tagtand anothert || s, the values || vundertand the valuevundert || swould hash the same bytes. Prefix-free tags keep two platforms, and an id and a handle of one platform, from ever sharing a node, and separate a node from a plainSHA256of the value another system may publish. -
REQ-PLAT-06 (upholds SP-BIND-01): The Implementation MUST require an X or GitHub identifier to be 1 to 20 ASCII digits, exactly as the platform sent them: the contents of X’s JSON string and the bytes of GitHub’s JSON number token. The Implementation MUST require a Google
subto be 1 to 31 bytes from0x20through0x7e, holding neither"nor\, as the bytes appear in the signed payload. Necessity: the digits, and asubwithout a quote or a backslash, keep a committed id from running past its token into the member after it, and 31 bytes is the Proving Circuit’ssubbuffer; whether the value is one the platform issues is the platform’s to keep.
The anchors of REQ-PLAT-31 and REQ-PLAT-51 fix the JSON type of each id: a quoted GitHub id or an unquoted X id is not framed. The Proving Circuit enforces REQ-PLAT-06 on the account identifier it hashes; the Platform Verifier never sees that identifier.
Conformance vectors, each node computed independently with SHA-256:
| Platform | Authenticated input | userId | Id node |
|---|---|---|---|
sub: "110169484474386276334" | 110169484474386276334 | 0x620866cfb4d2776a32cd94d8e8b55577526af86115ebf125d4ca1ca7bf07737e | |
| X | "id":"2244994945" | 2244994945 | 0x68291869976ffad2abf3e933ec9ab2623395ff8b3b9242e655e1da3ef43d4f94 |
| GitHub | "id":583231 | 583231 | 0x475902b27989feb395b9b0cd3156aa573a9676c13155b8e7e3ddaf3e77181847 |
| Platform | Authenticated handle | Folded | Handle node |
|---|---|---|---|
A.B+tag@Example.COM | a.b+tag@example.com | 0x40169b5cc6400158aeef2a13b73a14fc36e5e1df8595572b8fe282a269f26dd4 | |
| X | Alice_1 | alice_1 | 0xe09c4f5bfbbc723bc35701ea9d718a1c5edb29b1ed5bb0cb0fabb3c43d8136af |
| GitHub | OctoCat | octocat | 0x381fb9d7d3b01214e58b94202ea10f78e169adef68bcc20d40c130deca6dfe74 |
Email, audience, client identifier, X username, and GitHub login never
replace the immutable userId.
2.1a The token check and folding
Section titled “2.1a The token check and folding”The Proving Circuit owns the step from the raw authenticated values to their nodes. It opens the raw id and handle, checks that each is exactly the JSON token the platform sent, folds the handle, and outputs both nodes. The check refuses a value; it never trims, strips, or repairs one. The platform’s own format (a handle’s alphabet and length, an id’s grammar) is the platform’s to keep, and no role repeats it.
- REQ-PLAT-08A (upholds SP-BIND-01): The Notary Service MUST NOT case-fold, trim, or otherwise transform identity bytes. The Proving Circuit MUST reject an id or a handle that is not one whole JSON token as sent, per the check below. The Proving Circuit MUST hash the id unchanged and the handle folded under REQ-PLAT-05B, and MUST apply no other transform. Necessity: a commitment is framed only by revealed anchors, so a prover chooses where it ends; a value that stopped at a later quote, or past the digits, would give one account many nodes.
- REQ-PLAT-08B (upholds SP-BIND-01): The Consumer MUST key a binding only on the id node and handle node the Platform Verifier returns from a verified proof. The Consumer MUST NOT accept a caller-supplied node. Where a handle arrives as text, the Consumer MUST fold it under REQ-PLAT-05B and accept it only for the node it hashes to under the platform’s handle tag. It MUST NOT apply a character-set or shape rule to that text. Necessity: the node arrives inside a proof, so a caller supplying it could name any identity it liked; and node equality already proves the text is the canonical form the circuit admitted, so a second rule set on the chain is a second copy that can only drift.
- REQ-PLAT-08C: A browser-side check exists only for display and local checks. No proof statement or Consumer behavior may rely on it. Necessity: a check running in software the prover chooses whether to run is not a defense.
The check is:
- a handle, and a Google
sub: 1 or more bytes from0x20through0x7e, holding neither"nor\, at most the circuit’s buffer; nothing trimmed, so a space or an@is part of the value as sent; - an X or GitHub id: decimal digits under REQ-PLAT-06;
- a handle is then folded under REQ-PLAT-05B; an id never is.
The buffers are X and GitHub handles 39 bytes, Google addresses 62 bytes,
X and GitHub ids 20 digits, and a Google sub 31 bytes. A longer value fails
to prove; it is never truncated.
The circuits own the platform table. Each platform’s tags, buffers and id
shape are its entry in libID-circuits’ lib/identity/platforms.json, and
the shared cross-language vector table is its lib/identity/handles.json.
Every circuits release ships both files beside its verifiers; the circuits
compile their constants from them, and libID-contracts generates its
profile tags and its Rust and TypeScript tables from the shipped copies.
Every implementation reproduces the vector table; it, not this prose, is
the precision anchor. A profile that publishes no such table is ineligible.
- TEST-PLAT-20 (exercises REQ-PLAT-05A, REQ-PLAT-05B, REQ-PLAT-05C, REQ-PLAT-08A, REQ-PLAT-08B, REQ-PLAT-08C):
Every implementation reproduces the shared vector table byte for byte,
including every node in it; no tag is a prefix of another; a value a
refusing row names fails to prove,
including one holding
",\, a tab or a NUL, or longer than its buffer, while a handle with a leading@or a space proves as sent; a caller-supplied node is rejected; and a handle given as text in another letter case reaches the same node, while text no circuit hashes folds to a node no proof binds.
2.1b Bindings, disclosure, and names
Section titled “2.1b Bindings, disclosure, and names”A claim puts no plaintext user id, handle, or email on the Consumer Chain
unless it discloses the handle. The Consumer’s bind call takes the
Submission and, beside it, one optional handle, a primary flag, and the
service fee:
bind(platformId, version, payload, string handle, bool primary, uint256 feeAmount, address feeReceiver).
bindPrivately(platformId, version, payload, feeAmount, feeReceiver) is
that call with an empty handle and no primary flag. The fee rules are in
§2.1c. The Submission Payload has no field for a handle or an id. An empty
handle discloses nothing and keeps the bind private. A nonempty handle is
disclosed, and with the primary flag it also becomes the author’s name.
- REQ-PLAT-03A (upholds SP-PRIV-01): The Application MUST pass the delivered handle to the bind call only when the user asks to disclose it, and MUST pass an empty handle otherwise. The Application MUST set the primary flag only when the user asks to make that handle its name. The Application MUST NOT place an account identifier in any bind call. The rule binds the Application operator, who serves the Application: it is the part of the Canonical Runtime that builds the bind call, and an operator that serves one breaking this rule is outside ASM-BROWSER-01. Necessity: the Prover delivers the handle with every proof and builds no bind call, so the choice of sending it falls to the role that does, and no Consumer check can tell a handle or a name the user chose to show from one an operator sent.
- REQ-PLAT-08D (upholds SP-BIND-01, SP-PRIV-01): Where the bind call’s handle is nonempty, the Consumer MUST fold it under REQ-PLAT-05B and hash it under the platform’s handle tag, exactly as for a disclosure call (REQ-PLAT-08E), and MUST reject the bind call unless that node equals the handle node the Platform Verifier returned (common REQ-COMMON-05E). Where the handle is empty, the bind call discloses nothing. The Consumer MUST reject a bind call that sets the primary flag with an empty handle. The Platform Verifier never receives the handle. Necessity: the handle arrives beside the proof, not in it, so only the node comparison ties it to the account; the verification is what makes the node authentic, and with it the canonical form, so the chain needs no rule set to check the text. The Consumer already folds and hashes handle text for disclosure calls, so one rule serves both. A rejected bind protects the display, not the handle: its calldata is public whether or not the Consumer accepts it. A primary flag without a handle asks for a name the call does not carry.
The Consumer stores every binding under its two nodes. For each node it
records the owner, the Transaction Author that last bound it, and the
watermark, the metadataObservedAt of the binding that last wrote it
(common REQ-COMMON-25A). It also records two pairings: each id node’s
current handle node, and each handle node’s current id node. A Transaction
Author holds a handle on a platform while it owns that handle’s node.
- REQ-PLAT-08H (upholds SP-BIND-01, SP-PRIV-01): The Consumer MUST key a binding on the id node and the handle node themselves. The Consumer MUST NOT key a binding on a plaintext value. Necessity: the tags of REQ-PLAT-05C already keep every platform’s ids and handles apart, and a key that depended on more than the node could not be found from a node computed off chain.
- REQ-PLAT-08I (upholds SP-BIND-01, SP-FRESH-01):
The Consumer MUST reject a Submission whose
metadataObservedAtis not strictly newer than both the id node’s watermark and the handle node’s watermark. On accepting one, the Consumer MUST make the Transaction Author the owner of both nodes, set both watermarks to thatmetadataObservedAt, and pair the nodes in both directions. When it pairs an id node with a handle node other than its previous one, the Consumer MUST clear the owner of the previous handle node if that node’s current id node is still this one, and MUST keep that node’s watermark. The Consumer MUST NOT change an owner, a watermark, or a pairing on any other path. Necessity: an account that proves a new handle has stopped holding the old one, and leaving its owner in place would route to the renamed account whatever is sent to that handle; a handle another account has since proved is that account’s, and is left alone. The handle node’s own watermark keeps evidence older than another account’s proof of the handle from taking it back, and a retired node keeps its watermark, the time of the last binding that wrote it, so that evidence no newer than that binding cannot claim it either. - REQ-PLAT-08O (upholds SP-BIND-01): A Consumer that answers whether a handle and an id still go together MUST answer yes only when the handle node has an owner and its current paired id node is that id node. An id node it has never paired, or a handle node without an owner, MUST be answered no. The answer is advice to the caller and MUST NOT refuse a transaction. Necessity: a caller holding a handle and an id learned at one moment asks whether the same account still holds that handle before it pays or links it; two identities of one wallet can pass a handle between them, and comparing owners would answer yes to a pair the chain no longer holds.
A Transaction Author’s name is the one handle the Consumer stores for that author to display, together with the platform the handle is on: the analogue of an ENS primary name. An author has at most one name across all platforms, however many identities it holds on one platform or on several. A handle is disclosed once a bind call or disclosure call the Consumer accepted has carried it; disclosure is history and nothing undoes it. A handle is published while it is its author’s name; publication is state.
-
REQ-PLAT-08J (upholds SP-BIND-01): The Consumer MUST change a Transaction Author’s name only through the paths this rule lists. On accepting a bind call that discloses a handle with the primary flag set, the Consumer MUST store the handle it folded under REQ-PLAT-08D, with the bind call’s platform, as the author’s name. On accepting a publishing call (REQ-PLAT-08E), it MUST store that call’s folded handle and platform as the caller’s name. A stored name replaces the name the author had on any platform. On a withdrawal call, the Consumer MUST clear the caller’s name, whatever its platform. Whenever a handle node’s owner changes from one author to another, or REQ-PLAT-08I clears it, the Consumer MUST clear the previous owner’s name if that name is on the node’s platform and folds to that node. A name on another platform stays. The Consumer MUST leave the name unchanged on a bind call without the primary flag, whether or not it discloses a handle, and on a reveal call. Necessity: a name is the one handle an author chose to show, as an ENS primary name is the one name an address shows; a claim of another of its identities, or a handle shown without being chosen, must not replace that choice, a name left in place after its handle moved would show a handle its author no longer holds, and losing a handle on one platform says nothing about a handle held on another.
-
REQ-PLAT-08K (upholds SP-BIND-01): The Consumer MUST store a name folded, with its platform. The Consumer MUST answer a read of an author’s name with the stored platform and handle while the author holds that handle on that platform. The Consumer MUST answer that the author has no name when none is stored or the author no longer holds the stored handle. Necessity: the platform selects the handle tag a reader hashes the name under, and REQ-PLAT-08J clears a name on every owner change, so a reader that mirrors the events of REQ-PLAT-08F answers the same as the read.
-
REQ-PLAT-08E (upholds SP-BIND-01): The Consumer MUST offer two disclosure calls, each taking a platform and a handle: a publishing call, which makes the handle the caller’s name (REQ-PLAT-08J), and a reveal call, which shows the handle and changes no state. For either, the Consumer MUST fold the handle under REQ-PLAT-05B, hash it under the platform’s handle tag, and accept the call only when the caller holds that node. The Consumer MUST NOT require a new proof or the account identifier for a disclosure. The Consumer MUST NOT write storage on a reveal call. The Consumer MUST also offer a withdrawal call that takes no platform and clears the caller’s own name. Necessity: the handle’s preimage is the evidence and ownership of its node the authority. A handle a later claim of the same account renamed away from has lost its owner to retirement (REQ-PLAT-08I), so the one test refuses it. A reveal lets an author show a handle it holds without making it its name. An author has one name, so a withdrawal needs no platform. A refused call protects the display, not the handle: its calldata is public whether or not the Consumer accepts it.
-
REQ-PLAT-08F (upholds SP-BIND-01, SP-PRIV-01): The Consumer MUST put no plaintext handle in the event of a binding, and MUST put the nodes there. The Consumer, like every other libid contract that accepts handle text, MUST emit one shared reveal event, carrying the platform, the handle node, and the folded handle and no address, for every accepted call that carries handle text. The Consumer MUST put handle text in no other event. On the Consumer those calls are a bind call with a handle, a publishing call, and a reveal call. The Consumer MUST emit a name event carrying the author, the platform, and the handle node for every name it stores, after that call’s reveal event. The Consumer MUST emit an event carrying the author for every name it clears. A stored name replaces the earlier one without a clearing event. Necessity: an event that carried a handle is public for good; one event for all handle text lets a reader build one map from handle node to handle out of every libid contract’s logs; and a reader mirrors names only if the events say when one was stored or cleared. An author has one name, so the author alone keys it.
-
TEST-PLAT-20A (exercises REQ-PLAT-03A, REQ-PLAT-08D, REQ-PLAT-08E, REQ-PLAT-08F, REQ-PLAT-08H, REQ-PLAT-08J): On each platform, a bind call without a handle, made with recognizable test values, carries no id, handle, email, or
subbytes in its calldata, its events, or any storage it writes, including a value split across two storage words. The same account bound with and without its handle lands on the same two nodes. The Consumer rejects a bind call whose handle does not fold to the handle node the Platform Verifier returned, the Platform Verifier’s result carries no handle, and a disclosed handle in another letter case is stored folded. A bind call with the primary flag and an empty handle is rejected and writes nothing. A bind call with a handle and without the primary flag emits the reveal event with the folded handle and leaves the name. A publishing call and a reveal call are each rejected from a caller that does not hold the handle and for a handle retired by a later claim of the same account, and accepted, without a proof, for a held handle given in any letter case. A reveal call of a held handle emits the reveal event and writes no storage. A bind call with the primary flag and a publishing call each emit the reveal event before the name event, and only the reveal event carries handle text, and it carries no address. Every other libid contract that accepts handle text emits the same reveal event. The Application passes the handle to the bind call only when the user asks to disclose it, sets the primary flag only when the user asks to make it its name, and no Submission Payload has a field for a handle or an id. -
TEST-PLAT-20B (exercises REQ-PLAT-08E, REQ-PLAT-08F, REQ-PLAT-08I, REQ-PLAT-08J, REQ-PLAT-08K): An identity that proves a new handle leaves its old handle without an owner, unless another identity proved that handle meanwhile, which keeps it; a retired node keeps its watermark, so evidence older than it binds nothing. Evidence not strictly newer than either node’s watermark is rejected. For a wallet holding identities A and B of one platform with A’s handle as its name, a claim of B without a handle leaves the name; a claim of B disclosing B’s handle with the primary flag replaces it. A rename of A withdraws A’s name; another wallet proving A’s handle withdraws it; a wallet moving an identity to another wallet withdraws the old wallet’s name for that handle. For a wallet whose name is its handle on platform P, a bind call on platform Q that discloses its Q handle with the primary flag, and a publishing call of that Q handle, each replace the name with the Q handle and platform Q and emit no clearing event; a bind call on Q that discloses nothing, one that discloses the Q handle without the primary flag, and a reveal call of the Q handle each leave the name. For a wallet whose name is on Q, a rename of its identity on P, and another wallet proving its P handle, leave the name. The name reads with its platform, and as none whenever its author no longer holds the handle. A withdrawal clears the name on whichever platform it is, and emits an event only when there was a name.
-
TEST-PLAT-20D (exercises REQ-PLAT-08O): After an identity proves a handle, the pair of that handle and its id node is answered yes. When identity A of a wallet renames and identity B of the same wallet then proves A’s old handle, the pair with A’s id node is answered no and the pair with B’s is answered yes. An identity that binds the same handle from another wallet keeps its pair. An id node never bound, and a handle with no owner, are answered no; none of these answers refuses a transaction.
2.1c The bind operation
Section titled “2.1c The bind operation”The bind call is the Consumer’s transaction kind under operation domain
keccak256(UTF8("libid.bind-identity")). Its Authorized Transaction Data
is the target address alone; on EVM, abi.encode(address), 32 bytes
(chain profiles §3.3). It also takes the service fee
amount and receiver of §2.1b, which no proof commits.
-
REQ-PLAT-08P (upholds SP-BIND-01, SP-REPLAY-01): The Consumer MUST accept a bind call only under the operation domain
keccak256(UTF8("libid.bind-identity")). The Consumer MUST reject Authorized Transaction Data that is not exactly one target in the Chain Profile’s encoding. The Consumer MUST reject a bind call whose Transaction Author is not that target. Necessity: the target is the one value the user authorized, and requiring the Transaction Author to be the target leaves a proof usable only by the wallet it names. -
REQ-PLAT-08Q (upholds SP-BIND-01): The Consumer MUST reject a bind call whose fee amount is zero while its fee receiver is nonzero, or whose fee amount is nonzero while its fee receiver is the zero address. The Consumer MUST reject a bind call whose native value differs from the quotation of common REQ-COMMON-06E plus the fee amount. On accepting a bind call with a nonzero fee amount, the Consumer MUST transfer exactly that amount to the fee receiver. The Consumer MUST reject the bind call when that transfer fails. The Consumer MUST emit an event naming the Authorization Digest, the fee receiver, and the amount for every fee it transfers. Necessity: the proof authorizes a binding, not a price, so the fee stays outside the digest and the Transaction Author agrees to it by authoring the call. Exact value needs no refund path, and
(0, zero address)as the only free form leaves no fee without a receiver. -
TEST-PLAT-20E (exercises REQ-PLAT-08P, REQ-PLAT-08Q): A bind call under another operation domain is rejected. Authorized Transaction Data that is not exactly one encoded target, including a target followed by a chain identifier, is rejected, and so is a bind call whose Transaction Author is not the target. One Submission is accepted by the Consumers of two Consumer Chains, once at each, and a second bind call of it on either is rejected. A fee amount without a receiver, and a receiver without an amount, are rejected. A native value one unit above or below the quotation plus the fee amount is rejected. An accepted bind call with a fee delivers exactly the amount to the receiver and emits the fee event; a receiver that refuses the value rejects the bind call; and a bind call without a fee moves exactly the quotation.
2.2 Metadata ordering and validity ceilings
Section titled “2.2 Metadata ordering and validity ceilings”Proof validity and mutable-metadata ordering use the authenticated times below.
| Identity platform | metadataObservedAt | proofValidUntil |
|---|---|---|
signed ID-Token exp | signed ID-Token exp | |
| X | the token attestation’s signed creation time | metadataObservedAt + proofLifetime |
| GitHub | the token-exchange attestation’s signed creation time | metadataObservedAt + proofLifetime |
For X and GitHub, “timestamp” is the signed TLSNotary attestation creation
time. The token attestation is the one-time PKCE and Authorization Digest
binding, so it alone supplies evidence time: one signed timestamp anchors both metadata
ordering and proof validity, exactly as Google’s single signed exp does.
The identity attestation opens the same bearer and carries the identity
fields; its own creation time is not an evidence-time input and does not
refresh the authorization. proofLifetime and maxFutureAttestationSkew are
the protocol parameters each profile fixes.
Google’s signed exp already supplies the accepted one-hour ordering and
validity value. A Google proof also requires its signing modulus to remain in
the Platform Verifier’s active set. The Authorization Digest carries no expiration.
metadataObservedAt is the monotone metadata watermark of common
REQ-COMMON-25A. Older evidence cannot regress stored metadata and does not
block an otherwise valid authority operation.
- REQ-PLAT-09 (upholds SP-FRESH-01):
The Platform Verifier MUST reject an X or GitHub attestation timestamp more than
its profile’s
maxFutureAttestationSkewahead of Block Time. - REQ-PLAT-09A (upholds SP-FRESH-01):
The Platform Verifier MUST derive
metadataObservedAtandproofValidUntilfrom the exact sources in the table above and from nothing else.
3. Google OIDC ceremony
Section titled “3. Google OIDC ceremony”identityPlatform = "google"platformCeremonyVersion = 1Google uses direct authentication-only OIDC and has no server-side token exchange. Identity evidence is the signed ID Token delivered in the redirect fragment.
3.1 Authorization request
Section titled “3.1 Authorization request”GET https://accounts.google.com/o/oauth2/v2/auth, serialized per
common §6:
| Order | Field | Exact value |
|---|---|---|
| 1 | response_type | id_token |
| 2 | response_mode | fragment |
| 3 | client_id | configured client identifier |
| 4 | redirect_uri | immutable redirect URI |
| 5 | scope | openid email |
| 6 | state | immutable one-use OAuth state |
| 7 | nonce | BASE64URL_NOPAD(authorizationDigest) |
The authorization request is plain browser navigation and is never notarized; no proof semantics attach to any field above. The table is operational guidance for obtaining a token whose signed claims satisfy §3.2–§3.3. The signed ID Token is the only Google evidence.
- REQ-PLAT-10 (upholds SP-BIND-01):
The Canonical Runtime MUST set
nonceto the base64url encoding of the 32 Authorization Digest bytes, not to hexadecimal text. - REQ-PLAT-11 (upholds SP-DELIVERY-01):
The Canonical Runtime MUST request only
response_type=id_tokenwithresponse_mode=fragment. The Canonical Runtime MUST NOT request an authorization code or access token. Necessity: the signed identity evidence reaches the local Redirect Runtime without introducing a confidential backend or bearer capability. - REQ-PLAT-12 (upholds SP-DELIVERY-01):
The Callback MUST copy the bounded query and fragment into memory and
clear both before storage or network access. The Prover MUST require
an empty query and a fragment carrying exactly one
stateplus exactly oneid_tokenXORerror. The Prover MUST reject duplicate, additional authoritative, mixed-transport, or malformed fields and MUST ignore diagnostic fields. - REQ-PLAT-13 (upholds SP-DELIVERY-01):
The Prover MUST match
stateto its bound live ceremony and accept that return only once before using the ID Token. No server-side state, prepare request, or application-wide ceremony lookup participates in this check.
Conformance vector, for the Authorization Digest of common §5:
authorizationDigest = 0x24083f6c849372d1fa0a95615a7b2b6a888edef92e9fa2bebd520b2f536e9350Google nonce = JAg_bISTctH6CpVhWnsraoiO3vkun6K-vVILL1Nuk1A3.2 Browser token validation
Section titled “3.2 Browser token validation”- REQ-PLAT-14 (upholds SP-BIND-01):
The Prover MUST parse the token’s
nonceas canonical unpadded base64url encoding of exactly 32 bytes and use those bytes as the candidate Authorization Digest public input to the circuit. The expected digest remains Application-side and is not an additional Prover input; Prover performs no nonce-versus-expected-digest comparison. REQ-PLAT-16 and REQ-PLAT-18 bind the candidate to the signed token; common REQ-COMMON-02 and REQ-COMMON-02A bind the proof to the authorization that the Consumer is asked to accept. - REQ-PLAT-14A (upholds SP-BIND-01, SP-CLIENT-01): Before delivery, the Prover MUST require the generated proof’s public inputs to equal the projection of its parsed token claims and selected signing modulus under REQ-PLAT-16B, using the selected artifact’s field encoding.
- REQ-PLAT-14B (upholds SP-BIND-01, SP-CLIENT-01):
Before accepting a Google result, the Application MUST require its delivered
public inputs to equal the projection of the retained Authorization Digest,
validated identity fields, token expiry, and signing modulus under
REQ-PLAT-16B, using the same artifact encoding. This checks result consistency,
not the proof’s cryptographic validity or the modulus’s trusted-set membership.
It requires neither the ID Token nor a new
ProveIdentityfield. The projection takes the id node of the delivereduserId, hashed under REQ-PLAT-05A, and the handle node of the delivereduserName, folded and hashed under REQ-PLAT-05B. - REQ-PLAT-15:
The Prover MUST reject a Google response carrying
codeoraccess_token. Necessity: neither artifact belongs to this authentication-only profile.
3.3 Proof statement
Section titled “3.3 Proof statement”The Proving Circuit and Consumer enforce all of the following:
- REQ-PLAT-16 (upholds SP-CLIENT-01):
The Proving Circuit MUST hash the exact ASCII
BASE64URL_NOPAD(header) || "." || BASE64URL_NOPAD(payload)bytes with SHA-256. The Proving Circuit MUST verify the signature as RSASSA-PKCS1-v1_5 under the exact RSA modulusnand the profile-fixed exponente = 65537. The Proving Circuit MUST decode the claims checked below from that signed payload, not from a detached copy.
The profile fixes RS256; the circuit performs no algorithm dispatch and does
not parse the protected header. A signature that does not satisfy this fixed
relation under the supplied modulus fails in circuit. A valid signature under
an untrusted modulus can satisfy the circuit but fails REQ-PLAT-23 downstream.
Algorithm-confusion attacks require a verifier that dispatches on the header
alg; none exists here.
-
REQ-PLAT-16A (upholds SP-CLIENT-01): The Proving Circuit MUST expose the exact RSA modulus used for REQ-PLAT-16 as a public proof input. Its internal field or limb representation belongs to the proving and verifier artifacts, not the Platform Profile. The Proving Circuit MUST NOT decide trusted-set membership or take the active set as an input. The Platform Verifier alone checks the modulus under REQ-PLAT-23. JWK decoding and canonical-encoding validation happen where a modulus is admitted to the trusted set, per REQ-PLAT-24; the JWK encoding appears in no signed artifact, so proving it would add nothing.
-
REQ-PLAT-16B (upholds SP-BIND-01, SP-CLIENT-01, SP-FRESH-01): The Proving Circuit MUST expose exactly the following Google public inputs, each derived from the signed payload or verified signing key:
Public input Authenticated source Authorization Digest signed nonce, decoded as exactly 32 bytesclient-identifier digest SHA256of the signedaudid node signed sub, hashed per REQ-PLAT-05Ahandle node signed email, checked and folded per REQ-PLAT-16D and hashed per REQ-PLAT-05Bevidence timestamp signed exp; used for bothmetadataObservedAtandproofValidUntilRSA modulus exact nthat verified the JWS;e = 65537is profile-fixedThe Proving Circuit MUST NOT expose a detached second representation of a claim.
The launch oidc-google artifact encodes these as 57 field elements: the
digest as 32 one-byte elements (0-31), then each 32-byte digest or node as
[high, low] 16-byte big-endian halves, the client-identifier digest at
32-33, the id node at 34-35 and the handle node at 36-37, then exp at 38
and the modulus as 18 limbs at 39-56.
- REQ-PLAT-16C (upholds SP-BIND-01, SP-PRIV-01):
The Proving Circuit MUST NOT expose the signed
subor the signedemailin any public input. The Proving Circuit MUST reject asubthat REQ-PLAT-06 rejects, and MUST hash exactly the signedsubbytes under REQ-PLAT-05A. Necessity: thesuband theemailstay off the Consumer Chain, which leaves the Proving Circuit the only role that sees their bytes. - REQ-PLAT-16D (upholds SP-BIND-01):
The Proving Circuit MUST admit an
emailonly when the check of §2.1a admits it, MUST fold it under REQ-PLAT-05B, and MUST hash exactly the folded bytes. The Proving Circuit MUST fail to prove, rather than truncate, asublonger than 31 bytes or anemaillonger than 62 bytes. Necessity: neither value holds an escaped byte, since every JSON escape begins with\, which the check refuses, and the absence of"keeps theemailthe circuit reads inside its own JSON string. - REQ-PLAT-17 (upholds SP-BIND-01):
The Proving Circuit MUST prove the signed
issequalshttps://accounts.google.com. - REQ-PLAT-18 (upholds SP-BIND-01):
The Proving Circuit MUST bind the signed
nonceto the exact canonical encoding of its 32-byte Authorization Digest public input. The Platform Verifier MUST verify that proof against the digest recomputed under common REQ-COMMON-02 and REQ-COMMON-02A, not a browser-supplied candidate digest. - REQ-PLAT-19 (upholds SP-CLIENT-01):
The Proving Circuit MUST expose
SHA256of the signedaudas the client-binding public input. - REQ-PLAT-19A (upholds SP-CLIENT-01):
The Platform Verifier MUST require
SHA256of theaudbytes carried in the Submission to equal that public input. The Platform Verifier MUST return those bytes as the client identifier of common REQ-COMMON-16. Necessity: the digest authenticates the bytes without the circuit packing a variable-length string into public inputs, and the Consumer still receives the readable value. Admission stays permissionless per common REQ-COMMON-17C. - REQ-PLAT-20:
The Proving Circuit MUST prove
email_verifiedis the booleantrue. Necessity: an unverified email would let one account assert another party’s address as its handle. - REQ-PLAT-21 (upholds SP-BIND-01):
The prover supplies the offset of each checked claim as a private input.
The Proving Circuit MUST check the string claims
iss,sub,aud,nonce, andemailunder common REQ-COMMON-19 and REQ-COMMON-19B. The Proving Circuit MUST checkexpas a canonical unsigned JSON integer bounded by an unsigned 64-bit integer, andemail_verifiedas the exact unquoted JSON booleantrue, under common REQ-COMMON-19D. Duplicate-free top-level structure is the issuer’s behavior under ASM-PROV-06; the circuit performs no search and no duplicate scan. - REQ-PLAT-22 (upholds SP-FRESH-01):
The Platform Verifier MUST reject a proof whose signed
expplacesproofValidUntilat or before Block Time.
The signing key is fetched from Google’s JWKS endpoint as witness input.
- REQ-PLAT-23 (upholds SP-CLIENT-01): The Platform Verifier MUST reject a proof whose RSA modulus is absent from the Platform Verifier’s active trusted Google modulus set.
- REQ-PLAT-24: The Verifier Governance Process MUST add a newly published Google signing modulus to the trusted set before Google signs with it in production. Necessity: Google rotates signing keys on the order of weekly, so every Google ceremony fails closed while an active modulus is untrusted.
4. Browser TLSNotary launch transport
Section titled “4. Browser TLSNotary launch transport”Launch fixes X’s /2/oauth2/token and /2/users/me sessions and GitHub’s
/login/oauth/access_token and /user sessions to the Proxy profile.
| Property | Proxy profile | Browser MPC profile |
|---|---|---|
| Platform connection | notary connects to the pinned platform endpoint | deployment module supplies an encrypted WebSocket-to-TCP bridge |
| Platform sees | notary egress | application redirect-origin egress |
| Deployment sees OAuth plaintext | no | no |
| Soundness | notary-to-platform path must not be adversarial | survives an adversarial byte bridge |
| Cost | lower bandwidth, latency, rounds | higher browser and deployment cost |
- REQ-PLAT-25 (upholds SP-EXCHANGE-01): The Implementation MUST discard every partial transcript and TLS state before retrying a notarized request.
- REQ-PLAT-26 (upholds SP-EXCHANGE-01): The Implementation MUST NOT use application-controlled platform egress in the launch profile, because the prover holds the session keys and prover-egress collusion could inject authenticated server-direction records.
- REQ-PLAT-27 (upholds SP-EXCHANGE-01): The Canonical Runtime MUST NOT let an application, user, request, browser probe, failure, or retry select Browser MPC or switch transport within a launch ceremony.
- REQ-PLAT-28 (upholds SP-DELIVERY-01):
The Prover MUST require the X or GitHub authorization redirect to
carry an empty fragment and a query containing exactly one
stateplus exactly onecodeXORerror. The Prover MUST reject duplicate, mixed-transport, additional authoritative, and malformed fields. The single acceptedcodeis the code consumed at redirect ingress that REQ-PLAT-29 and REQ-PLAT-46 compare against. GitHub’s requiredissunder REQ-PLAT-34A is a profile field, not an additional authoritative field to reject. - REQ-PLAT-28A (upholds SP-DELIVERY-01):
The Prover MUST match the redirect’s
stateto its bound live ceremony and accept that return only once before starting the token request. No server-side state, prepare request, or application-wide ceremony lookup participates in this check.
Browser MPC is a deferred protocol alternative. It may remove the notary-to-platform path assumption and notary egress exposure, but it requires qualification of browser bandwidth, latency, memory, battery, WebSocket-to-TCP bridging, and mobile suspension. Adopting it requires a new ceremony profile whenever it changes the proof statement, attestation format, ceremony behavior, or security assumptions; it is not deployment configuration under the X or GitHub profile.
5. X ceremony
Section titled “5. X ceremony”identityPlatform = "x"platformCeremonyVersion = 1X uses a public client with S256 PKCE and two browser-owned TLSNotary sessions.
5.1 Authorization request
Section titled “5.1 Authorization request”GET https://x.com/i/oauth2/authorize:
| Order | Field | Exact value |
|---|---|---|
| 1 | response_type | code |
| 2 | client_id | configured client identifier |
| 3 | redirect_uri | immutable redirect URI |
| 4 | scope | tweet.read users.read |
| 5 | state | immutable one-use OAuth state |
| 6 | code_challenge | PKCE challenge per common §7 |
| 7 | code_challenge_method | S256 |
5.2 Token request
Section titled “5.2 Token request”POST https://api.x.com/2/oauth2/token, media type
application/x-www-form-urlencoded, Accept: application/json:
| Order | Field | Exact value |
|---|---|---|
| 1 | grant_type | authorization_code |
| 2 | client_id | configured client identifier |
| 3 | code | consumed redirect code |
| 4 | redirect_uri | immutable redirect URI |
| 5 | code_verifier | PKCE verifier per common §7 |
The whole request is revealed. The table fixes field order for canonical serialization under common §6, not to protect a hidden suffix; REQ-PLAT-63 holds the complete body to the canonical form grammar of common REQ-COMMON-07A.
- REQ-PLAT-29 (upholds SP-EXCHANGE-01):
The Implementation MUST reveal the token request’s
coderange. The Prover MUST require that revealed serialized value to equal the canonical form serialization of the code it parsed from the retained OAuth return, byte for byte, under common REQ-COMMON-07. - REQ-PLAT-30 (upholds SP-BIND-01): The Proving Circuit MUST constrain the opened bearer range to nonempty printable ASCII of at most 128 bytes. The carriage-return and line-feed exclusion of common REQ-COMMON-37 applies to this range, because the identity session sends it inside a header. Necessity: the range is opened to link two attestations, so it needs a bound and a charset; the circuit verifies no other property of the token response.
- REQ-PLAT-63 (upholds SP-EXCHANGE-01, SP-BIND-01, SP-CLIENT-01):
The Prover and Platform Verifier MUST require the complete request body to
satisfy the canonical form grammar of common REQ-COMMON-07A for exactly the
five fields in the table, in that order.
The Prover and Platform Verifier MUST reject malformed encoding, noncanonical
spelling, an extra or duplicate field, or bytes outside that complete body.
The Prover and Platform Verifier MUST enforce common REQ-COMMON-16B’s
charset for
client_idand common §7’s canonical unpadded base64url encoding of exactly 32 bytes forcode_verifier.codeandredirect_uriare nonempty values with no further constraint: the Platform Verifier reads neither and judges only their bytes under REQ-COMMON-07A, and the Canonical Runtime compares them with the values it serialized under REQ-PLAT-29 and REQ-PLAT-29C. Thegrant_typevalue is the exact ASCII bytesauthorization_code, which REQ-PLAT-56 compares. Norefresh_token, device-flow field, or other grant field is admitted; the pinned endpoint receives only this authorization-code request. Acceptance does not depend on X rejecting malformed or duplicate forms. The Canonical Runtime’s comparisons ofcode,grant_typeandredirect_uriunder REQ-PLAT-29 and REQ-PLAT-29C are separate local checks; the Platform Verifier’s digest-to-verifier comparison remains common REQ-COMMON-15A. Necessity: revealing a range does not reject a form delimiter inside it, and the circuit does not scan for duplicates under common REQ-COMMON-19C; holding the whole body removes the platform’s parser from the soundness argument.
The request is one revealed range: the request line, every header and the body. The rows below name what the Platform Verifier reads out of it, not separate ranges; the attested record carries adjacent revealed ranges as one, so a plan of one range per field would not survive signing.
Per common §9, the token session reveals exactly these ranges; every other byte stays behind a charset-constrained range commitment of the pinned attestation format:
| Range | Revealed | Why |
|---|---|---|
| the request line and every request header | yes | the Platform Verifier compares the method and path with its profile constants, requires host and the media type that selects the parser the platform applied to the body rows beneath this one (common REQ-COMMON-21B), and refuses the headers REQ-PLAT-56A forbids |
| endpoint authority | not a range | the Notary Service authenticated the TLS server identity, and the Platform Verifier compares the attested authority against its pinned constant per common REQ-COMMON-21A |
grant_type | yes | constant authorization_code; the Platform Verifier compares it byte for byte per REQ-PLAT-56 |
client_id | yes | the Platform Verifier reads and returns it |
code | yes | Prover compares the code parsed from its retained OAuth return |
redirect_uri | yes | Prover compares the Application’s frozen redirect URI; no chain or circuit value |
code_verifier | yes | the Platform Verifier recomputes it from the digest and authorizationNonce per common REQ-COMMON-15A |
| attestation timestamp | not a range | the attestation’s own signed creation time, which derives the authenticated validity ceiling per §2.2 |
"access_token":" and the closing quote immediately around the bearer value | yes | anchor the committed bearer range as that field’s value, per common REQ-COMMON-18A |
| bearer range | committed | a blinded commitment, opened only in circuit |
| everything else | no | the response status line and headers, scope, token_type, other response fields |
Neither the authority nor the attestation timestamp is a transcript range.
The authority reaches the Platform Verifier as
the TLS server identity the Notary Service authenticated under common
REQ-COMMON-21, carried in the attested data: the transcript
holds the authority only in a host header, which the first row reveals and
REQ-PLAT-56A holds to the pinned authority, and that header is
prover-composed text that says nothing about which server answered. The
timestamp is the signed creation time of the attested data itself, which is why common REQ-COMMON-25 can forbid inferring it from a
response header. The two delimiter reveals are what anchor the committed range in the
received direction, which would otherwise reveal no byte at all and leave that
range indistinguishable from a refresh_token value.
Those reveals and the Platform Verifier’s code_verifier comparison under
REQ-COMMON-15A bind the request, but revealing a range does not reject a form
delimiter inside it. REQ-PLAT-63 therefore holds the complete body to the exact
five-field form; X, like GitHub, does not depend on ASM-PROV-07.
- REQ-PLAT-29A (upholds SP-CLIENT-01):
The Implementation MUST reveal the
client_idrange of the token request in the notarized session. - REQ-PLAT-29B (upholds SP-CLIENT-01): The Platform Verifier MUST read the client identifier from that revealed range and return its exact bytes. The Proving Circuit MUST NOT expose a client identifier public input. Necessity: the attestation already authenticates those bytes, so a circuit copy would be a second representation of one fact.
- REQ-PLAT-29C (upholds SP-EXCHANGE-01):
The Implementation MUST reveal the token request’s
grant_typeandredirect_uriranges in the notarized session, including in the attestation the Platform Verifier checks. The Prover MUST reject a transcript whose revealed serializedgrant_typeorredirect_urivalue differs from the canonical form serialization of, respectively, the selected profile’sauthorization_codeconstant or the Application’s frozen redirect URI. Neither value is a circuit constraint or a public proof input, and neither is a value the Consumer reads; the Platform Verifier compares the revealedgrant_typeitself under REQ-PLAT-56. Revealing them narrows the body a prover can compose without being observed; it does not by itself exclude a duplicate field, which REQ-PLAT-63 rejects over the complete body. The Platform Verifier enforces the disclosure: an attestation hiding either range does not match the profile layout of common REQ-COMMON-17A and REQ-COMMON-18A and fails verification.
The request carries host: api.x.com,
content-type: application/x-www-form-urlencoded, and a content-length of the
body’s own count. The Canonical Runtime also sends accept: application/json
and connection: close, which nothing verifies, and may send any other header
REQ-PLAT-56A does not forbid.
- REQ-PLAT-56A (upholds SP-EXCHANGE-01):
The Implementation MUST reveal the token request’s request line and every
header. The Platform Verifier MUST reject a head without exactly one
hostnaming the pinned authority and exactly onecontent-typewhose value isapplication/x-www-form-urlencoded, comparing names lowercased with every space and tab removed and_read as-, as common REQ-COMMON-39B normalizes, and values exactly once the optional whitespace around them is removed. The Platform Verifier MUST reject a head carryingauthorizationor any name common REQ-COMMON-39B forbids, under any spelling of the name. The Platform Verifier MUST ignore every other header,content-lengthexcepted, which REQ-PLAT-56B holds to the body it frames. Necessity: common REQ-COMMON-21B fixes the media type because it “selects the platform’s request parser”, and a media type nothing compares is a pin in name only. The forbidden headers change what the platform does with the request in a way no revealed byte shows: which client it authenticates, which session it answers for, which bytes it parses, which method it runs. Any other header changes only what the platform answers, and a wrong answer is a response the verifier cannot read rather than one it can be fooled by, so requiring its absence would bind every prover to one HTTP library’s habits for nothing. - REQ-PLAT-56B (upholds SP-EXCHANGE-01):
The Platform Verifier MUST reject a token request whose revealed bytes carry
other than exactly one empty line, the one that ends the head. The Platform
Verifier MUST reject a head without exactly one
content-length, or with one other than the decimal count of the body it frames, written without a leading zero. Necessity: the verifier takes the body to be what follows the head while the platform takes it to becontent-lengthbytes, so where the two disagree the fields read are not the fields parsed; a second empty line is a second place a parser could end the head, and a second spelling of the count is a second thing to compare one spelling of. - REQ-PLAT-56C (upholds SP-EXCHANGE-01): The Platform Verifier MUST reject a head carrying a line feed not preceded by a carriage return, a carriage return not followed by a line feed, a line beginning with a space or a tab, or a header field line with no colon. The colon requirement excludes the separately validated request line and the empty line terminating the head. Necessity: a parser accepting a bare line feed, a bare carriage return or a fold ends the head somewhere this one does not, moving bytes between head and body, and a line no colon splits is not a header field, so a parser that tolerates one reads a head this one cannot. Common REQ-COMMON-39A asks the first three of the identity request.
- REQ-PLAT-56 (upholds SP-EXCHANGE-01):
The Platform Verifier MUST reject an X token attestation whose revealed
grant_typediffers from the exact ASCII bytesauthorization_code. Necessity: the Canonical Runtime’s comparison under REQ-PLAT-29C runs in software the prover chooses whether to run, andgrant_typeis the one revealed field that changes what X did with the request. A body sendinggrant_type=refresh_tokenwhile still carrying acode, aredirect_uri, and a digest-derivedcode_verifieris processed as a refresh: X ignores the fields that grant does not use, returns a fresh bearer, and every revealed range still checks out, so an application holding a user’s refresh token could mint identity proofs at arbitrary addresses indefinitely from a single consent. The check is one byte comparison on the Consumer Chain and adds nothing to the Proving Circuit. - REQ-PLAT-30A (upholds SP-EXCHANGE-01):
The Implementation MUST commit the returned
access_tokenrange of the notarized token session as the attestation format’s blinded hash commitment. The Implementation MUST keep the plaintext token bytes redacted. - REQ-PLAT-57 (upholds SP-EXCHANGE-01):
The Implementation MUST reveal the
"access_token":"delimiter bytes immediately preceding that committed range and the closing quote byte immediately following it. The Platform Verifier MUST reject an X token attestation whose committed range is not framed by exactly those revealed bytes. Necessity: common REQ-COMMON-18A wants a revealed anchor on every hidden range, and a received direction revealing nothing at all leaves the committed range indistinguishable from arefresh_tokenvalue or any other substring the prover chose to commit.
5.3 Identity request
Section titled “5.3 Identity request”GET https://api.x.com/2/users/me with no query and no body. The request
carries these four headers, in any order, and may carry others:
authorization: Bearer <access_token>, accept: application/json,
host: api.x.com, and connection: close. The Platform Verifier compares
the request line, the authorization line, and the host line under common
REQ-COMMON-39D, refuses the names common REQ-COMMON-39B forbids, and
compares no other header.
Per common §9, the identity session reveals exactly these request ranges; the bearer value is the only committed request range, and every other request byte is revealed:
| Range | Revealed | Why |
|---|---|---|
| request line and all request headers, except the bearer value | yes | the Platform Verifier runs the line-anchored uniqueness scan of common REQ-COMMON-39 over these bytes and frames the committed range per common REQ-COMMON-40 |
bearer value of the authorization header | committed | a blinded commitment, opened only in circuit |
The two ranges account for the request’s signed transcript length exactly, with no gap and no overlap, per common REQ-COMMON-35 and REQ-COMMON-36, so the request leaves no byte undisclosed and uncommitted.
The response direction reveals only the anchors around the two identity values. Each value is its own committed range, and every other response byte stays behind a range commitment of the pinned attestation format:
| Range | Revealed | Why |
|---|---|---|
"id":" and, after the value, its closing quote | yes | anchor the committed id range per REQ-PLAT-31 |
the data.id value | committed | its own range commitment, opened only in circuit (REQ-PLAT-32D) |
"username":" and, after the value, its closing quote | yes | anchor the committed username range per REQ-PLAT-31 |
the data.username value | committed | its own range commitment, opened only in circuit (REQ-PLAT-32D) |
| everything else | no | status line, headers, display name, and every other response field |
Every committed range of this direction is bounded by a revealed delimiter on each side that faces one, and by the signed transcript boundary of common REQ-COMMON-36 at the two ends, which is the anchoring common REQ-COMMON-18A requires.
-
REQ-PLAT-59 (upholds SP-BIND-01, SP-PRIV-01): The Implementation MUST reveal the full
"id":"and"username":"delimiters and their closing quotes in the/2/users/meresponse, and MUST commit each value, exactly the bytes between its delimiter and its closing quote, as its own range. The Implementation MUST redact every other response byte behind a range commitment. Necessity: the anchors are what tell the Platform Verifier which commitments hold the identity, and a revealed value would put the id and handle on the Consumer Chain. -
REQ-PLAT-31 (upholds SP-BIND-01): The Platform Verifier MUST find the
idcommitment and theusernamecommitment by their anchors: for each field, the one range commitment that starts where a revealed range ending with the full"field":"delimiter ends, and that is followed by a revealed". The Platform Verifier MUST reject a transcript in which either delimiter matches at more than one position of the revealed bytes, per common REQ-COMMON-19A, or in which no commitment, or more than one, is so framed. The Platform Verifier MUST pass those two commitments to the proof as REQ-PLAT-32B fixes. Necessity: the response carries account-holder-influenced text, such as the display name, that can embed a lookalike field. A second copy of a delimiter hidden inside a committed range is invisible to that scan; ASM-PROV-06 is what excludes it. -
REQ-PLAT-31A (upholds SP-BIND-01): On X and on GitHub, the Prover MUST derive the
userIdand raw handle from the bytes of the id and handle commitments the platform’s framing rule (REQ-PLAT-31, REQ-PLAT-51) finds, which are the bytes it opens in the proof, and MUST copy them, never through a number parse. The Proving Circuit’s opening is the authoritative extraction under common REQ-COMMON-19E; the Prover’s is the repeat it permits. The Proving Circuit MUST NOT expose a second independently supplied representation of either identity field. Before delivery, the Prover MUST require the proof’s id node and handle node public inputs to equal the id node of theuserIdand the handle node of the raw handle it delivers, under REQ-PLAT-05A and REQ-PLAT-05B with the platform’s tags. -
REQ-PLAT-32 (upholds SP-EXCHANGE-01): The Proving Circuit MUST assert that one private bearer value opens the bearer commitment of the token attestation and the
Authorizationbearer commitment of the/2/users/meattestation. The two commitment values differ, because common REQ-COMMON-44 draws an independent blinder for each notarized session. -
REQ-PLAT-32A (upholds SP-EXCHANGE-01): The Implementation MUST commit the
Authorizationbearer range of the notarized identity session as the attestation format’s blinded hash commitment. The Implementation MUST keep the plaintext token bytes redacted. Necessity: each notarized session carries its own blinder, so the same bearer commits to two different values. Nothing outside a proof can tell that they open to one bearer, which is the reason a circuit exists at all. -
REQ-PLAT-32D (upholds SP-BIND-01): The Proving Circuit MUST open the id commitment and the handle commitment of the
/2/users/meattestation to the rawidandusernamevalues, check them under §2.1a, and prove the id node and handle node of REQ-PLAT-05A and REQ-PLAT-05B over exactly those opened bytes, folding the handle from the same witness that opened its commitment. X and GitHub prove under one bearer-link circuit, which hashes under the two tags its public inputs carry. Necessity: the values stay committed (§5.3), so the circuit is the only role that sees them; and the Platform Verifier, not the prover, supplies the tags (REQ-PLAT-32C), so a proof made under one platform’s tags fails under another’s. -
REQ-PLAT-32B (upholds SP-EXCHANGE-01, SP-BIND-01): The Proving Circuit MUST expose exactly these sixteen public inputs, each 32-byte value as two field elements holding its high and low 16 bytes, big-endian, high first, and each tag as one field element holding its bytes from the top of 31 bytes, zero after, followed by its length:
Index Public input Meaning 0-1 token bearer commitment the commitment the Platform Verifier matches against the verified token attestation 2-3 identity bearer commitment the bearer commitment the Platform Verifier matches against the verified /users/meattestation4-5 id commitment the idcommitment the Platform Verifier frames in that attestation’s response6-7 handle commitment the usernamecommitment the Platform Verifier frames in that response8-9 id node REQ-PLAT-05A over the opened id10-11 handle node REQ-PLAT-05B over the opened username12-13 user-id tag, its length X’s user-id tag of REQ-PLAT-05C 14-15 handle tag, its length X’s handle tag of REQ-PLAT-05C The Proving Circuit MUST keep the bearer, the id, and the handle private. The Proving Circuit MUST NOT add an Authorization Digest, client identifier, timestamp, or endpoint public input.
-
REQ-PLAT-32C (upholds SP-EXCHANGE-01, SP-BIND-01): The Platform Verifier MUST take the two nodes from the Submission Payload, MUST verify the proof against public inputs it builds from the four commitments in the attestations it verified, those two nodes, and its own platform’s two tags, and MUST return the nodes only from a proof that verifies. Necessity: built from the attestations and the verifier’s platform, the inputs leave the circuit no way to prove a link between two attestations other than the ones submitted, nodes of values other than the ones the platform sent, or nodes under another platform’s tags; a node the payload names wrongly is a proof that does not verify.
The circuit proves two things: one hidden bearer opens both attestations’ bearer commitments, and the committed id and handle, each one whole JSON token, hash to the two nodes under X’s tags. Everything else is checked where it can be seen — the Platform Verifier binds the Authorization Digest by recomputing the verifier under common REQ-COMMON-15A, reads the client identifier, evidence timestamp, request method, and path from revealed attestation bytes, frames the id and handle commitments by their revealed anchors, and takes each session’s authority from the TLS server identity that session’s attestation authenticates. The circuit carries no copy of any of them, because a fact that can be checked in the open does not belong in a proof.
- REQ-PLAT-33 (upholds SP-FRESH-01): The Canonical Runtime MUST complete the request direction of X’s first notarized token session before X’s 30-second authorization-code deadline. The deadline ends when X has received the complete token request; receiving or notarizing the response, running the identity session, proving, and proof delivery are outside it. The Canonical Runtime MUST abandon the ceremony if it cannot complete that request direction in time.
6. GitHub ceremony
Section titled “6. GitHub ceremony”identityPlatform = "github"platformCeremonyVersion = 1GitHub uses a public client with S256 PKCE. The Prover, the browser proving
part of the Canonical Runtime, performs both the token exchange and /user
inside browser-owned notarization sessions. The OAuth application credential
called client_secret by GitHub is intentionally public: it is supplied in
public configuration, sent in the token request, and revealed in its attestation.
It is not proof of the caller’s authority.
This follows GitHub’s public-client guidance, which includes single-page applications and recommends PKCE. The profile removes server-side token-exchange custody; browser and service specifications own the configuration and delivery of these public inputs.
As on X, the evidence is two attestations and one hidden bearer linking
them, and the circuit opens the committed id and handle. GitHub proves under
X’s bearer-link circuit, with GitHub’s tags as its tag inputs.
The Platform Verifier binds the revealed code_verifier to the Authorization
Digest under common REQ-COMMON-15A; the circuit does not perform that binding.
6.1 Authorization request
Section titled “6.1 Authorization request”GET https://github.com/login/oauth/authorize:
| Order | Field | Exact value |
|---|---|---|
| 1 | client_id | configured client identifier |
| 2 | redirect_uri | immutable redirect URI |
| 3 | scope | read:user |
| 4 | state | immutable one-use OAuth state |
| 5 | code_challenge | PKCE challenge per common §7 |
| 6 | code_challenge_method | S256 |
- REQ-PLAT-34:
The Canonical Runtime MUST request exactly
read:user. Necessity: GitHub inherits previously granted scopes for the same OAuth application, so an omitted scope does not yield a known grant.
6.1a Authorization return
Section titled “6.1a Authorization return”GitHub returns the authorization response in the redirect query. In addition
to state and exactly one of code or error, the response carries iss, the
authorization-server issuer identifier defined by RFC9207. This profile pins
https://github.com/login/oauth, matching
GitHub’s authorization-server metadata.
The expected value is a profile constant, not discovered from the response or
the configured redirect URI.
- REQ-PLAT-34A (upholds SP-DELIVERY-01):
The Prover MUST accept a GitHub success, denial, or other OAuth error response
only when its query contains exactly one
isswhose value, decoded once asapplication/x-www-form-urlencoded, equalshttps://github.com/login/oauthby exact string comparison. Necessity: matching the response issuer to the selected authorization server prevents OAuth authorization-server mix-ups before credentials are used.
Missing, duplicate, malformed, or mismatched issuer values are browser
rejections before token exchange, not valid denials. Equivalent form-encoding
spellings are accepted, but URL normalization, case folding, trailing-slash
removal, and default-port removal do not repair a different decoded value.
This check precedes success/denial/error classification; a matching issuer
does not waive the state or remaining return checks. iss identifies the
authorization server, not the user or OAuth client, and is not itself signed
evidence. It adds no proof input or browser-protocol message field and does
not replace downstream verification.
6.2 Token exchange
Section titled “6.2 Token exchange”POST https://github.com/login/oauth/access_token, media type
application/x-www-form-urlencoded, Accept: application/json:
| Order | Field | Exact value |
|---|---|---|
| 1 | client_id | configured client identifier |
| 2 | code | consumed redirect code |
| 3 | redirect_uri | immutable redirect URI |
| 4 | code_verifier | PKCE verifier per common §7 |
| 5 | client_secret | public OAuth application credential frozen for this ceremony; revealed |
The whole request is revealed. The table fixes field order for canonical
serialization under common §6, not to protect a hidden suffix. The credential
stays in the form body so the authenticated client identifier and the complete
request grammar have one representation; an Authorization header remains
forbidden by REQ-PLAT-56A.
-
REQ-PLAT-61 (upholds SP-EXCHANGE-01, SP-BIND-01, SP-CLIENT-01): The Prover and Platform Verifier MUST require the complete request body to satisfy the canonical form grammar of common REQ-COMMON-07A for exactly the five fields in the table, in that order. The Prover and Platform Verifier MUST reject malformed encoding, noncanonical spelling, an extra or duplicate field, or bytes outside that complete body. The Prover and Platform Verifier MUST enforce common REQ-COMMON-16B’s charset for
client_idand common §7’s canonical unpadded base64url encoding of exactly 32 bytes forcode_verifier.code,redirect_uriandclient_secretare nonempty values with no further constraint: the Platform Verifier reads none of them and judges only their bytes under REQ-COMMON-07A, and the Prover comparescodeandredirect_uriwith the values it serialized under REQ-PLAT-46 and REQ-PLAT-48A. Nogrant_type,refresh_token, device-flow field, or other extension is admitted; the pinned endpoint receives only this authorization-code request. Acceptance does not depend on GitHub rejecting malformed or duplicate forms. The Prover additionally comparescodeandredirect_uriwith its captured code and frozen redirect under REQ-PLAT-46 and REQ-PLAT-48A. Those local expected values are not Platform Verifier inputs; its digest-to-verifier comparison remains common REQ-COMMON-15A. -
REQ-PLAT-35A (upholds SP-CLIENT-01): The Proving Circuit MUST NOT expose
client_secret, or any value derived from it, as a public proof input. Necessity: the complete request already reveals it; the circuit’s public inputs are commitments and nodes, not duplicated request fields. -
REQ-PLAT-35B (upholds SP-CLIENT-01): The Implementation MUST reveal the
client_idrange of the exchange request in the notarized session. -
REQ-PLAT-35C (upholds SP-CLIENT-01): The Platform Verifier MUST read the client identifier from that revealed range and return its exact bytes. The Proving Circuit MUST NOT expose a client identifier public input. Necessity: the attestation already authenticates those bytes, so a circuit copy would be a second representation of one fact.
-
REQ-PLAT-36 (upholds SP-BIND-01): The Proving Circuit MUST constrain the opened bearer range to nonempty printable ASCII of at most 128 bytes. The Proving Circuit MUST verify no other property of the exchange response. The carriage-return and line-feed exclusion of common REQ-COMMON-37 applies to this range, because the
/usersession sends it inside a header. Necessity:token_typeand the grantedscopeare response schema, and nothing on the Consumer Chain acts on them; the Canonical Runtime MAY check them locally.
6.3 Browser notarization
Section titled “6.3 Browser notarization”- REQ-PLAT-62 (upholds SP-EXCHANGE-01, SP-CLIENT-01): The Prover MUST perform the token and identity sessions using browser TLSNotary Proxy mode with the same selected notary address and the endpoints fixed by this profile. The Prover MUST use the client identifier, public application credential, and redirect URI frozen for the ceremony. The Prover MUST NOT delegate the token exchange to an OAuth Bridge or fetch replacement configuration during the ceremony. Notary routing does not select trusted signing keys; the Consumer Chain’s Notary Service checks those.
- REQ-PLAT-37 (upholds SP-BIND-01):
The Prover MUST use the exact authorization code consumed from the redirect
and the exact PKCE verifier derived for that ceremony as
codeandcode_verifierin §6.2. - REQ-PLAT-38 (upholds SP-EXCHANGE-01): The Prover MUST correlate the bearer, its commitment opening, and the final token attestation with the same browser notarization session. The Prover MUST reject a mixed-session tuple or a partial final result.
- REQ-PLAT-55 (upholds SP-CLIENT-01, SP-PRIV-01): On X and GitHub, the Canonical Runtime MUST treat the bearer commitment opening, and the openings, value and blinder, of the id and handle commitments of REQ-PLAT-59 and REQ-PLAT-60, as private witness material for the Proving Circuit. The Canonical Runtime MUST NOT include any of those openings in a Submission, log, or published artifact. The id and handle values alone travel as the delivered identity (REQ-PLAT-31A) and, when the user asks, the handle as the disclosed one (REQ-PLAT-03A); an opening, value with its blinder, never does. Necessity: publishing the bearer’s opening defeats the commitment’s protection against guessing the bearer, and an id or handle opening is the plaintext id or handle, which the commitment keeps off the Consumer Chain unless the user discloses the handle.
Session setup and proof preparation may overlap. The token response’s parsed
bearer can feed /user before the final token attestation arrives. That bearer
and any early witness material are provisional; proof delivery waits for both
final, structurally checked and correlated attestations. Neither session’s
failure can be turned into a partial successful ceremony.
- REQ-PLAT-43B: The Prover MUST reject redirects from the token endpoint. Necessity: a followed redirect would notarize a session other than the pinned endpoint.
6.4 Disclosure and verification
Section titled “6.4 Disclosure and verification”Prover obtains the token attestation from its browser notarization session.
The complete request, including client_secret, is one revealed range. There
is no committed request suffix. The response still commits the bearer and
reveals its framing; all other response bytes retain their existing layout.
The browser keeps the bearer and commitment openings as private witness
material under REQ-PLAT-55. The final proof carries the original signed
attestation bytes, not a separately supplied copy of the public credential.
The Notary Service verifies this attestation on the Consumer Chain, exactly
as it verifies the /user attestation.
| Range | Revealed | Why |
|---|---|---|
client_id | yes | the Platform Verifier reads and returns it; Prover compares it with the Application’s frozen client |
code | yes | Prover compares the code parsed from its retained OAuth return |
redirect_uri | yes | Prover compares the Application’s frozen redirect URI |
code_verifier | yes | the Platform Verifier recomputes it from the digest and authorizationNonce per common REQ-COMMON-15A |
"access_token":" and the closing quote immediately around the bearer value | yes | anchor the committed bearer range as that field’s value, per common REQ-COMMON-18A |
| bearer range | committed | a blinded commitment, opened only in circuit to link this attestation to /user |
| attestation timestamp | not a range | the attestation’s own signed creation time, which derives the authenticated validity ceiling per §2.2 |
| token endpoint authority | not a range | the Notary Service authenticated the TLS server identity, and the Platform Verifier compares the attested authority against its pinned constant per common REQ-COMMON-21A |
| the request line and every request header | yes | the Platform Verifier compares the method and path with its profile constants, requires host and the media type that selects the parser the platform applied to the body rows beneath this one (common REQ-COMMON-21B), and refuses the headers REQ-PLAT-56A forbids |
client_secret | yes | public application credential; complete form validation under REQ-PLAT-61 leaves no hidden request field |
| everything else | no | the response status line and headers, scope, token_type, other response fields |
Every unrevealed range stays behind the pinned attestation format’s range
commitment. The delimiter row is what anchors the committed bearer range in
the received direction, which would otherwise carry no revealed byte and
leave that range indistinguishable from a refresh_token value. Neither the
authority nor the attestation timestamp is a transcript range at all. The
authority reaches the Platform Verifier
as the TLS server identity the Notary Service authenticated under common
REQ-COMMON-21, carried in the attested data, because the
transcript holds the authority only in a Host header, and that header is
prover-composed text that says nothing about which server answered. Revealing
it, as REQ-PLAT-56A now requires, does not make it the authority: it is one
of the two headers the verifier holds to a pinned value, while the
authority continues to reach the verifier as the authenticated TLS server
identity. The timestamp is the signed creation time of the attested
data itself, which is why common REQ-COMMON-25 can forbid inferring it from a
response header. Revealing more than this would widen exposure without adding
a check — the full request is checked, while the undisclosed response fields
are platform-owned bytes that no verifier reads.
The exchange request carries host: github.com and the pinned media type.
Prover also sends accept: application/json and connection: close, which
nothing verifies. REQ-PLAT-56B holds Content-Length to the complete revealed
body; common REQ-COMMON-18A holds the request range to the signed layout.
-
REQ-PLAT-43D (upholds SP-EXCHANGE-01): The Prover and Platform Verifier MUST require the request direction to be one fully revealed range covering its complete signed length, without hidden, omitted, or overlapping bytes. The Prover MUST reveal no response range outside the rows marked
yesabove. The Prover MUST commit the bearer range rather than reveal it. REQ-PLAT-56A, REQ-PLAT-56B, REQ-PLAT-56C and REQ-PLAT-61 apply to this request. The previous hidden-client_secretrequest layout is not accepted. -
REQ-PLAT-58 (upholds SP-EXCHANGE-01): The Prover MUST reveal the
"access_token":"delimiter bytes immediately preceding that committed range and the closing quote byte immediately following it. The Platform Verifier MUST reject a token-exchange attestation whose committed range is not framed by exactly those revealed bytes. Necessity: the exchange response reveals no other byte, so without this anchor nothing distinguishes the committed range from arefresh_tokenvalue, and the revealed bound common REQ-COMMON-18A wants on every hidden range is absent. -
REQ-PLAT-43E (upholds SP-CLIENT-01): The Proving Circuit MUST NOT expose the bearer, or any value from which the bearer can be recovered, as a public proof input. The bearer commitments are public inputs under REQ-PLAT-52A; they reveal nothing about the bearer and are what ties the circuit to the two verified attestations.
-
REQ-PLAT-44 (upholds SP-EXCHANGE-01): The Prover MUST validate each final token attestation’s exact structure, canonical encoding, profile authority/method/path, request bindings, and commitment/opening correlation before proof delivery. Neither Prover nor Application performs local notary-signature verification. Signature-format checks do not establish authenticity; the Consumer Chain’s Notary Service authenticates the original signed bytes under common REQ-COMMON-33 and REQ-COMMON-33A. This omits early cryptographic forgery detection, not ledger verification.
-
REQ-PLAT-45 (upholds SP-EXCHANGE-01): The Prover MUST obtain an attestation carrying the selected notary’s signature and revealing the token request’s method and path. The Platform Verifier MUST compare the attested authority and the revealed method and path with this profile under common REQ-COMMON-21A.
-
REQ-PLAT-46 (upholds SP-EXCHANGE-01): The Prover MUST require the disclosed serialized
codevalue to equal the canonical form serialization of the code it consumed at redirect ingress, byte for byte. -
REQ-PLAT-47 (upholds SP-CLIENT-01): The Prover MUST require the disclosed
client_idto equal the client frozen by the Application. Common REQ-COMMON-16B makes those bytes identical before and after form serialization. -
REQ-PLAT-48 (upholds SP-BIND-01): The Prover MUST require the disclosed
code_verifierto equal the derived verifier supplied by the Application; it does not rederive it. Its base64url alphabet is byte-identical under form serialization. -
REQ-PLAT-48A (upholds SP-EXCHANGE-01): The Prover MUST require the disclosed serialized
redirect_urivalue to equal the canonical form serialization of the immutable value supplied by the Application. -
REQ-PLAT-49 (upholds SP-EXCHANGE-01): The Prover MUST require the token-response bearer, under its same-session commitment opening, to open the token attestation’s bearer commitment.
-
REQ-PLAT-50 (upholds SP-EXCHANGE-01): The Prover MUST fail the ceremony without proof delivery when any check in REQ-PLAT-38 or REQ-PLAT-44 through REQ-PLAT-49 fails. The Prover MUST discard provisional witness/proof material and stop pending dependent work on that failure. A failure observed before
/userstarts prevents it; a later failure does not undo an already sent request. A structurally valid forged signature alone is not a browser rejection; authoritative notary verification rejects it downstream.
Full disclosure is not sufficient by itself: REQ-PLAT-61 verifies the complete form grammar instead of matching arbitrary substrings. GitHub therefore has no hidden request-field assumption and no dependency on ASM-PROV-07’s platform-side duplicate-field rejection. TLS authority, PKCE, one-use code, notary authenticity, and bearer-link verification remain required.
6.5 Identity request
Section titled “6.5 Identity request”GET https://api.github.com/user with no query and no body. The request
carries these six headers, in any order, and may carry others:
authorization: Bearer <access_token>, accept: application/vnd.github+json,
x-github-api-version: 2022-11-28, host: api.github.com,
connection: close, and a user-agent of the Canonical Runtime’s choosing,
which GitHub requires of every API request and answers 403 without. The
Platform Verifier compares the request line, the authorization line, and
the host line under common REQ-COMMON-39D, refuses the names common
REQ-COMMON-39B forbids, and compares no other header.
Per common §9, the identity session reveals exactly these request ranges; the bearer value is the only committed request range, and every other request byte is revealed:
| Range | Revealed | Why |
|---|---|---|
| request line and all request headers, except the bearer value | yes | the Platform Verifier runs the line-anchored uniqueness scan of common REQ-COMMON-39 over these bytes and frames the committed range per common REQ-COMMON-40 |
bearer value of the authorization header | committed | a blinded commitment, opened only in circuit |
The two ranges account for the request’s signed transcript length exactly, with no gap and no overlap, per common REQ-COMMON-35 and REQ-COMMON-36, so the request leaves no byte undisclosed and uncommitted.
The response direction reveals only the anchors around the two identity values. Each value is its own committed range, and every other response byte stays behind a range commitment of the pinned attestation format:
| Range | Revealed | Why |
|---|---|---|
"id": and, after the digits, the structural byte that ends them, , or } | yes | anchor the committed id range and prove it is the whole number per REQ-PLAT-51 |
the id integer token’s digits | committed | its own range commitment, opened only in circuit (REQ-PLAT-52C) |
"login":" and, after the value, its closing quote | yes | anchor the committed login range per REQ-PLAT-51 |
the login value | committed | its own range commitment, opened only in circuit (REQ-PLAT-52C) |
| everything else | no | status line, headers, and every other response field |
Every committed range of this direction is bounded by a revealed delimiter on each side that faces one, and by the signed transcript boundary of common REQ-COMMON-36 at the two ends, which is the anchoring common REQ-COMMON-18A requires.
-
REQ-PLAT-60 (upholds SP-BIND-01, SP-PRIV-01): The Implementation MUST reveal the full
"id":delimiter and the structural byte after the integer token, together with the full"login":"delimiter and its closing quote, in the/userresponse. The Implementation MUST commit theiddigits and theloginvalue each as its own range. The Implementation MUST keep the JSON whitespace GitHub puts inside either member in a revealed range, per common REQ-COMMON-19F, and out of the committed value. The Implementation MUST redact every other response byte behind a range commitment. Necessity: the anchors are what tell the Platform Verifier which commitments hold the identity, and a revealed value would put the id and handle on the Consumer Chain. -
REQ-PLAT-51 (upholds SP-BIND-01): The Platform Verifier MUST find the
idcommitment and thelogincommitment by their anchors, as REQ-PLAT-31 does: the one commitment that starts where a revealed range ending with the full delimiter ends, and that is followed, forlogin, by a revealed", and forid, by a revealed range starting at the commitment’s end whose first byte after the removal common REQ-COMMON-19F fixes is,or}. The GitHub profile fixes that structural byte as,or}and no other byte. The Platform Verifier MUST reject a transcript in which either delimiter matches at more than one position of the revealed bytes, per common REQ-COMMON-19A, or in which no commitment, or more than one, is so framed. That the committedidbytes are digits is the Proving Circuit’s to check under REQ-PLAT-06. The Prover derives the GitHub identity from those two commitments under REQ-PLAT-31A. Necessity: the terminator is what proves the committed digits are the whole number rather than a prefix of a longer one, and JSON member order does not guarantee which of the two closes it. -
REQ-PLAT-52 (upholds SP-EXCHANGE-01): The Proving Circuit MUST assert that one private bearer value opens the bearer commitment of the token-exchange attestation and the
Authorizationbearer commitment of the/userattestation. The Platform Verifier MUST compare the method and path revealed in each attestation, and the authority each attestation authenticates, with the GitHub profile. -
REQ-PLAT-52C (upholds SP-BIND-01): The Proving Circuit MUST open the id commitment and the handle commitment of the
/userattestation to the rawiddigits andloginvalue, and prove their nodes as REQ-PLAT-32D does, under GitHub’s tags. -
REQ-PLAT-52A (upholds SP-EXCHANGE-01, SP-BIND-01): The Proving Circuit MUST expose exactly the sixteen public inputs of REQ-PLAT-32B, in its order and encoding, with the token-exchange attestation in place of X’s token attestation, the
/userattestation in place of/users/me,loginin place ofusername, and GitHub’s tags in place of X’s. The Proving Circuit MUST NOT add an Authorization Digest, client identifier, timestamp, or endpoint public input. Necessity: the digest is bound by the Platform Verifier under common REQ-COMMON-15A, and the rest are revealed bytes it reads directly, so a circuit copy would be a second representation of a fact that already has one. -
REQ-PLAT-52B (upholds SP-EXCHANGE-01, SP-BIND-01): The Platform Verifier MUST build the public inputs as REQ-PLAT-32C does, with GitHub’s tags. Necessity: as REQ-PLAT-32C.
Changing the pinned API version requires a new Platform Ceremony Version; it
is not mutable configuration. The granted scope is no proof property at all:
REQ-PLAT-36 leaves the exchange response unverified beyond the opened bearer
range, and the Canonical Runtime’s local reading of scope and token_type
binds nothing on the Consumer Chain. The bearer is never disclosed by the
proof.
- REQ-PLAT-53: The Canonical Runtime MUST start a fresh ceremony when GitHub consumed the code but usable evidence was lost. Necessity: browser token exchange remains a single-use, non-recoverable step; no Bridge result or replay exists.
7. Adding an identity platform
Section titled “7. Adding an identity platform”The Platform Profile for a new platform MUST define a stable platform identifier and immutable user-ID namespace; its user-id and handle tags under REQ-PLAT-05C, none a prefix of any other tag and no other tag a prefix of them, and its circuit’s buffers, as its entry in the circuits’ platform table, with vectors in the shared table (§2.1a); how the id and handle reach that circuit without being revealed; authenticated observation ordering; client portability or a bounded client family; exact authorization and redirect transport; every authenticated request and response field with its provenance; how the Authorization Digest is carried through that platform’s authorization; its authenticated client-binding source; an authenticated proof-validity rule and the value of each protocol parameter it names; its trust-root lifecycle; browser and deployment data exposure, retry, interruption, and withholding behavior; and conformance vectors.
8. Conformance
Section titled “8. Conformance”Roles: Canonical Runtime (including Prover), Proving Circuit, Platform Verifier, Notary Service, Consumer.
- TEST-PLAT-01 (exercises REQ-PLAT-10, REQ-PLAT-18): The §3.1 nonce vector reproduces exactly. A proof bound to another token nonce is rejected downstream when verified against this authorization’s recomputed digest, independently of the browser result-consistency checks.
- TEST-PLAT-02 (exercises REQ-PLAT-05A, REQ-PLAT-05B, REQ-PLAT-05C, REQ-PLAT-06):
The §2.1 id and handle node vectors reproduce, and each listed malformed
identifier fails to prove: an X or GitHub id holding a non-digit, a space
before it, or more than 20 digits, while
0123proves as sent; and a Googlesubof 32 bytes or holding",\, or a tab, while one with a leading0proves unchanged. - TEST-PLAT-03 (exercises REQ-PLAT-11, REQ-PLAT-12):
A Google authorization request not using the exact direct-ID-token fragment
profile is rejected. A nonempty query, mixed query/fragment response, or
fragment carrying duplicate
state, bothid_tokenanderror,code, oraccess_tokenis rejected. - TEST-PLAT-04 (exercises REQ-PLAT-13, REQ-PLAT-14, REQ-PLAT-14A, REQ-PLAT-14B):
The browser Prover rejects a return whose
statedoes not match its bound live ceremony, or has already been consumed, and rejects a missing, malformed, padded, noncanonical, or non-32-byte nonce. No server-side state lookup occurs. An otherwise valid signed token with a canonical nonce for another digest supplies that candidate to Prover’s circuit, but Application rejects the delivered public inputs against its retained digest. Downstream verification independently rejects that proof against the requested authorization’s recomputed digest. The browser test adds no expected-digest field toProveIdentity. A changed public input, delivered identity field, expiry, or signing modulus that no longer matches its projection fails the corresponding browser check. A structurally valid forged proof whose supplied public inputs still match the projection has no local cryptographic rejection and must fail downstream proof verification; matching fields alone do not authenticate them. - TEST-PLAT-05 (exercises REQ-PLAT-15): A Google response carrying an authorization code or access token is rejected, and the deployment contains no Google exchange route or client secret. Verification: inspection of emitted artifacts.
- TEST-PLAT-06 (exercises REQ-COMMON-19D, REQ-PLAT-16, REQ-PLAT-16A, REQ-PLAT-16B, REQ-PLAT-16C, REQ-PLAT-16D, REQ-PLAT-17, REQ-PLAT-19, REQ-PLAT-19A, REQ-PLAT-20, REQ-PLAT-21, REQ-PLAT-23):
The browser Prover rejects an audience differing from its frozen client.
A token with a foreign issuer,
email_verified: false, a quoted or non-booleanemail_verified, a quoted, negative, fractional, exponent, leading-zero, or overflowingexpcannot satisfy the circuit. A signature incompatible with the fixed RS256 relation or supplied modulus likewise fails the circuit. Substitution of signed header/payload bytes or signature invalidates that relation; public-output substitution fails proof verification downstream. These are not separate browser proof-verification requirements. A cryptographically valid proof under an inactive signing modulus passes circuit verification but is rejected by the Platform Verifier. A submission whose suppliedaudbytes do not hash to the audience public input is rejected, and an accepted one returns those exact bytes as the client identifier. A token whosesubis empty, longer than 31 bytes, or carries",\, or a byte outside0x20through0x7efails the Proving Circuit, as does anemailthat carries"or\or is longer than 62 bytes. Anemailwith a space or an@anywhere proves as sent.A.B+tag@Example.COManda.b+tag@example.comprove the same handle node, the §2.1 vector, and no public input carries asuboremailbyte. - TEST-PLAT-07 (exercises REQ-PLAT-22, REQ-PLAT-09, REQ-PLAT-09A):
A proof at or after
proofValidUntil, and a token-attestation creation time more than its profile’smaxFutureAttestationSkewahead of Block Time, are rejected. An X or GitHub identity-attestation timestamp changes neithermetadataObservedAtnorproofValidUntil; Google uses its signedexpfor both values. - TEST-PLAT-08 (exercises REQ-PLAT-24): The trusted modulus set contains every modulus currently published at Google’s JWKS endpoint, and every corresponding exponent is 65537.
- TEST-PLAT-09 (exercises REQ-PLAT-29, REQ-PLAT-46):
A transcript whose disclosed
codediffers from the code consumed at redirect ingress is rejected on X and on GitHub. A code containing a form-reserved byte matches only its canonical serialized value range, never the unencoded bytes or a noncanonical alternative. - TEST-PLAT-09A (exercises REQ-PLAT-29A, REQ-PLAT-29B):
An X attestation that does not reveal the token request’s
client_idis rejected; a proof exposing a client identifier public input is rejected; and the identifier the Platform Verifier returns equals the revealed bytes. - TEST-PLAT-09B (exercises REQ-PLAT-30A, REQ-PLAT-32A):
An X transcript that reveals plaintext
access_tokenbytes in either session, or omits the bearer hash commitment, is rejected. - TEST-PLAT-09C (exercises REQ-PLAT-29C, REQ-PLAT-56, REQ-PLAT-56A, REQ-PLAT-56B, REQ-PLAT-56C, REQ-PLAT-63):
The Platform Verifier rejects an X attestation that hides the
grant_typeorredirect_urirange, and Prover rejects a revealed value differing from the canonical form serialization of the profile’s grant type or the Application’s frozen redirect URI, respectively. A redirect URI containing:and/passes in that encoded form and fails as literal unencoded bytes. The Platform Verifier rejects an attestation whose revealedgrant_typeisrefresh_token, and one whosegrant_typediffers fromauthorization_codein any byte, even when every other revealed range and the proof itself check out. The Platform Verifier accepts a token request whose headers arrive in another order, or carry a header the profile does not name, and one whose required names are spelled in another letter case, with_for-, or padded before the colon; it rejects a head missinghostorcontent-type, carrying either twice, or carryingauthorization,cookie,content-encoding,transfer-encodingor a method-override name under any spelling; it rejects a head with no empty line or a second one, with nocontent-lengthor two, or with a count that is not the body’s length, is not decimal digits, or carries a leading zero, and accepts the count wherever it sits in the head; and it rejects a head carrying a bare line feed, a bare carriage return, an obsolete line fold, or a header field line with no colon. An otherwise valid request with a colon-free request line and its terminating empty line passes. The canonical five-field body passes; missing, empty, additional, duplicate, reordered, malformed, or noncanonical fields fail even if X were to accept them. Encoded duplicate names cannot evade the exact name/serialization check. A value with encoded delimiters remains one value and passes; raw delimiters creating more fields fail. Refresh or device-grant fields fail. Invalid client-identifier bytes and a noncanonical or wrong-length PKCE verifier fail. Canonical form escaping incodeandredirect_uripasses the form check; a mismatch with the consumed code or the deployment profile’s redirect still fails under REQ-PLAT-29 and REQ-PLAT-29C. Acodeorredirect_uriwhose canonical escapes decode to bytes that are not UTF-8 passes the form check, which the Implementation never emits, and fails those comparisons. - TEST-PLAT-10 (exercises REQ-PLAT-30, REQ-PLAT-31, REQ-PLAT-32, REQ-PLAT-32D, REQ-PLAT-36, REQ-PLAT-51, REQ-PLAT-52, REQ-PLAT-52C):
An opened bearer range that is empty, over 128 bytes, or outside printable
ASCII fails to prove; an identity response with no framed
idor handle commitment is rejected; a proof whose two attestations commit different bearers is rejected; an opened id or handle that is not one JSON token fails to prove; a proof under the other launch platform’s tags is rejected; and an id node or handle node that is not the node of the opened value fails to prove, including one off by one in its low half. No proof statement coverstoken_typeor the granted scope. - TEST-PLAT-11 (exercises REQ-PLAT-33): The complete request direction of X’s first notarized session reaches X before the authorization-code deadline in the success case; delaying its completion past the deadline abandons the ceremony, while delaying only the response or later proof work does not trigger that deadline.
- TEST-PLAT-12 (exercises REQ-PLAT-34, REQ-PLAT-35A, REQ-PLAT-35B, REQ-PLAT-35C, REQ-PLAT-61):
Scope other than
read:userrejects locally. The complete token request reveals the public credential and client ID, while the circuit adds neither as a public input. The Platform Verifier returns the revealed client ID. The canonical five-field form passes; missing, empty, additional, duplicate, reordered, malformed, or noncanonical fields fail downstream even if the platform were to accept them. Encoded duplicate names cannot evade the exact name/serialization check. A credential with encoded delimiters remains one value and passes; raw delimiters creating more fields fail. Refresh or device-grant fields fail. All accepted lengths cover the complete body. Invalid client-identifier bytes and a noncanonical or wrong-length PKCE verifier fail. Canonical form escaping incodeandredirect_uripasses the form check; a mismatch with the captured code or frozen redirect still fails the Prover’s local comparisons. Aclient_secretwhose canonical escapes decode to bytes that are not UTF-8 passes: the Platform Verifier judges its bytes and decodes nothing. The same incodeorredirect_uripasses the form check and fails the Prover’s local comparisons. - TEST-PLAT-12A (exercises REQ-PLAT-28, REQ-PLAT-28A, REQ-PLAT-34A):
For otherwise valid GitHub success,
access_denied, and other OAuth error returns, the browser Prover accepts the exact issuer in literal or equivalent form-encoded spelling. It rejects missing or duplicateiss, malformed encoding, a foreign issuer, changed case, an added default port, or a trailing slash before token exchange; an invalid issuer never resolves denied. Correctisswith wrong state remains rejected. X acquires no GitHub issuer prerequisite, and no proof or browser-protocol record gains an issuer field. - TEST-PLAT-13 (exercises REQ-PLAT-37, REQ-PLAT-38, REQ-PLAT-62):
Prover sends the frozen client/credential/redirect and ceremony code/verifier
through its token Proxy session, using the same selected notary as identity.
No Bridge token request or configuration refetch occurs. Both session setups
and proof preparation may overlap;
/userwaits for a parsed bearer but not necessarily the final token attestation. A mixed-session tuple or partial final result fails; both final attestations are required for delivery. Qualification uses the matched browser WASM and real Proxy notary for both GitHub endpoints, not only mocks or a native MPC token session. - TEST-PLAT-14 (exercises REQ-PLAT-43B, REQ-PLAT-43D, REQ-PLAT-43E, REQ-PLAT-56A, REQ-PLAT-56B, REQ-PLAT-56C, REQ-PLAT-61): A redirected token exchange rejects in Prover. Downstream layout validation rejects a hidden request suffix, omitted request bytes, gaps, overlaps, response disclosures outside the profile, or a revealed bearer. The head vectors of TEST-PLAT-09C run on this request too: unlisted headers pass, forbidden or missing required headers reject, and Content-Length matches the complete revealed body. The previous hidden-secret layout fails.
- TEST-PLAT-15 (exercises REQ-PLAT-44, REQ-PLAT-45, REQ-PLAT-46, REQ-PLAT-47, REQ-PLAT-48, REQ-PLAT-48A, REQ-PLAT-49, REQ-PLAT-50): Malformed canonical attestation bytes, authority/method/path, request bindings, signature shape, or bearer/opening correlation fail in Prover. Failure before identity HTTP prevents it. Failure after provisional bearer use stops remaining work and discards any speculative proof; it never delivers partial evidence. A structurally valid forged signature preserving all checked correlations has no local cryptographic rejection, but fails trusted-notary verification downstream. Application structural acceptance does not establish signature authenticity.
- TEST-PLAT-15A (exercises REQ-PLAT-52, REQ-PLAT-52A, REQ-PLAT-52B, REQ-PLAT-52C): A GitHub proof whose bearer, id, or handle commitment public input differs from the commitment in either submitted attestation is rejected; substituting one attestation for another from a different ceremony is rejected; swapping the id node and the handle node, or giving another identity’s nodes, is rejected; and the proof carries no bearer, code, verifier, redirect URI, id, handle, time, client, or endpoint public input. An X proof submitted to the GitHub Platform Verifier is rejected.
- TEST-PLAT-15B (exercises REQ-PLAT-32B, REQ-PLAT-32C, REQ-PLAT-32D): An X proof whose bearer, id, or handle commitment public input differs from the commitment in the submitted attestation is rejected; a proof whose nodes are another identity’s, or swapped, is rejected; a proof carrying a client identifier, timestamp, or endpoint public input is rejected; and a GitHub proof submitted to the X Platform Verifier is rejected.
- TEST-PLAT-16 (exercises REQ-PLAT-53): A ceremony whose exchange response was lost restarts from authorization.
- TEST-PLAT-17 (exercises REQ-PLAT-01, REQ-PLAT-01A, REQ-PLAT-01B, REQ-PLAT-02, REQ-PLAT-03):
The launch profile pairs are exactly
("google", 1),("x", 1), and("github", 1); a suffixed platform string is not one of those profiles. A live ceremony that substitutes a newer profile is rejected, a profile missing from the Application, Distribution list, or Bridge configuration is ineligible, and the profile identifies the same proof statement and semantic public inputs across two chains using different conforming verifier artifacts. A destination chain does not support the pair without a conforming artifact or, for a TLSNotary profile, a compatible Notary Service. No local identity field originates outside proof public inputs, the signed ID Token whose claims a Google proof reads, and the attestation bytes, revealed or committed, of its Submission. Only the Consumer’s acceptance of that exact Submission makes the claim authoritative. - TEST-PLAT-17A (exercises REQ-PLAT-03, REQ-PLAT-31A):
Pair authenticated X or GitHub identity-response bytes for account B with a
detached
userId, handle, or metadata value for account A offered as an extraction input. Prover rejects that alternative source; without it, the Prover derives account B byte for byte from the committed ranges, and the proof’s nodes are account B’s; a proof whose id or handle node differs from the node of theuserIdor of the handle the Prover would deliver, folded, is not delivered. Prover may deliver its derived identity separately through CCDP, and Application accepts its valid structure without repeating evidence extraction. The delivereduserNamepreserves the raw handle even when display folding would change its spelling. Replacing the proof, attestation, platform, or version after deriving the local identity fields discards them and requires rederivation from the replacement Submission. - TEST-PLAT-18 (exercises REQ-PLAT-25, REQ-PLAT-26, REQ-PLAT-27, REQ-PLAT-28, REQ-PLAT-28A):
Launch uses Proxy mode, rejects application or request selection of Browser
MPC, uses no application-controlled platform egress, and carries no partial
transcript state into a retry. A nonempty fragment, mixed query/fragment
response, or redirect carrying two
codefields, twostatefields, bothcodeanderror, or a malformed field is rejected before any token request starts, as is a redirect whosestatematches no live local ceremony or a ceremony already consumed. - TEST-PLAT-19 (exercises REQ-COMMON-32; supports ASM-PROV-07): For each production Platform Profile that cites ASM-PROV-07, recurring integration probes send each profile-listed token request field twice, in both orders and using both literal and percent-encoded equivalent field names, and send the otherwise valid request under alternate media types. The production endpoint rejects every probe and issues no bearer. No launch profile cites ASM-PROV-07: X and GitHub hold the complete token body under REQ-PLAT-63 and REQ-PLAT-61, so the launch probe set is empty.
- TEST-PLAT-21 (exercises REQ-PLAT-38, REQ-PLAT-49, REQ-PLAT-55): A same-session bearer opening opens the token attestation commitment; a missing, mismatched, or other-session opening fails. The opening remains browser witness material and is absent from Submissions and published artifacts. There is no Bridge response whose fields supply it. On X and GitHub, the openings of the id and handle commitments, value and blinder, are likewise absent from Submissions, logs, and published artifacts.
- TEST-PLAT-22 (exercises REQ-PLAT-31, REQ-PLAT-51, REQ-PLAT-57, REQ-PLAT-58, REQ-PLAT-59, REQ-PLAT-60):
An X token attestation and a GitHub token-exchange attestation whose
committed bearer range is not framed by the revealed
"access_token":"delimiter and closing quote are each rejected, including one committing arefresh_tokenvalue instead. An X/2/users/meor GitHub/userattestation is rejected when no commitment is framed by theid,username, orloginanchors, when two are, when an anchor is revealed twice, or when a GitHubidcommitment is followed by a byte other than,or}; an anchor revealed with JSON whitespace around its colon still frames its commitment; and an attestation revealing the id or handle value carries no framed commitment for it and is rejected.
9. Security Considerations
Section titled “9. Security Considerations”Browser validation does not authenticate attestation signatures or verify ZK proofs. Google’s Prover checks generated public inputs against its parsed evidence; Application checks the delivered projection against its retained digest and result fields, without receiving the token. A well-formed forgery that preserves these checked relationships can still survive browser checks. REQ-PLAT-14, REQ-PLAT-14A, REQ-PLAT-14B, REQ-PLAT-18, and REQ-PLAT-44 preserve the distinction between local consistency and circuit/downstream verification; TEST-PLAT-04 and TEST-PLAT-15 distinguish those later rejections from early browser rejection. No ledger verification guarantee is weakened.
This document enforces SP-BIND-01, SP-CLIENT-01, SP-EXCHANGE-01, SP-FRESH-01, and SP-PRIV-01 for the launch platforms, under the assumptions of common §3.
Google is the only platform whose evidence is a bearer artifact: an ID Token is complete evidence to whoever holds it. Its delivery is therefore trust-bearing, and ASM-PROV-01 carries that weight. X and GitHub deliver an authorization code, which is not evidence until redeemed, so a code alone grants nothing.
X and GitHub are public clients. GitHub’s application credential is a required request field, not a confidential authenticator of the presenter. Possession of that credential alone grants neither a user’s bearer token nor a valid identity proof. Redemption still requires a usable code and its PKCE verifier; the registered redirect destinations and their code remain trust-bearing for the local ceremony. Compromise of a browser holding those inputs defeats their confidentiality.
Public application credentials can be reused outside this ceremony, including for app-authenticated API traffic GitHub permits. This accepts an unquantified availability risk from third-party abuse or platform suspension. The protocol does not claim abuse isolation, and renaming the public configuration field provides no confidentiality. This changes neither the downstream proof checks nor their trust roots.
SP-BIND-01 rests on the platform enforcing the PKCE challenge match
(ASM-PROV-02) for X and GitHub, and on Google reflecting the requested nonce
into a signed token (ASM-PROV-05). ASM-PROV-02 is a live dependency on
platform behavior rather than a proven property. The Implementation claiming
conformance MUST run a recurring check that each platform still rejects a
mismatched code_verifier.
X and GitHub each reject noncanonical, extra or duplicate fields over the fully revealed token request, under REQ-PLAT-63 and REQ-PLAT-61; neither depends on ASM-PROV-07, and TEST-PLAT-19 has no launch profile to probe for it. Both still assume the platform honors the canonical request, PKCE, and one-use authorization-code semantics.
The Prover can withhold work or supply malformed evidence. Local request and commitment checks detect structural substitution, not a well-formed forged notary signature; the Consumer Chain’s Notary Service remains authoritative. A proof built outside the Canonical Runtime still faces those downstream checks and the Transaction Author rule in common §12. The Application names the bind call’s service fee amount and receiver. No proof bounds them; the Transaction Author’s review of the call is the only check. Proxy notarization exposes session traffic to the notary; the protocol does not promise bearer confidentiality from that notary. The bearer remains hidden from the published proof by the two commitments and link circuit, and the id and handle by their own commitments and the nodes the circuit outputs in their place.
The notary key is a trust root for X and GitHub evidence. Its compromise mints fresh evidence until the key is removed, and does not revoke authority already committed.
Google has no server-side token exchange. Its fragment is not visible at
HTTP ingress, although the deployment controls Callback code. Its signed
ID Token reaches the redirect fragment and is cleared
before other work. The browser checks state, the configured audience, and
canonical nonce encoding and checks the delivered public-input projection;
the circuit binds the signed nonce and claims, and ledger verification binds
that proof to the recomputed authorization digest
and trusted signing key. A deployment backend can withhold the static redirect
document but cannot substitute an ID Token through a server exchange that
does not exist.
Google’s JWKS rotation makes the trusted modulus set a liveness dependency (REQ-PLAT-24): ledger verification rejects proofs while Google signs with an untrusted modulus, even if browser proof generation completes.
A binding on any platform publishes its id node and handle node, never the
id, the handle, or Google’s sub; the handle reaches the chain only when a
transaction discloses it (§2.1b). Common §12 states what that does and does
not hide. On Google the nodes keep the sub and the email from readers of
the Consumer Chain, not from a party that already holds them: every
application a user signs in to with Google knows that user’s sub and can
recompute the id node, and Google’s sub values are 21-digit decimal
strings in practice, too few to resist a well-resourced search. An email
address is a guessable handle like any other.
The check and fold of §2.1a run inside the Proving Circuits, so they are part of each profile’s proof statement. A Consumer holds only nodes for an undisclosed binding and cannot re-key it, so changing the check, the fold, a buffer, or a platform’s tags takes a new Platform Ceremony Version and new circuits.
A stored name is not evidence. The Consumer clears a name whenever its author stops holding the handle (REQ-PLAT-08J), and emits an event for each clearing (REQ-PLAT-08F), so a reader that mirrors names from events shows what the Consumer answers. An event that stores a name replaces the author’s earlier name on any platform; no clearing event precedes it.
10. References
Section titled “10. References”Normative: [RFC6749], [RFC7636], [RFC7515], [RFC7517], [RFC7518], [RFC7519], [RFC8017], [OIDC], [RFC8446], RFC9207.
Informative: [RFC9700], [TLSNotary-Proxy], GitHub-public-clients.