Skip to main content

Privacy disclosure

What the coordination network keeps private, how, and what it doesn't. It covers contracts, network-member-client, user-app-core, wallet, the identity, credential and network-member-proof libraries, account, expo-identity-keystore, issuer-service and indexer. The demos, testbeds, reference apps and circuits are out of scope.

Network members are public by design: their records, keys and governance votes sit onchain. Users are anonymised: no PII (personally identifiable information) goes onchain, and what directly links a user to their activity stays with the user and the network member that enrolled them.

Each section lists what one party can and can't see, as the threat model does. Cells give the technical reason first, then a plain one where it helps. "Threat model" names the matching section and row.

Contents​

Public chain observer​

Any entity observing the chain. May use primitive or sophisticated chain-analysis techniques.

DataVisibleHow / whyMitigationCodeThreat model
networkId → metaStealthAddress (a network ID and its public receiving address)yesBoth registries publish this mapping, with no personal data.Deliveries go to fresh stealth accounts, which only the viewing key links back.
The address doesn't show what was received.
IdentityRegistry.sol:33
IdentityRegistry.sol:35
NetworkMemberRegistry.sol:44
NetworkMemberRegistry.sol:68
2. Registration and deduplication: Public chain observer, "Reads the networkId => metaStealthAddress entry"
The account address linked to a user's networkIdnoBoth derive from the account public key, so once that key signs onchain anyone can link them.
The account would reveal its network ID once it transacts.
In Phase 1 the account key signs only offchain. Spec 01 accepts the link if a later phase signs onchain.
Nothing links them today.
network-id.ts:27
derive.ts:40
2. Registration and deduplication: Public chain observer, "Reads the networkId => metaStealthAddress entry"
The auth address linked to a network member's networkIdyesA network member's network ID ends in its auth address.
Anyone can see which account acts for a network member.
Accepted by design: network members are known, vetted entities.network-id.ts:39
NetworkMemberRegistry.sol:40
—
The link between a user's old and new meta-stealth addressyesupdateIdentity keeps the network ID, linking the new address to the old.Recovery doesn't call updateIdentity. It removes the old entry and registers a new network ID, each from a fresh sender.
A recovered user's old and new addresses aren't linked by the registry.
IdentityRegistry.sol:36
IdentityRegistry.sol:69
IdentityRegistry.sol:76
IdentityRegistry.sol:82
6. Revocation and recovery: Public chain observer, "Pairs the old entry's removal with the new registration"
Which network member wrote a registry entrynoA write from the network member's own address would name it.A Semaphore proof shows only group membership, and the write comes from a fresh sponsored address.
No network member is named.
NetworkMemberGate.sol:65
sponsored-relay.ts:208
2. Registration and deduplication: Public chain observer, "Attributes every user a network member enrols to it"
Guarantees: "Anonymous registry writes"
Link between two registry writesnoEach write publishes a nullifier, and a repeated one would link two writes.The client picks a fresh random scope for each proof, so nullifiers never repeat.
Two writes can't be matched to each other.
NetworkMemberGate.sol:31
message.ts:69
2. Registration and deduplication: Public chain observer, "Reads the nullifier and scope that every registry write publishes"
Network member onchain recordsyesNetworkMemberRegistry publishes each network member's auth address, meta-stealth address, Semaphore commitment and governance signer.Accepted by design: network members are known, vetted entities.NetworkMemberRegistry.sol:40
NetworkMemberRegistry.sol:48
NetworkMemberRegistry.sol:53
—
Join requestsyesNetworkMemberJoinRequested publishes the applicant's keys, commitment, signer and metadataURI, from its auth address.Accepted by design: network members can only vet an applicant they can identify.NetworkMemberRegistry.sol:58
NetworkMemberRegistry.sol:131
—
Sponsored operations from one senderyesOperations from one sender share its address, and its paymaster proof can be reused with the same nullifier for a period.Accepted: spec 05 reuses one proof per account to allow gas cost amortisation. The network member client uses a fresh sender for every operation.sponsored-relay.ts:208
SemaphorePaymaster.sol:88
2. Registration and deduplication: Sponsored sender, "One membership proof sponsors the same account without limit"
Sponsored operations from different sendersnoA fixed proof scope would repeat the nullifier across senders and link them.Each proof's scope is its own sender, so nullifiers differ between senders.
Operations from different addresses can't be matched.
sponsored-relay.ts:230
SemaphorePaymaster.sol:88
2. Registration and deduplication: Sponsored sender, "One membership proof sponsors the same account without limit"
Issuer keys per network memberyesApprovedIssuerSet publishes each signing key against its network member's network ID.Accepted by design: a presentation proves its key is in the set without saying which.ApprovedIssuerSet.sol:89
ApprovedIssuerSet.sol:109
ApprovedIssuerSet.sol:110
Guarantees: "Issuer anonymity"
Revoked credential commitmentsyesThe registry publishes every revoked commitment. A revokeMany batch shows its entries were revoked together.Accepted by design: verifiers need the public list to check a credential isn't revoked.revocation.ts:32
IRevocationRegistries.sol:36
IRevocationRegistries.sol:44
CredentialRevocationRegistry.sol:77
—
Whose credential was revokednoEach commitment is to a random secret for that credential, with no user or issuer field.All network members write to one shared tree through the sponsored path.
No entry traces to a user or network member.
revocation.ts:32
IRevocationRegistries.sol:36
IRevocationRegistries.sol:44
CredentialRevocationRegistry.sol:77
—
Stealth address delivery announcementsyesAn announcement carries the stealth address, an ephemeral key, a view tag and its caller.Each delivery uses a fresh stealth address, ephemeral key and sponsored caller.
Two deliveries to one user look unrelated.
buildAnnouncement.ts:16
ERC5564Announcer.sol:15
deliver.ts:22
config.ts:190
5. Stealth addresses: Public chain observer, "Reads every announcement and may try to cluster"
A user's stealth accountsnoAn observer could try to group them from the announcements.Linking one to its meta-stealth address needs the viewing key or the shared secret.
Nobody outside can list a user's accounts.
scanAnnouncements.ts:405. Stealth addresses: Public chain observer, "Reads every announcement and may try to cluster"
Transfers and balances at stealth addressesyesAmounts are public, and accounts spent together in one transaction link to each other.Accepted for Phase 1: fresh stealth addresses don't hide transaction-graph links.fetchBalances.ts:11Accepted threats: "Onchain graph leaks"
Governance (votes on who joins or leaves)yesThe Safe's owners and approvals are public, and each admission, removal and exit emits the network ID and signer.Accepted by design: decisions by known network members are meant to be auditable.NetworkMemberGovernance.sol:73
NetworkMemberRegistry.sol:69
—
Personal datanoAnything identifying written onchain, even hashed, identifies a user permanently.Only network IDs, keys, commitments and stealth addresses go onchain.
Nothing onchain names a user.
IdentityRegistry.sol:33Guarantees: "No personal data onchain"
Onchain keys and entries recorded todayyesA future quantum computer could break today's public keys and de-anonymise accounts.Accepted: the system can move to post-quantum cryptography, but recorded data stays exposed.—2. Registration and deduplication: Public chain observer, "Records the entry now to recover the key"
Accepted threats: "Harvest-now-decrypt-later on existing accounts"

