Zero-Knowledge Implementation
Why Zero-Knowledge?
Imagine you want to enter a bar that requires you to be 18+.
Traditional way: Show your ID card → Bartender sees your name, address, exact birthday.
Zero-Knowledge way: Prove "I am 18+" without revealing anything else.
How PolyPay Uses This
In a traditional multisig account:
Everyone sees WHO signed each transaction
Signer addresses are public on blockchain
In PolyPay:
You prove "I know the secret for an authorized commitment" without revealing your EOA address
Your Ethereum address stays private, only the commitment is visible
The Four Proofs
When you sign a transaction in PolyPay, the ZK circuit proves four things simultaneously. The circuit is written in Noir, a domain-specific language for zero-knowledge proofs, and compiled to UltraPlonk for efficient verification.
Proof 1: "I know the transaction"
Problem: We need to verify you're signing the correct transaction, not a fake one.
Solution: You provide a "fingerprint" (hash) of the transaction using Poseidon Hash. The circuit checks this fingerprint matches.
Analogy: Like a sealed envelope - you prove the content inside matches what's expected.
Why Poseidon? zkVerify limits public inputs to 32 fields. Since transaction hash is 32 bytes, we compress it into a single field using Poseidon hash.
Proof 2: "I signed it"
Problem: Anyone could claim they signed a transaction.
Solution: The circuit verifies your ECDSA signature is valid - the same signature standard Ethereum uses.
Analogy: Like your handwritten signature - unique to you and verifiable.
Technical note: Ethereum's personal_sign adds a prefix "\x19Ethereum Signed Message:\n32" before signing. The circuit reconstructs this prefixed message and hashes it with Keccak256 before verification.
Proof 3: "I am authorized"
Problem: How to prove you're in the signers list?
Solution:
Each signer has a secret "commitment" stored as:
commitment = hash(secret, secret)The circuit proves you know the secret for a given commitment
The smart contract checks if that commitment exists in the signers list
Analogy: Imagine a club membership list. You prove "I know the password for one of these memberships" and the club verifies that membership is on the list.
How it works:
The circuit verifies: hash(secret, secret) == commitment
Then the smart contract checks: commitment in signers list?
This two-step verification ensures only authorized signers can sign transactions while keeping their Ethereum addresses private.
Proof 4: "I haven't signed before"
Problem: Same signer could submit multiple proofs for one transaction.
Solution:
Each signature generates a unique "nullifier":
nullifier = hash(secret, tx_hash)Smart contract stores used nullifiers
Same person + same transaction = same nullifier = rejected
Analogy: Like a voting ballot with a unique barcode - you can only use it once.
Complete Flow
User Signs: User signs tx_hash with their Ethereum wallet → Produces signature, pub_key_x, pub_key_y
Frontend Generates Proof: Noir circuit receives private inputs (signature, pub_key, secret, tx_hash) and public inputs (tx_hash_commitment, commitment, nullifier) → Outputs ZK Proof
Backend Verifies via zkVerify: Proof submitted to zkVerify for verification → Returns aggregation_id, attestation
Smart Contract Executes: When threshold signatures reached, contract verifies all proofs on-chain, checks nullifiers not used, checks each commitment is in current signers list, then executes transaction
Circuit Inputs Reference
Private Inputs (Hidden from everyone)
signature
[u8; 64]
ECDSA signature (r, s) without recovery byte
pub_key_x
[u8; 32]
Public key X coordinate
pub_key_y
[u8; 32]
Public key Y coordinate
secret
Field
Signer's secret (from signing "polypay-identity")
tx_hash_bytes
[u8; 32]
Transaction hash to sign
Public Inputs (Visible on-chain)
tx_hash_commitment
Field
Poseidon hash of tx_hash
commitment
Field
hash(secret, secret) - checked against signers list
nullifier
Field
Prevents double-signing
More Detail
Learn More
Last updated