Private, free stablecoin transactions at scale on Ethereum
Pretty Good Payments (PGP) is a Layer 2 payment system that enables private transactions for ERC-20 tokens, settling to Ethereum mainnet. The system uses a simplified version of the Zcash private e-cash model with an optimistic rollup secured by a one-round fraud proof challenge game.
- Privacy: Transactions use zero-knowledge proofs to hide sender, receiver, and amounts
- Free transactions: Users pay nothing - sequencers are compensated through yield generated on deposited funds
- High throughput: Up to 400 transactions per second on Ethereum mainnet using EIP-4844 blobs
- Low cost: 0.01 to 0.0001 cents per transaction depending on network conditions
- Decentralized: Open sequencer registration with an slashable stake to discourage spam.
User Deposits Sequencer Batches L1 Settlement
| | |
v v v
[ERC-20] --> [L2 Notes] --> [Blob TX] --> [Entrypoint Contract]
^ ^ |
| | v
[Withdraw] <-- [ZK Proof] <-- [Challenge Window] <-- [Finalized]
-
Deposit: Users deposit ERC-20 tokens by calling the Entrypoint contract. Tokens are transferred to a yield-generating vault, and a note hash is recorded for inclusion in a future block. The sequencer MUST include this deposit or face slashing. Even if fraud causes a rollback, deposits are re-queued and will eventually be included.
-
Transact: Users create zero-knowledge proofs that demonstrate:
- They know a note exists in the merkle tree
- They are authorized to spend the note (know the private key)
- Output notes contain exactly the same total value as inputs
- Nullifiers are computed correctly to prevent double-spending
Each transaction can consume up to 2 input notes and create up to 3 output notes. Users submit their proofs to a sequencer for inclusion.
-
Sequence: Sequencers batch transactions into EIP-4844 blobs and post them to L1. Each blob can hold approximately 273 transactions. The blob data is committed using KZG polynomial commitments, enabling efficient fraud proofs without storing all data on-chain.
-
Challenge: During the challenge window, anyone can prove fraud by:
- Opening specific blob fields using KZG proofs (~50k gas per field)
- Demonstrating the sequencer violated protocol rules
- Triggering a rollback and claiming half the sequencer's stake
-
Withdraw: Users create a special note with
publicKey = 0andblinding = destinationAddress. After the challenge period, they prove this note exists using a KZG opening proof and receive their tokens on L1.
Unlike traditional L2s that charge transaction fees, PGP uses yield to pay sequencers:
- User deposits go into ERC-4626 yield vaults (Aave, Compound, etc.)
- Yield is tracked per period and distributed to sequencers
- Distribution is proportional to blob usage (more transactions = more yield)
- Priority sequencers receive a 2x multiplier during their exclusive windows
This model enables free transactions for users while ensuring sequencers are compensated.
PGP uses an optimistic rollup model with a one-round challenge game:
- Optimistic Execution: Blocks are assumed valid unless challenged
- Economic Security: Sequencers stake ETH that can be slashed for fraud
- Liveness: At least one honest challenger must be online during the challenge window
- Data Availability: Blob data remains available for the challenge period (~18 days on mainnet)
The challenge game has four entry points, each targeting a specific type of fraud:
| Challenge Type | Fraud Detected | Evidence Required |
|---|---|---|
| Deposit | Wrong deposit leaf or non-zero padding | KZG proof of actual value vs expected |
| Nullifier | Double-spend (same nullifier twice) | KZG proofs of both occurrences |
| Tree Update | Incorrect merkle root after update | ZK proof of correct root + KZG proofs |
| Transaction | Invalid ZK proof or missing authorization | Full transaction data via KZG |
When fraud is proven:
- The fraudulent block and all subsequent blocks are rolled back
- The sequencer loses 100% of their stake:
- 50% goes to the challenger who proved fraud
- 50% is burned (prevents collusion)
- Only the challenger who proves fraud at the earliest block number receives the reward
- Rewards are claimable after the challenge period passes
Note that the fraud slashing mechanic is not required to ensure security, it creates economic incentives to avoid spamming fraud and to incentive challenge submission in high gas regimes, but overall the system is also secure running with zero stake requirement if sufficient challengers exist.
The core transfer functionality uses Groth16 zero-knowledge proofs to enable fully private transactions:
- Sender privacy: No one can identify who sent a transaction
- Receiver privacy: No one can identify who received funds
- Amount privacy: Transaction amounts are hidden
- Unlinkable: Nullifiers cannot be connected to output notes without the private key
Users prove they own notes by demonstrating knowledge of the private key that hashes to the note's public key. The nullifier hash(privateKey, blinding, index) prevents double-spending while remaining unlinkable to the original note.
PGP supports a feature called Ethereum-owned accounts that enables L1-programmable privacy:
Any note with a publicKey value less than 160 bits is considered "Ethereum-owned." The public key is interpreted as an Ethereum address, and spending requires L1 authorization:
Standard Note: publicKey = hash(privateKey) -> Spend with ZK proof of privateKey
Eth-Owned Note: publicKey = ethereumAddress -> Spend with L1 registry approval
When spending an Ethereum-owned note, the ZK circuit outputs the ethKey as a public input. The on-chain verifier checks that this address has approved the transaction in the TransactionRegistry contract.
1. User creates transaction with eth-owned inputs
2. User signs approval on L1:
TransactionRegistry.approve([nullifier0, nullifier1, leaf0, leaf1, leaf2])
3. Sequencer sees approval and includes transaction
4. Challenge contract verifies registry approval exists
This dedicated L1 authorization flow can be used to make L2 payments programmable enabling multisig ownership, trustless swaps, escrows and other features which require resolution logic while at the same time giving destination, amount amd token type privacy for the users.
| Aspect | Standard Notes | Eth-Owned Notes |
|---|---|---|
| Sender privacy | Full | Partial (address visible on L1) |
| Receiver privacy | Full | Full |
| Amount privacy | Full | Full (unless blinding leaked) |
| Authorization | ZK proof only | Requires L1 transaction |
- Quick Start Guide - Set up a local development environment
- User Guide - How to deposit, transact, and withdraw
- Sequencer Guide - Run your own sequencer node
- Architecture Overview - Deep dive into system design
pretty_good_payments/
|-- src/ # Solidity smart contracts
| |-- Entrypoint.sol # Main entry point
| |-- *Challenge.sol # Fraud proof contracts
| |-- YieldRouter.sol # Yield management
| |-- library/ # Shared libraries
|
|-- offchain/ # Rust off-chain components
| |-- crates/
| |-- sequencer/ # Block building and submission
| |-- challenger/ # Fraud detection and challenge
| |-- common/ # Shared types and utilities
| |-- merkle/ # Merkle tree implementation
|
|-- circuits/ # Circom ZK circuits
|-- test/ # Solidity tests
|-- docs/ # Documentation
UNLICENSED - All rights reserved