Other network members​

Everything the public chain observer sees, plus:

DataVisibleHow / whyMitigationCodeThreat model
Which other network members serve a usernoRecords, registry writes, sponsored operations or deliveries could each name the network members a user deals with.Records stay on each network member's server. Registry writes and sponsored operations carry Semaphore proofs, and deliveries go to stealth addresses, so none names a network member.issuance-store.ts:11
NetworkMemberGate.sol:65
SemaphorePaymaster.sol:88
deliver.ts:22
—

The user's own network member​

What the network member that enrolled a user sees in normal operation.

DataVisibleHow / whyMitigationCodeThreat model
A user's meta-stealth addressyesAn individual network member holding a user's account public key can compute the network ID and look up the entry.Accepted: the threat model gives users no privacy from network members. A network member without the user's account public key can't tell which entry is theirs.IdentityRegistry.sol:33
adapters.ts:147
2. Registration and deduplication: Other network member, "Looks up a user's identifier to find their accounts"
Design trade-offs: "A user has no privacy from network members"
Which user holds which credentialyesThe issuer service links each credential to its user's network ID, programme, country and revocation commitment, so it can revoke it.Accepted: the network member enrolled the user and is trusted with this. The records stay on its own server.types.ts:24
issuance-store.ts:11
credentials.ts:152
Design trade-offs: "A user has no privacy from network members"
What the phone sends at issuanceyesIts public keys, the signed registration request, and an attestation chain with the OS, patch level and boot key.The attestation is discarded after checking, and only a commitment to the device key is kept.
Phone details aren't stored.
credential-issue.ts:183
credentials.ts:241
credentials.ts:247
types.ts:31
3. Credential issuance: User, "The device key is a software key"
Each user's full receiving historyyesThe indexer stores each user's viewing key, readable with ENCRYPTION_KEY.Accepted: a viewing key reveals receipts, never spending authority.schema.sql:14
store.ts:17
Accepted threats: "Receiving history is visible to the indexer"

RPC provider​

DataVisibleHow / whyMitigationCodeThreat model
Which users the network member works withyesThe provider sees identity(networkId) reads at issuance, registration and delivery, and a delivery's sender reads right after. That ties the network ID to the announced stealth address.Accepted: the network member can run its own node or use a provider it trusts.credentials.ts:116
submit.ts:64
deliver.ts:30
sponsored-relay.ts:125
Accepted threats: "RPC nodes are trusted"
One user's accounts, groupedyesThe indexer's /balances reads batch one user's stealth accounts together.Accepted: the indexer can use its own node, a trusted provider or several endpoints.fetchBalances.ts:26Accepted threats: "RPC nodes are trusted"
Which network member a user gets a credential fromnoAt issuance the wallet reads issuer keys through its RPC endpoint, and reading only the issuer's would name it.The user app reads every scanned network member's issuer keys and uses only the issuer's.
The provider can't tell which one issues.
credential-issue.ts:92
chain-reads.ts:179
ApprovedIssuerSet.sol:414
Accepted threats: "RPC nodes are trusted"

Bundler​

DataVisibleHow / whyMitigationCodeThreat model
Which network member sent a sponsored operationpartlyThe proof binds to the disposable sender, but the issuer service reaches BUNDLER_URL from its own IP.Accepted as out of scope for the reference deployment: use a bundler the network member runs or trusts.SemaphorePaymaster.sol:88
sponsored-relay.ts:174
config.ts:166
Accepted threats: "Network metadata is visible"

Verifier​

DataVisibleHow / whyMitigationCodeThreat model
Unrevoked credential from an approved issuer, for its programme and countrynoThe proof answers only for the verifier's own time, challenge, audience, programme and country.
The verifier learns only that the user qualifies.
Its only other public outputs are the issuer set and revocation roots, so asking for more returns nothing.zk-proof.ts:7
zk-proof.ts:202
4. Credential verification: Relying party, "Asks for more than the decision needs"
Issuer set and revocation roots used (which list versions the proof used)yesBoth roots are public outputs, and an old root shows roughly when the wallet last updated.Accepted: the wallet proves against the current root, and the window defaults to one hour.zk-proof.ts:172Accepted threats: "A superseded root narrows issuer anonymity"
Which network member issued the credentialnoEvery issuer key is public under its network member's network ID, so a proof that showed its key would name the issuer.The proof shows the key is in the approved set without saying which. Anonymity grows with the number of issuers.zk-proof.ts:7
ApprovedIssuerSet.sol:89
Guarantees: "Issuer anonymity"
Design trade-offs: "Issuer anonymity is bounded by the anonymity set"
Link between two presentationsnoA value repeated across proofs would let a verifier recognise a returning user.Each proof is fresh. Its public values are the verifier's own and the two current roots, which every holder shares.zk-proof.ts:7Guarantees: "Unlinkable presentation"
That the user holds a revoked credentialpartlyWhen the only match is revoked, the wallet can answer CREDENTIAL_REVOKED instead of a proof, which tells the verifier.The wallet asks for the user's consent first.presentation-create.ts:100
presentation-create.ts:104
—

Network observer​

IP addresses, timing and sizes of every request. Nothing here hides them.