diff --git a/.gitignore b/.gitignore index bc29d0c..bd44d77 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ mintlify-docs/ # Dependencies node_modules/ + +stash \ No newline at end of file diff --git a/compressed-pdas/create-a-program-with-compressed-pdas.mdx b/compressed-pdas/overview.mdx similarity index 100% rename from compressed-pdas/create-a-program-with-compressed-pdas.mdx rename to compressed-pdas/overview.mdx diff --git a/cspell.json b/cspell.json index 0a506eb..4ea32a3 100644 --- a/cspell.json +++ b/cspell.json @@ -175,7 +175,16 @@ "stablecoins", "fintechs", "micropayments", - "clawback" + "clawback", + "hackathon", + "altbn", + "circom", + "snarkjs", + "Zcash", + "zcash", + "circomlibjs", + "Jotaro", + "Yano" ], "ignorePaths": [ "node_modules", diff --git a/docs.json b/docs.json index 7b29115..050a5c2 100644 --- a/docs.json +++ b/docs.json @@ -99,6 +99,33 @@ "quickstart" ] }, + { + "group": "ZK", + "pages": [ + "zk/overview", + "zk/examples" + ] + }, + { + "group": "Compressed PDAs", + "pages": [ + "compressed-pdas/overview", + { + "group": "Program Guides", + "pages": [ + "compressed-pdas/guides", + "compressed-pdas/guides/how-to-create-compressed-accounts", + "compressed-pdas/guides/how-to-update-compressed-accounts", + "compressed-pdas/guides/how-to-close-compressed-accounts", + "compressed-pdas/guides/how-to-reinitialize-compressed-accounts", + "compressed-pdas/guides/how-to-burn-compressed-accounts" + ] + }, + "compressed-pdas/program-examples", + "client-library/client-guide", + "compressed-pdas/solana-attestation-service" + ] + }, { "group": "Compressed Tokens", "pages": [ @@ -144,26 +171,6 @@ } ] }, - { - "group": "Compressed PDAs", - "pages": [ - "compressed-pdas/create-a-program-with-compressed-pdas", - { - "group": "Program Guides", - "pages": [ - "compressed-pdas/guides", - "compressed-pdas/guides/how-to-create-compressed-accounts", - "compressed-pdas/guides/how-to-update-compressed-accounts", - "compressed-pdas/guides/how-to-close-compressed-accounts", - "compressed-pdas/guides/how-to-reinitialize-compressed-accounts", - "compressed-pdas/guides/how-to-burn-compressed-accounts" - ] - }, - "compressed-pdas/program-examples", - "client-library/client-guide", - "compressed-pdas/solana-attestation-service" - ] - }, { "group": "JSON RPC Methods", "pages": [ diff --git a/home.mdx b/home.mdx index 5beda94..061f242 100644 --- a/home.mdx +++ b/home.mdx @@ -189,7 +189,7 @@ import WelcomePageInstall from "/snippets/setup/welcome-page-install.mdx"; Program and client guides for rent-free PDA accounts. diff --git a/learn/core-concepts/considerations.mdx b/learn/core-concepts/considerations.mdx index 94eaaf3..0651fff 100644 --- a/learn/core-concepts/considerations.mdx +++ b/learn/core-concepts/considerations.mdx @@ -81,7 +81,7 @@ You're ready to take the next step and start building! title="Compressed PDAs" icon="chevron-right" color="#0066ff" - href="/compressed-pdas/create-a-program-with-compressed-pdas" + href="/compressed-pdas/overview" horizontal /> \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7b54323..0ca6b8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "docs-v2", "version": "1.0.0", "devDependencies": { - "cspell": "^8.6.0", + "cspell": "^8.19.4", "eslint": "^8.57.0", "eslint-plugin-react": "^7.34.1", "eslint-plugin-react-hooks": "^4.6.2", diff --git a/package.json b/package.json index 938ade2..2cecceb 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,10 @@ "ci": "npm run format:check && npm run lint && npm run spellcheck" }, "devDependencies": { - "cspell": "^8.6.0", + "cspell": "^8.19.4", "eslint": "^8.57.0", "eslint-plugin-react": "^7.34.1", "eslint-plugin-react-hooks": "^4.6.2", "prettier": "^3.2.5" } } - diff --git a/references/migration-v1-to-v2.mdx b/references/migration-v1-to-v2.mdx index 515dfda..907cb7c 100644 --- a/references/migration-v1-to-v2.mdx +++ b/references/migration-v1-to-v2.mdx @@ -4,15 +4,16 @@ description: V2 reduces CU consumption by up to 70%. V1 remains supported for ex sidebarTitle: V2 Migration Guide --- -## V2 Improvements +import V1ToV2MigrationPrompt from "/snippets/ai-prompts/v1-to-v2-migration.mdx"; -| | v1 | v2 | -|---------------------|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------| -| Merkle tree type | Concurrent | Batched | -| State tree depth | 26 (~67M leaves) | 32 (~4B leaves) | -| Address tree depth | 26 | 40 | -| Address tree accounts| Separate tree + queue | Single batch tree | +## V2 Improvements +| | v1 | v2 | +|-----------------------|-----------------------|--------------------| +| Merkle tree type | Concurrent | Batched | +| State tree depth | 26 (~67M leaves) | 32 (~4B leaves) | +| Address tree depth | 26 | 40 | +| Address tree accounts | Separate tree + queue | Single batch tree | @@ -250,6 +251,11 @@ let new_address_params = instruction_data .into_new_address_params_assigned_packed(address_seed, Some(0)); ``` + + + + + diff --git a/resources/addresses-and-urls.mdx b/resources/addresses-and-urls.mdx index 0c66e2f..c04589f 100644 --- a/resources/addresses-and-urls.mdx +++ b/resources/addresses-and-urls.mdx @@ -87,7 +87,6 @@ Find all JSON RPC Methods for ZK Compression [here](/api-reference/json-rpc-meth - ## Address Trees & Queues diff --git a/resources/cli-installation.mdx b/resources/cli-installation.mdx index ed7a6ce..d80cda7 100644 --- a/resources/cli-installation.mdx +++ b/resources/cli-installation.mdx @@ -253,7 +253,7 @@ FLAGS title="Build with compressed PDAs" icon="chevron-right" color="#0066ff" - href="/compressed-pdas/create-a-program-with-compressed-pdas" + href="/compressed-pdas/overview" horizontal /> diff --git a/snippets/ai-prompts/v1-to-v2-migration.mdx b/snippets/ai-prompts/v1-to-v2-migration.mdx new file mode 100644 index 0000000..f6743d3 --- /dev/null +++ b/snippets/ai-prompts/v1-to-v2-migration.mdx @@ -0,0 +1,105 @@ +--- +argument-hint: +description: Migrate Light Protocol program from v1 to v2 Merkle trees +allowed-tools: [Bash, Read, Glob, Grep, Task, WebFetch] +--- + +Migrate this Light Protocol program from v1 to v2 Merkle trees. + +## Goal + +Produce a **fully working migration** that builds and tests pass. + +## Available commands + +Via Bash tool: +- `cargo build-sbf`, `cargo test-sbf`, `cargo fmt`, `cargo clippy` +- `anchor build`, `anchor test` +- `grep`, `sed` + +## Documentation + +- Migration Guide: https://zkcompression.com/references/migration-v1-to-v2 +- Reference PR: https://github.com/Lightprotocol/program-examples/commit/54f0e7f15c2972a078f776cfb40b238d83c7e486 + +## Reference repos + +program-examples/counter/anchor/ +├── programs/counter/src/lib.rs # v2 patterns: derive_address, CpiAccounts +├── Cargo.toml # v2 feature flags +└── tests/counter.ts # v2 client patterns + +## Workflow + +### Phase 1: Index program + +Find all v1 patterns: + + grep -r "::v1::" src/ tests/ + grep -r "ADDRESS_TREE_V1" src/ + grep -r "into_new_address_params_packed" src/ + grep -r "get_address_tree_v1" tests/ + +### Phase 2: Update dependencies + +Add v2 feature to Cargo.toml: + + [dependencies] + light-sdk = { version = "0.17", features = ["anchor", "v2"] } + light-sdk-types = { version = "0.17", features = ["v2"] } + + [dev-dependencies] + light-program-test = { version = "0.17", features = ["v2"] } + light-client = { version = "0.17", features = ["v2"] } + +### Phase 3: Rust SDK replacements + +| v1 Pattern | v2 Replacement | +|------------|----------------| +| address::v1::derive_address | address::v2::derive_address | +| cpi::v1::CpiAccounts | cpi::v2::CpiAccounts | +| cpi::v1::LightSystemProgramCpi | cpi::v2::LightSystemProgramCpi | +| constants::ADDRESS_TREE_V1 | light_sdk_types::ADDRESS_TREE_V2 | +| .into_new_address_params_packed(seed) | .into_new_address_params_assigned_packed(seed, Some(0)) | +| .add_system_accounts(config) | .add_system_accounts_v2(config) | + +### Phase 4: TypeScript SDK replacements + +| v1 Pattern | v2 Replacement | +|------------|----------------| +| deriveAddress( | deriveAddressV2( | +| deriveAddressSeed( | deriveAddressSeedV2( | +| defaultTestStateTreeAccounts().addressTree | batchAddressTree | +| .newWithSystemAccounts( | .newWithSystemAccountsV2( | +| get_address_tree_v1() | get_address_tree_v2() | +| get_random_state_tree_info_v1() | get_random_state_tree_info() | + +### Phase 5: Build and test loop + +**Required commands (no shortcuts):** + +For Anchor programs: `anchor build && anchor test` + +For Native programs: `cargo build-sbf && cargo test-sbf` + +**NO shortcuts allowed:** + +- Do NOT use `cargo build` (must use `cargo build-sbf`) +- Do NOT use `cargo test` (must use `cargo test-sbf`) +- Tests MUST run against real BPF bytecode + +**On failure:** Spawn debugger agent with error context. + +**Loop rules:** + +1. Each debugger gets fresh context + previous debug reports +2. Each attempt tries something DIFFERENT +3. **NEVER GIVE UP** - keep spawning until fixed + +Do NOT proceed until all tests pass. + +## DeepWiki fallback + +If no matching pattern in reference repos: + + mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "How to migrate {pattern} from v1 to v2?") \ No newline at end of file diff --git a/snippets/mermaid/nullifier-flow.mdx b/snippets/mermaid/nullifier-flow.mdx new file mode 100644 index 0000000..fdf228b --- /dev/null +++ b/snippets/mermaid/nullifier-flow.mdx @@ -0,0 +1,18 @@ +```mermaid +sequenceDiagram + participant U as User + participant C as Client + participant P as Program + participant L as Light Protocol + + U->>C: secret + verification_id + C->>C: nullifier = Poseidon(vid, secret) + C->>C: proof = Groth16.prove(...) + C->>P: create_nullifier(proof, vid, nullifier) + P->>P: Groth16.verify(proof) + P->>L: derive_address(nullifier, vid) + L-->>P: address + P->>L: create_account(address) + L-->>P: success/fail + P-->>U: tx result +``` diff --git a/snippets/overview-tables/zk-examples-table.mdx b/snippets/overview-tables/zk-examples-table.mdx new file mode 100644 index 0000000..faf4bb3 --- /dev/null +++ b/snippets/overview-tables/zk-examples-table.mdx @@ -0,0 +1,4 @@ +| | Description | +|:--------|:------------| +| [ZK-ID](https://github.com/Lightprotocol/program-examples/tree/main/zk/zk-id) | Identity verification using Groth16 proofs. Issuers create credentials; users prove ownership without revealing the credential. | +| [Nullifier](https://github.com/Lightprotocol/program-examples/tree/main/zk/zk-nullifier) | Simple Program to Create Nullifiers. | diff --git a/welcome.mdx b/welcome.mdx index 9f0a4d6..16f17ef 100644 --- a/welcome.mdx +++ b/welcome.mdx @@ -24,7 +24,7 @@ import WelcomePageInstall from "/snippets/setup/welcome-page-install.mdx"; For App State. diff --git a/zk/examples.mdx b/zk/examples.mdx new file mode 100644 index 0000000..cb45dc4 --- /dev/null +++ b/zk/examples.mdx @@ -0,0 +1,10 @@ +--- +title: Examples +description: Example projects for building privacy applications on Solana. +keywords: ["privacy examples solana", "zk examples solana", "private payments examples", "zk identity solana"] +--- + +import ZkExamplesTable from "/snippets/overview-tables/zk-examples-table.mdx"; + + + diff --git a/zk/overview.mdx b/zk/overview.mdx new file mode 100644 index 0000000..969e82b --- /dev/null +++ b/zk/overview.mdx @@ -0,0 +1,128 @@ +--- +title: Primitives for ZK on Solana +sidebarTitle: Overview +description: Overview how to build a ZK program on Solana. +keywords: ["nullifiers on Solana", "zcash on solana", "privacy on solana", "zk on solana", "solana privacy hackathon", "private payments solana", "privacy tooling solana"] +--- + +import ZkExamplesTable from "/snippets/overview-tables/zk-examples-table.mdx"; + +--- + +Building a ZK Solana program requires: +1. Nullifiers to prevent double spending +2. Proof verification +3. A Merkle tree to store state +4. An indexer to serve Merkle proofs +5. Encrypted state + +## Nullifiers on Solana + +A nullifier is a deterministically derived hash to ensure an action can only be performed once. +The nullifier cannot be linked to the action or user. +For example Zcash uses nullifiers to prevent double spending. + +To implement nullifiers we need a data structure that ensures every nullifier is only created once and never deleted. +On Solana a straight forward way to implement nullifiers is to create a PDA account with the nullifier as seed. +* PDA accounts cannot be closed and permanently lock 890,880 lamports (per nullifier rent-exemption). +* Compressed PDAs are derived similar to Solana PDAs and cost 15,000 lamports to create (no rent-exemption). + +| Storage | Cost per nullifier | +|---------|-------------------| +| PDA | 890,880 lamports | +| Compressed PDA | 15,000 lamports | + + +[See full example with tests on on Github](https://github.com/Lightprotocol/program-examples/tree/main/zk/nullifier). + + +```rust +// add to your program +use anchor_lang::prelude::*; +use nullifier_creation::{create_nullifiers, NullifierInstructionData}; + +declare_id!("Bw8aty8LJY5Kg2b6djghjWGwt6cBc1tVQUoreUehvVq4"); + +#[program] +pub mod zk_nullifier { + use super::*; + + pub fn create_nullifier<'info>( + ctx: Context<'_, '_, '_, 'info, CreateNullifierAccounts<'info>>, + data: NullifierInstructionData, + nullifiers: Vec<[u8; 32]>, + ) -> Result<()> { + // Verify your proof here. Use nullifiers as public inputs + // among your other public inputs. + // Example: + // let public_inputs = [...nullifiers, ...your_other_inputs]; + // Groth16Verifier::new(...).verify()?; + + create_nullifiers( + &nullifiers, + data, + ctx.accounts.signer.as_ref(), + ctx.remaining_accounts, + ) + } +} + +#[derive(Accounts)] +pub struct CreateNullifierAccounts<'info> { + #[account(mut)] + pub signer: Signer<'info>, +} +``` + +## Groth16 Proof Verification on Solana + +Groth16's small proof size and fast verification (~200k compute units) make it the practical choice for Solana. + + +Find more information on [docs.rs](https://docs.rs/groth16-solana) and [Github](https://github.com/Lightprotocol/groth16-solana). + + +```rust +let mut public_inputs_vec = Vec::new(); +for input in PUBLIC_INPUTS.chunks(32) { + public_inputs_vec.push(input); +} + +let proof_a: G1 = + ::read(&*[&change_endianness(&PROOF[0..64])[..], &[0u8][..]].concat()) + .unwrap(); +let mut proof_a_neg = [0u8; 65]; +::write(&proof_a.neg(), &mut proof_a_neg[..]).unwrap(); + +let proof_a = change_endianness(&proof_a_neg[..64]).try_into().unwrap(); +let proof_b = PROOF[64..192].try_into().unwrap(); +let proof_c = PROOF[192..256].try_into().unwrap(); + +let mut verifier = Groth16Verifier::new( + &proof_a, + &proof_b, + &proof_c, + public_inputs_vec.as_slice(), + &VERIFYING_KEY, +) +.unwrap(); +verifier.verify().unwrap(); +``` + +## Merklelized State with Indexer Support + +ZK applications on Solana can use existing state Merkle trees to store state in rent-free accounts. +* This way you don't need to maintain your own Merkle tree and indexer. +* RPCs that support ZK Compression (Helius, Triton) index state changes. + +| Creation | Regular PDA Account | Compressed PDA | +| :------------- | :--------------------- | :---------------------- | +| 100-byte PDA | ~1,600,000 lamports | 15,000 lamports | + + +Your circuit must include compressed accounts. Find [guides to compressed accounts in the documentation](/compressed-pdas/overview) and the [full example with zk implementation here](https://github.com/Lightprotocol/program-examples/blob/99d260f9f356743b8fe3501c684f7926930d6079/zk-id/circuits/compressed_account.circom). + + +## Get Started & Examples + + \ No newline at end of file