Qanum L1: Threat Model & Accepted Risks (Testnet Era)
Adversaries, assumptions and the defenses of the Qanum node.
This page reproduces docs/THREAT-MODEL.md from qanum-core v0.9.0 (release tag). Links to files that are not published on this site are shown as plain text until the source repository is public.
Contents
This document consolidates in
one place the threats Qanum L1 is designed against, the mitigations that
exist in the tree today, and the residual risks the project knowingly
accepts at this stage. It is a status document, not a promise: every
mitigation cites the code or document that carries it, and every accepted
risk names what would change it. Changes ship with the registers they
touch (CVE-REGISTER.md, BIP-REGISTER.md, ROADMAP.md).
Method note. This is the testnet-era threat model. Mainnet may raise
the bar on any line below; if it does, the change lands as an AIP
(GOVERNANCE.md) and this document is updated in the same commit batch.
1. Consensus and chain integrity
| Threat | Mitigation in tree | Residual risk / status |
|---|---|---|
| 51 % / hashrate attack on a young chain | PoW with big-endian target comparison; chain selection by accumulated work (spec §13.4); deep reorgs (> 100 detach) accepted only with more work, loudly logged and raised as a node warning (RPC warnings, -alertnotify) (whitepaper §9.6) |
Accepted. Launch hashrate is the critical monitorable (monetary-policy-decision-record.md §5). There is no checkpoint floor (Bitcoin Core v30 parity); the defense is accumulated work alone. |
| Alternative-chain / history-rewrite attack | Most-work chain selection with full validation; no checkpoints (Bitcoin Core v30 parity, CHECKPOINT-POLICY.md). A rewrite needs more accumulated work than the honest chain. Low-work alternative histories cannot fill the header index (header pre-synchronization + per-network minimum_chain_work); assumevalid never selects the chain and falls back to full signature checks when its block is off the best chain |
As in Bitcoin Core, a majority-hashrate attacker can rewrite history; deep reorgs (> 100 blocks) are logged loudly. The small testnet hashrate makes this cheaper than on a large network; accepted for testnet. |
| Quantum-accelerated mining (Grover) | Nothing specific, by design: no PoW function removes Grover’s quadratic advantage. Per-block anchored ASERT absorbs hash-power jumps (spec §14.2); no maximum reorg depth (it would be a checkpoint); stale-rate and deep-reorg monitoring (TESTNET-RUNBOOK.md §10; RPC warnings and -alertnotify) |
Accepted. Same as a classical hashrate share of that size; the PoW is not claimed to be quantum-resistant (whitepaper §9.9). Open: the effect on the stale-block rate (Sattath), reviewed against the measured stale rate before the mainnet freeze (whitepaper §9.9). Equal-work ties go to the block seen first, as in Bitcoin Core (whitepaper §9.6). |
| Difficulty / time manipulation | Anchored ASERT with clamp [1, POW_LIMIT] (a past timestamp hardens the target, never explodes it; spec §14.2); MTP-11 strict >; +2 h future window (spec §12.2) |
Median of peer offsets (Core-style) is not a consensus rule; future network hardening (spec §12.2). ASERT half-life stability under hash swings is a benchmark-agenda item (spec §22.2). |
| State corruption / invalid state acceptance | Every header commits utxo_root over the full UTXO set (256-level SMT, spec §10); reconstructive commitment evaluation is the only consensus engine (whitepaper §11.2); undos rebuilt and chainstate snapshot shape validated (docs/TESTSUITE.md inventory) |
SMT memory footprint without pruning is high for domestic nodes; accepted risk (spec §22.1). |
| Forged snapshot accepted as chainstate | The artifact must recompute to the utxo_root committed in the tip header (consensus, spec §10); the embedded header chain 0..=H is re-validated contextually (PoW, ASERT, MTP-11) against the local genesis (check_header_chain_contextual, src/utxo_snapshot.rs); network magic and genesis hash are mandatory; install refuses non-genesis datadirs and overwrite without --force; writes are atomic; exporters self-verify the artifact root against the tip commitment before serving (AIP-0001) |
Forging an installable artifact needs a header chain with valid PoW from the local genesis, but not the real chain’s work: ASERT lets an attacker choose timestamps, so a chain of the same height can carry far less work. Installing from the network therefore also requires the network’s minimum chain work (zero on testnet until it has history) and independent corroboration (§3, “Eclipse + forged snapshot”). Serving invalid artifacts is an availability problem only: clients reject them at final verification. |
| Subsidy / issuance bugs | Exact integer arithmetic with overflow detection (src/economics.rs), locked by unit tests; monetary parameters frozen by decision record (monetary-policy-decision-record.md) |
Long-term security budget rests on a low permanent tail (~84 096 QAN/year) plus fees: deliberate trade-off (whitepaper §16.3, §19.3), not an oversight. |
2. Cryptography
| Threat | Mitigation in tree | Residual risk / status |
|---|---|---|
| Quantum attack on funds | Post-quantum signatures from genesis, NIST-standardized and provided by liboqs: the NIST level-5 suites ML-DSA-87 (the wallet default) and SLH-DSA-SHAKE-256s (recovery) (0x0104 / 0x0203), and the level-1/2 suites ML-DSA-44 and SLH-DSA-SHAKE-128s (0x0101 / 0x0201) in new outputs only below the retire12 height (height 0 on testnet, which therefore has level 5 only); old outputs always stay spendable; outputs store KeyID (hash of the key), not raw keys; suite versioning with stable reserved IDs and a binding activation rule (spec §2.4) |
No quantum-resistance claim is made for unaudited experimental code (TESTNET-TERMS.md §1): algorithms are standard, the implementation is not yet externally audited (ROADMAP Phase 3). |
| Harvest-now-decrypt-later on P2P traffic | Every P2P connection, on every network, is a Noise hybridXX handshake: X25519 + ML-KEM-1024 (FIPS 203, liboqs, checked by the startup self-test), ChaCha20-Poly1305, SHA-512; the session keys depend on both key exchanges. There is no TLS and no plaintext port; each direction rekeys after 1 GiB or 1 hour (src/net_v2/transport/noise.rs) |
Traffic analysis (message sizes, timing, peer IPs) is not hidden. |
| Grover on hashes | 256-bit SHA3 for every commitment (chain hash, txid, Merkle, utxo_root, key_id, sighash; whitepaper §6.3) |
Preimage resistance about 2^128 quantum operations (NIST category 5 margin); collision resistance 2^128, as SHA-256 in Bitcoin (NIST category 2). Accepted; no change needed. |
| Signature implementation flaws | Signature verification is mandatory on every network: no production build contains a non-verifying verifier, and the backend passes a known-answer self-test at startup or the node does not start (validate_verifier_selection, run_self_test); crypto paths fuzzed (fuzz/fuzz_targets/, tests/fuzz_crypto*.rs) |
liboqs is a third-party dependency pinned by Cargo.lock; supply-chain watch via cargo-audit in CI (ci.yml). |
| Transaction malleability | txid covers base serialization only, wtxid covers auth (spec §3.5); sighash has a domain separator (QANUM-SIGHASH-V1) and per-algorithm separators (spec §6.2-6.3); single SIGHASH_ALL; minimal CompactSize enforced |
Malleability of the relay identifier is bounded by design; there is no BIP339-style wtxid relay (BIP-REGISTER: not implemented, with reason). |
| TOFU first-use window | Noise + TOFU: each node proves its ML-DSA-87 identity inside the encrypted handshake, bound to that connection; an identity mismatch at a pinned SocketAddr always rejects (inbound/outbound/feeler), except a one-step rotation signed by the pinned identity (rotatep2pidentity, sent for 60 days); pins are created by outbound/feeler contacts only; operator removal via removetofuentry (src/net_v2/transport/tofu.rs) |
An active MITM at the first connection can still pin itself. Relay privacy is the remaining hardening line (whitepaper §4.5/§13.2). Accepted for testnet; small operator meshes mitigate via out-of-band peer_id exchange (getnetworkinfo.localpeerid). |
3. Network / P2P
| Threat | Mitigation in tree | Residual risk / status |
|---|---|---|
| Eclipse of a small mesh | 8 outbound slots reserved (inbound cannot fill maxconnections); outbound candidates 1-per-netgroup; inbound quota per /16 /32; no per-IP cap (as in Core); when inbound is full, Bitcoin Core SelectNodeToEvict protects diverse netgroups, low ping, novel tx/block relayers and long uptime, then evicts the youngest peer of the most-populated netgroup (a Sybil crowding one address evicts itself first); self-connection rejection by local nonce; addrman hygiene (last_seen clamped; strict routability on every network except regtest and -onlynet family filter at intake, outbound selection and relay; eviction after 3/10 failed attempts; 30-day purge at load; SEEDS.md §2); whitepaper §13.1; deterministic anti-eclipse test (tests/net_v2_antieclipse.rs) |
The public testnet mesh is small by nature; the hardening line (identity rotation, private relay) is not started (ROADMAP.md). |
| Header/block spam and DoS | Dynamic anti-DoS minimum chain work for header admission (src/chain_manager.rs) plus Bitcoin Core’s header pre-synchronization (#25717, src/headers_presync.rs): a low-work header chain is followed in bounded per-peer memory and stored only after its work crosses the threshold and a second download matches; every network compiles a zero minimum_chain_work floor until the chain has history (CHECKPOINT-POLICY.md §5), so the dynamic threshold does the work; a block with a new header is checked contextually before it is written, and unrequested low-work blocks are ignored (Core AcceptBlock); strict frame and per-message size limits (fuzz target contract in FUZZING.md); bounded queues with scheduler<->connman backpressure; rate-limited logging (CVE-REGISTER, CVE-2025-54604/54605-class fix) |
A sustained botnet can still consume bandwidth; Qanum does not implement BIP152 compact blocks (BIP-REGISTER, with reason); bandwidth profile is honest, not optimized. |
| Malicious snapshot server (invalid or truncated artifacts) | Per-chunk Merkle proofs verified before any write (leaf/node domain-separated roots); manifest identity checked field-by-field before peers are mixed; 60 s serve rate-limit per peer that survives reconnects; 4 chunks in flight per peer with misbehavior scoring; empty meta on rate-limit; exporters self-verify before serving (src/net_v2/snapshot_sync.rs, src/net_v2/connman/manager.rs, src/utxo_snapshot.rs) |
A hostile server can waste bandwidth but cannot install state; every failure path falls back to full IBD. Rate-limit state on long-lived seeds is pruned after 1 h. |
| Eclipse + forged snapshot offered to a fresh node | Same anchor as §1: no peer can produce an installable artifact without redoing genuine PoW against the local genesis; the Noise handshake with TOFU-pinned identities authenticates the channel, the artifact verifies itself. A P2P bootstrap also requires the snapshot tip to carry the network’s compiled minimum chain work and to be confirmed by -snapshotcorroborators (default 1) independent peers (not a server of the artifact, in network groups distinct from the servers and from each other) answering GetHeaders from the tip’s parent with the tip itself; a contradicting peer discards the artifact (fail-closed; src/net_v2/snapshot_sync.rs, fuzz target snapshot_corroboration) |
A valid-PoW minority chain above the minimum work could still be installed only under a full eclipse with a colluding corroborator in another network group. Such a node cannot reorganize below the snapshot height: it logs a WARN asking for a wipe and resync, and blames no peer (src/node/reorg_apply.rs). Otherwise availability only: too few corroborators means full IBD. Offline utxo-snapshot-install is an operator decision and skips both checks. |
| Ban abuse and shared-IP (NAT) poisoning | Misbehavior is scored per IP and bans at 100 (24 h); peers the operator added (-addnode/-connect, addnode add) are never scored or banned and are still dialled under a ban (warning only); the Noise handshake has a 10-second limit so silent sockets cannot pin inbound slots; bans are managed only through the authenticated RPCs setban / listbanned / clearbanned and persist in banlist.json (src/net_v2/connman/, src/rpc.rs) |
An attacker behind the same NAT as an honest peer can still get that shared IP banned for inbound connections; the operator lifts it with setban <ip> remove or clearbanned, or pins the peer with addnode. |
| Peer poisons backfilled historical bodies | A backfilled body’s serialized header must hash to the PoW-committed header at that height, its prev link must match the header below, and its merkle_root/auth_root must recompute from the body itself before the write; duplicate transactions are rejected; the chainstate is never touched (AIP-0001, “Background IBD”; regression tests in tests/body_backfill_core.rs) |
Residual is availability, not integrity: the body is fully pinned by the committed header. Refused bodies are re-fetched or left for full IBD. |
| Snapshot downloader resource abuse | Wire ceiling 16 GiB with resumable 2 MiB chunks; resume fingerprints (manifest + root + size + chunks) validated against the .part on disk before ranges are trusted; resume.json bounds validated (1 ≤ next_height ≤ snapshot_height+1, corrupt state discarded); install ceiling 512 MiB enforced end-to-end (offer, staging, serve, export) with chunk_count cross-checked; global bootstrap budget 15 min (src/net_v2/snapshot_sync.rs, src/net_v2/body_backfill.rs, src/utxo_snapshot.rs) |
The 512 MiB install ceiling caps snapshot size; larger artifacts must wait for pruning or full IBD; ceilings are enforced at offer/staging, so oversized artifacts fail early, not unsafely. |
| Mempool abuse | Entry budgets: 40 000 entries / 32 MB (defaults; operator-tunable via maxmempoolentries / maxmempool); per-tx and per-input auth caps; MAX_TX_VERIFY_UNITS gate before crypto; orphan cap 100 with 15-min TTL; 14-day expiry; full-RBF replacement rules with descendant-package eviction (whitepaper §13.3; spec §7.5) |
Always-replaceable mempool is a deliberate policy (spec §3.2), which trades pinning-resistance for the absence of first-seen-safe semantics; documented (whitepaper §19.6). |
| Traffic analysis / relay privacy | Inventory trickle, echo suppression for blocks and txs, FeeFilter-honored announcements (whitepaper §13.1, §4.5 item 1) | No Tor/I2P (strategic decision, no design yet; ROADMAP.md); stronger relay privacy is Phase-2 hardening, not shipped. |
4. Wallet and custody
| Threat | Mitigation in tree | Residual risk / status |
|---|---|---|
| Key theft from disk | Argon2id KDF + AES-256-GCM store with zeroize (128 bits against Grover); sensitive files written 0600 from birth with legacy-permission repair |
A passphrase-encrypted store is only as strong as the passphrase and the host OS; hardware-wallet support is Phase 4 (ROADMAP.md). |
| Key loss | Native recovery paths: P2RKPQ delayed recovery (SLH-DSA), VAULT delayed hot spend / immediate cold recovery, cancel and finalize flows (whitepaper §14) | No HD / mnemonic backup yet; design parked (BIP-REGISTER.md, BIP32/39/44; ROADMAP Phase 2). Until it ships, backup means exporting the encrypted store; the gap is tracked, not silent. |
| Hot-key theft (reaction window) | VAULT forces the hot branch through a cancellable VAULT_PENDING window (144..5 256 000 blocks); cancel authority is the cold key alone, no height condition; urgent-cancel behaviour under congestion pinned by tests/mempool_congestion_vault_cancel.rs |
Bypass enumeration, reorg/censorship/congestion behaviour, reaction budget and surveillance requirements in VAULT-ADVERSARIAL.md; residual: cold-key theft spends instantly (§5.1 there), confirmed finalize is irreversible. |
| RPC exposure | Loopback bind by default; timing-safe auth; body limits; host allowlist only when auth is disabled; recognized non-GET/POST methods answered 405 | Exposing RPC publicly remains operator error (TESTNET-TERMS.md §5). |
| Unsafe build modes | Closed: qanum-core does not compile without crypto-oqs; the non-verifying verifiers exist only in tests and in the regtest simulation build (sim-verifiers, version -sim), which refuses testnet and mainnet |
A -sim binary run on regtest by a developer. It cannot join testnet or mainnet. |
5. Implementation and supply chain
| Threat | Mitigation in tree | Residual risk / status |
|---|---|---|
| Memory-safety / parser bugs | Rust (no C++ in the node); eleven in-tree libFuzzer targets over the exact receive path, parsers, address decoding, the header pre-synchronization state machine, snapshot corroboration and the Noise transport (frames, handshake messages and the peer identity payload); reference campaigns 6 × 22 073 440 executions over the six parser targets, 0 crashes, 0 ASan reports; the two state-machine targets have 0 crashes and each found a planted bug in under 100 executions (FUZZING.md); proptest wire-decoder properties |
Fuzzing reduces, does not eliminate. No independent external audit yet; ROADMAP Phase 3 “Audits” (external). |
| Regressions in consensus behaviour | full test suite (current count in TESTSUITE.md) with per-file coverage criterion inverted in CI (every tests/ file runs on push; tests/ci_test_coverage.rs enforces it); monthly full release-profile pass; cargo-audit against RustSec (ci.yml) |
The full suite has never run in one process on the maintainer host (RAM policy); sync-related binaries are run individually (TESTSUITE.md header states this plainly). |
| Fake or tampered releases | SHA512SUMS signed with ML-DSA-87 and SLH-DSA-SHAKE-256s, checked offline by qanum-cli verifyrelease; classical PGP signature SHA512SUMS.asc for compatibility; a CycloneDX SBOM per crate; disclosure PGP key on keys.openpgp.org (SECURITY.md) |
On a first download, trust rests on comparing the release key ids (SECURITY.md) through the official channels. Bit-reproducible builds are deferred while releases are source-only (ROADMAP.md). |
| Dependency compromise | Cargo.lock pins everything; dev-only dependencies (tempfile, proptest and the test-fixtures self-dependency) are declared under [dev-dependencies] and never enter a release build (README, Testing) |
Standard Rust supply-chain exposure; monitored by the CI cargo-audit job. |
6. Governance and operations
| Threat | Mitigation in tree | Residual risk / status |
|---|---|---|
| Unilateral consensus change | AIP process defined for the testnet era; suite activation already bound to two independent audited implementations + published vectors (spec §2.4); frozen parameters require decision records (GOVERNANCE.md) |
Single maintainer decides in the testnet era; stated in GOVERNANCE.md §2.4 together with the obligation: a mainnet-era decision process must itself be adopted by AIP before launch. |
| Bootstrap centralization | DNS seeds are one-shot-on-success and replaceable by -seednode / -addnode (SEEDS.md); genesis and the compiled chain-security parameters (minimum_chain_work, assumevalid) are public data and cannot select a chain |
Testnet seed domains are operator-run; mainnet seed operations are a launch criterion (ROADMAP.md). |
| Testnet state loss | TESTNET-TERMS.md: coins have no value, resets allowed, reset communication channels defined |
By design; nothing to mitigate beyond the terms. |
7. Out of scope here
- Attacks requiring physical access or social engineering (
SECURITY.mdscope). - Economic/price risks beyond the security budget (§1 covers the part that touches consensus).
- Higher layers (channels, shielded pool, compute domains): they add no consensus rule and carry their own threat models in their own repositories (whitepaper §4.3-4.5).
Maintenance rule. When a mitigation ships or a risk is retired, the row moves in the same commit batch as the code/register change. When a new risk is accepted, it lands here with its reason; silence is not an option in this document.