From 047e70f882e35900f1f847255fb94bf85f7822b2 Mon Sep 17 00:00:00 2001 From: kaankacar Date: Mon, 2 Feb 2026 15:37:00 +0300 Subject: [PATCH 1/5] Add dedicated ZK skill for Protocol 25 X-Ray - Create comprehensive zk-proofs.md covering BN254, Poseidon, Groth16 verification patterns, ZK development workflow, and security - Update SKILL.md with ZK routing and keywords - Update resources.md with ZK-specific resources section - Refactor contracts-soroban.md ZK section to reference new dedicated file --- skill/SKILL.md | 10 +- skill/contracts-soroban.md | 2 + skill/resources.md | 20 ++ skill/zk-proofs.md | 496 +++++++++++++++++++++++++++++++++++++ 4 files changed, 526 insertions(+), 2 deletions(-) create mode 100644 skill/zk-proofs.md diff --git a/skill/SKILL.md b/skill/SKILL.md index 0d1c577..a5040c3 100644 --- a/skill/SKILL.md +++ b/skill/SKILL.md @@ -1,6 +1,6 @@ --- name: stellar-dev -description: End-to-end Stellar development playbook (Jan 2026). Covers Soroban smart contracts (Rust SDK), Stellar CLI, JavaScript/Python/Go SDKs for client apps, Stellar RPC (preferred) and Horizon API (legacy), Stellar Assets vs Soroban tokens (SAC bridge), wallet integration (Freighter, Stellar Wallets Kit), smart accounts with passkeys, testing strategies, security patterns, and common pitfalls. Optimized for payments, asset tokenization, DeFi, and financial applications. Use when building on Stellar, Soroban, or working with XLM, Stellar Assets, trustlines, anchors, SEPs, or the Stellar RPC/Horizon APIs. +description: End-to-end Stellar development playbook (Jan 2026). Covers Soroban smart contracts (Rust SDK), Stellar CLI, JavaScript/Python/Go SDKs for client apps, Stellar RPC (preferred) and Horizon API (legacy), Stellar Assets vs Soroban tokens (SAC bridge), wallet integration (Freighter, Stellar Wallets Kit), smart accounts with passkeys, zero-knowledge proofs (Protocol 25 X-Ray, BN254, Poseidon), testing strategies, security patterns, and common pitfalls. Optimized for payments, asset tokenization, DeFi, privacy-preserving applications, and financial applications. Use when building on Stellar, Soroban, or working with XLM, Stellar Assets, trustlines, anchors, SEPs, ZK proofs, privacy pools, or the Stellar RPC/Horizon APIs. user-invocable: true argument-hint: "[task-description]" --- @@ -15,6 +15,8 @@ Use this Skill when the user asks for: - Transaction building / sending / confirmation - Stellar Asset issuance and management - Client SDK usage (JavaScript, Python, Go, Rust) +- Zero-knowledge proof verification (BN254, Poseidon, Groth16) +- Privacy-preserving applications (privacy pools, confidential tokens) - Local testing and deployment - Security hardening and audit-style reviews @@ -76,6 +78,7 @@ Use this Skill when the user asks for: - Need custom on-chain logic? → [contracts-soroban.md](contracts-soroban.md) - Building a frontend/dApp? → [frontend-stellar-sdk.md](frontend-stellar-sdk.md) - Issuing or managing tokens? → [stellar-assets.md](stellar-assets.md) +- Zero-knowledge proofs or privacy? → [zk-proofs.md](zk-proofs.md) - Setting up tests/CI? → [testing.md](testing.md) - Querying chain data or indexing? → [api-rpc-horizon.md](api-rpc-horizon.md) (also see [Data Docs](https://developers.stellar.org/docs/data)) - Security review? → [security.md](security.md) @@ -115,6 +118,7 @@ When you implement changes, provide: - Frontend + wallets: [frontend-stellar-sdk.md](frontend-stellar-sdk.md) - Testing strategy: [testing.md](testing.md) - Stellar Assets: [stellar-assets.md](stellar-assets.md) +- Zero-knowledge proofs: [zk-proofs.md](zk-proofs.md) - API access (RPC/Horizon): [api-rpc-horizon.md](api-rpc-horizon.md) - Security checklist: [security.md](security.md) - Common pitfalls: [common-pitfalls.md](common-pitfalls.md) @@ -124,4 +128,6 @@ When you implement changes, provide: ## Keywords stellar, soroban, xlm, smart contracts, rust, wasm, webassembly, rpc, horizon, freighter, stellar-sdk, soroban-sdk, stellar-cli, trustline, anchor, sep, passkey, -smart wallet, sac, stellar asset contract, defi, token, nft, scaffold stellar +smart wallet, sac, stellar asset contract, defi, token, nft, scaffold stellar, +zero-knowledge, zk, zk-snark, groth16, bn254, poseidon, pairing, privacy, confidential, +x-ray, protocol 25, noir, risc zero, privacy pool, merkle tree diff --git a/skill/contracts-soroban.md b/skill/contracts-soroban.md index 6f37c43..a386cec 100644 --- a/skill/contracts-soroban.md +++ b/skill/contracts-soroban.md @@ -523,3 +523,5 @@ ZK-friendly hash functions (two orders of magnitude fewer ZK constraints than SH - [BLS Signature](https://github.com/stellar/soroban-examples) — BLS12-381 signature verification > **Note**: BLS12-381 curve operations were added in Protocol 22 via CAP-0059. Protocol 25 adds BN254 as a complement, matching Ethereum's curve for easier migration of EVM ZK applications. + +> See [zk-proofs.md](zk-proofs.md) for Groth16 verification patterns, Poseidon usage, Noir/RISC Zero integration, and complete implementation guidance. diff --git a/skill/resources.md b/skill/resources.md index 37d04f2..cfbb428 100644 --- a/skill/resources.md +++ b/skill/resources.md @@ -109,6 +109,26 @@ For vulnerability patterns, checklists, and detailed tooling guides, see [securi - [CoinFabrik Audit Reports](https://www.coinfabrik.com/smart-contract-audit-reports/) - [Certora Security Reports](https://github.com/Certora/SecurityReports) - Includes Stellar verifications +## Zero-Knowledge Proofs (Protocol 25 X-Ray) + +For comprehensive ZK development guidance, see [zk-proofs.md](zk-proofs.md). + +### Protocol & Specifications +- [X-Ray Announcement](https://stellar.org/blog/developers/announcing-stellar-x-ray-protocol-25) - Protocol 25 overview +- [CAP-0074](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0074.md) - BN254 elliptic curve specification +- [CAP-0075](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0075.md) - Poseidon hash function specification + +### SDK Documentation +- [Soroban SDK BN254](https://docs.rs/soroban-sdk/latest/soroban_sdk/crypto/bn254/) - BN254 types and functions +- [Soroban SDK Crypto](https://docs.rs/soroban-sdk/latest/soroban_sdk/crypto/) - Full crypto module reference + +### Proving Systems & Tooling +- [Noir Documentation](https://noir-lang.org/docs/) - Aztec's ZK domain-specific language +- [RISC Zero](https://dev.risczero.com/) - General-purpose zkVM for Rust programs + +### Example Contracts +- [Soroban Examples](https://github.com/stellar/soroban-examples) - Official examples (includes `groth16_verifier`, `privacy-pools`, `import_ark_bn254`) + ## Testing ### Testing Guides diff --git a/skill/zk-proofs.md b/skill/zk-proofs.md new file mode 100644 index 0000000..82116cd --- /dev/null +++ b/skill/zk-proofs.md @@ -0,0 +1,496 @@ +# Zero-Knowledge Proofs on Stellar (Protocol 25 X-Ray) + +Protocol 25 "X-Ray" (Mainnet January 22, 2026) introduced native ZK cryptographic primitives, enabling privacy-preserving applications on Stellar. + +## When to Use ZK on Stellar + +- **On-chain proof verification** — Verify zk-SNARK proofs (Groth16, PLONK, UltraHonk) +- **Privacy pools** — Prove lawful source of funds without revealing transaction history +- **Confidential tokens** — Hidden balances with validity proofs +- **ZK Merkle trees** — Efficient membership proofs using Poseidon hashes +- **Cross-chain bridges** — Verify state proofs from other chains +- **Compliance-forward privacy** — KYC/AML compliance with minimal data exposure +- **ZK Email** — Prove email contents without revealing the full email + +## Prerequisites + +```toml +# Cargo.toml +[dependencies] +soroban-sdk = "25.0.1" +``` + +Ensure your Stellar CLI and network target Protocol 25+. + +--- + +## Core Primitives + +### BN254 Elliptic Curve (CAP-0074) + +BN254 (alt_bn128) is a pairing-friendly curve matching Ethereum's EIP-196/EIP-197 precompiles. This enables migration of existing EVM ZK applications. + +#### Access Pattern + +```rust +use soroban_sdk::{Env, BytesN}; +use soroban_sdk::crypto::bn254::{Bn254, Bn254G1Affine, Bn254G2Affine, Fr, Bn254Fp}; + +pub fn zk_operations(env: &Env) { + let bn254: Bn254 = env.crypto().bn254(); + + // Use bn254.g1_add(), bn254.g1_mul(), bn254.pairing_check() +} +``` + +#### Types + +| Type | Size | Description | +|------|------|-------------| +| `Bn254Fp` | 32 bytes | Base field element | +| `Fr` | 32 bytes | Scalar field element (U256 internally) | +| `Bn254G1Affine` | 64 bytes | G1 point (x, y coordinates) | +| `Bn254G2Affine` | 128 bytes | G2 point (extension field coordinates) | + +#### Host Functions + +```rust +// G1 Point Addition +// Combines two points on the G1 curve +let sum: Bn254G1Affine = bn254.g1_add(&p1, &p2); + +// G1 Scalar Multiplication +// Multiplies a G1 point by a scalar +let product: Bn254G1Affine = bn254.g1_mul(&point, &scalar); + +// Multi-Pairing Check (core of zk-SNARK verification) +// Returns true if: e(g1[0], g2[0]) × e(g1[1], g2[1]) × ... = 1 +let valid: bool = bn254.pairing_check(g1_points, g2_points); +``` + +#### Encoding Format + +Points use **uncompressed big-endian** encoding (no flag bits): + +- **G1**: `be_encode(X) || be_encode(Y)` — 64 bytes +- **G2**: `be_encode(X_c1) || be_encode(X_c0) || be_encode(Y_c1) || be_encode(Y_c0)` — 128 bytes +- **Point at infinity**: All zeros + +Field element max value: `0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47` + +--- + +### Poseidon Hash Functions (CAP-0075) + +Poseidon is optimized for ZK circuits — ~300 constraints vs ~27,000 for SHA-256. Essential for efficient Merkle trees and commitments in ZK applications. + +#### Host Functions + +The Poseidon functions expose the raw permutation primitive, requiring you to specify all parameters: + +```rust +use soroban_sdk::{Env, Vec, U256, Symbol}; + +pub fn poseidon_example(env: &Env, inputs: Vec) -> U256 { + // Poseidon permutation + // Parameters: input, field_type, state_size, sbox_degree, full_rounds, partial_rounds, mds_matrix, round_constants + let result = env.crypto().poseidon_permutation( + inputs, + 0, // 0 = BLS12-381 Fr, 1 = BN254 Fr + 3, // state size (t) + 5, // S-box degree (d) + 8, // full rounds (must be even) + 22, // partial rounds + mds_matrix, // t×t MDS matrix + round_constants, + ); + result.get(0).unwrap() +} +``` + +#### Field Types + +| Field ID | Curve | Order | +|----------|-------|-------| +| 0 | BLS12-381 Fr | `0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001` | +| 1 | BN254 Fr | `0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001` | + +#### Poseidon2 Variant + +Poseidon2 uses optimized diagonal matrices instead of full MDS: + +```rust +let result = env.crypto().poseidon2_permutation( + inputs, + 1, // BN254 field + 3, // state size + 5, // S-box degree (also supports 3, 7, 11) + 8, // full rounds + 22, // partial rounds + internal_diagonal, // diagonal matrix (not full MDS) + round_constants, +); +``` + +--- + +## Groth16 Verification Pattern + +Groth16 is the most common zk-SNARK proof system. Verification uses the pairing equation: + +``` +e(A, B) = e(α, β) × e(L, γ) × e(C, δ) +``` + +Where: +- `(A, B, C)` = proof elements +- `(α, β, γ, δ)` = verification key +- `L` = linear combination of public inputs + +### Verification Contract Structure + +```rust +use soroban_sdk::{contract, contractimpl, Env, Vec, BytesN}; +use soroban_sdk::crypto::bn254::{Bn254G1Affine, Bn254G2Affine, Fr}; + +#[derive(Clone)] +pub struct VerifyingKey { + pub alpha_g1: Bn254G1Affine, + pub beta_g2: Bn254G2Affine, + pub gamma_g2: Bn254G2Affine, + pub delta_g2: Bn254G2Affine, + pub ic: Vec, // Input commitments +} + +#[derive(Clone)] +pub struct Proof { + pub a: Bn254G1Affine, + pub b: Bn254G2Affine, + pub c: Bn254G1Affine, +} + +#[contract] +pub struct Groth16Verifier; + +#[contractimpl] +impl Groth16Verifier { + /// Verify a Groth16 proof with public inputs + pub fn verify( + env: Env, + vk: VerifyingKey, + proof: Proof, + public_inputs: Vec, + ) -> bool { + let bn254 = env.crypto().bn254(); + + // Compute vk_x = ic[0] + sum(public_inputs[i] * ic[i+1]) + let mut vk_x = vk.ic.get(0).unwrap(); + for i in 0..public_inputs.len() { + let ic_i = vk.ic.get(i + 1).unwrap(); + let input_i = public_inputs.get(i).unwrap(); + let term = bn254.g1_mul(&ic_i, &input_i); + vk_x = bn254.g1_add(&vk_x, &term); + } + + // Negate proof.a for the pairing equation + let neg_a = negate_g1(&env, &proof.a); + + // Pairing check: e(-A, B) * e(alpha, beta) * e(vk_x, gamma) * e(C, delta) = 1 + let g1_points = Vec::from_array(&env, [ + neg_a, + vk.alpha_g1, + vk_x, + proof.c, + ]); + let g2_points = Vec::from_array(&env, [ + proof.b, + vk.beta_g2, + vk.gamma_g2, + vk.delta_g2, + ]); + + bn254.pairing_check(g1_points, g2_points) + } +} + +/// Negate a G1 point (flip y-coordinate in the field) +fn negate_g1(env: &Env, point: &Bn254G1Affine) -> Bn254G1Affine { + // y_neg = p - y where p is the field modulus + // Implementation depends on how you access the point coordinates + // This is a simplified placeholder + todo!("Implement G1 negation") +} +``` + +--- + +## ZK Development Workflow + +### 1. Write the Circuit (Off-chain) + +Choose a proving system and write your circuit logic: + +**Noir (Aztec)** — Domain-specific language for ZK: +```noir +// circuit.nr +fn main(x: Field, y: pub Field) { + assert(x * x == y); +} +``` + +**RISC Zero** — Write in Rust, prove any computation: +```rust +// guest/src/main.rs +#![no_main] +risc0_zkvm::guest::entry!(main); + +fn main() { + let input: u64 = risc0_zkvm::guest::env::read(); + let result = expensive_computation(input); + risc0_zkvm::guest::env::commit(&result); +} +``` + +### 2. Generate Proofs (Off-chain) + +Compile the circuit and generate proofs using the respective toolchain: + +```bash +# Noir +nargo compile +nargo prove + +# RISC Zero +cargo risczero build +cargo run --release +``` + +### 3. Deploy Verifier Contract (On-chain) + +Deploy a Soroban contract that verifies the proofs using BN254 primitives. + +### 4. Verify Proofs (On-chain) + +Submit proofs to your verifier contract for on-chain verification. + +--- + +## Multi-Scalar Multiplication (MSM) + +Soroban doesn't provide native MSM. Implement it using g1_mul and g1_add: + +```rust +/// Compute sum of scalar[i] * point[i] +pub fn g1_msm( + env: &Env, + scalars: &Vec, + points: &Vec, +) -> Bn254G1Affine { + let bn254 = env.crypto().bn254(); + + assert_eq!(scalars.len(), points.len()); + + // Start with point at infinity (identity) + let mut result = identity_g1(env); + + for i in 0..scalars.len() { + let scalar = scalars.get(i).unwrap(); + let point = points.get(i).unwrap(); + let term = bn254.g1_mul(&point, &scalar); + result = bn254.g1_add(&result, &term); + } + + result +} +``` + +--- + +## ZK Merkle Tree with Poseidon + +```rust +use soroban_sdk::{Env, Vec, U256, BytesN}; + +/// Verify a Merkle proof using Poseidon hash +pub fn verify_merkle_proof( + env: &Env, + leaf: U256, + proof: Vec, + path_indices: Vec, // true = right, false = left + root: U256, +) -> bool { + let mut current = leaf; + + for i in 0..proof.len() { + let sibling = proof.get(i).unwrap(); + let is_right = path_indices.get(i).unwrap(); + + // Hash pair in correct order + let inputs = if is_right { + Vec::from_array(env, [sibling, current]) + } else { + Vec::from_array(env, [current, sibling]) + }; + + current = poseidon_hash(env, inputs); + } + + current == root +} +``` + +--- + +## Resource Costs + +BN254 operations have associated cost types for metering: + +| Cost Type | Operation | +|-----------|-----------| +| `Bn254G1Add` | G1 point addition | +| `Bn254G1Mul` | G1 scalar multiplication | +| `Bn254Pairing` | Pairing (linear with vector length) | +| `Bn254EncodeFp` / `Bn254DecodeFp` | Field element encoding | +| `Bn254G1CheckPointOnCurve` | Curve membership check | +| `Bn254G2CheckPointInSubgroup` | Subgroup membership check | +| `Bn254FrFromU256` / `Bn254FrToU256` | Scalar conversion | +| `Bn254FrMul` / `Bn254FrAddSub` | Scalar arithmetic | +| `Bn254FrPow` / `Bn254FrInv` | Exponentiation / Inversion | + +Poseidon costs scale linearly with rounds and quadratically with state size. + +--- + +## Security Considerations + +### Proof Verification + +- **Validate all inputs** — Malformed G1/G2 points will cause host function traps +- **Check proof freshness** — Prevent replay attacks with nullifiers or nonces +- **Verify public inputs** — Don't trust client-provided public input commitments + +### Encoding Errors + +Host functions trap on: +- G1 point byte length ≠ 64 +- G2 point byte length ≠ 128 +- Point not on curve +- G2 point not in correct subgroup +- Mismatched vector lengths in pairing_check + +### Privacy Pool Patterns + +```rust +// Use nullifiers to prevent double-spending +pub fn withdraw( + env: Env, + proof: Proof, + nullifier_hash: U256, + // ... other params +) { + // Check nullifier hasn't been used + if env.storage().persistent().has(&DataKey::Nullifier(nullifier_hash)) { + panic!("nullifier already used"); + } + + // Verify the ZK proof + if !verify_proof(&env, &proof) { + panic!("invalid proof"); + } + + // Mark nullifier as used + env.storage().persistent().set(&DataKey::Nullifier(nullifier_hash), &true); + + // Process withdrawal... +} +``` + +--- + +## Testing ZK Contracts + +### Unit Tests + +```rust +#[cfg(test)] +mod tests { + use super::*; + use soroban_sdk::testutils::Env as _; + + #[test] + fn test_pairing_check() { + let env = Env::default(); + let bn254 = env.crypto().bn254(); + + // Test with known valid pairing inputs + // (You'll need actual test vectors from your proving system) + let g1_points = create_test_g1_points(&env); + let g2_points = create_test_g2_points(&env); + + let result = bn254.pairing_check(g1_points, g2_points); + assert!(result); + } + + #[test] + fn test_invalid_proof_rejected() { + let env = Env::default(); + + // Modify a valid proof to make it invalid + let invalid_proof = create_invalid_proof(&env); + + let result = Groth16Verifier::verify( + env.clone(), + get_vk(&env), + invalid_proof, + get_public_inputs(&env), + ); + + assert!(!result); + } +} +``` + +### Integration Tests + +1. Generate real proofs using your off-chain prover +2. Deploy verifier to local Quickstart or Testnet +3. Submit proofs via Stellar CLI or SDK +4. Verify correct acceptance/rejection + +```bash +# Deploy verifier +stellar contract deploy --wasm target/wasm32-unknown-unknown/release/verifier.wasm \ + --source alice --network testnet + +# Invoke with proof data +stellar contract invoke --id --source alice --network testnet \ + -- verify --proof --public_inputs +``` + +--- + +## Examples & Resources + +### Official Resources +- [X-Ray Announcement](https://stellar.org/blog/developers/announcing-stellar-x-ray-protocol-25) — Protocol 25 overview +- [CAP-0074](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0074.md) — BN254 specification +- [CAP-0075](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0075.md) — Poseidon specification +- [Soroban SDK BN254 Docs](https://docs.rs/soroban-sdk/latest/soroban_sdk/crypto/bn254/) — Type and function reference + +### Example Contracts +- [Soroban Examples](https://github.com/stellar/soroban-examples) — Official examples (check for `groth16_verifier`, `privacy-pools`, `import_ark_bn254`) + +### Proving Systems +- [Noir Documentation](https://noir-lang.org/docs/) — Aztec's ZK DSL +- [RISC Zero](https://dev.risczero.com/) — General-purpose zkVM + +### Community Resources +- [UltraHonk Soroban Verifier](https://github.com/indextree/ultrahonk_soroban_contract) — Noir proof verification (verify SDK compatibility) + +> **Note**: Protocol 25 launched January 22, 2026. Some community projects may still be updating to soroban-sdk v25. Always verify SDK version compatibility before using third-party code. + +--- + +## Keywords +zero-knowledge, zk, zk-snark, groth16, plonk, bn254, alt_bn128, poseidon, pairing, elliptic curve, +privacy, confidential, merkle tree, nullifier, proof verification, noir, risc zero, x-ray, protocol 25 From 79b0181b493733a2a0c739f308d72a9020c074d1 Mon Sep 17 00:00:00 2001 From: kaankacar Date: Mon, 2 Feb 2026 16:06:05 +0300 Subject: [PATCH 2/5] Fix ZK skill to match actual Protocol 25 APIs - BN254: Use operator overloading (+, *, -) instead of method calls - BN254: Fix type names (Fr, G1Affine, G2Affine not Bn254G1Affine) - Poseidon: Use simple poseidon_hash/poseidon2_hash API with Symbol field - Poseidon: Remove incorrect permutation API with 8 parameters - Groth16: Update example to use correct operators and BytesN types - Add note that official groth16_verifier uses BLS12-381 - Update resource links to p25-preview examples - Remove unverified resource cost types --- skill/zk-proofs.md | 323 ++++++++++++++++++++++----------------------- 1 file changed, 155 insertions(+), 168 deletions(-) diff --git a/skill/zk-proofs.md b/skill/zk-proofs.md index 82116cd..0079960 100644 --- a/skill/zk-proofs.md +++ b/skill/zk-proofs.md @@ -10,7 +10,6 @@ Protocol 25 "X-Ray" (Mainnet January 22, 2026) introduced native ZK cryptographi - **ZK Merkle trees** — Efficient membership proofs using Poseidon hashes - **Cross-chain bridges** — Verify state proofs from other chains - **Compliance-forward privacy** — KYC/AML compliance with minimal data exposure -- **ZK Email** — Prove email contents without revealing the full email ## Prerequisites @@ -30,53 +29,71 @@ Ensure your Stellar CLI and network target Protocol 25+. BN254 (alt_bn128) is a pairing-friendly curve matching Ethereum's EIP-196/EIP-197 precompiles. This enables migration of existing EVM ZK applications. -#### Access Pattern +#### Types ```rust -use soroban_sdk::{Env, BytesN}; -use soroban_sdk::crypto::bn254::{Bn254, Bn254G1Affine, Bn254G2Affine, Fr, Bn254Fp}; - -pub fn zk_operations(env: &Env) { - let bn254: Bn254 = env.crypto().bn254(); - - // Use bn254.g1_add(), bn254.g1_mul(), bn254.pairing_check() -} +use soroban_sdk::crypto::bn254::{Fr, G1Affine, G2Affine}; ``` -#### Types - | Type | Size | Description | |------|------|-------------| -| `Bn254Fp` | 32 bytes | Base field element | -| `Fr` | 32 bytes | Scalar field element (U256 internally) | -| `Bn254G1Affine` | 64 bytes | G1 point (x, y coordinates) | -| `Bn254G2Affine` | 128 bytes | G2 point (extension field coordinates) | +| `Fr` | 32 bytes | Scalar field element (converts to/from U256) | +| `G1Affine` | 64 bytes | G1 point (x, y coordinates) | +| `G2Affine` | 128 bytes | G2 point (extension field coordinates) | -#### Host Functions +#### Operations + +BN254 uses **operator overloading** for point arithmetic: ```rust -// G1 Point Addition -// Combines two points on the G1 curve -let sum: Bn254G1Affine = bn254.g1_add(&p1, &p2); +use soroban_sdk::{contract, contractimpl, Env, BytesN, U256, Vec}; +use soroban_sdk::crypto::bn254::{Fr, G1Affine, G2Affine}; + +#[contract] +pub struct Bn254Example; + +#[contractimpl] +impl Bn254Example { + /// Add two G1 points + pub fn g1_add(a: BytesN<64>, b: BytesN<64>) -> BytesN<64> { + let a = G1Affine::from_bytes(a); + let b = G1Affine::from_bytes(b); + (a + b).to_bytes() // Use + operator + } + + /// Scalar multiplication + pub fn g1_mul(p: BytesN<64>, s: U256) -> BytesN<64> { + let p = G1Affine::from_bytes(p); + let s = Fr::from(s); + (p * s).to_bytes() // Use * operator + } + + /// Multi-pairing check (core of zk-SNARK verification) + /// Returns true if: e(g1[0], g2[0]) × e(g1[1], g2[1]) × ... = 1 + pub fn verify_pairing(env: Env, g1_bytes: Vec>, g2_bytes: Vec>) -> bool { + let mut g1_points = Vec::new(&env); + for bytes in g1_bytes.iter() { + g1_points.push_back(G1Affine::from_bytes(bytes)); + } -// G1 Scalar Multiplication -// Multiplies a G1 point by a scalar -let product: Bn254G1Affine = bn254.g1_mul(&point, &scalar); + let mut g2_points = Vec::new(&env); + for bytes in g2_bytes.iter() { + g2_points.push_back(G2Affine::from_bytes(bytes)); + } -// Multi-Pairing Check (core of zk-SNARK verification) -// Returns true if: e(g1[0], g2[0]) × e(g1[1], g2[1]) × ... = 1 -let valid: bool = bn254.pairing_check(g1_points, g2_points); + env.crypto().bn254().pairing_check(g1_points, g2_points) + } +} ``` #### Encoding Format Points use **uncompressed big-endian** encoding (no flag bits): -- **G1**: `be_encode(X) || be_encode(Y)` — 64 bytes -- **G2**: `be_encode(X_c1) || be_encode(X_c0) || be_encode(Y_c1) || be_encode(Y_c0)` — 128 bytes +- **G1**: 64 bytes — `be_encode(X) || be_encode(Y)` +- **G2**: 128 bytes — `be_encode(X_c1) || be_encode(X_c0) || be_encode(Y_c1) || be_encode(Y_c0)` - **Point at infinity**: All zeros - -Field element max value: `0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47` +- **Fr (scalar)**: U256 / 32 bytes --- @@ -84,52 +101,45 @@ Field element max value: `0x30644e72e131a029b85045b68181585d97816a916871ca8d3c20 Poseidon is optimized for ZK circuits — ~300 constraints vs ~27,000 for SHA-256. Essential for efficient Merkle trees and commitments in ZK applications. -#### Host Functions - -The Poseidon functions expose the raw permutation primitive, requiring you to specify all parameters: +#### API ```rust -use soroban_sdk::{Env, Vec, U256, Symbol}; +use soroban_sdk::{contract, contractimpl, Env, Symbol, U256, Vec}; + +#[contract] +pub struct PoseidonExample; -pub fn poseidon_example(env: &Env, inputs: Vec) -> U256 { - // Poseidon permutation - // Parameters: input, field_type, state_size, sbox_degree, full_rounds, partial_rounds, mds_matrix, round_constants - let result = env.crypto().poseidon_permutation( - inputs, - 0, // 0 = BLS12-381 Fr, 1 = BN254 Fr - 3, // state size (t) - 5, // S-box degree (d) - 8, // full rounds (must be even) - 22, // partial rounds - mds_matrix, // t×t MDS matrix - round_constants, - ); - result.get(0).unwrap() +#[contractimpl] +impl PoseidonExample { + /// Poseidon hash over BN254 scalar field + pub fn poseidon(env: Env, inputs: Vec) -> U256 { + let field = Symbol::new(&env, "BN254"); + env.crypto().poseidon_hash(&inputs, field) + } + + /// Poseidon2 hash (optimized variant) + pub fn poseidon2(env: Env, inputs: Vec) -> U256 { + let field = Symbol::new(&env, "BN254"); + env.crypto().poseidon2_hash(&inputs, field) + } } ``` -#### Field Types +#### Supported Fields -| Field ID | Curve | Order | -|----------|-------|-------| -| 0 | BLS12-381 Fr | `0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001` | -| 1 | BN254 Fr | `0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001` | +| Field Symbol | Curve | +|--------------|-------| +| `"BN254"` | BN254 scalar field | +| `"BLS12_381"` | BLS12-381 scalar field | -#### Poseidon2 Variant +#### CLI Example -Poseidon2 uses optimized diagonal matrices instead of full MDS: +```bash +# Hash two field elements [3, 4] +stellar contract invoke --id poseidon --network futurenet -- poseidon \ + --inputs '["3", "4"]' -```rust -let result = env.crypto().poseidon2_permutation( - inputs, - 1, // BN254 field - 3, // state size - 5, // S-box degree (also supports 3, 7, 11) - 8, // full rounds - 22, // partial rounds - internal_diagonal, // diagonal matrix (not full MDS) - round_constants, -); +# Output: "14763215145315200506921711489642608356394854266165572616578112107564877678998" ``` --- @@ -150,23 +160,25 @@ Where: ### Verification Contract Structure ```rust -use soroban_sdk::{contract, contractimpl, Env, Vec, BytesN}; -use soroban_sdk::crypto::bn254::{Bn254G1Affine, Bn254G2Affine, Fr}; +use soroban_sdk::{contract, contractimpl, contracttype, Env, Vec, BytesN}; +use soroban_sdk::crypto::bn254::{Fr, G1Affine, G2Affine}; #[derive(Clone)] +#[contracttype] pub struct VerifyingKey { - pub alpha_g1: Bn254G1Affine, - pub beta_g2: Bn254G2Affine, - pub gamma_g2: Bn254G2Affine, - pub delta_g2: Bn254G2Affine, - pub ic: Vec, // Input commitments + pub alpha_g1: BytesN<64>, + pub beta_g2: BytesN<128>, + pub gamma_g2: BytesN<128>, + pub delta_g2: BytesN<128>, + pub ic: Vec>, // Input commitments } #[derive(Clone)] +#[contracttype] pub struct Proof { - pub a: Bn254G1Affine, - pub b: Bn254G2Affine, - pub c: Bn254G1Affine, + pub a: BytesN<64>, + pub b: BytesN<128>, + pub c: BytesN<64>, } #[contract] @@ -179,49 +191,45 @@ impl Groth16Verifier { env: Env, vk: VerifyingKey, proof: Proof, - public_inputs: Vec, + public_inputs: Vec, ) -> bool { - let bn254 = env.crypto().bn254(); - // Compute vk_x = ic[0] + sum(public_inputs[i] * ic[i+1]) - let mut vk_x = vk.ic.get(0).unwrap(); + let mut vk_x = G1Affine::from_bytes(vk.ic.get(0).unwrap()); + for i in 0..public_inputs.len() { - let ic_i = vk.ic.get(i + 1).unwrap(); - let input_i = public_inputs.get(i).unwrap(); - let term = bn254.g1_mul(&ic_i, &input_i); - vk_x = bn254.g1_add(&vk_x, &term); + let ic_i = G1Affine::from_bytes(vk.ic.get(i + 1).unwrap()); + let input_i = Fr::from(public_inputs.get(i).unwrap()); + let term = ic_i * input_i; + vk_x = vk_x + term; } // Negate proof.a for the pairing equation - let neg_a = negate_g1(&env, &proof.a); + let proof_a = G1Affine::from_bytes(proof.a); + let neg_a = -proof_a; - // Pairing check: e(-A, B) * e(alpha, beta) * e(vk_x, gamma) * e(C, delta) = 1 + // Build point vectors for pairing check let g1_points = Vec::from_array(&env, [ neg_a, - vk.alpha_g1, + G1Affine::from_bytes(vk.alpha_g1), vk_x, - proof.c, + G1Affine::from_bytes(proof.c), ]); + let g2_points = Vec::from_array(&env, [ - proof.b, - vk.beta_g2, - vk.gamma_g2, - vk.delta_g2, + G2Affine::from_bytes(proof.b), + G2Affine::from_bytes(vk.beta_g2), + G2Affine::from_bytes(vk.gamma_g2), + G2Affine::from_bytes(vk.delta_g2), ]); - bn254.pairing_check(g1_points, g2_points) + // Pairing check: e(-A, B) * e(alpha, beta) * e(vk_x, gamma) * e(C, delta) = 1 + env.crypto().bn254().pairing_check(g1_points, g2_points) } } - -/// Negate a G1 point (flip y-coordinate in the field) -fn negate_g1(env: &Env, point: &Bn254G1Affine) -> Bn254G1Affine { - // y_neg = p - y where p is the field modulus - // Implementation depends on how you access the point coordinates - // This is a simplified placeholder - todo!("Implement G1 negation") -} ``` +> **Note**: The official `groth16_verifier` example in soroban-examples uses BLS12-381. The pattern above adapts it for BN254 (Ethereum-compatible). + --- ## ZK Development Workflow @@ -277,27 +285,28 @@ Submit proofs to your verifier contract for on-chain verification. ## Multi-Scalar Multiplication (MSM) -Soroban doesn't provide native MSM. Implement it using g1_mul and g1_add: +Soroban doesn't provide native MSM. Implement using operator overloading: ```rust +use soroban_sdk::{Env, Vec, BytesN, U256}; +use soroban_sdk::crypto::bn254::{Fr, G1Affine}; + /// Compute sum of scalar[i] * point[i] pub fn g1_msm( - env: &Env, - scalars: &Vec, - points: &Vec, -) -> Bn254G1Affine { - let bn254 = env.crypto().bn254(); - + scalars: &Vec, + points: &Vec>, +) -> G1Affine { assert_eq!(scalars.len(), points.len()); - // Start with point at infinity (identity) - let mut result = identity_g1(env); + // Start with first term + let mut result = G1Affine::from_bytes(points.get(0).unwrap()) + * Fr::from(scalars.get(0).unwrap()); - for i in 0..scalars.len() { - let scalar = scalars.get(i).unwrap(); - let point = points.get(i).unwrap(); - let term = bn254.g1_mul(&point, &scalar); - result = bn254.g1_add(&result, &term); + for i in 1..scalars.len() { + let scalar = Fr::from(scalars.get(i).unwrap()); + let point = G1Affine::from_bytes(points.get(i).unwrap()); + let term = point * scalar; + result = result + term; } result @@ -309,7 +318,7 @@ pub fn g1_msm( ## ZK Merkle Tree with Poseidon ```rust -use soroban_sdk::{Env, Vec, U256, BytesN}; +use soroban_sdk::{Env, Vec, U256, Symbol}; /// Verify a Merkle proof using Poseidon hash pub fn verify_merkle_proof( @@ -319,6 +328,7 @@ pub fn verify_merkle_proof( path_indices: Vec, // true = right, false = left root: U256, ) -> bool { + let field = Symbol::new(env, "BN254"); let mut current = leaf; for i in 0..proof.len() { @@ -332,7 +342,7 @@ pub fn verify_merkle_proof( Vec::from_array(env, [current, sibling]) }; - current = poseidon_hash(env, inputs); + current = env.crypto().poseidon_hash(&inputs, field.clone()); } current == root @@ -341,26 +351,6 @@ pub fn verify_merkle_proof( --- -## Resource Costs - -BN254 operations have associated cost types for metering: - -| Cost Type | Operation | -|-----------|-----------| -| `Bn254G1Add` | G1 point addition | -| `Bn254G1Mul` | G1 scalar multiplication | -| `Bn254Pairing` | Pairing (linear with vector length) | -| `Bn254EncodeFp` / `Bn254DecodeFp` | Field element encoding | -| `Bn254G1CheckPointOnCurve` | Curve membership check | -| `Bn254G2CheckPointInSubgroup` | Subgroup membership check | -| `Bn254FrFromU256` / `Bn254FrToU256` | Scalar conversion | -| `Bn254FrMul` / `Bn254FrAddSub` | Scalar arithmetic | -| `Bn254FrPow` / `Bn254FrInv` | Exponentiation / Inversion | - -Poseidon costs scale linearly with rounds and quadratically with state size. - ---- - ## Security Considerations ### Proof Verification @@ -381,6 +371,8 @@ Host functions trap on: ### Privacy Pool Patterns ```rust +use soroban_sdk::{Env, U256}; + // Use nullifiers to prevent double-spending pub fn withdraw( env: Env, @@ -415,37 +407,31 @@ pub fn withdraw( #[cfg(test)] mod tests { use super::*; - use soroban_sdk::testutils::Env as _; + use soroban_sdk::Env; #[test] - fn test_pairing_check() { + fn test_poseidon_hash() { let env = Env::default(); - let bn254 = env.crypto().bn254(); + let field = Symbol::new(&env, "BN254"); - // Test with known valid pairing inputs - // (You'll need actual test vectors from your proving system) - let g1_points = create_test_g1_points(&env); - let g2_points = create_test_g2_points(&env); + let inputs = Vec::from_array(&env, [U256::from_u32(&env, 1), U256::from_u32(&env, 2)]); + let hash = env.crypto().poseidon_hash(&inputs, field); - let result = bn254.pairing_check(g1_points, g2_points); - assert!(result); + // Expected: 7853200120776062878684798364095072458815029376092732009249414926327459813530 + assert!(hash != U256::from_u32(&env, 0)); } #[test] - fn test_invalid_proof_rejected() { + fn test_pairing_check() { let env = Env::default(); - // Modify a valid proof to make it invalid - let invalid_proof = create_invalid_proof(&env); - - let result = Groth16Verifier::verify( - env.clone(), - get_vk(&env), - invalid_proof, - get_public_inputs(&env), - ); + // Test with known valid pairing inputs + // (You'll need actual test vectors from your proving system) + let g1_points = create_test_g1_points(&env); + let g2_points = create_test_g2_points(&env); - assert!(!result); + let result = env.crypto().bn254().pairing_check(g1_points, g2_points); + assert!(result); } } ``` @@ -453,17 +439,19 @@ mod tests { ### Integration Tests 1. Generate real proofs using your off-chain prover -2. Deploy verifier to local Quickstart or Testnet +2. Deploy verifier to Futurenet or Testnet 3. Submit proofs via Stellar CLI or SDK 4. Verify correct acceptance/rejection ```bash # Deploy verifier -stellar contract deploy --wasm target/wasm32-unknown-unknown/release/verifier.wasm \ - --source alice --network testnet +stellar contract deploy \ + --wasm target/wasm32v1-none/release/verifier.optimized.wasm \ + --alias verifier \ + --network futurenet # Invoke with proof data -stellar contract invoke --id --source alice --network testnet \ +stellar contract invoke --id verifier --network futurenet \ -- verify --proof --public_inputs ``` @@ -475,22 +463,21 @@ stellar contract invoke --id --source alice --network testnet \ - [X-Ray Announcement](https://stellar.org/blog/developers/announcing-stellar-x-ray-protocol-25) — Protocol 25 overview - [CAP-0074](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0074.md) — BN254 specification - [CAP-0075](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0075.md) — Poseidon specification -- [Soroban SDK BN254 Docs](https://docs.rs/soroban-sdk/latest/soroban_sdk/crypto/bn254/) — Type and function reference +- [Soroban SDK BN254 Source](https://github.com/stellar/rs-soroban-sdk/blob/release/v25-preview/soroban-sdk/src/crypto/bn254.rs) — Implementation reference ### Example Contracts -- [Soroban Examples](https://github.com/stellar/soroban-examples) — Official examples (check for `groth16_verifier`, `privacy-pools`, `import_ark_bn254`) +- [P25 Preview Examples](https://github.com/jayz22/soroban-examples/tree/p25-preview/p25-preview) — BN254 and Poseidon examples +- [Groth16 Verifier (BLS12-381)](https://github.com/stellar/soroban-examples/tree/main/groth16_verifier) — Official verifier example +- [Import Ark BN254](https://github.com/jayz22/soroban-examples/tree/p25-preview/import_ark_bn254) — Using ark-bn254 crate ### Proving Systems - [Noir Documentation](https://noir-lang.org/docs/) — Aztec's ZK DSL - [RISC Zero](https://dev.risczero.com/) — General-purpose zkVM -### Community Resources -- [UltraHonk Soroban Verifier](https://github.com/indextree/ultrahonk_soroban_contract) — Noir proof verification (verify SDK compatibility) - -> **Note**: Protocol 25 launched January 22, 2026. Some community projects may still be updating to soroban-sdk v25. Always verify SDK version compatibility before using third-party code. +> **Note**: Protocol 25 launched January 22, 2026. Always verify SDK version compatibility (soroban-sdk v25+) when using examples. --- ## Keywords -zero-knowledge, zk, zk-snark, groth16, plonk, bn254, alt_bn128, poseidon, pairing, elliptic curve, +zero-knowledge, zk, zk-snark, groth16, plonk, bn254, alt_bn128, poseidon, poseidon2, pairing, elliptic curve, privacy, confidential, merkle tree, nullifier, proof verification, noir, risc zero, x-ray, protocol 25 From d8dbca1f44088d611b0457acdccff11cd0bb8dce Mon Sep 17 00:00:00 2001 From: Kaan Kacar Date: Mon, 2 Feb 2026 17:46:13 +0300 Subject: [PATCH 3/5] Update skill/zk-proofs.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- skill/zk-proofs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skill/zk-proofs.md b/skill/zk-proofs.md index 0079960..8d43291 100644 --- a/skill/zk-proofs.md +++ b/skill/zk-proofs.md @@ -463,7 +463,7 @@ stellar contract invoke --id verifier --network futurenet \ - [X-Ray Announcement](https://stellar.org/blog/developers/announcing-stellar-x-ray-protocol-25) — Protocol 25 overview - [CAP-0074](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0074.md) — BN254 specification - [CAP-0075](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0075.md) — Poseidon specification -- [Soroban SDK BN254 Source](https://github.com/stellar/rs-soroban-sdk/blob/release/v25-preview/soroban-sdk/src/crypto/bn254.rs) — Implementation reference +- [Soroban SDK BN254 Source](https://github.com/stellar/rs-soroban-sdk/blob/v25.0.1/soroban-sdk/src/crypto/bn254.rs) — Implementation reference ### Example Contracts - [P25 Preview Examples](https://github.com/jayz22/soroban-examples/tree/p25-preview/p25-preview) — BN254 and Poseidon examples From 6c4bec17a9d6d4ac8b14e5ae462505a521463db6 Mon Sep 17 00:00:00 2001 From: kaankacar Date: Mon, 2 Feb 2026 18:33:59 +0300 Subject: [PATCH 4/5] Address Copilot review feedback - Add ZkError enum and Result type for Groth16 verify() - Add MsmError enum and Result type for g1_msm(), replace assert_eq! - Add MerkleError enum and Result type for verify_merkle_proof() - Add DataKey enum definition to privacy pool example - Comment out pairing test as pseudo-code template - Remove unused Env import from MSM --- skill/zk-proofs.md | 70 +++++++++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/skill/zk-proofs.md b/skill/zk-proofs.md index 8d43291..d3b2bdc 100644 --- a/skill/zk-proofs.md +++ b/skill/zk-proofs.md @@ -160,9 +160,17 @@ Where: ### Verification Contract Structure ```rust -use soroban_sdk::{contract, contractimpl, contracttype, Env, Vec, BytesN}; +use soroban_sdk::{contract, contractimpl, contracttype, contracterror, Env, Vec, BytesN, U256}; use soroban_sdk::crypto::bn254::{Fr, G1Affine, G2Affine}; +#[contracterror] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum ZkError { + InvalidVerifyingKey = 1, + InvalidPublicInputs = 2, +} + #[derive(Clone)] #[contracttype] pub struct VerifyingKey { @@ -192,13 +200,19 @@ impl Groth16Verifier { vk: VerifyingKey, proof: Proof, public_inputs: Vec, - ) -> bool { + ) -> Result { // Compute vk_x = ic[0] + sum(public_inputs[i] * ic[i+1]) - let mut vk_x = G1Affine::from_bytes(vk.ic.get(0).unwrap()); + let mut vk_x = G1Affine::from_bytes( + vk.ic.get(0).ok_or(ZkError::InvalidVerifyingKey)? + ); for i in 0..public_inputs.len() { - let ic_i = G1Affine::from_bytes(vk.ic.get(i + 1).unwrap()); - let input_i = Fr::from(public_inputs.get(i).unwrap()); + let ic_i = G1Affine::from_bytes( + vk.ic.get(i + 1).ok_or(ZkError::InvalidVerifyingKey)? + ); + let input_i = Fr::from( + public_inputs.get(i).ok_or(ZkError::InvalidPublicInputs)? + ); let term = ic_i * input_i; vk_x = vk_x + term; } @@ -223,7 +237,7 @@ impl Groth16Verifier { ]); // Pairing check: e(-A, B) * e(alpha, beta) * e(vk_x, gamma) * e(C, delta) = 1 - env.crypto().bn254().pairing_check(g1_points, g2_points) + Ok(env.crypto().bn254().pairing_check(g1_points, g2_points)) } } ``` @@ -288,15 +302,19 @@ Submit proofs to your verifier contract for on-chain verification. Soroban doesn't provide native MSM. Implement using operator overloading: ```rust -use soroban_sdk::{Env, Vec, BytesN, U256}; +use soroban_sdk::{Vec, BytesN, U256}; use soroban_sdk::crypto::bn254::{Fr, G1Affine}; +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum MsmError { EmptyInput, LengthMismatch } + /// Compute sum of scalar[i] * point[i] pub fn g1_msm( scalars: &Vec, points: &Vec>, -) -> G1Affine { - assert_eq!(scalars.len(), points.len()); +) -> Result { + if scalars.len() != points.len() { return Err(MsmError::LengthMismatch); } + if scalars.is_empty() { return Err(MsmError::EmptyInput); } // Start with first term let mut result = G1Affine::from_bytes(points.get(0).unwrap()) @@ -309,7 +327,7 @@ pub fn g1_msm( result = result + term; } - result + Ok(result) } ``` @@ -320,6 +338,9 @@ pub fn g1_msm( ```rust use soroban_sdk::{Env, Vec, U256, Symbol}; +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum MerkleError { LengthMismatch, InvalidProof } + /// Verify a Merkle proof using Poseidon hash pub fn verify_merkle_proof( env: &Env, @@ -327,13 +348,15 @@ pub fn verify_merkle_proof( proof: Vec, path_indices: Vec, // true = right, false = left root: U256, -) -> bool { +) -> Result { + if proof.len() != path_indices.len() { return Err(MerkleError::LengthMismatch); } + let field = Symbol::new(env, "BN254"); let mut current = leaf; for i in 0..proof.len() { - let sibling = proof.get(i).unwrap(); - let is_right = path_indices.get(i).unwrap(); + let sibling = proof.get(i).ok_or(MerkleError::InvalidProof)?; + let is_right = path_indices.get(i).ok_or(MerkleError::InvalidProof)?; // Hash pair in correct order let inputs = if is_right { @@ -345,7 +368,7 @@ pub fn verify_merkle_proof( current = env.crypto().poseidon_hash(&inputs, field.clone()); } - current == root + Ok(current == root) } ``` @@ -371,7 +394,12 @@ Host functions trap on: ### Privacy Pool Patterns ```rust -use soroban_sdk::{Env, U256}; +use soroban_sdk::{contracttype, Env, U256}; + +#[contracttype] +pub enum DataKey { + Nullifier(U256), +} // Use nullifiers to prevent double-spending pub fn withdraw( @@ -425,13 +453,11 @@ mod tests { fn test_pairing_check() { let env = Env::default(); - // Test with known valid pairing inputs - // (You'll need actual test vectors from your proving system) - let g1_points = create_test_g1_points(&env); - let g2_points = create_test_g2_points(&env); - - let result = env.crypto().bn254().pairing_check(g1_points, g2_points); - assert!(result); + // TODO: Add test vectors from your proving system + // let g1_points = Vec::from_array(&env, [/* G1 points */]); + // let g2_points = Vec::from_array(&env, [/* G2 points */]); + // let result = env.crypto().bn254().pairing_check(g1_points, g2_points); + // assert!(result); } } ``` From 0bb4612535aeb29b8a006557b64e3d8262368b80 Mon Sep 17 00:00:00 2001 From: Kaan Kacar Date: Mon, 2 Feb 2026 18:56:16 +0300 Subject: [PATCH 5/5] Update skill/zk-proofs.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- skill/zk-proofs.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/skill/zk-proofs.md b/skill/zk-proofs.md index d3b2bdc..74807a3 100644 --- a/skill/zk-proofs.md +++ b/skill/zk-proofs.md @@ -317,12 +317,15 @@ pub fn g1_msm( if scalars.is_empty() { return Err(MsmError::EmptyInput); } // Start with first term - let mut result = G1Affine::from_bytes(points.get(0).unwrap()) - * Fr::from(scalars.get(0).unwrap()); + let first_point = points.get(0).ok_or(MsmError::LengthMismatch)?; + let first_scalar = scalars.get(0).ok_or(MsmError::LengthMismatch)?; + let mut result = G1Affine::from_bytes(first_point) * Fr::from(first_scalar); for i in 1..scalars.len() { - let scalar = Fr::from(scalars.get(i).unwrap()); - let point = G1Affine::from_bytes(points.get(i).unwrap()); + let scalar_u256 = scalars.get(i).ok_or(MsmError::LengthMismatch)?; + let point_bytes = points.get(i).ok_or(MsmError::LengthMismatch)?; + let scalar = Fr::from(scalar_u256); + let point = G1Affine::from_bytes(point_bytes); let term = point * scalar; result = result + term; }