Private Payments for Autonomous AI Agents on Stellar
How a scoped session key + a Groth16 shielded pool let an agent spend without your key — and without publishing your counterparties.
An AI agent that can spend money on-chain is only as trustworthy as the key it holds. Hand it a raw private key and two things go wrong at once:
- Custody — the agent (or whoever compromises it) can drain the whole treasury. There is no "just a little" with an ed25519 key.
- Transparency — every payment the agent makes is a public record of who you paid, how much, and a running map of everything your treasury touches.
Kage fixes both, on Stellar, with two primitives that compose cleanly:
a scoped session account (autonomy without custody) settling through a
zero-knowledge shielded pool (privacy without a trusted relayer).
The problem, precisely
- A scoped key that just caps spend still leaks to whom.
- A private pool that hides recipients still needs a way to scope what an agent may pull.
- On a transparent ledger, the two are usually solved by opposite designs (custodial scope vs. trustless privacy).
The two layers
1. Autonomy without custody — the SessionAccount
A Soroban account contract delegates one ephemeral agent key, and its
__check_auth permits exactly one thing:
-
Veil.depositon the configured pool, - moving the configured token,
- within a cumulative cap,
- before an expiry.
The agent signs a Soroban auth entry with its session key — never the owner's key. The owner can revoke or extend at any time. Worst case for a compromised agent: it spends the remaining cap. It cannot drain, cannot redirect, cannot change the policy.
2. Privacy by math — the ZK shielded pool
Each payment is a deposit into a Tornado/Privacy-Pools-style pool, then a withdrawal to a fresh one-time stealth address:
- Hides the recipient — Umbra-style stealth notes; each payee is paid at an address only the recipient can derive.
- Hides the amount + link — a zero-knowledge membership proof breaks the deposit↔withdrawal trail. The proof shows "I own some unspent note" without revealing which.
- Stops double-spends — a per-note nullifier reverts any replay on-chain.
- Trustless tree — every deposit carries a Groth16 insert proof; the contract verifies the new Merkle root itself. No custodian.
Crucially, the contract verifies Groth16 on Soroban's native BN254 host functions — no hand-rolled pairing math in the wasm, and the amount is bound into the proof so a depositor can't commit a large note while under-funding the pool.
Why Stellar
- Soroban native BN254 — the pairing check is a host function, so the ZK verifier is a few dozen lines of trust in the protocol, not a ported library.
- Native-asset settlement — the pool runs on XLM via its Stellar Asset Contract, so there's no wrapped token, no bridge, no extra trust.
- x402 / agent payments — Kage slots into the x402 "agent pays for its own API calls" model, giving autonomous agents a private, scoped payment rail.
What's live
A testnet deployment runs the full flow end to end — deposit (insert proof verified, USDC/XLM pulled), withdraw (membership proof verified, paid to a stealth address), and a rejected double-spend attempt. On mainnet, the same contracts settle in XLM.
The honest scope
This is not the full two-key Umbra scheme, and Groth16 means a trusted setup (phase 1 reuses the Hermez powers-of-tau). The deposit amount is published in the event (needed for the amount-binding check), so fixed-size notes are the recommended practice. See the repo's security review for the full ledger of limits.
Kage is open source (MIT). Repo: github.com/Venkat5599/kagezks
Top comments (0)