Skip to content

Venkat5599/kagezks

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

61 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Kage

Kage

Private Payments for Autonomous AI Agents on Stellar
A scoped session key the agent can't drain β€” settling through a ZK shielded pool that hides who it paid and how much.

Live on Stellar Live Demo ZK Soroban


πŸ“‹ Project Overview

Kage lets an AI agent pay in USDC on Stellar without holding your key and without leaking a thing. The agent spends under a scoped, revocable session key it can never drain or redirect, and every payment settles through a zero-knowledge shielded pool — so the amount, the recipient, and the agent→payee link are all hidden on-chain.

What It Does

  • Autonomy without custody β€” a Soroban account contract delegates one agent session key bounded by policy in __check_auth
  • Hides the recipient β€” Umbra-style stealth notes; each payee is paid at a fresh one-time address
  • Hides the amount + link β€” a Tornado/Privacy-Pools-style ZK pool breaks the deposit↔withdrawal trail
  • Stops double-claims β€” per-note nullifier reverts any replay on-chain
  • Trustless tree β€” every deposit carries a Groth16 insert proof; the contract verifies the new root, no custodian

Key Innovation

On a transparent ledger, handing an agent a raw key publishes every counterparty, every amount, and a map of everything your treasury touches β€” and lets the agent (or an attacker) drain you. Kage fixes both: scope is enforced by the account contract, privacy by math and the chain.

Raw key on transparent chain:  Agent β†’ Wallet β†’ Ledger   (drainable + fully public)
With Kage:                     Agent β†’ Scoped Session Key β†’ ZK Pool β†’ Ledger
                               (can't drain Β· can't redirect Β· who/how-much sealed)

🌐 Why This Matters

The problem, precisely

Hand an agent a raw key on a transparent chain… Kage fixes it with…
The agent (or an attacker) can move all your funds A scoped session key: only deposit, only USDC β†’ pool, up to a cap, before an expiry
Every payment publishes counterparty + amount Amounts and the agent→payee link hidden in a ZK pool
Recurring transfers deanonymise everyone the agent pays Each payee is paid at a fresh one-time stealth address
"Just encrypt it / trust our server" still trusts a custodian Scope + unlinkability enforced by math and the chain, not a custodian

Why ZK is load-bearing

  • Remove the pool proof β†’ each withdrawal must name the agent's deposit β†’ the whole payment graph is public β†’ no privacy.
  • Remove the nullifier β†’ a note is claimable twice β†’ the pool drains.
  • Remove the recipient binding β†’ a relayer/observer front-runs a payee's withdrawal and redirects the funds.

πŸš€ Deployment Information

Live Contracts on Stellar Testnet

Contract Address Explorer
Kage Shielded Pool CCQWGM2CBTFTY4B3OTKNTQO3GMBJUHWTJOSU7NC2QRDZ26KCSMJQGJXC βœ… View
Scoped Session Account CB3A5QRRIULWBBADWGYH6QA3XEJHJZJCJ7DV3CE6NBZFQBH5WWLKF636 βœ… View
USDC (SAC) CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC βœ… View

Network Details

Network:     Stellar Testnet
RPC URL:     https://soroban-testnet.stellar.org
Explorer:    https://stellar.expert/explorer/testnet
Asset:       USDC (Soroban Asset Contract)
Live demo:   https://kageai.me

Deploy Your Own

# 1. Clone
git clone https://github.com/Venkat5599/stellar.git
cd stellar

# 2. Install (bun; Rust GNU toolchain on Windows, circom, snarkjs, stellar CLI)
bun install

# 3. Build circuits (reuses the Hermez pot14 ptau)
bun run circuit:withdraw && bun run circuit:insert
# (one-time) snarkjs groth16 setup + zkey contribute + export verificationkey for each

# 4. Build + deploy the Soroban contracts
cd contracts/solvency && stellar contract build && cd ../..
bun run convert          # snarkjs vk/proof -> Soroban BN254 bytes

# 5. Provision a scoped agent session (autonomy without custody)
bun run agent:provision  # deploy session account, delegate agent key, set policy + cap, fund it

πŸ“– How to Use

The end-to-end flow

// The agent pays a payee β€” scoped, and ZK-private.
// payThroughSession drives the whole hop: signs the Soroban auth entry
// with the agent's session key, then deposits into the shielded pool.
import { payThroughSession } from './sdk/kage-onchain';

