Trust & security
RustAG's security story is about the EvidenceBundle — not the rehearser. The bundle must be independently verifiable even if the rehearsal service is compromised. This page covers the cryptographic integrity model, Grade A verification, service-level objectives, and an honest list of what the Phase 1 MVP does and does not do.
Security & threat model
The primary adversary in the GroundTruth threat model is a malicious or compromised transaction proposer — not the rehearsal service itself. A multisig signer should be able to verify an EvidenceBundle without trusting:
- The proposer's UI or wallet.
- The rehearsal service that produced the bundle.
- The RPC endpoint the rehearsal service used.
The hosted service threat model (Phase 2+) also covers tenant isolation: each rehearsal runs in its own context with no shared account namespace. The local CLI runs entirely on your own machine and is out of scope for multi-tenant isolation.
EvidenceBundle integrity
The rustag-attest crate signs a structured digest — not a JSON blob. The signing input is built from a fixed field order with length-prefixed, domain-tagged fields:
pre_state_root— SHA-256 Merkle root over the pubkey-sorted closure. Leaves are domain-separated with0x00; nodes with0x01to prevent second-preimage attacks. Account leaves commit to consensus-visible fields only (lamports, data, owner, executable, rent_epoch); the internal dirty/clean/pinned bookkeeping is deliberately excluded.payload_hash— SHA-256 of the raw bincode-serialized transaction bytes.post_state_root— same Merkle construction over post-execution accounts.semantic_diff_hash— SHA-256 of the canonical JSON-serialized diff.alarms_hash— SHA-256 of the canonical JSON-serialized alarm list.fidelity_grade— 1 byte: 0x41 = A, 0x42 = B.
use rustag_attest::EvidenceBundle; let bundle: EvidenceBundle = serde_json::from_str(&bundle_json)?;let closure = PortableBundle::from_file("groundtruth-closure.json")?; // Re-derives pre_state_root from closure, re-executes payload,// checks Ed25519 signature — exits Err if INVALID.let report = bundle.verify_against(&closure)?;assert!(report.grade == FidelityGrade::A);assert!(report.alarms.is_empty() || report.alarms.iter().all(|a| a.severity < Severity::High));Grade A verification
A Grade A bundle is deterministically re-executable by any verifier who has the portable closure.json. The verification steps are:
- Re-derive
pre_state_rootfrom closure → confirm it matches the bundle. - Execute the payload in a fresh EVM sandbox instance loaded from the closure.
- Re-derive
post_state_root→ confirm it matches the bundle. - Check the Ed25519 signature over the structured digest described above.
Steps 1–4 require zero network access — just the bundle + closure files and a Rust binary. The attester's pubkey is embedded in the bundle and is the only trust anchor a verifier needs to establish out-of-band (e.g., from the multisig multisig's governance configuration).
# Three-command verification workflowrustag rehearse --proposal <PUBKEY> --rpc $RPC # produces bundle + closurerustag verify bundle.json --closure closure.json # offline, exit 1 if INVALID# ✓ Grade A · Signature valid · pre/post roots matchAudit-log tamper-evidence
AuditLog is an append-only, hash-chained log — the SOC 2 groundwork. Each entry carries a monotonic seq, a prev_hash, and its own hash; the chain is genesis-anchored at the all-zero hash. Any insertion, deletion, or edit anywhere in the log breaks the chain from that point forward, and verify() returns Err(index) at the exact first inconsistent entry.
Service levels
SLO targets apply to the hosted service (Phase 2+). Local CLI rehearsals run on your own machine with your own RPC key and are best-effort. Targets are deliberately modest; under-promising at this stage is intentional.
| Objective | Target | Status |
|---|---|---|
| Rehearsal API (POST /api/rehearse) uptime | 99.5% / mo | Target |
| Grade A bundle latency (Alchemy RPC) | p99 < 8 s | Target |
| Grade A bundle latency (cached closure) | p99 < 2 s | Target |
| getAccountInfo closure hit | p99 < 50 ms | Target |
| Oracle price staleness (realtime, Phase 2) | p99 < 2 s | Target |
| Cross-tenant data-access incidents | hard 0 | Enforced + tested |
| Evidence Registry write durability | 99.99% | Target |
Known limitations
Other known limitations in Phase 1:
- Foreign program execution — the closure resolver fetches program accounts verbatim (readable and present), but does not yet JIT-load BPF bytecode from the program-data account. Rehearsing a multisig proposal that itself invokes a complex foreign program (e.g., a full Uniswap swap CPI) may produce a Grade B bundle if the bytecode is unavailable. Your own deployed program reading real mainnet state works today.
- Address lookup tables — v0 transactions with ALTs are resolved via a mirror fetch; ALT-heavy DeFi bundles are resolved but may produce Grade B if any ALT account is unavailable.
- Datastore — SQLite + moka in the stagenet runtime (not Postgres); correct for the single-node local MVP.
- Observability —
tracingspans + a JSON/api/metricstime-series; Prometheus scrape endpoint deferred. - Supply-chain CI —
cargo audit/cargo denyare not yet wired into CI; crates not yet published to crates.io / npm.
FAQ
Can a malicious proposer forge an EvidenceBundle?
No. A valid Grade A bundle requires a valid Ed25519 signature over the pre_state_root + post_state_root + semantic_diff + alarms. The pre_state_root is derived by independently re-fetching the closure from mainnet — a forged pre-state would not match what any honest verifier fetches. A compromised rehearser UI cannot produce a bundle whose pre-state root is consistent with mainnet AND whose signature is valid under a known attester key.
What does Grade A guarantee?
Grade A means the EvidenceBundle is deterministically re-executable: every account in the closure was fetched from mainnet at a recorded slot, content-hashed, and the pre_state_root matches. Any verifier who re-fetches those pubkeys at the same slot (or uses the portable closure.json) will produce the same pre_state_root, execute the same payload, and produce the same post_state_root — without trusting the rehearser.
What is Grade B?
Grade B means one or more accounts in the touch set could not be resolved (rate-limited RPC, account does not exist yet, etc.). The bundle is still signed but the post_state_root cannot be independently reproduced without a complete closure. Treat Grade B bundles as advisory — they show what the rehearser saw, but they are not independently verifiable.
Is this safe to run — can anything break on mainnet?
No. The rehearsal runs inside a sealed, isolated EVM sandbox instance with no write path to mainnet. The ingest layer only calls getMultipleAccounts (public read-only RPC). Nothing is signed and broadcast to any Robinhood Chain cluster. The actual proposal remains pending in multisig until M-of-N signers manually approve it.
How is this different from just simulating a transaction in a wallet?
Wallet simulators run simulateTransaction via the cluster RPC — the result is a log dump with no pre-state commitment, no semantic diff, no invariant alarms, and no signature. Anyone can alter the simulation result before showing it to a signer. RustAG's EvidenceBundle commits to the exact pre-state used, what changed semantically (not just logs), which alarm rules fired, and signs all of it — so a signer can verify the bundle independently before approving.
What invariant rules fire alarms?
Phase 1 ships 6 rules: upgrade_authority_changed (CRITICAL), large_sol_drain (HIGH, >100 ETH delta), nonce_authority_combo (MEDIUM — nonce + authority rotation in same tx), program_freeze_guard (HIGH), token_authority_changed (HIGH), account_closed_drain (MEDIUM). More rules are additive in future phases.
Can I gate CI on an EvidenceBundle?
Yes. rustag rehearse --proposal <PUBKEY> --fail-on high exits non-zero if any alarm reaches HIGH or above. The upgrade-rehearsal.yml GitHub Action in the repo shows how to wire this into a PR gate: it fetches the proposal, runs the rehearsal, and blocks merge if any HIGH/CRITICAL alarms fire.
Is the closed-source hosted service the only way to use this?
No. Everything described in Phase 1 runs entirely from source — cargo build --release, then rustag rehearse. The hosted service (Phase 2+) adds the Evidence Registry, multisig UI embed, and N-of-M provenance chain. The CLI + local server is and will remain open source.
rustag rehearse --proposal <PUBKEY> --rpc $RPC # → bundle.json + closure.json (signed, Grade A)rustag verify bundle.json --closure closure.json # offline; exits non-zero if INVALIDrustag rehearse --proposal <PUBKEY> --rpc $RPC --fail-on high # CI gate: exit 1 on HIGH/CRITICAL alarms