Protocol

Ombre the shadow

Private payment rails

Ombre wraps x402 with selective disclosure. Agents deposit into a shared pool under stealth commitments. Settlements release funds only after Serrure clears the payment. The public chain sees that a payment happened and that its policy was satisfied — not who paid whom, for how much, or for what.

Note
The reference implementation in programs/ombre uses simple hash commitments so the code is readable. Production uses Light Protocol compressed accounts and ZK proofs that the commitment decommits to a valid (recipient, amount) pair. The CPI shape and the Serrure check are identical in both versions.

Deposit → Settle flow

txt
AGENT / PRINCIPAL                                   PUBLIC CHAIN SEES
─────────────────                                   ─────────────────
1. generate secret:     vkey = rand32()
                        nonce = rand32()
2. commitment =         H(vkey || amount || nonce)  ◄── commitment (opaque)
3. deposit(commitment, amount)                      ◄── amount moves to vault
                                                        commitment recorded

  ... time passes ...                               (agent accumulates notes)

4. decide to pay merchant M for x402 resource
5. serrure_proof = merkle_proof(M, policy.root)
6. settle(commitment, amount, M, x402_hash, proof):
     ├─ CPI serrure.check(amount, M, proof)  ✓  ◄── policy check emits event
     └─ vault ──► merchant_account                 ◄── lamports move (to a
                                                        one-time address, not
                                                        the merchant's "real"
                                                        wallet graph)

Disclosure model

There are three views of the same payment:

Public view
Sees commitment hash, policy pass, lamport amount leaving the pool. Cannot link sender to receiver. Cannot determine per-agent spend.
Merchant view
Receives funds at a one-time address, plus the x402 descriptor hash. Can confirm payment matches their invoice. Cannot trivially link to other payments from the same agent.
Principal view
Holds view keys. Decrypts every note, reconstructs the full audit trail: which agent paid which merchant, for what, with what policy check outcome.

View keys

Principal keys come in two flavors: the spending key (signs issuance, revocation, policy updates) and the view key (decrypts Ombre notes to reconstruct the ledger). Give the view key to your finance team, your auditor, your bookkeeping agent. Keep the spending key in the hardware wallet.

Settle instruction signature

programs/ombre/src/lib.rs
pub fn settle<'info>(
    ctx: Context<'_, '_, '_, 'info, Settle<'info>>,
    amount_lamports: u64,
    merchant: Pubkey,
    x402_descriptor_hash: [u8; 32],
    serrure_merkle_proof: Vec<[u8; 32]>,
) -> Result<()>

Relayers

To keep agents unlinkable from their own Solana addresses, settlement transactions are submitted by permissionless relayers. Relayers earn a small fee, see only hashes, and are staked on a reputation graph of their own. Agent → Relayer communication is a signed JSON blob over HTTP; the relayer assembles the Solana transaction and pays the base fee.

Tip
You can run your own relayer for infrastructure independence. The public relayer network is the default for teams that don't want that operational overhead.