Confidential Guardrails
How EUint64 FHE ciphertexts keep spending limits private while still being enforced on-chain via Ika Encrypt.
The Problem
A naive treasury stores daily_limit_usd = 10_000 in a public Solana account. Any observer can read it, infer the agent's strategy, and front-run or manipulate it. AURA solves this with Fully Homomorphic Encryption (FHE) via the Ika Encrypt network — the limit value is stored as an EUint64 ciphertext and never decrypted during evaluation.
What Gets Encrypted
The core confidential path attaches three scalar EUint64 ciphertext accounts to a treasury via configure_confidential_guardrails:
| Ciphertext account | What it holds |
|---|---|
daily_limit_ciphertext | Encrypted daily spending limit in USD cents |
per_tx_limit_ciphertext | Encrypted per-transaction limit in USD cents |
spent_today_ciphertext | Encrypted running total spent today in USD cents |
These are Solana accounts owned by the Ika Encrypt program. Their addresses are stored in the TreasuryAccount and passed as accounts to every confidential instruction.
The newer ConfidentialGuardrailsAccount sidecar adds an epoch marker, an enabled flag, and optional expanded ciphertext pointers. Current proposal execution uses that sidecar for the extended weekly-limit path when both weekly_limit_ciphertext and weekly_spent_ciphertext are supplied.
Instruction Flow
CPI Accounts for propose_confidential_transaction
The instruction requires these accounts beyond the standard treasury/AI authority pair:
| Account | Seed / Source | Purpose |
|---|---|---|
daily_limit_ciphertext | stored in TreasuryAccount | Encrypted daily limit |
per_tx_limit_ciphertext | stored in TreasuryAccount | Encrypted per-tx limit |
spent_today_ciphertext | stored in TreasuryAccount | Encrypted running counter |
amount_ciphertext | freshly created | Encrypted proposal amount |
policy_output_ciphertext | freshly created | Output — encrypted verdict |
weekly_limit_ciphertext | optional sidecar pointer | Extended graph weekly limit |
weekly_spent_ciphertext | optional sidecar pointer | Extended graph weekly counter |
confidential_guardrails | ["confidential_guardrails", treasury] | Optional sidecar required for extended weekly path |
encrypt_program | 4ebfzWdKnrnGseuQpezXdG8yCdHqwQ1SSBHD3bWArND8 | Ika Encrypt program |
config | Encrypt program global config | Encrypt network config |
deposit | payer-funded | Pays for FHE compute |
caller_program | aura-core program ID | CPI caller identity |
cpi_authority | ["__encrypt_cpi_authority"] on aura-core | Signs the CPI |
network_encryption_key | Encrypt program account | Network's public key |
event_authority | ["__event_authority"] on Encrypt program | Emit Encrypt events |
system_program | 11111111111111111111111111111111 | Account creation |
What the FHE Circuit Does
The default Ika Encrypt graph evaluates a circuit over the four core ciphertexts:
verdict = if (amount > per_tx_limit) → PerTransactionLimit
else if (spent_today + amount > daily_limit) → DailyLimit
else → None (approved)When the weekly ciphertext accounts and sidecar are supplied, aura-core submits the extended graph:
verdict = if (amount > per_tx_limit) → PerTransactionLimit
else if (spent_today + amount > daily_limit) → DailyLimit
else if (weekly_spent + amount > weekly_limit) → WeeklyLimit
else → None (approved)The output carries an encrypted violation code and update lanes for the encrypted counters. Only the small violation code and validated counter update are consumed on-chain; the limit values remain encrypted.
What Stays Private
| Data | On-chain visibility |
|---|---|
| Daily limit value | Private — EUint64 ciphertext only |
| Per-tx limit value | Private — EUint64 ciphertext only |
| Running spent-today total | Private — EUint64 ciphertext only |
| Weekly limit / weekly spent | Private when the extended sidecar path is used |
| Proposal amount | Public — in PendingTransaction.amount_usd |
| Violation code (0 or N) | Public — decrypted on-chain |
| Recipient / chain | Public — in PendingTransaction |
Public Precheck Before FHE
evaluate_public_precheck runs before the FHE CPI. Current source evaluates the public subset: scoped pauses, budget envelopes, Bitcoin manual-review threshold, time window, protocol allowlist, slippage, quote freshness, counterparty risk, shared pool, velocity, and approval ladder. It skips direct per-transaction, daily, weekly, monthly, and recipient cap checks; encrypted per-tx/daily checks are always deferred to Encrypt, and the weekly check is included only in the extended sidecar path. If any public rule fails, the proposal is rejected immediately — no FHE call is made, saving compute and cost.
Decryption Accounts for request_policy_decryption
| Account | Purpose |
|---|---|
request_account | Freshly created — tracks the decryption request |
ciphertext | The policy_output_ciphertext from the pending proposal |
encrypt_program | Ika Encrypt program |
config | Encrypt global config |
deposit | Pays for decryption |
caller_program | aura-core program ID |
cpi_authority | ["__encrypt_cpi_authority"] on aura-core |
network_encryption_key | Encrypt network's public key |
event_authority | ["__event_authority"] on Encrypt program |
system_program | Account creation |
SDK Usage
import { instructions } from "@aura-protocol/sdk-ts";
// Configure confidential guardrails (owner signs)
await instructions.confidential.sendConfigureConfidentialGuardrails(client, owner, {
accounts: {
owner: owner.publicKey,
treasury,
dailyLimitCiphertext,
perTxLimitCiphertext,
spentTodayCiphertext,
},
args: { now },
});
// Propose a confidential transaction (AI authority signs)
await instructions.confidential.sendProposeConfidentialTransaction(
client,
aiAuthority,
{
accounts: {
aiAuthority: aiAuthority.publicKey,
treasury,
dailyLimitCiphertext,
perTxLimitCiphertext,
spentTodayCiphertext,
amountCiphertext,
policyOutputCiphertext,
weeklyLimitCiphertext: null,
weeklySpentCiphertext: null,
confidentialGuardrails: null,
encryptProgram: ENCRYPT_DEVNET_PROGRAM_ID,
config: encryptConfig,
deposit: depositAccount,
callerProgram: AURA_PROGRAM_ID,
cpiAuthority: deriveEncryptCpiAuthorityAddress()[0],
networkEncryptionKey,
eventAuthority: deriveEncryptEventAuthorityAddress(ENCRYPT_DEVNET_PROGRAM_ID)[0],
systemProgram: SystemProgram.programId,
},
args,
},
);