From 02dc48a7de5179cda1f420ec5cd06957984a8242 Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 12 Jan 2026 20:37:12 +0000 Subject: [PATCH 1/7] stash first wip --- zk-blog-prompt.md | 70 +++++++++++++++++++++++++ zk-compression-blog.md | 113 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 zk-blog-prompt.md create mode 100644 zk-compression-blog.md diff --git a/zk-blog-prompt.md b/zk-blog-prompt.md new file mode 100644 index 0000000..3dd3c99 --- /dev/null +++ b/zk-blog-prompt.md @@ -0,0 +1,70 @@ +# Prompt: Generate ZK Identity Protocol Blog Post + +## Context +You are writing a technical blog post about a ZK identity protocol. The post is for Solana developers who are familiar with Solana but new to ZK. + +## Style +- Paul Graham style: concise, direct, simple language, short sentences, no fluff +- No filler phrases ("Here's the thing", "Let that sink in", etc.) +- No binary contrasts ("Not X. But Y.") +- Trust the reader +- ~800-1000 words total + +## Resource +[INSERT RESOURCE HERE: GitHub repo, documentation, or codebase path] + +Analyze this resource to understand: +1. How identities/credentials are stored +2. How nullifiers work (if applicable) +3. The proof system used (Groth16, PLONK, etc.) +4. The instruction/function flow +5. How privacy is achieved + +## Required Sections + +### Section 1: Building Blocks of Privacy (~250 words) +Structure: +- P1 (2-3 sentences): Hook - why public transactions are a problem +- P2 (3-4 sentences): How state is stored in Merkle trees, why this is private (hashing is one-way) +- P3 (3-4 sentences): Brief ZKP explainer - what proof system is used, public inputs vs private, any setup requirements +- P4 (3-4 sentences): Nullifiers - what they are, how they prevent double-spending, why they don't reveal which leaf was spent +- P5: ASCII diagram showing offchain/onchain flow (user, indexer, program, state tree, nullifier set) +- P6 (2-3 sentences): Flow summary + +### Section 2: Tools (~100 words) +Structure: +- One intro sentence +- Grouped list of tools used by this protocol: + - Circuit tools (circom, noir, etc.) + - Proof generation tools (snarkjs, etc.) + - Protocol-specific libraries + +### Section 3: How [Protocol Name] Works (~200 words) +Structure: +- P1 (4-5 sentences): The naive/DIY approach without this protocol +- P2 (2-3 sentences): What this protocol provides +- List the key patterns/features the protocol enables +- P3 (1-2 sentences): How the example uses these patterns + +### Section 4: [Example Name] Walkthrough (~200 words) +Structure: +- P1 (2 sentences): What the example does +- P2 (3-4 sentences): The main instructions/functions +- P3 (4-5 sentences): Step-by-step verification flow +- P4 (2-3 sentences): Context-specific nullifiers or replay protection mechanism + +## Output Format +Produce a single markdown file with: +- Title: `# [Protocol Name] for ZK Applications` +- Four sections with `##` headers +- One ASCII diagram in Section 1 +- Tool lists with bold tool names +- No code blocks except the diagram +- No emojis + +## Process +1. Read the resource thoroughly +2. Extract the key concepts: storage model, proof system, nullifier scheme, user flow +3. Draft each section +4. Review for Paul Graham style compliance +5. Output final markdown diff --git a/zk-compression-blog.md b/zk-compression-blog.md new file mode 100644 index 0000000..25f1b55 --- /dev/null +++ b/zk-compression-blog.md @@ -0,0 +1,113 @@ +# ZK Compression for ZK Applications + +Every transaction on Solana is public. Anyone can see who sent what to whom. For many real-world applications, this is a dealbreaker. (TODO: replace dealbreaker with a more appropriate term) + +Zero knowledge proofs allow to build private applications in Solana programs, such as private transactions, private voting, and private identity verification. + +The key building blocks for private Solana programs are usually Zero Knowledge Proofs (ZKPs), +to prove application logic privately, +Poseidon Merkle Trees to store data so that it is accessible in a Zero Knowledge Proof, +and Nullifiers to prevent double spending. +Let's dive in. + +### Zero Knowledge Proofs +Zero knowledge proofs allow to prove ownership of data and application logic without revealing the data itself. +(TODO: add how zk proofs work and what very highlevel tradeoffs exist, we use Groth16, small proof size (128 bytes) it requires a trusted setup other proof systems don't require a trusted setup and or feature faster proof generation at the expense of larger proof sizes (eg Plonk, Plonky3, Halo2, ..)) +Every zk proof has 2 parts, circuit to program what to prove, circuit has public and private inputs. +1. Private inputs, all data the proof is computed over. +2. Public inputs, tie the private inputs to existing application state. + +### Poseidon Merkle Tree + +A Poseidon Merkle tree is a binary tree where each node is the hash of its children. +Poseidon hash is zk friendly so that we can efficiently prove membership of a leaf in the tree in a zero knowledge proof. + +### Nullifier +A nullifier is a hash derived from your secret and the leaf you're spending. +When you spend, you publish the nullifier. The contract stores it in a set. +If anyone tries to spend the same leaf again, the nullifier would match one already stored, so the transaction fails. +But here's the key: the nullifier reveals nothing about which leaf was spent. +Different secrets produce different nullifiers, so observers can't link a nullifier back to its source leaf. + +``` + OFFCHAIN ONCHAIN + + +------------------+ +------------------------+ + | User | | Solana Program | + | | ZK Proof | | + | - private key | ----------------------> | 1. verify proof | + | - generates | | 2. check root matches | + | ZK proof | | 3. insert nullifier | + +------------------+ | 4. insert new leaf | + ^ +------------------------+ + | | | + | Merkle proof v v + | (inclusion path) +-----------+ +-----------+ + | | Nullifier | | State | + +------------------+ | Set | | Tree | + | Indexer | | (spent) | | (leaves) | + | | +-----------+ +-----------+ + | - reads tree | ^ + | - serves | | + | Merkle proofs | <-----------------------------------------+ + +------------------+ reads state +``` + +The user generates a ZK proof offchain using their private key and a Merkle inclusion path from the indexer, then submits it onchain where the program verifies the proof, checks the root, records the nullifier, and inserts new state. + +You prove you know a valid leaf in the tree without revealing which one. You publish a nullifier that prevents reuse. The contract inserts your new state as a fresh leaf. Throughout this flow, observers see a valid proof and a nullifier—nothing about which leaf you spent or what data it contained. +Privacy is achieved by storing state differently and using a ZKP to prove transaction correctness over private data. +Instead of storing data in a Solana account, you hash it and store only the hash as a leaf in a Poseidon Merkle tree. +Once stored you can use a ZKP to prove ownership of the data and execute application logic without revealing the data itself. +To ensure that a transaction can only be executed once, you use a nullifier. +The nullifier is a deterministically derived hash of the data hash but unlinkable to the data itself. +The blockchain sees just the tree's root, nullifier and the ZKP. +The actual data stays with the user. + + +## How ZK Compression Fits + +The DIY approach is straightforward but tedious. You write your own Merkle tree program and store the tree onchain. Users call it to insert leaves. You run an indexer offchain to track these transactions and build a local copy of the tree for generating proofs. For nullifiers, you create a PDA derived from the nullifier hash. PDAs can only be created once, so double-spends fail automatically. This works, but you're now maintaining a custom tree program, running your own indexer, and paying for all that storage. + +Building a ZK system from scratch is a lot of work. You need a Merkle tree program, nullifier storage, and an indexer to track state. Light Protocol already has all of this. + +**Pattern 1: Addresses as Nullifiers** + +Derive your compressed account address from a nullifier hash. To check if something is spent, try creating an account at that address. If it already exists, it was already spent. + +**Pattern 2: Compressed Accounts as Merkle Leaves** + +Light computes each compressed account's hash from six fields: owner, leaf index, tree address, account address, discriminator, and data hash. This hash becomes the leaf in the state Merkle tree. To use this directly, your circuit must compute the identical hash structure using Poseidon. More setup work upfront. But you get native integration with Light's proof system and indexing infrastructure. + +**Pattern 3: Store Leaves in Compressed Accounts** + +Simpler approach: store your Merkle leaf data in the account's data field. You manage your own tree logic, but get Light's indexing and cheap storage for free. + +The zk-id example combines both patterns. Credentials are stored as Poseidon-hashed compressed accounts. When used, the nullifier becomes the new account address. + +## Putting It Together: zk-id + +zk-id is a credential system built on Solana. Issuers create credentials for users. Users prove they hold a valid credential without revealing which one. + +The program has three instructions. `create_issuer` registers an issuer with the system. `add_credential` lets an issuer create a credential for a user—stored as a Poseidon-hashed compressed account. `zk_verify_credential` is where the magic happens: users submit a ZK proof showing they own a credential, without exposing the credential itself. + +Here's what happens when you verify. You have your private key. You fetch your credential's Merkle path from an indexer. Your browser generates a ZK proof: "I know a private key whose corresponding credential is in this tree." You also compute a nullifier—hash of your private key plus a verification context. You submit proof and nullifier to the Solana program. The program verifies the proof and creates an event account at an address derived from the nullifier. Try to verify twice? Same nullifier, same address, account already exists, transaction fails. + +The nullifier includes a verification context—an event ID, a vote proposal, a claim period. This means the same credential can verify across different contexts. But within any single context, exactly once. + +## Tools & Resources + +**Circuits** +- **circom** - Domain-specific language for writing ZK constraints +- **circomlib** - Standard library (Poseidon hash, comparators, binary operations) +- **noir** - Rust-like circuit language +- **ark-works** - Rust cryptography library to write circuits among other things. + +**Proof Generation & Verification** +- **snarkjs** - Generates proofs from circom circuits in JavaScript +- **circomlibjs** - Offchain implementations of circomlib functions (hash inputs before proving) +- **groth16-solana** - Verifies Groth16 proofs onchain for ~200k compute units + +**Light Protocol** +- **light-hasher** - Poseidon/SHA256 matching circuit implementations exactly +- **light-sdk** - Compressed accounts, state trees, address derivation From 17a482c23641e65376bf29acbf81afbee6f42138 Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 12 Jan 2026 21:46:43 +0000 Subject: [PATCH 2/7] stash next iteration --- zk-compression-blog.md | 75 +++++++++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/zk-compression-blog.md b/zk-compression-blog.md index 25f1b55..086aef9 100644 --- a/zk-compression-blog.md +++ b/zk-compression-blog.md @@ -1,26 +1,25 @@ # ZK Compression for ZK Applications -Every transaction on Solana is public. Anyone can see who sent what to whom. For many real-world applications, this is a dealbreaker. (TODO: replace dealbreaker with a more appropriate term) +Every transaction on Solana is public. Anyone can see who sent what to whom. This lack of privacy prevents mainstream adoption of many use cases. Zero knowledge proofs allow to build private applications in Solana programs, such as private transactions, private voting, and private identity verification. -The key building blocks for private Solana programs are usually Zero Knowledge Proofs (ZKPs), -to prove application logic privately, -Poseidon Merkle Trees to store data so that it is accessible in a Zero Knowledge Proof, -and Nullifiers to prevent double spending. +The key building blocks for private Solana programs are: Zero Knowledge Proofs (ZKPs) to prove application logic privately, Poseidon Merkle Trees to store data in a format that can be efficiently proven in a ZK circuit, and Nullifiers to prevent double spending. Let's dive in. ### Zero Knowledge Proofs Zero knowledge proofs allow to prove ownership of data and application logic without revealing the data itself. -(TODO: add how zk proofs work and what very highlevel tradeoffs exist, we use Groth16, small proof size (128 bytes) it requires a trusted setup other proof systems don't require a trusted setup and or feature faster proof generation at the expense of larger proof sizes (eg Plonk, Plonky3, Halo2, ..)) -Every zk proof has 2 parts, circuit to program what to prove, circuit has public and private inputs. -1. Private inputs, all data the proof is computed over. -2. Public inputs, tie the private inputs to existing application state. +First you need to select a proof system. Different proof systems trade off proof size, proving time, and setup requirements. Groth16 produces small proofs (256 bytes, compressed 128 bytes) and verifies cheaply onchain, but requires a trusted setup per circuit. Plonk, Halo2, and Plonky3 skip the trusted setup and prove faster, but produce larger proofs. For Solana, Groth16's small proof size and fast verification (~200k compute units) make it the practical choice. +A ZK proof is generated from a circuit - a program that defines what you're proving. Circuits are written in languages like circom or noir. Every circuit has two types of inputs: +1. Private inputs - the secret data only the prover knows. +2. Public inputs - values visible to the verifier that anchor the proof to onchain state. + +For example, in a private vote: the private input is your credential, the public input is the Merkle root of the voter registry. ### Poseidon Merkle Tree A Poseidon Merkle tree is a binary tree where each node is the hash of its children. -Poseidon hash is zk friendly so that we can efficiently prove membership of a leaf in the tree in a zero knowledge proof. +Poseidon is designed for ZK circuits - it uses fewer constraints than SHA256, making proofs faster and cheaper to generate. ### Nullifier A nullifier is a hash derived from your secret and the leaf you're spending. @@ -55,31 +54,55 @@ Different secrets produce different nullifiers, so observers can't link a nullif The user generates a ZK proof offchain using their private key and a Merkle inclusion path from the indexer, then submits it onchain where the program verifies the proof, checks the root, records the nullifier, and inserts new state. -You prove you know a valid leaf in the tree without revealing which one. You publish a nullifier that prevents reuse. The contract inserts your new state as a fresh leaf. Throughout this flow, observers see a valid proof and a nullifier—nothing about which leaf you spent or what data it contained. -Privacy is achieved by storing state differently and using a ZKP to prove transaction correctness over private data. -Instead of storing data in a Solana account, you hash it and store only the hash as a leaf in a Poseidon Merkle tree. -Once stored you can use a ZKP to prove ownership of the data and execute application logic without revealing the data itself. -To ensure that a transaction can only be executed once, you use a nullifier. -The nullifier is a deterministically derived hash of the data hash but unlinkable to the data itself. -The blockchain sees just the tree's root, nullifier and the ZKP. -The actual data stays with the user. - - -## How ZK Compression Fits +The indexer is an offchain service that watches the blockchain, maintains a copy of the Merkle tree, and provides Merkle proofs to users. It can be integrated into the client for maximum privacy. + +**User Flow:** +1. User stores data as a leaf in the Merkle tree (hashed with Poseidon) +2. Blockchain stores the Merkle root in a Solana account +3. User fetches Merkle proof from the indexer +4. User generates ZK proof offchain (proves they know a leaf without revealing it) +5. User submits proof + nullifier onchain +6. Program verifies the proof against the stored root +7. Program checks the nullifier was not used before +8. Program stores the nullifier (prevents reuse) +9. Program inserts new state as a fresh leaf +10. Indexer updates local copy of Merkle tree + +Your actual data stays on your device. Only the proof and nullifier travel onchain. + + +## Implementation + +Design Choices: +1. How to store Merkle tree data onchain? + - Sparse Merkle tree in Solana account + indexing instruction data + - Zk compression state Merkle tree + Solana Rpc indexer +2. How to store Nullifiers onchain? + - create a PDA derived from the nullifier hash (899,000 lamports) + - create a compressed address derived from the nullifier hash (10k lamports) +3. Client Proof Generation + - Snarkjs (typescript) + - mopro (Mobile proving) + - ark circom (rust) + The DIY approach is straightforward but tedious. You write your own Merkle tree program and store the tree onchain. Users call it to insert leaves. You run an indexer offchain to track these transactions and build a local copy of the tree for generating proofs. For nullifiers, you create a PDA derived from the nullifier hash. PDAs can only be created once, so double-spends fail automatically. This works, but you're now maintaining a custom tree program, running your own indexer, and paying for all that storage. Building a ZK system from scratch is a lot of work. You need a Merkle tree program, nullifier storage, and an indexer to track state. Light Protocol already has all of this. -**Pattern 1: Addresses as Nullifiers** + + +**1. Compressed Addresses as Nullifiers** Derive your compressed account address from a nullifier hash. To check if something is spent, try creating an account at that address. If it already exists, it was already spent. -**Pattern 2: Compressed Accounts as Merkle Leaves** +**2. Compressed Accounts as Merkle Leaves** -Light computes each compressed account's hash from six fields: owner, leaf index, tree address, account address, discriminator, and data hash. This hash becomes the leaf in the state Merkle tree. To use this directly, your circuit must compute the identical hash structure using Poseidon. More setup work upfront. But you get native integration with Light's proof system and indexing infrastructure. +Store hashed application in compressed accounts. Compressed accounts are Poseidon hashes stored in Poseidon Merkle trees of height 26. +This way you can request your Merkle proofs from Solana RPCs supporting indexing and don't need to index the tree yourself. +Your circuit proves the Compressed account hash and the Merkle proof of the compressed account. -**Pattern 3: Store Leaves in Compressed Accounts** +**3. Store Leaves in Compressed Accounts** Simpler approach: store your Merkle leaf data in the account's data field. You manage your own tree logic, but get Light's indexing and cheap storage for free. @@ -89,7 +112,7 @@ The zk-id example combines both patterns. Credentials are stored as Poseidon-has zk-id is a credential system built on Solana. Issuers create credentials for users. Users prove they hold a valid credential without revealing which one. -The program has three instructions. `create_issuer` registers an issuer with the system. `add_credential` lets an issuer create a credential for a user—stored as a Poseidon-hashed compressed account. `zk_verify_credential` is where the magic happens: users submit a ZK proof showing they own a credential, without exposing the credential itself. +The program has three instructions. `create_issuer` registers an issuer with the system. `add_credential` lets an issuer create a credential for a user—stored as a Poseidon-hashed compressed account. `zk_verify_credential` verifies the ZK proof and creates a nullifier account - users prove they own a credential without exposing it. Here's what happens when you verify. You have your private key. You fetch your credential's Merkle path from an indexer. Your browser generates a ZK proof: "I know a private key whose corresponding credential is in this tree." You also compute a nullifier—hash of your private key plus a verification context. You submit proof and nullifier to the Solana program. The program verifies the proof and creates an event account at an address derived from the nullifier. Try to verify twice? Same nullifier, same address, account already exists, transaction fails. From 654102d0535051e84533ae1709ae84ac3d43a8b2 Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 12 Jan 2026 22:19:41 +0000 Subject: [PATCH 3/7] stash next iter --- zk-compression-blog.md | 162 +++++++++++++++++++++++------------------ 1 file changed, 91 insertions(+), 71 deletions(-) diff --git a/zk-compression-blog.md b/zk-compression-blog.md index 086aef9..1f75944 100644 --- a/zk-compression-blog.md +++ b/zk-compression-blog.md @@ -1,6 +1,6 @@ # ZK Compression for ZK Applications -Every transaction on Solana is public. Anyone can see who sent what to whom. This lack of privacy prevents mainstream adoption of many use cases. +Every transaction on Solana is public. This lack of privacy prevents mainstream adoption of many use cases. Zero knowledge proofs allow to build private applications in Solana programs, such as private transactions, private voting, and private identity verification. @@ -9,12 +9,12 @@ Let's dive in. ### Zero Knowledge Proofs Zero knowledge proofs allow to prove ownership of data and application logic without revealing the data itself. -First you need to select a proof system. Different proof systems trade off proof size, proving time, and setup requirements. Groth16 produces small proofs (256 bytes, compressed 128 bytes) and verifies cheaply onchain, but requires a trusted setup per circuit. Plonk, Halo2, and Plonky3 skip the trusted setup and prove faster, but produce larger proofs. For Solana, Groth16's small proof size and fast verification (~200k compute units) make it the practical choice. +To start you need to select a proof system. Different proof systems trade off proof size, proving time, and setup requirements. Groth16 produces small proofs (256 bytes, compressed 128 bytes) and verifies cheaply onchain, but requires a trusted setup per circuit. Plonk, Halo2, and Plonky3 skip the trusted setup and prove faster, but produce larger proofs. For Solana, Groth16's small proof size and fast verification (~200k compute units) make it the practical choice. A ZK proof is generated from a circuit - a program that defines what you're proving. Circuits are written in languages like circom or noir. Every circuit has two types of inputs: 1. Private inputs - the secret data only the prover knows. 2. Public inputs - values visible to the verifier that anchor the proof to onchain state. -For example, in a private vote: the private input is your credential, the public input is the Merkle root of the voter registry. +For example, in a private KYC program, the private input is your credential, the public input is the Merkle root of the identity registry. ### Poseidon Merkle Tree @@ -28,95 +28,115 @@ If anyone tries to spend the same leaf again, the nullifier would match one alre But here's the key: the nullifier reveals nothing about which leaf was spent. Different secrets produce different nullifiers, so observers can't link a nullifier back to its source leaf. +## Zk Id example + +### Creating a Credential + +An issuer registers with the system by calling `create_issuer`. This creates a compressed account storing the issuer's public key and a credential counter. + +The issuer then calls `add_credential` for each user. The user generates a credential keypair: a private key (random 248-bit value) and a public key (Poseidon hash of the private key). The issuer creates a compressed account containing: +- The issuer's public key +- The user's credential public key + +The account uses Poseidon hashing. This stores the credential as a leaf in a 26-level Merkle tree. The tree root lives onchain. The indexer maintains a full copy of the tree. + +The credential private key never touches the blockchain. Only the user knows it. The public key is a one-way hash, so even though the credential account is onchain, no one can reverse it to obtain the private key. + +**Create Credential:** ``` OFFCHAIN ONCHAIN +------------------+ +------------------------+ + | Issuer | | Solana Program | + | | create_issuer | | + | - authority | ----------------------> | 1. validate issuer | + | - signs creds | | 2. create account | + +------------------+ +------------------------+ + | + v + +------------------+ +------------------------+ | User | | Solana Program | - | | ZK Proof | | - | - private key | ----------------------> | 1. verify proof | - | - generates | | 2. check root matches | - | ZK proof | | 3. insert nullifier | - +------------------+ | 4. insert new leaf | - ^ +------------------------+ - | | | - | Merkle proof v v - | (inclusion path) +-----------+ +-----------+ - | | Nullifier | | State | - +------------------+ | Set | | Tree | - | Indexer | | (spent) | | (leaves) | - | | +-----------+ +-----------+ - | - reads tree | ^ - | - serves | | - | Merkle proofs | <-----------------------------------------+ - +------------------+ reads state -``` - -The user generates a ZK proof offchain using their private key and a Merkle inclusion path from the indexer, then submits it onchain where the program verifies the proof, checks the root, records the nullifier, and inserts new state. - -The indexer is an offchain service that watches the blockchain, maintains a copy of the Merkle tree, and provides Merkle proofs to users. It can be integrated into the client for maximum privacy. - -**User Flow:** -1. User stores data as a leaf in the Merkle tree (hashed with Poseidon) -2. Blockchain stores the Merkle root in a Solana account -3. User fetches Merkle proof from the indexer -4. User generates ZK proof offchain (proves they know a leaf without revealing it) -5. User submits proof + nullifier onchain -6. Program verifies the proof against the stored root -7. Program checks the nullifier was not used before -8. Program stores the nullifier (prevents reuse) -9. Program inserts new state as a fresh leaf -10. Indexer updates local copy of Merkle tree + | | add_credential | | + | - generates | ----------------------> | 1. verify issuer sig | + | keypair | (cred_pubkey) | 2. hash credential | + | - stores | | 3. insert leaf | + | private key | +------------------------+ + +------------------+ | + v + +------------------------+ + +------------------+ | State Tree | + | Indexer | | (26-level Poseidon) | + | | reads state | | + | - watches chain | <---------------------- | [credential leaves] | + | - builds tree | | | + +------------------+ +------------------------+ -Your actual data stays on your device. Only the proof and nullifier travel onchain. +``` +The issuer registers once, then creates credentials for users. Each credential is a compressed account containing the issuer's pubkey and the user's credential pubkey. The account is Poseidon-hashed and stored as a leaf in the state tree. The user's private key never touches the chain. -## Implementation +### Verifying a Credential -Design Choices: -1. How to store Merkle tree data onchain? - - Sparse Merkle tree in Solana account + indexing instruction data - - Zk compression state Merkle tree + Solana Rpc indexer -2. How to store Nullifiers onchain? - - create a PDA derived from the nullifier hash (899,000 lamports) - - create a compressed address derived from the nullifier hash (10k lamports) -3. Client Proof Generation - - Snarkjs (typescript) - - mopro (Mobile proving) - - ark circom (rust) - +Verification proves two things: the user knows a credential private key corresponding to a leaf in the tree, and they haven't used that credential in this context before. -The DIY approach is straightforward but tedious. You write your own Merkle tree program and store the tree onchain. Users call it to insert leaves. You run an indexer offchain to track these transactions and build a local copy of the tree for generating proofs. For nullifiers, you create a PDA derived from the nullifier hash. PDAs can only be created once, so double-spends fail automatically. This works, but you're now maintaining a custom tree program, running your own indexer, and paying for all that storage. +The user fetches their credential's Merkle path from the indexer. Their browser computes a nullifier: `Poseidon(verification_id, credential_private_key)`. The verification_id is context-specific, an event ID, a vote proposal, or a claim period. -Building a ZK system from scratch is a lot of work. You need a Merkle tree program, nullifier storage, and an indexer to track state. Light Protocol already has all of this. +The ZK circuit takes the private key as private input. Public inputs include the Merkle root, the verification_id, and the nullifier. The circuit verifies: +1. The credential public key derives correctly from the private key +2. The credential exists in the tree (Merkle proof) +3. The nullifier derives correctly from the private key and verification_id +The user submits the proof and nullifier to `zk_verify_credential`. The program verifies the Groth16 proof against the onchain root. It creates an event account at an address derived from the nullifier and verification_id. +The address derivation is the double-spend check. Compressed addresses can only be created once. If the nullifier was already used for this verification_id, the address exists, and the transaction fails. -**1. Compressed Addresses as Nullifiers** +Same credential, different verification_id means different nullifier, different address. A credential can verify across multiple contexts. Within any single context, exactly once. -Derive your compressed account address from a nullifier hash. To check if something is spent, try creating an account at that address. If it already exists, it was already spent. +**Verify a Credential:** +``` + OFFCHAIN ONCHAIN -**2. Compressed Accounts as Merkle Leaves** + +------------------+ +------------------------+ + | User | | Solana Program | + | | ZK Proof | | + | - private key | ----------------------> | 1. verify proof | + | - generates | | 2. check root matches | + | ZK proof | | 3. derive address | + +------------------+ | 4. create event acct | + ^ +------------------------+ + | | | + | Merkle proof v v + | (inclusion path) +-----------+ +-----------+ + | | Event | | State | + +------------------+ | Account | | Tree | + | Indexer | | (address= | | (creds) | + | | | nullifier)| +-----------+ + | - reads tree | +-----------+ ^ + | - serves | | + | Merkle proofs | <-----------------------------------------+ + +------------------+ reads state -Store hashed application in compressed accounts. Compressed accounts are Poseidon hashes stored in Poseidon Merkle trees of height 26. -This way you can request your Merkle proofs from Solana RPCs supporting indexing and don't need to index the tree yourself. -Your circuit proves the Compressed account hash and the Merkle proof of the compressed account. +``` -**3. Store Leaves in Compressed Accounts** +The user fetches a Merkle proof from the indexer, computes a nullifier from their private key and the verification context, and generates a ZK proof. The program verifies the proof and creates an event account at an address derived from the nullifier. If the address already exists, the transaction fails. The credential itself is never revealed. -Simpler approach: store your Merkle leaf data in the account's data field. You manage your own tree logic, but get Light's indexing and cheap storage for free. +The indexer watches the blockchain and maintains a local copy of the Merkle tree. Users query it for Merkle proofs. The indexer sees which addresses exist but cannot link them to specific credentials. -The zk-id example combines both patterns. Credentials are stored as Poseidon-hashed compressed accounts. When used, the nullifier becomes the new account address. +## Implementation Design Choices -## Putting It Together: zk-id +A zk Solana program similar to zk-id requires a Merkle tree, nullifier storage, and an indexer for the Merkle tree. -zk-id is a credential system built on Solana. Issuers create credentials for users. Users prove they hold a valid credential without revealing which one. +**Compressed accounts as credentials**: Store credential data in Poseidon-hashed compressed accounts. The account data becomes a leaf in Zk compression's 26-level Merkle tree. Standard Solana RPCs serve Merkle proofs. -The program has three instructions. `create_issuer` registers an issuer with the system. `add_credential` lets an issuer create a credential for a user—stored as a Poseidon-hashed compressed account. `zk_verify_credential` verifies the ZK proof and creates a nullifier account - users prove they own a credential without exposing it. +**Compressed addresses as nullifiers**: Derive the event account address from the nullifier hash. Addresses can only be created once. Attempting to create a duplicate fails automatically. -Here's what happens when you verify. You have your private key. You fetch your credential's Merkle path from an indexer. Your browser generates a ZK proof: "I know a private key whose corresponding credential is in this tree." You also compute a nullifier—hash of your private key plus a verification context. You submit proof and nullifier to the Solana program. The program verifies the proof and creates an event account at an address derived from the nullifier. Try to verify twice? Same nullifier, same address, account already exists, transaction fails. +The zk-id program combines both patterns. `add_credential` stores credentials as Poseidon-hashed compressed accounts. `zk_verify_credential` creates event accounts at nullifier-derived addresses. -The nullifier includes a verification context—an event ID, a vote proposal, a claim period. This means the same credential can verify across different contexts. But within any single context, exactly once. +Design choices for your own ZK Solana Program: +1. Merkle tree storage: + Light's state trees with RPC indexing, or a custom sparse Merkle tree +2. Nullifier storage: compressed addresses (10k lamports) or PDAs (899k lamports) +3. Proof generation: snarkjs (TypeScript), mopro (mobile), or ark-circom (Rust) ## Tools & Resources @@ -124,13 +144,13 @@ The nullifier includes a verification context—an event ID, a vote proposal, a - **circom** - Domain-specific language for writing ZK constraints - **circomlib** - Standard library (Poseidon hash, comparators, binary operations) - **noir** - Rust-like circuit language -- **ark-works** - Rust cryptography library to write circuits among other things. +- **ark-works** - Rust cryptography library for circuits **Proof Generation & Verification** - **snarkjs** - Generates proofs from circom circuits in JavaScript -- **circomlibjs** - Offchain implementations of circomlib functions (hash inputs before proving) -- **groth16-solana** - Verifies Groth16 proofs onchain for ~200k compute units +- **circomlibjs** - Offchain implementations of circomlib functions +- **groth16-solana** - Verifies Groth16 proofs onchain (~200k compute units) **Light Protocol** -- **light-hasher** - Poseidon/SHA256 matching circuit implementations exactly +- **light-hasher** - Poseidon/SHA256 implementations matching circuit behavior - **light-sdk** - Compressed accounts, state trees, address derivation From 4566af1c460540180c362bd6fd7579cc1b0373ed Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 12 Jan 2026 22:34:16 +0000 Subject: [PATCH 4/7] stash first review --- zk-compression-blog.md | 43 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/zk-compression-blog.md b/zk-compression-blog.md index 1f75944..885f854 100644 --- a/zk-compression-blog.md +++ b/zk-compression-blog.md @@ -1,11 +1,13 @@ # ZK Compression for ZK Applications -Every transaction on Solana is public. This lack of privacy prevents mainstream adoption of many use cases. +Every transaction on Solana is public. This lack of privacy prevents mainstream adoption for many use cases. -Zero knowledge proofs allow to build private applications in Solana programs, such as private transactions, private voting, and private identity verification. +Zero knowledge proofs allow to build privacy into Solana programs, such as private transactions, private voting, and private identity verification. -The key building blocks for private Solana programs are: Zero Knowledge Proofs (ZKPs) to prove application logic privately, Poseidon Merkle Trees to store data in a format that can be efficiently proven in a ZK circuit, and Nullifiers to prevent double spending. -Let's dive in. +The key building blocks for private Solana programs are: +1. Zero Knowledge Proofs (ZKPs) to prove application logic privately. +2. Poseidon Merkle Trees to store data in a format that can be efficiently proven in a ZK circuit. +3. Nullifiers to prevent double spending. ### Zero Knowledge Proofs Zero knowledge proofs allow to prove ownership of data and application logic without revealing the data itself. @@ -22,11 +24,11 @@ A Poseidon Merkle tree is a binary tree where each node is the hash of its child Poseidon is designed for ZK circuits - it uses fewer constraints than SHA256, making proofs faster and cheaper to generate. ### Nullifier -A nullifier is a hash derived from your secret and the leaf you're spending. -When you spend, you publish the nullifier. The contract stores it in a set. +A nullifier is a hash derived from your secret and the leaf the transaction is using. +When you use private state (stored in a Merkle tree leaf), you publish the nullifier. The program stores it in a set. If anyone tries to spend the same leaf again, the nullifier would match one already stored, so the transaction fails. -But here's the key: the nullifier reveals nothing about which leaf was spent. -Different secrets produce different nullifiers, so observers can't link a nullifier back to its source leaf. +The nullifier reveals nothing about which leaf was spent. +Different state produces different nullifiers, so observers can't link a nullifier back to its source leaf. ## Zk Id example @@ -70,7 +72,6 @@ The credential private key never touches the blockchain. Only the user knows it. | - watches chain | <---------------------- | [credential leaves] | | - builds tree | | | +------------------+ +------------------------+ - ``` The issuer registers once, then creates credentials for users. Each credential is a compressed account containing the issuer's pubkey and the user's credential pubkey. The account is Poseidon-hashed and stored as a leaf in the state tree. The user's private key never touches the chain. @@ -122,26 +123,26 @@ The user fetches a Merkle proof from the indexer, computes a nullifier from thei The indexer watches the blockchain and maintains a local copy of the Merkle tree. Users query it for Merkle proofs. The indexer sees which addresses exist but cannot link them to specific credentials. -## Implementation Design Choices +## Implementation + +Building a ZK program on Solana requires a Merkle tree to store state, a way to track nullifiers, and an indexer to serve Merkle proofs. + +**Nullifier storage: PDAs vs compressed addresses** -A zk Solana program similar to zk-id requires a Merkle tree, nullifier storage, and an indexer for the Merkle tree. +PDAs are the straightforward choice. Derive an address from the nullifier hash, create an account there. If the account exists, the nullifier was used. The cost is ~899k lamports per nullifier for rent exemption. -**Compressed accounts as credentials**: Store credential data in Poseidon-hashed compressed accounts. The account data becomes a leaf in Zk compression's 26-level Merkle tree. Standard Solana RPCs serve Merkle proofs. +Compressed addresses work the same way but cost ~10k lamports. The tradeoff: you need an additional ZK proof to create the account and a CPI to the Light system program. If you're already generating a ZK proof for your application logic, the marginal cost of the extra proof is low. If not, PDAs are simpler. -**Compressed addresses as nullifiers**: Derive the event account address from the nullifier hash. Addresses can only be created once. Attempting to create a duplicate fails automatically. +**Merkle tree: custom vs state trees** -The zk-id program combines both patterns. `add_credential` stores credentials as Poseidon-hashed compressed accounts. `zk_verify_credential` creates event accounts at nullifier-derived addresses. +A custom sparse Merkle tree gives you full control. You design the leaf structure and proof format to match your circuit exactly. The cost: you build and run your own indexer to track the tree and serve Merkle proofs. -Design choices for your own ZK Solana Program: -1. Merkle tree storage: - Light's state trees with RPC indexing, or a custom sparse Merkle tree -2. Nullifier storage: compressed addresses (10k lamports) or PDAs (899k lamports) -3. Proof generation: snarkjs (TypeScript), mopro (mobile), or ark-circom (Rust) +State trees handle indexing for you. Standard Solana RPCs serve Merkle proofs. The tradeoff: your circuit must prove inclusion of your data inside the compressed account structure, a Poseidon hash of account fields plus metadata. This adds constraints to your circuit but eliminates infrastructure overhead. ## Tools & Resources **Circuits** -- **circom** - Domain-specific language for writing ZK constraints +- **circom** - Domain-specific language for writing ZK circuits - **circomlib** - Standard library (Poseidon hash, comparators, binary operations) - **noir** - Rust-like circuit language - **ark-works** - Rust cryptography library for circuits @@ -151,6 +152,6 @@ Design choices for your own ZK Solana Program: - **circomlibjs** - Offchain implementations of circomlib functions - **groth16-solana** - Verifies Groth16 proofs onchain (~200k compute units) -**Light Protocol** +**Zk compression** - **light-hasher** - Poseidon/SHA256 implementations matching circuit behavior - **light-sdk** - Compressed accounts, state trees, address derivation From ed015c6a11381993797b2ffae4f128f83b3614a9 Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 12 Jan 2026 23:24:02 +0000 Subject: [PATCH 5/7] stash second review --- zk-compression-blog.md | 80 +++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/zk-compression-blog.md b/zk-compression-blog.md index 885f854..7d71630 100644 --- a/zk-compression-blog.md +++ b/zk-compression-blog.md @@ -2,7 +2,7 @@ Every transaction on Solana is public. This lack of privacy prevents mainstream adoption for many use cases. -Zero knowledge proofs allow to build privacy into Solana programs, such as private transactions, private voting, and private identity verification. +Zero knowledge proofs enable privacy in Solana programs, such as private transactions, private voting, and private identity verification. The key building blocks for private Solana programs are: 1. Zero Knowledge Proofs (ZKPs) to prove application logic privately. @@ -10,9 +10,13 @@ The key building blocks for private Solana programs are: 3. Nullifiers to prevent double spending. ### Zero Knowledge Proofs -Zero knowledge proofs allow to prove ownership of data and application logic without revealing the data itself. -To start you need to select a proof system. Different proof systems trade off proof size, proving time, and setup requirements. Groth16 produces small proofs (256 bytes, compressed 128 bytes) and verifies cheaply onchain, but requires a trusted setup per circuit. Plonk, Halo2, and Plonky3 skip the trusted setup and prove faster, but produce larger proofs. For Solana, Groth16's small proof size and fast verification (~200k compute units) make it the practical choice. -A ZK proof is generated from a circuit - a program that defines what you're proving. Circuits are written in languages like circom or noir. Every circuit has two types of inputs: +Zero knowledge proofs enable proving ownership of data and application logic without revealing the data itself. +First, select a proof system. +Different proof systems trade off proof size, proving time, and setup requirements. Groth16 produces small proofs (256 bytes, compressed 128 bytes) and verifies cheaply onchain, but requires a trusted setup (TODO: add reference) per circuit. +Other established proof systems avoid trusted setups or prove faster, but produce larger proofs (kilobytes instead of bytes). +For Solana, Groth16's small proof size and fast verification (~200k compute units) make it the practical choice. +A ZK proof is generated from a circuit - a program that defines what you're proving. +Circuits are written in languages like circom or noir. Every circuit has two types of inputs: 1. Private inputs - the secret data only the prover knows. 2. Public inputs - values visible to the verifier that anchor the proof to onchain state. @@ -21,7 +25,32 @@ For example, in a private KYC program, the private input is your credential, the ### Poseidon Merkle Tree A Poseidon Merkle tree is a binary tree where each node is the hash of its children. -Poseidon is designed for ZK circuits - it uses fewer constraints than SHA256, making proofs faster and cheaper to generate. +Poseidon is designed for ZK circuits - it uses fewer constraints (TODO: add reference) than SHA256, making proofs faster and cheaper to generate. + +**Merkle tree: custom vs state trees** + +A custom sparse Merkle tree gives you full control. You design the leaf structure and proof format to match your circuit exactly. The tradeoff: you build and run your own indexer to track the tree and serve Merkle proofs. + +State trees handle indexing for you. Standard Solana RPCs serve Merkle proofs. The tradeoff: your circuit must prove inclusion of your data inside the compressed account (accounts stored in Poseidon Merkle trees with Solana RPC support) structure. This adds constraints to your circuit but eliminates infrastructure overhead. + +``` +Compressed Account Hash: ++----------------------------------------------------------+ +| Poseidon( | +| owner_hash, | +| leaf_index, | +| merkle_tree_pubkey, | +| address, | +| discriminator, | +| data_hash <-- developer-defined, hash anything here | +| ) | ++----------------------------------------------------------+ +``` + +The `data_hash` is entirely yours. Hash whatever structure your application needs. The outer fields are protocol overhead, but they don't limit what you store inside. + +Note, for offchain privacy a user client should fetch a complete (sub)tree not Merkle proof from an indexer. If only onchain privacy is sufficient fetching Merkle proofs from an indexer is more efficient. + ### Nullifier A nullifier is a hash derived from your secret and the leaf the transaction is using. @@ -30,17 +59,34 @@ If anyone tries to spend the same leaf again, the nullifier would match one alre The nullifier reveals nothing about which leaf was spent. Different state produces different nullifiers, so observers can't link a nullifier back to its source leaf. +**Nullifier storage: PDAs vs compressed addresses** + +PDAs are the straightforward choice. Derive an address from the nullifier hash, create an account there. If the account exists, the nullifier was used. The cost is ~899k lamports per nullifier for rent exemption. + +Compressed addresses work the same way but cost ~10k lamports. The tradeoff: you need an additional ZK proof to create the account and a CPI to the Light system program. If you're already generating a ZK proof for your application logic, the marginal cost of the extra proof is low. If not, PDAs are simpler. + + ## Zk Id example +zk-id is a proof of concept credential system built with zk compression and the following tools: + +| Component | Implementation | +|-----------|----------------| +| Merkle leaves | compressed accounts (light-sdk) | +| Nullifiers | compressed addresses (light-sdk) | +| Circuit | circom | +| Proof generation | circom-prover (Rust) | +| On-chain verification | groth16-solana | + ### Creating a Credential An issuer registers with the system by calling `create_issuer`. This creates a compressed account storing the issuer's public key and a credential counter. -The issuer then calls `add_credential` for each user. The user generates a credential keypair: a private key (random 248-bit value) and a public key (Poseidon hash of the private key). The issuer creates a compressed account containing: +The issuer then calls `add_credential` for each user. The user generates a credential keypair: a private key (random 248-bit value, sized to fit the BN254 field) and a public key (Poseidon hash of the private key). The issuer creates a compressed account containing: - The issuer's public key - The user's credential public key -The account uses Poseidon hashing. This stores the credential as a leaf in a 26-level Merkle tree. The tree root lives onchain. The indexer maintains a full copy of the tree. +The account uses Poseidon hashing. This stores the credential as a leaf in a 26-level Merkle tree (supporting ~67 million leaves). The tree root lives onchain. An indexer (a server that indexes Solana transactions, in this case leaves) maintains a full copy of the tree. The credential private key never touches the blockchain. Only the user knows it. The public key is a one-way hash, so even though the credential account is onchain, no one can reverse it to obtain the private key. @@ -89,7 +135,7 @@ The ZK circuit takes the private key as private input. Public inputs include the The user submits the proof and nullifier to `zk_verify_credential`. The program verifies the Groth16 proof against the onchain root. It creates an event account at an address derived from the nullifier and verification_id. -The address derivation is the double-spend check. Compressed addresses can only be created once. If the nullifier was already used for this verification_id, the address exists, and the transaction fails. +The address derivation is the double-spend check. Compressed addresses can only be created once (the Light system program rejects duplicates). If the nullifier was already used for this verification_id, the address exists, and the transaction fails. Same credential, different verification_id means different nullifier, different address. A credential can verify across multiple contexts. Within any single context, exactly once. @@ -123,23 +169,7 @@ The user fetches a Merkle proof from the indexer, computes a nullifier from thei The indexer watches the blockchain and maintains a local copy of the Merkle tree. Users query it for Merkle proofs. The indexer sees which addresses exist but cannot link them to specific credentials. -## Implementation - -Building a ZK program on Solana requires a Merkle tree to store state, a way to track nullifiers, and an indexer to serve Merkle proofs. - -**Nullifier storage: PDAs vs compressed addresses** - -PDAs are the straightforward choice. Derive an address from the nullifier hash, create an account there. If the account exists, the nullifier was used. The cost is ~899k lamports per nullifier for rent exemption. - -Compressed addresses work the same way but cost ~10k lamports. The tradeoff: you need an additional ZK proof to create the account and a CPI to the Light system program. If you're already generating a ZK proof for your application logic, the marginal cost of the extra proof is low. If not, PDAs are simpler. - -**Merkle tree: custom vs state trees** - -A custom sparse Merkle tree gives you full control. You design the leaf structure and proof format to match your circuit exactly. The cost: you build and run your own indexer to track the tree and serve Merkle proofs. - -State trees handle indexing for you. Standard Solana RPCs serve Merkle proofs. The tradeoff: your circuit must prove inclusion of your data inside the compressed account structure, a Poseidon hash of account fields plus metadata. This adds constraints to your circuit but eliminates infrastructure overhead. - -## Tools & Resources +## Resources **Circuits** - **circom** - Domain-specific language for writing ZK circuits From c761176f03a9f2b9c61e8d66351e72f2217c8e13 Mon Sep 17 00:00:00 2001 From: ananas Date: Mon, 12 Jan 2026 23:55:47 +0000 Subject: [PATCH 6/7] stash third review --- zk-compression-blog.md | 67 +++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/zk-compression-blog.md b/zk-compression-blog.md index 7d71630..aa4f639 100644 --- a/zk-compression-blog.md +++ b/zk-compression-blog.md @@ -24,59 +24,47 @@ For example, in a private KYC program, the private input is your credential, the ### Poseidon Merkle Tree -A Poseidon Merkle tree is a binary tree where each node is the hash of its children. +Merkle trees store zk application state. +Specifically, a Poseidon Merkle tree is a binary tree where each node is the hash of its children. Poseidon is designed for ZK circuits - it uses fewer constraints (TODO: add reference) than SHA256, making proofs faster and cheaper to generate. -**Merkle tree: custom vs state trees** +**Merkle trees on Solana:** -A custom sparse Merkle tree gives you full control. You design the leaf structure and proof format to match your circuit exactly. The tradeoff: you build and run your own indexer to track the tree and serve Merkle proofs. +A **custom sparse Merkle tree** gives you full control. You design the leaf structure and proof format to match your circuit exactly. Create Solana accounts with your program to store a sparse Merkle tree and its roots. +The tradeoff: you build and run your own indexer to track the tree and serve Merkle proofs. -State trees handle indexing for you. Standard Solana RPCs serve Merkle proofs. The tradeoff: your circuit must prove inclusion of your data inside the compressed account (accounts stored in Poseidon Merkle trees with Solana RPC support) structure. This adds constraints to your circuit but eliminates infrastructure overhead. - -``` -Compressed Account Hash: -+----------------------------------------------------------+ -| Poseidon( | -| owner_hash, | -| leaf_index, | -| merkle_tree_pubkey, | -| address, | -| discriminator, | -| data_hash <-- developer-defined, hash anything here | -| ) | -+----------------------------------------------------------+ -``` - -The `data_hash` is entirely yours. Hash whatever structure your application needs. The outer fields are protocol overhead, but they don't limit what you store inside. +**Zk compression state Merkle trees** Solana RPCs handle indexing for you and serve Merkle proofs. The Light Protocol programs create and maintain Poseidon state Merkle trees for you in Solana accounts. Once a state Merkle tree fills up the protocol creates a new one. +The tradeoff: your circuit must prove inclusion of your data inside the compressed account structure. Compressed accounts are stored as hashes in Poseidon Merkle trees with Solana RPC support. This adds constraints to your circuit but RPCs index the Merkle tree for you. Note, for offchain privacy a user client should fetch a complete (sub)tree not Merkle proof from an indexer. If only onchain privacy is sufficient fetching Merkle proofs from an indexer is more efficient. ### Nullifier -A nullifier is a hash derived from your secret and the leaf the transaction is using. +Nullifiers prevent double spending. +In detail, a nullifier is a hash derived from your secret and the leaf the transaction is using. When you use private state (stored in a Merkle tree leaf), you publish the nullifier. The program stores it in a set. If anyone tries to spend the same leaf again, the nullifier would match one already stored, so the transaction fails. The nullifier reveals nothing about which leaf was spent. Different state produces different nullifiers, so observers can't link a nullifier back to its source leaf. -**Nullifier storage: PDAs vs compressed addresses** +**Nullifiers on Solana:** -PDAs are the straightforward choice. Derive an address from the nullifier hash, create an account there. If the account exists, the nullifier was used. The cost is ~899k lamports per nullifier for rent exemption. +**PDAs** are a straightforward choice. Derive an address from the nullifier hash, create an account there. If the account exists, the nullifier was used. The cost is ~899k lamports per nullifier for rent exemption. -Compressed addresses work the same way but cost ~10k lamports. The tradeoff: you need an additional ZK proof to create the account and a CPI to the Light system program. If you're already generating a ZK proof for your application logic, the marginal cost of the extra proof is low. If not, PDAs are simpler. +**Compressed addresses** work the same way but cost ~10k lamports. The tradeoff: you need an additional ZK proof to create the account and a CPI to the Light system program. If you're already generating a ZK proof for your application logic, the marginal cost of the extra proof is low. If not, PDAs are simpler. ## Zk Id example -zk-id is a proof of concept credential system built with zk compression and the following tools: +[zk-id](https://github.com/Lightprotocol/program-examples/tree/main/zk-id) is a proof of concept credential system built with zk compression and the following tools: | Component | Implementation | |-----------|----------------| -| Merkle leaves | compressed accounts (light-sdk) | -| Nullifiers | compressed addresses (light-sdk) | -| Circuit | circom | -| Proof generation | circom-prover (Rust) | -| On-chain verification | groth16-solana | +| Merkle leaves | [compressed accounts](https://github.com/Lightprotocol/program-examples/blob/main/zk-id/src/lib.rs#L141) (light-sdk) | +| Nullifiers | [compressed addresses](https://github.com/Lightprotocol/program-examples/blob/main/zk-id/src/lib.rs#L192) (light-sdk) | +| Circuit | [circom](https://github.com/Lightprotocol/program-examples/tree/main/zk-id/circuits) | +| Proof generation | [circom-prover](https://github.com/Lightprotocol/program-examples/blob/main/zk-id/tests/test.rs#L575) (Rust) | +| On-chain verification | [groth16-solana](https://github.com/Lightprotocol/program-examples/blob/main/zk-id/src/lib.rs#L269) | ### Creating a Credential @@ -185,3 +173,22 @@ The indexer watches the blockchain and maintains a local copy of the Merkle tree **Zk compression** - **light-hasher** - Poseidon/SHA256 implementations matching circuit behavior - **light-sdk** - Compressed accounts, state trees, address derivation + +## Appendix + +1. Compressed Account Hashing: +``` +Compressed Account Hash: ++----------------------------------------------------------+ +| Poseidon( | +| owner_hash, | +| leaf_index, | +| merkle_tree_pubkey, | +| address, | +| discriminator, | +| data_hash <-- developer-defined, hash anything here | +| ) | ++----------------------------------------------------------+ +``` + +The `data_hash` is entirely yours. Hash whatever structure your application needs. The outer fields are protocol overhead, but they don't limit what you store inside. From a5f1a71d492028aa164d4f402cfdc119c83cecb7 Mon Sep 17 00:00:00 2001 From: ananas Date: Thu, 15 Jan 2026 22:24:45 +0000 Subject: [PATCH 7/7] stash --- zk-blog-prompt.md | 70 ------------------------------------------ zk-compression-blog.md | 4 +-- 2 files changed, 2 insertions(+), 72 deletions(-) delete mode 100644 zk-blog-prompt.md diff --git a/zk-blog-prompt.md b/zk-blog-prompt.md deleted file mode 100644 index 3dd3c99..0000000 --- a/zk-blog-prompt.md +++ /dev/null @@ -1,70 +0,0 @@ -# Prompt: Generate ZK Identity Protocol Blog Post - -## Context -You are writing a technical blog post about a ZK identity protocol. The post is for Solana developers who are familiar with Solana but new to ZK. - -## Style -- Paul Graham style: concise, direct, simple language, short sentences, no fluff -- No filler phrases ("Here's the thing", "Let that sink in", etc.) -- No binary contrasts ("Not X. But Y.") -- Trust the reader -- ~800-1000 words total - -## Resource -[INSERT RESOURCE HERE: GitHub repo, documentation, or codebase path] - -Analyze this resource to understand: -1. How identities/credentials are stored -2. How nullifiers work (if applicable) -3. The proof system used (Groth16, PLONK, etc.) -4. The instruction/function flow -5. How privacy is achieved - -## Required Sections - -### Section 1: Building Blocks of Privacy (~250 words) -Structure: -- P1 (2-3 sentences): Hook - why public transactions are a problem -- P2 (3-4 sentences): How state is stored in Merkle trees, why this is private (hashing is one-way) -- P3 (3-4 sentences): Brief ZKP explainer - what proof system is used, public inputs vs private, any setup requirements -- P4 (3-4 sentences): Nullifiers - what they are, how they prevent double-spending, why they don't reveal which leaf was spent -- P5: ASCII diagram showing offchain/onchain flow (user, indexer, program, state tree, nullifier set) -- P6 (2-3 sentences): Flow summary - -### Section 2: Tools (~100 words) -Structure: -- One intro sentence -- Grouped list of tools used by this protocol: - - Circuit tools (circom, noir, etc.) - - Proof generation tools (snarkjs, etc.) - - Protocol-specific libraries - -### Section 3: How [Protocol Name] Works (~200 words) -Structure: -- P1 (4-5 sentences): The naive/DIY approach without this protocol -- P2 (2-3 sentences): What this protocol provides -- List the key patterns/features the protocol enables -- P3 (1-2 sentences): How the example uses these patterns - -### Section 4: [Example Name] Walkthrough (~200 words) -Structure: -- P1 (2 sentences): What the example does -- P2 (3-4 sentences): The main instructions/functions -- P3 (4-5 sentences): Step-by-step verification flow -- P4 (2-3 sentences): Context-specific nullifiers or replay protection mechanism - -## Output Format -Produce a single markdown file with: -- Title: `# [Protocol Name] for ZK Applications` -- Four sections with `##` headers -- One ASCII diagram in Section 1 -- Tool lists with bold tool names -- No code blocks except the diagram -- No emojis - -## Process -1. Read the resource thoroughly -2. Extract the key concepts: storage model, proof system, nullifier scheme, user flow -3. Draft each section -4. Review for Paul Graham style compliance -5. Output final markdown diff --git a/zk-compression-blog.md b/zk-compression-blog.md index aa4f639..5fb4caa 100644 --- a/zk-compression-blog.md +++ b/zk-compression-blog.md @@ -1,10 +1,10 @@ -# ZK Compression for ZK Applications +# How to use Zk compression in ZK Solana Programs Every transaction on Solana is public. This lack of privacy prevents mainstream adoption for many use cases. Zero knowledge proofs enable privacy in Solana programs, such as private transactions, private voting, and private identity verification. -The key building blocks for private Solana programs are: +The key building blocks for zk in Solana programs are: 1. Zero Knowledge Proofs (ZKPs) to prove application logic privately. 2. Poseidon Merkle Trees to store data in a format that can be efficiently proven in a ZK circuit. 3. Nullifiers to prevent double spending.