Skip to content

docs(specs): identity digests and disclosure for the Google profile - #45

Open
SupremaLex wants to merge 32 commits into
mainfrom
design/private-gmail-handle
Open

SupremaLex wants to merge 32 commits into
mainfrom
design/private-gmail-handle

Conversation

@SupremaLex

@SupremaLex SupremaLex commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

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 main specifies 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's userId digest of the sub (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 later publish(platformId, handle). The sub never does. Whoever knows an address can still resolve it; nobody reads it off the chain.

design/private-gmail-handle.md holds the rationale, the options weighed and the decisions; the rest is specification text.

Versions

  • google/1 is byte-identical to main: raw email bytes, sub of 1 to 255 bytes, and the libid-circuits v0.6.0 oidc-google circuit.
  • google/2, in the new platform §3.4, states only what changes:
    • REQ-PLAT-04A: sub of 1 to 31 bytes, with no " or \, checked on the signed bytes.
    • REQ-PLAT-16E: the raw email public input is replaced by the handle digest.
    • REQ-PLAT-16F: the circuit validates and normalizes the email, then digests it.
    • REQ-PLAT-16D: the verifier returns the userId and the handle digest, and passes a carried email through unchecked.
  • Not yet implementable. No libid-circuits release proves google/2. §3.4 says it becomes implementable once a release that enforces 16E and 16F publishes its artifacts.
  • ASM-PROOF-01 is unchanged from 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-05A userId. The spec states the consequences:

  • REQ-PLAT-08M: the handle digest is keccak256 of the normalized handle, except under google/2. Each version's construction is recorded and frozen (08L, 08G).
  • Rebinding (08I): binding under google/2 retires the identity's google/1 handle key, as a rename does.
  • Resolution, new REQ-PLAT-08N:
    • The Consumer derives a plaintext handle's key under every construction the platform's versions use.
    • It answers the owner of the owned key with the newest watermark. If neither key is owned, it answers "unbound" with the google/2 key.
    • Holding, the disclosure call and the rename path all go through this rule.
  • Escrow: payers take the key from the Consumer's resolution. A deposit addressed by a self-computed keccak256 hash reaches only a google/1 holder. For contracts, handleHashOf follows the resolution. That is contract work the design note lists.
  • Migration: an account bound under google/1 published its email in an event, and binding again under google/2 hides nothing already public.
  • TEST-PLAT-20C covers the move from v1 to v2, the resolution order and escrow addressing.

Unifying the keys by putting keccak256 in the circuit is listed as an open decision.

What else the PR specifies

  • Keys and names for every platform (§2.1b):
    • Watermarks: identity and handle keys, each with its owner and watermark. A write must be strictly newer than both keys' watermarks, and retirement keeps the watermark.
    • One name per wallet per platform:
      • The name is written only from strictly newer evidence that carries the handle and asks to publish, or that renames the named identity.
      • A name is read only while its wallet holds the handle.
      • "Carries" and "asks to publish" are defined for byte profiles too, so X and GitHub authors get names.
  • Digest-profile rules:
    • disclosure (08E), events (08F), and the frozen normalization and construction (08G);
    • result shapes (08L): a verified handle is either bytes or a digest; a carried email is not verified bytes.
  • Who decides disclosure (REQ-PLAT-03A): the Canonical Runtime's Application includes the email only when the user asks, and never the sub. The Prover derives local fields from the verified ID Token, and the delivered handle stays raw.
  • Privacy property (SP-PRIV-01): it bounds what the chain yields: the Consumer, Proof Verifier and Platform Verifier put no handle on chain that a transaction did not carry, and never the account identifier (Google's 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.
  • Terms and trust:
    • "Account identifier" names the value a userId is derived from.
    • ASM-HASH-01 covers keccak256 and SHA-256.
    • The trust table in libid.md says the Application decides disclosure, and the Distribution's Prover holds the ID Token.

Why the sub is never published

In short: leaking the sub would 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.

  • GitHub and X: the id and the handle are already public on the platform (api.github.com/users/<login> returns both, and an X username and id are public on X). Putting them on chain reveals nothing new.
  • Google: an account's email and sub are 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 sub stays hidden. A Google account has one sub: 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.

  • The email is a display name. Publishing it is a choice about what people resolve and pay.
  • The sub has no display value. Its only effect on chain would be to let each of those sites link its user to a wallet.
  • That linkability is modest, not critical. A site that also stores the email can already link by email once the email is public. What the sub adds is:
    • sites that never stored the email, or hold an old one;
    • a key that survives an email change.

Nothing needs it as text. Every check that uses the sub works on its digest:

  • Recency of an email. The same account proving again with a new email is recognised by its unchanged idNode. The old email is retired and stops resolving, and byHandle(node) gives any email's current owner and observedAt. That check needs the email alone.
  • "Is this still the email of account X?" Only a party that already knows X can ask. It computes X's REQ-PLAT-05A digest and passes that to resolveId. Neither the sub nor anything new reaches the chain.
  • Keys and disclosure. The Consumer keys the identity on the digest (idNode), and the disclosure call is publish(platformId, handle) with no sub.

Keeping it out also simplifies the protocol.

  • There is no both-or-neither rule and no refusal of a lone field.
  • There is one disclosure call, taking the handle alone.
  • A plaintext sub could 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 sub can hash it and confirm the binding (ASM-HASH-01). What this prevents is reading sub values 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 on main, once a wallet has published a name, every later claim from that wallet overwrites it with the handle just proved, even with publishName: 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 to libid-contracts.

Review

Five /code-review xhigh rounds, each fixed. Since the merge with main, the most serious findings were:

  • the spec contradicted itself on disclosing results;
  • a handle could be taken back with older evidence;
  • the digest-profile flag was not frozen;
  • the Prover was bound to a disclosure choice it cannot make;
  • Google version 1 was being edited after it went live, which led to version 2.

The review-fix commits carry Assisted-by: trailers, as AGENTS.md requires.

Also on this branch: a neutral Google client (option, not specified)

design/neutral-google-client.md asks 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.

  • Feasibility, checked on 2026-09-23: Google's policies allow a shared broker client, MetaMask Embedded Wallets ship one, and openid email needs no app verification. A top-level redirect flow avoids the popup-opener and storage-partitioning problems; FedCM does not fit.
  • New risk: consent to a shared client is global, so 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.
  • Cost: libID gains silent re-authentication over every user; the Distribution operator holds every Google token; one client carries every application's quota; the consent screen names libID.

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, then bb gates --oracle_hash keccak), and median proving time with witness generation (about 0.4 s for each) excluded:

Circuit Honk gates bb.js WASM, 4 threads bb.js WASM, 1 thread native bb prove
today 179,443 5.64 s 14.13 s 1.69 s
public-only (a hypothetical second circuit: sub digest, email bytes raw) 200,011 6.26 s 16.12 s 1.90 s
this design (both digests, one circuit) 223,663 7.27 s 17.70 s 2.15 s
  • A second, public-only circuit would save a public claim about 1.0 s (14%) at the four threads the proof worker uses. The note weighs that against what it costs: two normalizers that can put one address on two nodes without anything noticing (the single circuit refuses such a claim instead), two artifacts and verifiers to release and pin, and a disclosure choice made before proving rather than at submission. One circuit stays the recommendation.
  • Every proof verified. The in-circuit email digest equals keccak256 of the lowercased email computed outside the circuit. All three stay under the 2^18 SRS the proof worker loads.
  • Method: bb.js 5.2.0 and noir_js 1.0.0-beta.25 in Node 22 with the proof worker's settings (WASM backend, keccak, ZK, verification key supplied); native 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.
  • The noir-lang/keccak256 library compiles under the pinned Noir at v0.1.3, not at the vendored v0.1.1.

Verification

  • Requirement IDs: a script over specs/*.md and design/*.md finds no duplicate definition and no dangling reference.
  • Unchanged parts: §3.1 to §3.3 and ASM-PROOF-01 diff clean against main, apart from the new §3.4 heading.
  • Test vectors: the google/2 handle-digest vector (alice@gmail.com, 0x5ecf…23e1) and the userId vector reproduce.
  • Site: pnpm -C site install --frozen-lockfile, build and test pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ar21wPcHzNprswTzLJd3MA

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
SupremaLex marked this pull request as ready for review September 22, 2026 15:22
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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
SupremaLex force-pushed the design/private-gmail-handle branch from 2a3049f to 01db383 Compare September 23, 2026 14:27
… 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>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://design-private-gmail-handle.previews.lib.id, https://design-private-gmail-handle-libid.grounded-systems.workers.dev (commit 00a54b9)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://049f563b.previews.lib.id, https://049f563b-libid.grounded-systems.workers.dev 00a54b9 2026-10-05T18:24:44.169Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://df6aafa9.previews.lib.id, https://df6aafa9-libid.grounded-systems.workers.dev 570aeaf 2026-10-05T16:43:26.228Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://563903eb.previews.lib.id, https://563903eb-libid.grounded-systems.workers.dev 589d707 2026-10-05T15:17:10.559Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://e265ba55.previews.lib.id, https://e265ba55-libid.grounded-systems.workers.dev 605b340 2026-10-05T10:40:29.354Z Visit the dashboard ↗
  • Build: Failed ❌

View logs ↗
7872fef 2026-09-28T15:27:34.129Z View logs ↗
  • Build: Failed ❌

View logs ↗
9c2dcc8 2026-09-28T11:14:37.793Z View logs ↗

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

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant