aethel-vault — the agent-held wallet
Tier: Preview · MSRV: 1.85 · License: Apache-2.0 · Not independently audited, see the crate family
Crate: aethel-vault. Repository: aethel-runtime.
Not to be confused with:
pqc-privacyhas an unrelated AES-GCM + Reed-Solomon shard module historically also called "vault" there — that is a research component in a different crate. This crate owns the word "wallet."
Two assets, not one
This crate holds and moves two entirely separate things. There is no bridge between them in this crate.
| Confidential internal balance | Settlement asset | |
|---|---|---|
| What | FheUint64 ciphertext |
USDC on Base / Ethereum / Arbitrum One |
| Feature | fhe-state (optional) |
signer (default) |
| Where it lives | The agent's own storage (possibly hostile) | Public chain state |
| Privacy is against | That storage | Nothing — it's public by nature |
| Moved by | This crate's homomorphic transfer ([vault]) |
An EIP-3009 authorization the external wallet signs ([settlement]) |
| Is it a token / on a chain? | No. Not a token, not on any chain, not USDC. | Yes — it is USDC. |
An FHE transfer never moves USDC. An EIP-3009 authorization never changes
an FHE balance unless the agent explicitly records it. See
src/vault.rs's module docs for more.
Zero ECDSA / 100% PQC signer
secp256k1 never lives in this crate — not as a dependency, not as code.
This crate's own signatures (spend intents, settlement receipts, wallet
binds, HITL approvals) are ML-DSA-65, via aethel_core::signing::Identity,
under purpose-separated contexts (see aethel-core's docs/PURPOSES.md).
The settlement authorization's ECDSA (v, r, s) is produced by the agent's
own external wallet through the injected [settlement::SettlementSigner]
trait — no implementation of that trait ships in this crate except a
test-only mock. secp256k1 lives in the agent's wallet; a hybrid
(PQC + classical) posture, if wanted, lives at SAGP (the gateway), not here.
tests//src/settlement.rs's manifest_contains_no_ecdsa_crates test
greps this crate's own Cargo.toml for k256/secp256k1/alloy/ethers
and fails the build if any of them ever appear.
Custody rule: the ServerKey — and the settlement signer — never leave the agent
- ServerKey (
fhe-state): held only in a process-local, never-serialized runtime structure. It is never part of the persisted/exportedVaultState— see Vault ID Derivation anddocs/TFHE-VAULT-SPEC.md. - Settlement signer (
signer): the agent's own wallet process, hardware key, MPC signer, or HSM. This crate only builds the digest; it never holds a secp256k1 private key. - There is no "hosted vault" mode, and there never will be. Enabling a
Cargo feature literally named
hostedis a hardcompile_error!— seesrc/lib.rs. Do not hostaethel-vaulton third-party infrastructure.
Two modes
| Mode | Feature | Default? | What it gives you |
|---|---|---|---|
| Signer | signer |
Yes | EIP-3009/x402 settlement export, standing spend policy, optional signed receipts. No tfhe, no HelixDB. This is the Wave-1 "pay a 402 unattended" path. |
| Confidential ledger | fhe-state |
No | The homomorphic internal balance state machine (vault/client). A differentiator for agents storing state on a hostile host — not a Wave-1 gate. |
| HelixDB storage | helixdb |
No | The gRPC storage adapter for the confidential ledger. Pulls tokio/tonic/prost; runs prost-build. |
A plain cargo add aethel-vault builds signer only. Confirm with:
cargo tree -e normal --no-default-features --features signer
— it must contain no tfhe, tokio, tonic, prost, k256, secp256k1,
or ed25519-dalek.
Pay a 402 in three calls
use ;
use Identity;
// 1. An external, ECDSA-capable signer the agent already has (hardware,
// MPC, HSM, browser wallet) — implement `SettlementSigner` around it.
let signer = connect?;
// 2. The vault's own ML-DSA-65 identity (for spend-intent/receipt
// signatures — never for the on-chain authorization itself).
let identity = generate?;
// 3. A standing policy: no HITL below $0.50, hard cap at $2/day.
let policy = SpendPolicy ;
let mut wallet = new;
let auth = wallet.authorize_eip3009?;
// post `auth` (or its fields) in the `X-PAYMENT` header.
Policy lives in the agent
SpendPolicy {
max_per_call: 50_000, // 0.05 USDC
daily_cap: 2_000_000, // 2.00 USDC
allow_rails: [Eip3009Base],
hitl_above: 500_000, // 0.50 USDC
ttl_secs: 3600,
hitl_approver_pk: None,
}
Enforced by Wallet::authorize_eip3009 before anything is built or
signed — a denied or unsatisfied-HITL request produces no signature of any
kind. This is "G-POLICY living in the agent, not on the gateway."
Receipts are the agent's to disclose
Wallet::last_receipt() / Wallet::sign_receipt() are separate, opt-in
calls. Nothing is signed or transmitted unless the agent explicitly asks —
see src/receipt.rs.
Modules
| Module | Feature | Description |
|---|---|---|
settlement |
signer (default) |
EIP-3009/x402 export: Chain, TransferWithAuthorization, SettlementSigner, Wallet |
policy |
signer (default) |
SpendPolicy, PolicyState, Rail, HitlApproval |
receipt |
signer (default) |
Receipt, SignedReceipt |
error |
signer (default) |
VaultError |
vault |
always (FHE ops need fhe-state) |
confidential-ledger state machine; derive_vault_id always available |
client |
fhe-state / wasm |
confidential ledger client SDK |
storage::helixdb |
helixdb |
HelixDB gRPC adapter |
sdk |
always | TypeScript/Rust SDK utilities |
Error Codes (confidential-ledger extern "C" ABI, vault module)
| Code | Constant | Meaning |
|---|---|---|
0 |
ERR_OK |
Success |
1 |
ERR_NOT_FOUND |
Vault ID not registered |
2 |
ERR_INSUFFICIENT_BALANCE |
Homomorphic solvency check failed |
3 |
ERR_DESER |
Deserialization error |
4 |
ERR_INVALID_KEY |
Invalid or absent ServerKey (also returned when fhe-state is disabled — there is no FHE backend compiled in) |
5 |
ERR_UNAUTHORIZED |
homomorphic_transfer_authenticated: sender has no identity binding, the supplied projection doesn't match it, or the PLP ownership proof failed to verify |
The signer-mode surface (settlement/policy/receipt) uses the
Rust-native VaultError enum instead — see src/error.rs.
Building
Prerequisites
- Rust with the
wasm32-unknown-unknowntarget for WASM builds:rustup target add wasm32-unknown-unknown protoconly if building with--features helixdb.
Thin signer build (default)
Confidential-ledger (fhe-state) build
WASM (confidential-ledger contract)
Output: target/wasm32-unknown-unknown/debug/aethel_vault.wasm
Vault ID Derivation
derive_vault_id hashes whatever bytes it is given, and is available
regardless of the fhe-state feature (pure hashing, no TFHE dependency):
vault_id = SHAKE-256("AETHEL_VAULT_ID_V1" ∥ plp_projection_bytes)[0..32]
That hash alone binds nothing: it derives the same ID from a real
aethel-core EphemeralProjection's bytes as it would from any other 32+
bytes a caller hands it. Two registration paths use it differently
(fhe-state feature):
register_vault_ciphertexttakes a caller-supplied vault ID directly. Nothing checks it came from a real projection. Transfers against vaults registered this way (homomorphic_transfer) are authorized by ciphertext possession alone.register_vault_with_identitytakesaethel-coreEphemeralProjectionbytes, validates they decode, and derives the vault ID from them server-side. Transfers against vaults registered this way (homomorphic_transfer_authenticated) additionally require a PLP ownership proof, verified viaaethel_core::plp::Verifier::verify.
See docs/ROADMAP.md for why a caller-submitted
ownership proof can't yet cross the WASM/extern "C" boundary.
HelixDB Storage (helixdb feature)
// proto/aethel_helix.proto
service HelixStateStore {
rpc UpsertStateNode(StateNodeRequest) returns (StateNodeResponse);
rpc VectorProximitySearch(VectorSearchRequest) returns (VectorSearchResponse);
rpc TraverseTemporalTrajectory(TraversalRequest) returns (TraversalResponse);
rpc PruneTemporalNode(PruneRequest) returns (PruneResponse);
}
Security Properties
- Zero ECDSA in this crate: no
k256/secp256k1/alloy/ethersdependency, ever (enforced by a manifest-grep test). - 100% PQC vault-side signer: spend intents, receipts, wallet binds, and HITL approvals are all ML-DSA-65.
- Policy before signature:
Wallet::authorize_eip3009checksSpendPolicybefore calling the injectedSettlementSigneror signing aSpendIntent. Denied ⇒ no signature at all. - Confidential-ledger blind-state (
fhe-state): balances are always encrypted; this crate never decrypts them. - No hosted mode: enforced structurally (
hostedfeature ⇒compile_error!), not just documented.
Known Gap: Balances Are Not the Whole Privacy Story
Encrypting a balance into an FheUint64 hides transaction amounts. It
does nothing for the transaction graph if accounts are still keyed by a
public, correlatable identifier. Blind state is only meaningful once the
account is no longer keyed by a correlatable public identifier. See
docs/ROADMAP.md.
Shared WASM Modules
aethel-runtime is designed to run alongside these independently-upgradeable shared WASM modules:
| Module | Purpose |
|---|---|
pqvm |
Post-Quantum Virtual Machine for PQ operations |
qies |
Quantum-Isolated Enclave System (TEE bridge) |
awre |
Attested WebAssembly Runtime Engine |
wamr |
WebAssembly Micro Runtime sandboxed execution |
License
Apache-2.0 — see LICENSE
References
- IETF Draft: draft-harper-aethel-id-00
- tfhe-rs: Fully Homomorphic Encryption
- HelixDB
- aethel-core
- EIP-3009: Transfer With Authorization
- EIP-712: Typed structured data hashing and signing
The 0x307 crate family
aethel-vault is one of six open-source crates from 0x307, held to one
audit and stability standard.
| Crate | Tier | What it does |
|---|---|---|
| pqc-sig | Production | ML-DSA, SLH-DSA and FN-DSA signatures (FIPS 204/205/206) |
| pqc-kem | Production | ML-KEM (FIPS 203), the X25519 + ML-KEM-768 hybrid, X-Wing, and sealed boxes |
| aethel-core | Production | Post-quantum anonymous identity: a separate identifier per context, context-bound ML-DSA signing |
| aethel-sdk | Preview | The SDK over aethel-core, and the place to start |
| aethel-vault (this crate) | Preview | Agent-held wallet: policy-gated x402 / EIP-3009 signing with ML-DSA-65 spend records |
| pqc-privacy | Lab | Research bundle, kept off every identity and payment path |
Production crates are thin, standards-bound libraries meant to be depended on today. Preview crates work and are published, with APIs still settling. Lab crates are research, never on an identity or payment path.
Runnable examples: 0x307/examples, one program per crate, pinned to the published versions.
Audit status: None of these crates has been independently audited, and none holds a CMVP / FIPS 140-3 validation. "FIPS 203/204/205/206" means the algorithms follow those standards, not that the code is certified. Known issues for this crate are in SECURITY.md. Versioning and yanks: STABILITY.md.