await payThroughSession({
  scanKey: payeeScanKeyV,     // payee's published meta-address (scan pubkey V)
  amount: 10_000000n,         // 10 USDC (7 decimals) β€” bound into the ZK commitment
});
// On-chain: only a commitment, a random ephemeral R, a new Merkle root.
// The chain never learns who was paid or how much is tied to them.

Payee side β€” recognise & withdraw

// 1. Scan announcements: for each ephemeral R, recompute shared = vΒ·R and
//    check if the derived commitment is in the tree. Match β‡’ it's yours.
// 2. Prove membership in zero knowledge + a fresh nullifier, bind a one-time
//    stealth payout address, and withdraw β€” no link to the agent's deposit.
bun run flow   // full off-chain derive -> tree -> recognise -> prove

Contract surface

Method Description Proof checked
deposit(commitment, R, amount) Pull USDC, append commitment to the Merkle tree Groth16 insert proof (old_root→new_root + amount binding), BN254 pairing
withdraw(proof, root, nullifierHash, payout) Pay a stealth address from the pool Groth16 membership proof + nullifier unused
set_vks(...) Register the insert/withdraw verifying keys Owner only

Public-input layouts (contract mirrors circuits exactly):

  • insert: [old_root, new_root, commitment, leaf_index, amount]
  • withdraw: [root, nullifier_hash, recipient, amount]

πŸ›‘οΈ The Two Privacy Layers

Layer Hides How
Stealth notes (Umbra-style) which payee the agent paid Payee publishes a scan key V once. Agent does ECDH (shared = rΒ·V), derives note secrets from shared, announces only ephemeral R. Only V's holder recomputes shared = vΒ·R and finds their payment.
ZK shielded pool (Tornado/Privacy-Pools-style) that two payouts share one agent, and the amount link Each deposit inserts a Poseidon commitment into a Merkle tree. A withdrawal proves in ZK it owns some unspent leaf β€” without revealing which β€” plus a fresh nullifier (no double-claim).

The chain only ever sees: commitments, random R values, a Merkle root, and nullifier hashes. Never a payee's identity, an amount tied to a person, or a link from the agent's deposit to a payee's withdrawal.

How the tree stays trustless without on-chain Poseidon

Stellar's host Poseidon2 constants don't match circomlib's Poseidon, so the contract can't recompute the circuit's root on-chain. Instead, every deposit carries a Groth16 "insert" proof that new_root correctly appends commitment to the tree at the contract's current root. The contract checks old_root == current, runs only the BN254 pairing check, and advances the root. The insert proof also binds the deposited amount into the commitment, so what is deposited is exactly what can be withdrawn β€” no accounting desync.


πŸ—οΈ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                            OWNER  (holds real key)                        β”‚
β”‚              delegates ONE scoped session key to the agent                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                     β”‚
                                     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     SCOPED SESSION ACCOUNT (Soroban)                      β”‚
β”‚              CB3A5QRRIULWBBADWGYH6QA3XEJHJZJCJ7DV3CE6NBZFQBH5WWLKF636     β”‚
β”‚                                                                          β”‚
β”‚   __check_auth policy β€” agent may ONLY:                                  β”‚
β”‚   β”œβ”€β”€ call deposit on the configured pool                                β”‚
β”‚   β”œβ”€β”€ move USDC, into that pool only                                     β”‚
β”‚   β”œβ”€β”€ up to a spend cap                                                  β”‚
β”‚   └── before an expiry                                                   β”‚
β”‚   anything else β‡’ BadPayout / CapExceeded / Expired / ContextNotAllowed  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                    β”‚  agent signs the Soroban auth entry
                                    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      KAGE SHIELDED POOL (Soroban)                         β”‚
