TL;DR (30s): Pre-1.0,
publish = false— NO backward-compat guarantee. Minimal implementation first: smallest correct change, no speculative APIs. Grow layer by layer: core → crypto/signer → wallet/keyagent → netagent/webui/CLI. Delete old paths when replacing them (retired crates leave no skeleton behind). Secrets viaHardenedBytes, neverString/Vec<u8>; R56/R12 gates are non-negotiable. Details: layout · hard gates · workflow. Storage split:oc-vault= bytes,oc-wallet= ops,oc-secret= user secrets (boundary).
This is the OneCipher workspace — a policy-gated, local-key-custody signing stack fully designed and implemented in accordance with the WalletConnect v2 protocol and the Open Wallet Standard (OWS).
bin/contains binary crates (oc-cli→onecipher).crates/contains reusable library crates (oc-core,oc-crypto, …).
.
├── bin/ # Binary crates
│ └── oc-cli/ # `onecipher` CLI
├── crates/ # Library crates
│ ├── oc-core/ # Core types, CAIP, error types
│ ├── oc-crypto/ # Memory hardening (mlock, zeroize, page guards)
│ ├── oc-keyagent/ # Key-Agent lib (sync std, NO tokio — R56)
│ ├── oc-netagent/ # Network-Agent lib (tokio + WC v2 + intent layer)
│ ├── oc-policy/ # Policy Engine v2/v3 (11-step evaluation)
│ ├── oc-secret/ # Secret vault (age-encrypted secrets + TOTP)
│ ├── oc-session-key/ # Multi-chain SessionKeyProvider (EVM/Solana)
│ ├── oc-signer/ # Multi-chain signing
│ ├── oc-vault/ # Wallet vault (filesystem 700/600, .ocbk backup)
│ ├── oc-wallet/ # Wallet operations (key store, policy, migration)
│ ├── oc-walletconnect/ # WalletConnect v2 protocol wrapper (relay, crypto)
│ └── oc-webui/ # Web UI HTTP server (approval queue, WebAuthn auth, static dashboard)
├── docs/ # Specification documents
└── Cargo.toml # Workspace root (pure [workspace] declaration)
Note: The payment protocol layer (
oc-pay, x402/MPP) was removed from this workspace; it is owned by the sister projectledgerflow. OneCipher now acts as a pure wallet and exposes a loopback JSON-RPC 2.0 WalletSigner server (onecipher wallet-rpc) for ledgerflow. The daemon keeps this endpoint disabled by default unlessOC_WALLET_RPC_LISTENis set, and signing methods on that surface require per-request Passkey authorization.
Three crates deal with persisted secrets/state. Their responsibilities are deliberately disjoint — do NOT merge them and do NOT add cross-calls:
| Crate | Owns | Format | Notes |
|---|---|---|---|
oc-wallet::key_store |
API tokens (oc_key_…) |
JSON, 0600 | Agent/CLI auth tokens; agent-mode tokens additionally carry age-X25519-encrypted wallet-key copies for the token recipient (intentional design — NOT user secrets). |
oc-secret |
User secrets (age-encrypted) + TOTP | age ciphertext | Never holds keys; keys live in oc-vault. |
oc-vault |
Wallet keyfiles (age-encrypted mnemonics/keys) | age/JSON, 0700 dir / 0600 file, .ocbk age bundle |
The persistence format; oc-wallet::ops is the operation layer that reads/writes it. |
oc-wallet::policy_store |
Signed policy docs | JSON, 0600 (+ .sig sidecar) |
Policies are signed on save with a dedicated Ed25519 key (policy_signing.key); load verifies the sidecar .sig (fail-closed on mismatch; legacy unsigned files load with a loud warning). Counters live in oc-policy state. |
Rule of thumb: oc-vault = how bytes are stored on disk; oc-wallet =
what wallet operations do with them; oc-secret = user secrets (not
keys). oc-wallet::key_store API tokens are NOT user secrets and stay in
oc-wallet.
The Intent types (Intent, IntentKind, execute_intent, simulate_intent)
live in crates/oc-netagent/src/intent (the sole consumer is oc-netagent
via HpxRpcClient). The retired oc-intent crate was removed from the
workspace entirely; there is no directory to keep.
- Maximize parallelism by dispatching subagents aggressively for independent tasks. Consume tokens freely to complete tasks faster.
- When fixing errors, edit files FIRST, wait for the edit to succeed, and only THEN run cargo commands to verify. Do not parallelize file edits with cargo builds.
- NEVER execute
cargocommands in parallel. Rust's cargo uses strict file locks on thetarget/directory — concurrent invocations will fail withBlocking waiting for file lock. - ALWAYS run
cargo check,cargo build, orcargo testsequentially. - If the local machine has a
rustc-wrapper(sccache / kache) configured globally but the wrapper binary is missing, prefix cargo commands withRUSTC_WRAPPER=to disable the wrapper for that invocation. - Use
just <recipe>for common tasks — seeJustfile.
- The root
Cargo.tomlis a pure workspace declaration — it has no[package]section. All package metadata lives in[workspace.package]and is inherited by sub-crates viaversion.workspace = true,edition.workspace = true, etc. - Sub-crates MUST use
workspace = trueforversion,edition,license,repository,publish, and[lints]. - Shared dependencies are declared once in
[workspace.dependencies]and referenced in sub-crates viadep = { workspace = true }. - Sub-crates MAY add features on top of the workspace dep:
tokio = { workspace = true, features = ["full"] }. - When adding a new dependency used by 2+ crates, add it to
[workspace.dependencies]in the rootCargo.tomlfirst. - Single-crate dependencies may stay inline in the sub-crate's
Cargo.toml(e.g.uuid,criterion,bs58). - Never manually type dependency versions for workspace deps; use
cargo add <crate> --workspace -p <sub-crate>.
These are non-negotiable invariants verified by cargo tree inspection
(R56) and binary symbol analysis (R12), verified by dev-dependency and source scans:
- R56 (dependency isolation):
oc-crypto,oc-policy,oc-keyagent,oc-session-keyMUST NOT depend ontokio,reqwest,tungstenite,hyper,async-std, orsmol— even as dev-deps. Verified viacargo tree -p <crate>inspection. - R12 (no TCP in isolated crates; loopback-only in daemon): Revised from
the original
nm | grep -i tcpcheck (which produces false negatives on stripped binaries). Now verified via five sub-rules:- R12a (source isolation):
oc-keyagent,oc-crypto,oc-policy,oc-session-keysource MUST NOT containTcpListenerorTcpStream. Verified viarg -n 'TcpListener|TcpStream' crates/oc-keyagent/src/ .... - R12b (daemon may use TCP): The
onecipherdaemon binary MAY contain TCP symbols from axum/hyper for the Web UI HTTP server and WC relay. - R12c (loopback-only bind): Any
TcpListenerin the daemon MUST bind127.0.0.1exclusively. Verified vialsof -iTCP -sTCP:LISTEN -P -n. - R12d (T12 seccomp enforcement): At runtime, the Key-Agent's seccomp
BPF filter denies
connect(2)/bind(2)to non-UDS sockets. - R12e (non-loopback rejection): If a non-loopback bind address is
configured for
[webui] listen, the daemon MUST reject it and refuse to start the Web UI server.
- R12a (source isolation):
- R51/R52 (zero I/O in crypto):
oc-cryptoMUST have zero I/O and zero network dependencies. - R55 (no tokio in Key-Agent): The Key-Agent main loop uses sync
std::os::unix::net+std::thread, NOT tokio.
# Build entire workspace
cargo build --workspace
# Build a specific binary (use --bin, not -p, for bin crates)
cargo build --release --bin onecipher
# Build a specific library crate
cargo build -p oc-crypto
# Check without producing artifacts (faster)
cargo check --workspace --all-targets# All unit + integration tests
cargo test --workspace --all-features
# Mutation testing (requires cargo-mutants)
cargo mutants --workspace --all-features
# Incremental mutation testing (only files changed vs main)
cargo mutants --in-place --since main --all-features# Format check (requires nightly rustfmt)
cargo +nightly fmt --all -- --check
# Clippy (workspace lints are pedantic + nursery)
RUSTC_WRAPPER= cargo +nightly clippy --all -- -D warnings
# R56 hard gate — verify no forbidden async/network deps in isolated crates
cargo tree -p oc-crypto
cargo tree -p oc-policy
cargo tree -p oc-keyagent
# R12 hard gate — source-level isolation (replaces broken `nm` check)
rg -n 'TcpListener|TcpStream' crates/oc-keyagent/src/ crates/oc-crypto/src/ \
crates/oc-policy/src/ crates/oc-session-key/src/
# Expected: no matches (exit code 1)just # list recipes
just format # cargo sort + cargo +nightly fmt
just fix # auto-fix clippy warnings
just lint # fmt check + clippy + cargo sort check
just test # unit + integration tests
just test-all # alias for `test`
just build # cargo build --workspace
just check # cargo check --all-targets --all-features
just ci # full CI check (lint + test + build)
just docs # cargo doc --no-deps --open
just setup # install dev tools (cargo-sort, nightly toolchain)
just mutants # full mutation testing (cargo-mutants)
just mutants-incremental # incremental mutation testing- Error handling:
- Library layer:
thiserror. - Application/CLI layer:
eyre(currentlyoc-cliusesthiserror).
- Library layer:
- Concurrency:
- Key-Agent: sync
std::thread+std::os::unix::net(R55 — NO tokio). - Network-Agent:
tokio+ WalletConnect v2 (WSS relay) + async UDS to Key-Agent (length-prefixed prost frames — seeoc_keyagent::frame). - Prefer lock-free patterns where possible;
Mutexis acceptable for low-contention state.
- Key-Agent: sync
- Safety:
unsafeis confined tooc-crypto/src/page_guard.rs(mlock/madvise) andoc-keyagent/src/sandbox.rs(seccomp/prctl on Linux).- Every
unsafeblock MUST document the safety invariant.
- Memory hardening:
- Sensitive material (mnemonics, private keys) MUST go through
oc_crypto::HardenedBytes(mlock + MADV_DONTDUMP + zeroize on drop). - Never hold sensitive material in
StringorVec<u8>— useHardenedBytesorsecrecy::SecretBox.
- Sensitive material (mnemonics, private keys) MUST go through
- Logging:
- Libraries SHOULD use
tracing(NOTprintln!/eprintln!). - The workspace lints allow
print_stdout/print_stderrbecause some modules fully designed and implemented in accordance with the Open Wallet Standard (process_hardening, chain deprecation warnings) useeprintln!for user-facing diagnostics. New code should still prefertracing.
- Libraries SHOULD use
- Modularity: Each crate is a standalone library with clear boundaries.
oc-cryptohas zero I/O;oc-keyagenthas zero async runtime. - Type Safety: Strong static typing across interfaces. Newtypes for
distinguished types (e.g.
PasskeyPubkey,SessionKeyId). - Defense in Depth: Policy engine (pre-signing) + sandbox (runtime) + memory hardening (in-process) + audit log (post-hoc).
- Unit tests: Colocate with implementation (
#[cfg(test)]). - Property tests: Use
proptestfor invariant checking (colocated). - Integration tests: Place in crate-level
tests/. - Mutation tests: Run
cargo-mutantsto verify test quality. Mutants that survive indicate gaps in test coverage or insufficient assertions. Target: zero surviving mutants in security-critical crates (oc-crypto,oc-policy,oc-keyagent). - Hard-gate tests: R56 (dependency isolation) is verified via
cargo treeinspection; R12a (source-level isolation) viargscan of isolated crate sources; R12c (loopback-only) vialsofruntime check.
- Do NOT add
tokiotooc-keyagent,oc-crypto,oc-policy, oroc-session-key— R56 hard gate. - Do NOT hold sensitive material in
String/Vec<u8>— useHardenedBytes. - Do NOT use
cargo build -p oc-keyagentexpecting a binary —oc-keyagentis a library crate only. The sole binary isonecipher(fromoc-cli): usecargo build --bin onecipher. - Do NOT introduce
unwrap()/expect()/panic!in production code paths — the workspace lints allow them (for test code), but code review enforces this for non-test code.
Use test-driven development for behavior changes:
- Start with a failing unit test expressing the expected behavior.
- Drive implementation until the test passes.
- Add
proptestproperty tests for invariant checking. - Run
just mutantsto verify test quality — any surviving mutant indicates a gap that needs a new test or stronger assertion.
After each feature or bug fix, run:
just format
just lint
just testIf any command fails, report the failure and do not claim completion.
- Documentation, comments, and commit messages must be English only.
- Code identifiers must be English.