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)