Threat model
Threats to a testing simulator are not the same as threats to a production ledger. Here they are attacks on the correctness and trustworthiness of the simulation itself: an attacker (or a bug, or a malformed fixture) tries to make the simulator produce wrong state, hide a violation, or diverge between runs. Every threat below maps to a defense that exists in the code and is exercised by the test suites.
State corruption
Threat. A sequence of operations leaves the store internally inconsistent — value created or destroyed, commitments referencing nothing, logs out of step with state.
Defense. All flows apply changes inside a TransactionScope; failures
roll back completely (atomicity). The state root digests the canonical
store, so any drift changes the root and is caught by replay. The seeded
stress suite (tests/flows) runs random sequences and asserts
conservation, nullifier integrity, and atomicity after every step.
Commitment reuse / double spend
Threat. A consumed commitment is spent again, creating value from nothing.
Defense. Consumption registers a deterministic nullifier derived from
the commitment ID. Replaying the same commitment fails with
consumed_commitment; assert_not_consumed gates every consume path.
Dedicated tests and fuzz seeds probe this surface.
Nullifier forgery / registry drift
Threat. Nullifiers registered for commitments that were not consumed (locking funds) or missing for consumed ones (allowing double spend).
Defense. The registry is append-only and derived from consumption; the fuzz suite asserts the registry exactly matches the consumed set after every scenario.
Nondeterministic execution
Threat. The same inputs produce different outputs across runs, machines, or releases — destroying reproducibility.
Defense. No system entropy: every random draw comes from the seeded
DeterministicRng. All collections are BTree*. Hashing is fixed
SHA-256 with domain separation. The determinism suite replays transcripts
and compares roots; a Merkle root regression anchor pins the digest
scheme across releases.
Fixture poisoning
Threat. A malformed or malicious fixture (bad schema version, wrong types, impossible amounts) crashes the loader or produces corrupted state.
Defense. Fixtures are versioned JSON validated against their schemas; the loader rejects unknown schema versions and type errors with structured errors. Regression fixtures permanently pin every discovered bug.
Rollback failure
Threat. A mid-operation failure leaves partial state (some commitments consumed, others not).
Defense. TransactionScope wraps validate→apply→record; any error
discards the scope and the store is untouched. The failure fixtures and
the fuzz suite assert that failed operations never move the state root.
Incorrect proof-provider handling
Threat. The simulator accepts an operation whose proof is missing or invalid, or trusts a proof for the wrong operation.
Defense. The validation pipeline requires a present, valid proof
reference for confidential transfers; validate_proof_requirement
rejects missing and invalid references before any state change. The
provider is behind the ProofProvider trait — the simulator never proves
itself, and a broken provider cannot silently weaken validation.
State leakage
Threat. Private simulation values (amounts, balances, commitment values) appear in observable output such as events or published fixtures.
Defense. Event and observable records are value-free by
construction; private values are reachable only through the explicitly
labeled inspect_private_* APIs. See docs/security.md.
Adapter inconsistencies
Threat. Local simulation and Soroban/testnet execution disagree about operation semantics, so a scenario passes locally but fails on the real contract.
Defense. Both execution paths go through the same normalized
AdapterRequest shapes and the same flow engine. The testnet adapter is
an explicit, structured “unimplemented” boundary today — divergence is
impossible until the SDK wiring exists, and the local adapter is fully
covered end to end (see docs/soroban.md).
Testnet/local divergence
Threat. A scenario relies on a property that only exists in one mode.
Defense. Local simulation is the default and never requires network
access. Testnet execution is optional, gated behind the adapter trait, and
runs separately from the deterministic suites (see docs/testnet.md).
Malicious operation sequences
Threat. An adversarial ordering of otherwise-valid operations — e.g. depositing after withdrawal, merging consumed inputs, transferring to yourself to reset nonces — exploits an assumption in the state machine.
Defense. Structural, authorization, and state validation reject the classes above (self-transfer, zero amounts, unregistered recipients, duplicate registration, wrong-owner merges). The fuzz suite generates precisely such sequences — ghost accounts, wrong-owner signatures, zero amounts, empty merges — and asserts nothing corrupts.