Repository navigation
docs(specs): identity digests and disclosure for the Google profile - #45
Open
SupremaLex wants to merge 32 commits into
Open
SupremaLex wants to merge 32 commits into
SupremaLex wants to merge 32 commits into
Conversation
A design proposal, nothing built: how a Google binding can reach the chain as a hash of the normalized email instead of the address, with the address disclosed only when its owner sends it. The note maps where the address is published today, lays out the options on each axis (what the circuit exposes, where normalization runs, which hash, where the mode lives, defaults and transitions, downstream readers, spec changes), measures the gate cost of the two viable hashes on the pinned toolchain, recommends one combination, and lists what it does not protect against and the decisions it leaves open. Assisted-by: Claude Fable 5.1 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
A digest profile's Proving Circuit exposes the handle and the canonical userId as keccak256 digests instead of bytes, and the Consumer keys the binding on them, so an identity resolves for whoever knows it and is published only when its owner sends the plaintext. Google is the launch digest profile, at Platform Ceremony Version 2. platform-ceremonies.md: REQ-PLAT-08A and 08B admit a circuit that normalizes what it digests and a Consumer that keys on digests; §2.1b adds REQ-PLAT-08D to 08F (keys from the digests, plaintext accepted only when it hashes to them, the disclosure call and what it refuses, the event's disclosed flag) with TEST-PLAT-20A; REQ-PLAT-16B lists the two digests as public inputs; REQ-PLAT-16C returns them and passes plaintext through unchecked; REQ-PLAT-16D moves the sub and email validation into the circuit; TEST-PLAT-06A exercises the three; §9 states what the digests protect and what they do not. ceremony-common.md: ASM-HASH-01 (preimage resistance, with the enumeration caveat), SP-PRIV-01, REQ-COMMON-05E returns digests where a profile exposes them, and §12 replaces the statement that every handle is published deliberately with the digest profile's confidentiality and its limits. libid.md names the guarantee among the enforceable ones. The design note carries the rationale and now points at this text. Assisted-by: Claude Fable 5.1 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
SupremaLex
marked this pull request as ready for review
September 22, 2026 15:22
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
libid | 32d167e | Sep 23 2026, 03:20 PM |
Nothing is released, so the digest statement replaces version 1 in place rather than arriving as version 2 beside it: platformCeremonyVersion stays 1, and the prose on version 1 exposing raw bytes and on a Consumer accepting version 2 only is gone. §9 says the handle rules are part of the profile's proof statement instead of prescribing a new version for every rules change. The design note drops its version-2 references and the old-version claim. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
…r chooses §2.1b defines the two facts it had used without defining: an identity is disclosed once any transaction has carried its plaintext, and a name is published while the Consumer holds one to display. - REQ-PLAT-08D: the Consumer's Authorized Transaction Data for a binding carries whether the Submission discloses, so the choice is the user's and committed in the digest; a Submission carrying one of the two plaintexts, or plaintext its data does not authorize, is rejected; a private Submission keeps the published name when it re-proves the same handle and clears it otherwise (moved here from 08E, and narrowed). - REQ-PLAT-08E: the disclosure call publishes, and requires the identity and handle keys to be each other's current pair, in both directions, and the caller to own both, which refuses a handle another account of the same wallet took over; its necessity says a refusal protects the display, not the plaintext. - REQ-PLAT-08F: events carry the normalized handle. - REQ-PLAT-08A/08B: the digesting circuit refuses where the table trims; the Consumer normalizes whatever handle bytes it receives, taking none as already normalized. - TEST-PLAT-20A exercises each new rule. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
- REQ-COMMON-05E returns the plaintext a Submission carried, marked unverified; the Consumer trusts it only once it hashes to the digests, and the Proof Verifier forwards it with the rest under REQ-COMMON-06. - REQ-PLAT-03: for a digest profile the Canonical Runtime derives the local fields from the signed ID Token, places them in the Submission exactly when the authorized operation discloses them, and never returns them to the application otherwise. - SP-PRIV-01 says what it holds: plaintext reaches the chain only in a Submission whose digest commits its disclosure or in a disclosure call, and a transaction carrying plaintext publishes it when sent, accepted or not. It depends on the new ASM-ZK-01, and REQ-COMMON-45 has governance select only zero-knowledge artifacts for a digest profile. - Common §12 and libid.md follow. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
- REQ-PLAT-04 and 16D refuse a backslash in `sub`: every JSON escape begins with one, so no value the circuit digests holds an escaped byte; the email alphabet already lacks it. The old claim that an escaped value fails the byte comparison was false for `sub`. - REQ-PLAT-16D states the Google buffers, 31 bytes of `sub` and 62 of `email` (the handle rules' own maximum), so TEST-PLAT-06A's over-length cases are writable. - REQ-PLAT-08G: a digest profile's Consumer holds its handle rules equal to the circuit's and refuses to change them once it has bound an identity, since it cannot re-key digests; §9 points to it. - REQ-PLAT-16B lists SP-PRIV-01. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
- The mode lives in the operation the user authorizes: `disclose` joins the claim's Authorized Transaction Data, the Canonical Runtime hands the email to the application only for a disclosing claim, and choosing by plaintext presence alone is recorded as the rejected alternative. - `publish` checks the pairing in both directions, with the two-account case that needs it; a private claim of the published handle keeps the publication; a refused `publish` still publishes its calldata. - The ENS gateway reads the indexer's store, keyed by the handle string, so private names resolve only once that lookup is keyed by node; the earlier "no change at all" and the RPC fallback it named were wrong. - The public-input count is stated once, per digest; the status line points at the spec map instead of repeating it; the map, the implementation steps and the decided list carry the new rules. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
…kslash - REQ-PLAT-08D: the claim's Authorized Transaction Data carries the disclosure choice for every platform, one encoding per transaction kind, so a profile that exposes bytes rejects a Submission that does not disclose; TEST-PLAT-20A exercises it. - REQ-COMMON-45: governance selects artifacts that verify zero-knowledge proofs. - The design note lists the backslash refusal among the circuit's `sub` checks and in its done criteria: today's circuit accepts one. The digest vectors use opaque transactionData, so the new field leaves them as they are. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The second review showed the user-authorized disclosure could not be enforced: the ID Token lands on the application's origin, no trusted screen shows the choice, and Authorized Transaction Data is opaque to the Canonical Runtime. So `disclose` leaves the Authorized Transaction Data, X and GitHub claims keep their encoding, and SP-PRIV-01 says plainly that it does not survive a malicious application operator; it bounds what the Consumer and the Proof Verifier put on chain. - The `sub` is never sent: a Google Submission may carry the handle alone, and the disclosure call takes the handle and finds the identity through the handle key, so showing an address no longer publishes the cross-site account id. Both-or-neither and the lone-field drop go with it. - "Disclosed" counts accepted transactions; a refused one still publishes its calldata (common §12). - REQ-PLAT-08D's keep-or-clear compares with the handle the identity holds once the Submission is applied, so an older proof clears nothing. - REQ-PLAT-08F requires the event of a disclosing Submission or disclosure call to carry the normalized handle, and forbids the `sub` in any event. - REQ-PLAT-08G fixes the Consumer's normalization to the profile's published handle table, and says how a Consumer knows a digest profile. - ASM-ZK-01 is a property of the zero-knowledge proving mode, and the new REQ-COMMON-45A has the Canonical Runtime prove in it with fresh randomness; TEST-COMMON-22 checks two proofs of one witness differ. - REQ-PLAT-03 and TEST-PLAT-17 name the ID Token as a digest profile's local source, the local handle normalized; REQ-PLAT-04 and the §2.1 table bound a Google `sub` at the circuit's 31 bytes. - §12's pointer to the consent-screen case is gone with the text it served. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
…sign - The mode is the email's presence in the payload; `GoogleProof` gains `bytes email` alone and never a `sub`. The Authorized-Transaction-Data flag moves to the rejected options with its reason: no trusted screen shows it, the Canonical Runtime cannot decode it, and the ID Token has already reached the application. - `publish(platformId, handle)` finds the identity through the handle's node; keep-or-clear compares with the handle held after the write. - Disclosure counts accepted transactions, the indexer's `disclosed` says so, and the private-refresh sequence reads back as published, matching REQ-PLAT-08D; the step-4 criteria follow. - eden-testnet does deploy IdentityNames and the Google verifier; the sentence saying no network did is corrected, pre-release test data. - The SDK keeps returning the email and `sub`; the prover's zero-knowledge mode is named as the requirement it now is. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
SupremaLex
force-pushed
the
design/private-gmail-handle
branch
from
September 23, 2026 14:27
2a3049f to
01db383
Compare
… profile A second note asks whether the application can be kept from the address too, and answers with what was checked on 2026-09-23: one Google client operated by libID, its redirect on the Distribution origin, the ID Token verified and proved there, and only the proof, the digests and, on the user's yes, the email returned to the application. - Feasible: Google's policies allow a shared broker client, MetaMask Embedded Wallets ship one, `openid email` needs no app verification, and a top-level redirect avoids the opener and storage-partitioning problems; FedCM does not fit, and a popup rests on Google's COOP staying report-only. - The new risk: consent to one shared client is global, so a hostile site could bind a victim's account to its own wallet; the runtime must show its own confirmation screen, ideally with the wallet signing for the target. That screen would also make a user-authorized disclosure enforceable again. - The cost: libID gains silent re-authentication over every user of every application, the Distribution operator holds every Google token, one client carries every application's quota, and the consent screen names libID. What would change in the specs is listed, not written. `private-gmail-handle.md` points to it from what it deliberately does not do. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
…y Consumer A wallet may hold several accounts of one platform and has one name there, an ENS-style primary name per platform. The name is written only by a claim carrying the handle that asks to publish it or renames the named account, by the disclosure call, or cleared by withdrawal, and it reads as a name only while the wallet holds the handle. Holding is ownership of the handle key, which retirement makes current. That replaces 08D's keep-or-clear rule, which cleared a still-valid name when a wallet re-proved its other account, cleared names with no event saying so, and depended on older proofs our Consumer never accepts. Also: identity and handle keys, owners, pairings and retirement are defined (08H, 08I); the Consumer records per platform whether it is a digest profile (08L); "carries" means a nonempty field; the disclosure call accepts any handle the caller holds, including one its other account took over; events say whether a claim stored the name; TEST-PLAT-20 follows 08E; REQ-PLAT-04's checks apply to the signed bytes; ASM-PROV-05 records the Google sub shape as a liveness clause; REQ-COMMON-45A requires a CSPRNG; REQ-COMMON-19E covers digest profiles; §12 no longer calls a guessable handle confidential; the trust table names the application operator's role in SP-PRIV-01; §7 asks a new profile whether it is a digest profile; and "digest profile" is a common term. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The publication section, the downstream indexer, the spec map, the implementation plan and the decisions follow the spec: one name per wallet and platform, written only by a publishing or renaming claim carrying the handle, by publish, or cleared by unpublish; read through primaryOf's ownership test; publish accepted while the caller owns the handle's node. The sub bound reads 31 throughout and points at ASM-PROV-05. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Gate counts and bb.js WASM and native proving times for today's circuit, a public-only circuit and the one-circuit design, with the method and machine, and what the numbers mean for the two-circuit option. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Fit the digest profile onto main's Google model. The canonical userId is main's REQ-PLAT-05A digest of the sub, up to 255 bytes, so the profile adds the handle digest and optional email disclosure on top of it; the circuit rule REQ-PLAT-16C carries both digests, and the verifier's return becomes REQ-PLAT-16D. The Prover, not the Canonical Runtime, derives local fields and proves, and the disclosure trust moves to main's Application and CCDP Distribution roles. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
libid-circuits v0.6.0 fixes SUB_MAX at 31: the tag and the sub fit one SHA-256 block. The merge took main's 255, which no deployed circuit proves. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
REQ-PLAT-08M defines the handle digest: keccak256 of the normalized
handle, except on Google, where it is
SHA256(UTF8("libid.google-handle") || email). The circuit already
hashes the sub with SHA-256, so the email costs one more SHA-256 rather
than a keccak256. The handle key keeps its outer construction.
TEST-PLAT-06A carries a vector.
Assisted-by: Claude Opus 5.5
Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Under CCDP the Prover delivers an IdentityProof and builds no Submission, so the disclosure rule moves to a new REQ-PLAT-03A bound to the Canonical Runtime (its Application). REQ-PLAT-03 names the signed ID Token whose digests the returned proof carries as a digest profile's local source, and keeps the delivered handle raw, as CCDP's userName is; the digests are over the normalized handle. REQ-COMMON-45A and ASM-ZK-01 bind the Canonical Runtime (its Prover), the common terminology says what the Prover is, and the conformance roles list it. TEST-COMMON-22 stays the Platform Verifier's; the fresh-randomness check moves to TEST-COMMON-22A, which the Canonical Runtime claims. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
"Platform identifier" meant both platformId and Google's sub. Common §2 defines the account identifier, the value a profile derives userId from, and the digest-profile text uses it. The userId digest of a digest profile reaches the chain; only the account identifier does not. Common §12 and libid.md say so. SP-PRIV-01 binds the Platform Verifier beside the Consumer and the Proof Verifier, so REQ-COMMON-05E and REQ-PLAT-16D uphold it; 16D forbids the verifier to emit or store the email. libid.md's trust table and guarantees name the CCDP Distribution, whose Prover holds the ID Token and must prove in the zero-knowledge mode. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
REQ-PLAT-08J wrote names only for a Submission that carries a plaintext handle, which only digest profiles defined, so an X or GitHub author could never get one. §2.1b now defines carrying for every profile: a byte profile's Submission always carries its revealed handle. It also places the publish request where IdentityRegistry's bind(platformId, version, payload, publish) has it, as an argument of the Consumer's call, outside the Submission Payload and the Authorized Transaction Data. 08J changes a name only when the Consumer writes the Submission's binding, so a stale Submission that REQ-COMMON-25A lets complete cannot restore a handle its identity has left. TEST-PLAT-20B covers it. The handle-digest construction becomes a profile constant the Consumer configures beside the normalization (08L) and freezes with it (08G); REQ-PLAT-08M states it per profile kind, with Google's value, and drops a constraint-count claim. Common §2 and §7 list the constant. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
libid-circuits v0.6.0, which the ceremony package pins, exposes the raw email bytes, normalizes nothing, and admits a backslash in the sub, so it does not enforce the edited version 1 statement. §3.3 says the profile is implementable once a release that enforces REQ-PLAT-16B and 16C publishes its artifacts, as libid.md requires of every profile. REQ-PLAT-16C's escape argument covers the sub and email it digests, not every hashed value: the aud and the signing input may hold escapes. REQ-PLAT-16B drops the sentence 16C already states. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
private-gmail-handle.md: the spec map swaps REQ-PLAT-16C (circuit validation) and 16D (verifier return) back to their content, names the Prover as the role that proves and the Application as the one that decides disclosure, and lists the account identifier, the carrying and publish-request definitions, the newer-evidence gate on names, and the configured handle-digest construction. "Which hash" and the recommendation take the tagged SHA-256 of REQ-PLAT-05A and 08M; the measurements are marked as taken at nargo 1.0.0-beta.25 and bb 5.2.0 with keccak256 builds. The one open item is the circuit release that enforces Google version 1. neutral-google-client.md: the token lands in the Prover, REQ-PLAT-03A's disclosure choice moves to the runtime's screen under that option, and the result never carries the sub. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
REQ-PLAT-08I writes a binding only when its evidence is strictly newer than both keys' watermarks, and retirement clears the handle key's owner but keeps its watermark, as IdentityRegistry's _write and _retirePreviousHandle do. Otherwise an account's older proof of a handle could take it back from the account that proved it since. REQ-PLAT-08L distinguishes verified handle bytes from the unverified handle a digest-profile result passes through, so a disclosing Google Submission is no longer rejected. It also requires every ceremony version of a platform to share its normalization, digest-profile flag and handle-digest construction. REQ-PLAT-08G freezes the flag once the platform has bound anything. REQ-PLAT-08J skips, without reverting, its rename path when the stored name no longer normalizes and so has no handle key. TEST-PLAT-20A and 20B cover each case. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Common §4 said every property but SP-PRIV-01 survives a malicious application operator, while SP-PRIV-01 itself constrains only the chain's roles and artifacts, which an operator sending a handle does not breach. Drop the exception: the property bounds what the chain yields, a transaction that carries a handle is one it permits, and it rests on the unmodified Canonical Runtime (its Prover) §4 already assumes. §12 and libid.md drop the Application's "rests on" claim; the Application row says it decides whether a Submission carries the email, and REQ-PLAT-03A's necessity says why that rule, not a Consumer check, carries the user's choice. The OAuth Bridge's Callback captures the Google ID token fragment, so §4, §12 and libid.md's Bridge row and guarantees name it beside the Application and the Distribution. Common §12 regains the blank line before the PKCE paragraph. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
ASM-PROOF-01 required a new Platform Ceremony Version for any change of statement, while this branch edits Google version 1 in place. The assumption now applies the rule to released versions, says when a version is released, and says that an unreleased version's statement is edited under the same number, with artifacts for an earlier edit enforcing none of it. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
ccdp.md called normalization only a consumption-time derivation, and libid.md said the Google circuit proves only the signature relation. For a digest profile the Proving Circuit also validates the hidden sub and email, normalizes the email, and computes both digests; both documents say so. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
The note cited IdentityNames, claim, publishName, primaryOf and reverseOf; contracts main has IdentityRegistry with bind(..., publish), publishedHandleOf, identitiesOf and HandleUnpublished, and the note now uses those, with line numbers against 0a6c5d3. The Google userId stays the REQ-PLAT-05A digest the verifier returns as a hex string: idNode is keccak256 of that string, and the event and the indexer carry it rather than leaving it empty. The ID token lands in the Bridge's Callback and the Distribution's Prover; only the email and the userId digest reach the Application. Public inputs are 57 with the email at offset 36, and the handle digest keeps that count. The tagged Google handle digest also changes handleHashOf and every client computing HandleEscrow.deposit's handleHash; the downstream list and the contracts step say so. The note maps the round's spec changes: watermarks on both keys, the frozen digest-profile flag, constants shared across ceremony versions, SP-PRIV-01's scope, and ASM-PROOF-01. Both notes drop their history: the sub section states the design instead of what was first proposed, and the neutral-client note states what its screen would enforce instead of which review found it missing. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Google Platform Ceremony Version 1 is released: Ethereum mainnet
accepts google/1 bindings on the libid-circuits v0.6.0 circuit, which
exposes the raw email. Version 1 keeps main's statement: REQ-PLAT-04,
REQ-PLAT-16B and REQ-PLAT-16C are restored verbatim, and ASM-PROOF-01
returns to main's text, so a new statement takes a new version.
The digest profile becomes version 2, in a new §3.4 that states only
what it changes: REQ-PLAT-04A bounds its sub at 31 bytes without " or
\, checked on the signed bytes; REQ-PLAT-16E replaces the raw email
public input with the handle digest; REQ-PLAT-16F holds the circuit's
validation and normalization; REQ-PLAT-16D the verifier's return.
§3.4 says version 1's artifacts are the v0.6.0 circuit and version 2
awaits a release enforcing 16E and 16F.
The handle digest is keccak256 of the normalized handle on every
platform and version (REQ-PLAT-08M), replacing the tagged SHA-256. The
Consumer cannot turn one digest into another without the email, so
only a version 2 circuit that exposes v1's inner digest lets a v1 and a
v2 binding of one address reach the same handle key; the identity key
was already shared through REQ-PLAT-05A. handleHashOf and
HandleEscrow.deposit keep working for Google. TEST-PLAT-06A's vector is
keccak256("alice@gmail.com").
REQ-PLAT-08L now records whether a platform admits digests, since one
platform has a byte version and a digest version; every version shares
the normalization. REQ-PLAT-08G freezes the normalization once a
platform admits digests and has bound anything, lets admission start
after bindings exist, and refuses to withdraw it once a digest result
was accepted.
Common §2 and §12, ASM-PROV-05, and libid.md's overview and parameter
table name version 2; §12 notes that an account bound under version 1
has its email in a public event that a later version 2 binding does not
hide.
Assisted-by: Claude Opus 5.5
Signed-off-by: SupremaLex <georglutsenko@gmail.com>
Google version 1 is released on Ethereum mainnet, so the notes describe the digest profile as version 2 beside it. "Which hash" takes keccak256 of the normalized email: mainnet already holds Google bindings under keccak256 nodes, and a Consumer holding only a digest cannot map one hash onto another, so the tagged SHA-256 would split every address across two nodes. The escrow item and the handleHashOf step go, since nothing downstream of the node changes. The spec map, the implementation steps, the open item (a circuit release for version 2) and the decided list follow the spec as now written; the map no longer lists the OAuth Bridge in SP-PRIV-01's trust text or ASM-PROOF-01's in-place editing. The notes say a version 1 account's address is already public whatever version 2 binds later. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
…ndle key
The handle digest is keccak256 of the normalized handle, except under
Google version 2, where it stays SHA256("libid.google-handle" ||
email): the construction belongs to the Platform Ceremony Version, is
recorded per accepted version (REQ-PLAT-08L) and is frozen once that
version has bound (REQ-PLAT-08G). Versions of a platform still share
its normalization. TEST-PLAT-06A's vector is the tagged digest again.
So one address has a version 1 and a version 2 Google handle key; the
identity key stays shared through REQ-PLAT-05A. REQ-PLAT-08I says a
binding under a version with another construction pairs the identity
with a different handle key and retires the previous one as a rename.
A new REQ-PLAT-08N resolves a plaintext handle under every construction
the platform's versions use, answers the owned key with the newest
watermark, and for an unbound handle answers the newest version's key,
so a payer takes the key from the Consumer instead of hashing one
construction. Holding, the disclosure call and the rename path of
REQ-PLAT-08J go through that resolution. TEST-PLAT-20C covers it.
§3.4, §9, common §2 and §12 state the split plainly: a version 2
rebinding writes a new handle key, a version 1 account's email stays
public, and a digest computed by the payer reaches one construction
only.
Assisted-by: Claude Opus 5.5
Signed-off-by: SupremaLex <georglutsenko@gmail.com>
"Which hash" takes the tagged SHA-256 again: the circuit already computes SHA-256, and it measured about half keccak256's extra gates. The note records the price: a version 1 and a version 2 binding of one address have different handle nodes, a version 2 rebinding retires the version 1 node, resolution tries both constructions, and handleHashOf must answer the resolved node's digest for HandleEscrow.deposit callers. The downstream list names every node derivation that grows a per-version case. Unifying the nodes is listed as an open decision. Assisted-by: Claude Opus 5.5 Signed-off-by: SupremaLex <georglutsenko@gmail.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A private mode for Google handles, specified as Google Platform Ceremony Version 2. google/1 is live on Ethereum mainnet (since 2026-10-04) with the raw email as a public input, so it stays exactly as
mainspecifies it, and the digest profile is a new version.Under google/2, the Proving Circuit exposes the handle as a digest,
SHA256(UTF8("libid.google-handle") || normalized email), beside main'suserIddigest of thesub(REQ-PLAT-05A). The Consumer keys the binding on those digests. The plaintext email reaches the chain only when a transaction carries it: a claim that carries the email, or a laterpublish(platformId, handle). Thesubnever does. Whoever knows an address can still resolve it; nobody reads it off the chain.design/private-gmail-handle.mdholds the rationale, the options weighed and the decisions; the rest is specification text.Versions
main: raw email bytes,subof 1 to 255 bytes, and the libid-circuits v0.6.0oidc-googlecircuit.subof 1 to 31 bytes, with no"or\, checked on the signed bytes.emailpublic input is replaced by the handle digest.userIdand the handle digest, and passes a carried email through unchecked.main: a new statement takes a new version.Split handle keys, for now
google/2 keeps the tagged SHA-256 for its handle digest, because the circuit already runs SHA-256 and keccak256 costs more gates. google/1 keys handles on
keccak256(email), so one Google address now has two handle keys. The identity key is shared, because both versions use the REQ-PLAT-05AuserId. The spec states the consequences:handleHashOffollows the resolution. That is contract work the design note lists.Unifying the keys by putting keccak256 in the circuit is listed as an open decision.
What else the PR specifies
sub. The Prover derives local fields from the verified ID Token, and the delivered handle stays raw.sub). It rests on the unmodified Canonical Runtime, with its Prover proving in zero-knowledge mode with fresh randomness (REQ-COMMON-45A, ASM-ZK-01). It does not stop an operator sending the handle itself.userIdis derived from.Why the
subis never publishedIn short: leaking the
subwould not be critical, but nothing needs it in plaintext, so there is no reason to publish it.GitHub and X publish both of their values, and Google publishes neither by default.
api.github.com/users/<login>returns both, and an X username and id are public on X). Putting them on chain reveals nothing new.subare not public anywhere, so the Google profile hides both by default. The user may choose to publish the email.Once the email is published, the
substays hidden. A Google account has onesub: a permanent identifier, the same at every site where the user has used "Sign in with Google", and each of those sites stores it as its key for that user.subhas no display value. Its only effect on chain would be to let each of those sites link its user to a wallet.subadds is:Nothing needs it as text. Every check that uses the
subworks on its digest:idNode. The old email is retired and stops resolving, andbyHandle(node)gives any email's current owner andobservedAt. That check needs the email alone.resolveId. Neither thesubnor anything new reaches the chain.idNode), and the disclosure call ispublish(platformId, handle)with nosub.Keeping it out also simplifies the protocol.
subcould never be taken back, so not publishing one that nobody needs costs nothing now and nothing later.The limit is unchanged: a party that already holds a user's
subcan hash it and confirm the binding (ASM-HASH-01). What this prevents is readingsubvalues off the chain.Name rules for X and GitHub too
The name slot (
published[wallet][platform]) is shared by every platform, so the new write rule applies to X and GitHub as well. Today onmain, once a wallet has published a name, every later claim from that wallet overwrites it with the handle just proved, even withpublishName: false, so claiming a second account replaces the first account's name. Under this PR's rule only a claim of the named account, or one that asks to publish, writes it. The contract change (_write's refresh condition,publish, the digest-profile flag) is described in the note's implementation plan and belongs tolibid-contracts.Review
Five
/code-review xhighrounds, each fixed. Since the merge withmain, the most serious findings were:The review-fix commits carry
Assisted-by:trailers, asAGENTS.mdrequires.Also on this branch: a neutral Google client (option, not specified)
design/neutral-google-client.mdasks whether the application can be kept from the address as well. It proposes one Google client operated by libID, with its redirect on the Distribution origin; the runtime proves there and returns only the proof, the digests and, on the user's yes, the email.openid emailneeds no app verification. A top-level redirect flow avoids the popup-opener and storage-partitioning problems; FedCM does not fit.It is layered on Google version 2, and would be specified in its own PR if pursued.
Measured
These numbers come from the old toolchain, with keccak256 builds; nothing has been measured on main's nargo 1.0.0-rc.3 and bb 6.0.0-rc.2, or with SHA-256.
Gate counts on scratch copies of the circuit at the pinned toolchain (nargo 1.0.0-beta.25, bb 5.2.0;
nargo compile, thenbb gates --oracle_hash keccak), and median proving time with witness generation (about 0.4 s for each) excluded:bb provesubdigest, email bytes raw)keccak256of the lowercased email computed outside the circuit. All three stay under the 2^18 SRS the proof worker loads.bb prove -t evm. Inputs are a Google-shaped ID token signed by a generated RSA-2048 key. Intel i7-1165G7 (4 cores, 8 threads), 15 GB RAM; eight threads were no faster than four. Node WASM excludes a browser's worker overhead, so a user waits somewhat longer.noir-lang/keccak256library compiles under the pinned Noir at v0.1.3, not at the vendored v0.1.1.Verification
specs/*.mdanddesign/*.mdfinds no duplicate definition and no dangling reference.main, apart from the new §3.4 heading.alice@gmail.com,0x5ecf…23e1) and theuserIdvector reproduce.pnpm -C site install --frozen-lockfile,buildandtestpass.🤖 Generated with Claude Code
https://claude.ai/code/session_01Ar21wPcHzNprswTzLJd3MA