This is a Proof-of-Concept, and is not intended for in-production use. Nethermind and the authors take no responsibility for the use of the protocol/code in-production.
A privacy-preserving payment channel protocol that enables users to allocate budgets to multiple agents without revealing individual amounts on-chain.
Private X402 lets a user open a payment channel and allocate budgets to many agents inside a single Merkle tree. Payments happen off-chain via signed cumulative receipts. Settlement is on-chain: only the total funding and encrypted data are visible. Agents claim funds by proving membership and decryption correctness.
The protocol supports two interchangeable trust backends:
- ZK proofs (Noir circuits + Barretenberg): cryptographic settlement verification via SNARKs (planned, not yet integrated)
- TEE (Intel SGX via Gramine): trusted execution replaces circuit constraints (current MVP)
For the formal protocol specification with mathematical definitions and circuit routines, see docs/protocol-specification.md.
User Library ──────── Agent Server ──────── Smart Contracts ──────── TEE / ZK
(crates/user) (crates/agent) (contracts/) (crates/proofs, crates/tee, circuits/)
1. Setup Channel User → Contract Fund channel, commit Merkle root, submit P_alloc proof
2. Register User → Agent Send Merkle proof + leaf to prove budget allocation
3. Make Payments User → Agent Signed cumulative receipts (off-chain)
4. Settle User → Contract Submit encrypted amounts + P_settle proof, reclaim remainder
5. Finalize Contract Dispute window expires, channel data accumulated into epoch hash
6. Dispute (opt) Agent → Contract Challenge incorrect settlement with receipt + Merkle proof
7. Claim Agent → Contract Submit P_claim proof to withdraw from finalized epoch
See docs/protocol-specification.md for formal protocol details.
private-x402/
├── circuits/ # Noir ZK circuits
│ ├── lib/ # Shared circuit library
│ ├── user_setup/ # Channel setup circuit (P_alloc)
│ ├── settlement/ # Settlement verification circuit (P_settle)
│ └── agent_claim/ # Claim aggregation circuit (P_claim)
├── contracts/ # Solidity smart contracts (Foundry)
│ ├── src/ # Contract source code
│ ├── test/ # Foundry tests + cross-language fixtures
│ └── script/ # Deployment scripts
├── crates/ # Rust workspace
│ ├── agent/ # Agent HTTP server
│ ├── common/ # Merkle trees, hash chain, blinding, signatures, CLF
│ ├── ethereum/ # Typed blockchain clients + contract bindings
│ ├── proof/ # Proof trait, ProofClient, TEE implementations
│ ├── user/ # User-side channel management
│ └── tee/ # TEE attestation backend
│ ├── guest/ # Enclave binary (runs inside Gramine)
│ ├── host/ # HTTP server + library (registration, renewal)
│ └── config/ # Gramine manifests and mock attestation data
├── docs/ # Protocol specification and roadmap
└── scripts/ # Build automation
| Component | Status | Notes |
|---|---|---|
| Smart Contracts | Working | Full lifecycle, pluggable verifiers |
| TEE Backend | Working (mock) | HTTP server, registration, renewal, proof dispatch |
| Ethereum Crate | Working | Typed clients for User, Agent, TEE interactions |
| Agent Server | Working | Registration with agent_id validation, EIP-191 payment verification |
| User Library | Working | Generic over ProofClient, Anvil integration tests. Currently no CLI |
| Noir Circuits | Exist, not integrated | Poseidon2 inconsistency, SC stack-too-deep |
| TEE on real SGX | Not tested | Only mock mode validated |
| CLI / UI | None | Library API only |
| HTTP 402 flow | Not implemented | Coinbase integration out of scope |
| Dispute E2E | Not tested | Contract unit tests only |
See docs/next-steps.md for detailed blockers and roadmap.
We currently provide the following E2E test (crates/user/tests/e2e_integration.rs), which covers the full
User-Agent-Blockchain lifecycle:
- Deploy contracts on Anvil
- Register TEE key
- Create channel (setup proof)
- Register with agent + make payments
- Settle channel
- Finalize after dispute window
- Verify channel is closed
Requires anvil in PATH; runs with mock TEE mode.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shThe project includes rust-toolchain.toml which automatically selects the correct toolchain (nightly, edition 2024).
curl -L https://foundry.paradigm.xyz | bash
foundryupgit clone https://github.com/NethermindEth/private-x402.git
cd private-x402
git submodule update --init
# Build contracts
cd contracts && forge install && forge build && cd ..
# Build Rust workspace
cargo build --workspace
# Run all tests (requires anvil in PATH)
cargo test --workspaceProof trait (crates/proof/src/proof.rs): Generic over PublicInput, PrivateInput, ProverContext, and
Output. Each proof type has a selector (keccak256(flow_name)) enabling dispatch without knowing the concrete
type. Implementations: TeeUserSetup<H>, TeeSettlement<H>, TeeAgentClaim.
ProofClient trait (crates/proof/src/io/client.rs): Async prove() method, Send + Sync. This is the
integration point; the same application code works with different backends:
TeeHttpClient: remote TEE server (production)- In-process TEE: synchronous proving wrapped in async (development)
NoopProofClient: always returns error (unit tests)
ProofClientuses native async traits (Rust 2024) and is not dyn-compatible. All usage sites use concrete types via generics.
TEE Architecture
The TEE subsystem separates concerns into Guest and Host:
- Guest (
tee/guest): Runs inside the enclave, holds a signing key, dispatches proofs via a selector registry (PROVERSmap). The guest does not depend on proof internals; anyProofimplementation can be registered. - Host (
tee/host): WrapsGuestin aMutexfor thread safety, manages on-chain TEE key registration and attestation renewal. Exposes/proveHTTP endpoint. - TeeHttpClient (
tee/host): ImplementsProofClienttrait for remote proof delegation over HTTP.
Hasher (crates/common/src/hash/): Keccak256Hasher (default) and Poseidon2Hasher (feature-gated). The same
BalanceTree<H> works with either backend via the MerkleHash trait.
BlindingScheme (crates/common/src/blinding/): Hides settlement amounts. Keccak256BlindingScheme wraps uint256
XOR blinding; Poseidon2BlindingScheme operates over BN254 field elements.
SignatureScheme (crates/common/src/signature/): EIP-191 currently, pluggable via SignatureScheme trait.
UserApp<P: ProofClient> and Channel<P: ProofClient> are generic over the proof backend. The proof client is injected
at construction via Arc<P>; the same code works for TEE, ZK, or mock testing. Channel IDs are contract-assigned (
returned from the setup transaction), not client-generated.
PrivateX402.sol manages the full channel lifecycle: setup, settlement, finalize/dispute, and agent claim.
Pluggable components (all injected at deployment):
- Verifiers via
IVerifier:TeeVerifier(MVP),MockVerifier(tests), future ZK verifiers - Blinding via
IBlindingScheme:Keccak256BlindingScheme,Poseidon2BlindingScheme - Hashing via
IHasher:KeccakHasher,Poseidon2Hasher - Signatures via
ISignatureVerifier:Eip191Verifier
Epoch hash chain: Finalized channels are accumulated into a rolling hash per epoch. Agents batch-claim across multiple channels in a single proof, reducing gas costs.
| Crate | Purpose | Key Types |
|---|---|---|
common |
Merkle trees, hash chain, blinding, signatures, CLF | BalanceTree<H>, MerklePath, HashChain<H>, Eip191Scheme |
proof |
Proving abstractions + TEE implementations | Proof, ProofClient, TeeUserSetup<H>, ProveRequest |
ethereum |
Typed blockchain clients + contract bindings | UserClient, AgentClient, TeeClient |
user |
User-side channel management | UserApp<P>, Channel<P> |
agent |
Agent HTTP server (axum) | AppState, registration/payment handlers |
tee/guest |
Enclave binary | TeeGuest, PROVERS registry |
tee/host |
TEE orchestrator + HTTP server | TeeHost, TeeHttpClient |
Dependency graph:
common
├── proof
│ └── tee/guest
│ └── tee/host ──── ethereum
├── ethereum
├── user ────── proof, ethereum, common
└── agent ───── common
Rust, Solidity, and Noir must agree on cryptographic encodings. A golden fixture pattern ensures consistency:
- Fixture file:
contracts/test/fixtures/cross_language_vectors.json(committed, auto-generated) - Rust side: Each module owns its vectors via
rstesttests that callclf::write(tag, value); the fixture is rewritten on everycargo test -p common - Solidity side:
CrossLanguageFixtures.solprovides typedstdJsonaccessors - CI drift detection: The
strict-fixturesfeature asserts values match before overwriting
Coverage: Keccak hashing, hash chains, blinding, payment signatures, TEE proofs, u64 encoding.
To add a new vector: Write an rstest in the Rust module's #[cfg(test)] block that computes the value and calls
clf::write(tag, json!({...})), then add a Solidity accessor and commit the updated JSON.
Apache 2.0. See LICENSE for details.