β”‚              CCQWGM2CBTFTY4B3OTKNTQO3GMBJUHWTJOSU7NC2QRDZ26KCSMJQGJXC     β”‚
β”‚                                                                          β”‚
β”‚  deposit(C, R, amount)              withdraw(proof, root, nullifier, pay) β”‚
β”‚  β”œβ”€β”€ verify INSERT proof (BN254)    β”œβ”€β”€ verify MEMBERSHIP proof (BN254)   β”‚
β”‚  β”œβ”€β”€ amount bound into commitment   β”œβ”€β”€ nullifier unused? else revert #9  β”‚
β”‚  β”œβ”€β”€ pull USDC via SAC              └── pay USDC β†’ one-time STEALTH addr   β”‚
β”‚  └── advance Merkle root                                                  β”‚
β”‚                                                                          β”‚
β”‚  CHAIN SEES: commitments Β· random R Β· Merkle root Β· nullifier hashes     β”‚
β”‚  NEVER:      who paid whom Β· amount tied to identity Β· deposit↔withdraw   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“ Project Structure

stellar/
β”œβ”€β”€ circuits/
β”‚   β”œβ”€β”€ veil_withdraw.circom   # membership + nullifier + amount range + recipient bind
β”‚   └── veil_insert.circom     # old_root -> new_root append proof + amount binding
β”œβ”€β”€ contracts/                 # Soroban: veil (pool) + session (scoped account)
β”œβ”€β”€ sdk/
β”‚   β”œβ”€β”€ veil.ts                # X25519 ECDH stealth notes, Poseidon Merkle tree
β”‚   β”œβ”€β”€ kage-onchain.ts        # payThroughSession: scoped, ZK-private deposit
β”‚   └── kage-convert.ts        # snarkjs -> Soroban BN254 byte layout
β”œβ”€β”€ agent/                     # MCP server + agent fabric (proxy tools, workflows)
β”œβ”€β”€ frontend/                  # Next.js dashboard (kageai.me)
β”œβ”€β”€ scripts/                   # provision session, flow, gen-insert, e2e
└── deploy/                    # Caddy, pm2 ecosystem, MCP config

πŸ§ͺ Proven End-to-End (real testnet transactions)

Step Result Detail
Deposit βœ… verified On-chain insert proof verified (BN254) with amount binding; USDC pulled; commitment + ephemeral key announced. TX
Withdraw βœ… verified Membership proof verified; payout paid to a stealth address bound into the proof (keccak(ScAddress) matched cross-language). TX
Double-spend ❌ rejected Replaying the same nullifier reverts with NullifierUsed (#9).

Local (real Groth16)

  • Withdraw circuit: 3005 constraints β€” proves + snarkjs verify OK.
  • Insert circuit: 5238 constraints β€” proves + snarkjs verify OK (binds amount into the commitment).
  • Under-funded deposit fails to prove (amount β‰  committed value β†’ constraint violation).
  • SDK ⇄ circuit: real X25519 note β†’ SDK Merkle proof β†’ withdraw proof verifies (Poseidon matches in and out of circuit).

πŸ”— Links

Resource URL
Live Demo kageai.me
Pool Contract View on Explorer
Session Account View on Explorer
Deposit TX View TX
Withdraw TX View TX
Testnet Faucet Friendbot

πŸ› οΈ Tech Stack

  • Smart Contracts: Soroban (Rust) β€” shielded pool + scoped session account
  • Zero-Knowledge: Circom + snarkjs, Groth16 over BN254 (alt_bn128), circomlib Poseidon
  • Stealth crypto: X25519 ECDH one-time addresses (Umbra-style)
  • Runtime / SDK: Bun, TypeScript, @stellar/stellar-sdk, @noble/curves
  • Agent layer: Model Context Protocol (MCP) server + agent fabric
  • Frontend: Next.js dashboard (kageai.me)
  • Trusted setup: Hermez Perpetual Powers of Tau (pot14)

🧾 Honesty Ledger

  • Testnet only. No mainnet, no real funds.
  • Stealth v1 = single-derived-key (no view/spend separation β€” documented stretch; ed25519 clamping blocks the classic dual-key scheme without custom signing).
  • Demo tree depth 10 (1024 notes); identical circuit scales to depth 20.
  • Fixed-denomination notes in the demo for a clean anonymity set (the circuit range-checks any amount < 2^64).
  • Trusted setup reuses the real Hermez Perpetual Powers of Tau.
  • The ZK and every transaction are real; only the parties are ours.

See KAGE.md for the full architecture deep-dive.


Built for Stellar Hacks Β· Real-World ZK πŸ•ΆοΈ

Autonomy without custody. Settlement without surveillance.

Private payments for autonomous agents on Stellar.

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors