Skip to content

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

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

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

Conversation

@SupremaLex

@SupremaLex SupremaLex commented Sep 22, 2026

Copy link
Copy Markdown
Member

A private mode for Gmail handles. The Google profile's Proving Circuit exposes the handle and the canonical userId as keccak256 digests instead of bytes, and the Consumer keys the binding on those digests. The plaintext reaches the chain only in a transaction that discloses it: a claim whose authorized operation says so, or a later disclosure call from the owner. Whoever knows an address still resolves it; nobody reads it off the chain.

The design note (design/private-gmail-handle.md) holds the rationale, the options considered, the measured circuit cost, and every decision taken. The rest is the specification text it graduates into, followed by the review fixes.

The specification

specs/platform-ceremonies.md

  • §2.1a's lead-in and REQ-PLAT-08A/08B admit a circuit that normalizes what it digests, refusing where the vector table trims, and a Consumer that keys on digests and normalizes every handle it receives.
  • A new §2.1b defines disclosure (history: once any transaction has carried the plaintext) and publication (Consumer state), with these requirements:
    • REQ-PLAT-08D: keys come from the digests. The disclosure choice sits in the claim's Authorized Transaction Data. Plaintext is accepted only when authorized and only when it hashes to the digests, both or neither. A bytes profile always discloses. A private claim keeps the published name when it re-proves the same handle, and clears it otherwise.
    • 08E: the disclosure call publishes. It requires the identity and handle keys to be each other's current pair, in both directions, and the caller to own both.
    • 08F: the event carries a disclosed flag and the normalized handle.
    • 08G: the Consumer holds a digest platform's handle rules fixed once it has bound anything.
    • TEST-PLAT-20A exercises all of them.
  • REQ-PLAT-03: the Canonical Runtime releases the plaintext to the application only for a disclosing claim.
  • REQ-PLAT-04 and 16D: a backslash in sub is refused, so nothing the circuit digests holds an escaped byte.
  • REQ-PLAT-16B/16C/16D: the digests become public inputs, the Platform Verifier returns them and passes plaintext through unchecked, and the sub/email validation moves into the circuit, with 31- and 62-byte buffers. TEST-PLAT-06A exercises them.
  • §9 states what the digests protect and what they do not.
  • The Google profile stays Platform Ceremony Version 1. Nothing is released, so its statement is edited in place.

specs/ceremony-common.md

  • ASM-HASH-01 (preimage resistance, with the enumeration caveat) and ASM-ZK-01 (a zero-knowledge proof system).
  • SP-PRIV-01: plaintext reaches the chain only in a transaction that discloses it. That transaction publishes it when sent, accepted or not.
  • REQ-COMMON-05E returns the digests, and any plaintext marked unverified, which the Consumer trusts only once it hashes to them.
  • REQ-COMMON-45 selects only zero-knowledge artifacts for a digest profile.
  • §12 replaces "published deliberately" for the handle and user identifier with the digest profile's confidentiality and its limits.

specs/libid.md: one sentence among the enforceable guarantees.

Decisions carried

  • What private means: hidden but resolvable by the exact address, not a salted commitment. sub is hidden with the handle.
  • Who chooses: the user, not the application. disclose joins the claim's Authorized Transaction Data, committed in the digest like the fee, so the choice is fixed before proving. Making a private binding public later is a publish call.
  • Defaults: Google is private unless disclosed; X and GitHub are unchanged, and always disclose.
  • publish: refused for a handle retired by a later claim, or taken over by another account of the same wallet.
  • Private re-claims: a private claim of the published handle keeps the publication; one of another handle clears it.
  • Handle rules: Google's handle rules freeze at the first Google binding. After that they change only by contract upgrade.
  • Resolution: ENS forward names resolve for private bindings, once the indexer's lookup is keyed by node; the gateway reads the indexer, not the chain. The indexer's plaintext resolve routes stay.

Review

/code-review xhigh raised 15 findings plus 5 smaller ones. All are addressed, in the commits after 543b6c5:

  • trust in the pass-through plaintext;
  • who chooses disclosure;
  • the zero-knowledge assumption;
  • reverted calldata;
  • the undefined disclosed/published terms;
  • the one-sided pairing check;
  • the publication-clearing rule;
  • escaped sub bytes;
  • the unstated buffers;
  • the frozen handle rules;
  • REQ-PLAT-03 for digest profiles;
  • 08B's caller-bytes wording;
  • the note's ENS gateway claim;
  • the public-input count.

The version-2 finding went away when the digest profile became version 1.

Measured

Gate counts on scratch copies of the circuit at the pinned toolchain (nargo 1.0.0-beta.25, bb 5.2.0):

  • today: 179,443;
  • with the email digest: 203,878 (+13.6%);
  • with both digests: 223,582 (+24.6%).

The noir-lang/keccak256 library compiles under the pinned Noir at v0.1.3, not at the vendored v0.1.1.

Verification

The authoring skill's linter (.agents/skills/trust-min-protocol-specs/scripts/lint_spec.py, run by hand) gives the same count as the pre-existing baseline, with no finding in the changed text. The digest vectors use opaque transactionData, so the new disclose field leaves them valid. Spec references in the note are against origin/main at 002c201; contract, indexer and deployer references are against their origin/main.

Based on main.

🤖 Generated with Claude Code

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

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 328b7d8 Sep 22 2026, 03:20 PM

SupremaLex and others added 6 commits September 23, 2026 16:27
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.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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