DocsIn Pigeon today

Zero knowledge in Pigeon today

  • ComponentCore · Node
  • StatusProtocol
  • SecuritySealed

Which parts of Pigeon use a zero-knowledge proof, which use something else, and what each one does.

The pieces

PieceWhat it isA zero-knowledge proof?In this alpha
Invite tokens: blindingA verifiable oblivious pseudorandom function (VOPRF, RFC 9497, ristretto255 with SHA-512)No. The privacy comes from blindingOn
Invite tokens: the server's proofA DLEQ proof, from the same RFC: the server used the key it publishedYes, non-interactive (Fiat-Shamir)On
MessagesEnd-to-end encryption with MLS (RFC 9420), through OpenMLS, then sealed again for the network (HPKE, RFC 9180, with X-Wing)No. EncryptionOn
Reports: message frankingAsymmetric message franking (Tyagi, Grubbs, Len, Miers and Ristenpart, 2019) over ristretto255: a signature of knowledge on each messageYes, a signature of knowledge is a non-interactive zero-knowledge proofBuilding toward Built and tested; reporting is switched off

Invite tokens, step by step

ComponentCoreComponentNode

An invite code carries a token minted blind, and the new account spends it for a second token, also blind (invite codes). Each mint goes like this:

  1. The app picks a random nonce, hashes it with a label and the key's period to a point on the curve, and multiplies that point by a random blind. Only the blinded point goes to the server.
  2. The server multiplies the blinded point by its secret key for that period and sends back the result with a DLEQ proof: the result and its public key were made with the same secret.
  3. The app checks the proof against the public key the server published for that period, removes the blind, and keeps the token: the nonce and the unblinded output.
  4. Spending a token, the app shows the nonce and output. The server checks them with its key and stores a hash of the nonce, so the token can't be spent twice.

The server never saw the nonce or the output at minting time, so what it stores (a roster, a mint counter and the hashes of spent nonces) can't join the person who invited you to the code, or the code to your new account. The DLEQ proof closes the remaining hole: without it, the server could answer one person with a key of its own and recognise their token when it is spent. Network addresses and timing are a separate gap, set out in our privacy policy.

These tokens are privately verifiable: only the server, which holds the key, can check one. That is enough for a gate the server runs itself.

Reports: proving who sent a message

ComponentCoreStatusProtocol

A report will prove that a message really came from its sender, without telling us who reported it. Each message carries a frank: a commitment to its content, checked on arrival (message franking). The next layer adds a signature of knowledge over the message, which convinces the recipient and, once the message is reported, the moderator who judges it (the "judge", in the paper's words). To anyone else it proves nothing: the scheme lets anyone make one that looks the same from public keys alone. That layer is built and tested in Pigeon's core; reporting is switched off in this alpha.

What we are building toward

A public key log on the Midnight network, so anyone can check that the key behind a handle is the real one: Midnight and the key log.