montana/Montana-Protocol/Montana Network v1.2.0.md
2026-05-27 16:44:03 +03:00

261 KiB
Raw Blame History

Montana — Network Layer Specification

Version: 1.2.0 (2026-05-26)

Layer: Network — sits between Protocol (low) and App (high).


Introduction

Montana's network layer covers transport and discovery between consensus nodes and clients. This specification historically lived as inline sections inside Montana Protocol; it has been split into its own file to separate the layers per the [I-7] minimal-cryptographic-surface principle and to make independent audit easier.

What this spec covers:

  • Transport layer via libp2p (TCP + Noise_PQ XX → Yamux), where Noise_PQ XX is the post-quantum security upgrade replacing the classical TLS 1.3 + Noise XK chain
  • Traffic obfuscation (TLS-mimicry, ECH, padding, timing)
  • Identity-Bound Tunnel (IBT) — proof that a peer holds the privkey corresponding to its node_id
  • Transport Randomness — unpredictable network-bound seeds
  • PeerRecord and discovery
  • Mesh Transport (Bluetooth / Wi-Fi Direct, store-and-forward)
  • Sync protocols (FastSync, BatchLookup, RangeSubscribe, Label Rotation)
  • Network-layer Threat Model
  • Network-layer binding KAT vectors
  • apply_mesh_frame and apply_store_and_forward — normative rules
  • Final Gate audit, M6 milestone

What this spec does NOT cover:

  • State machine, apply_proposal, operations (Transfer / OpenAccount / ...) — see Montana Protocol
  • Crypto primitives (ML-DSA-65, ML-KEM-768, SHA-256, PBKDF2 / HKDF) — see Montana Protocol §«Криптография» (Cryptography section)
  • UI / Wallet / Messenger / Channels / Contacts / Profile / Junona / Browser — see Montana App

Network layer

All time parameters at the network layer (frame rate, padding window, feeler interval, Dandelion timers) are implementation guidance for the node's local network stack. They operate on the node's local clock and are outside the scope of consensus state.

Transport obfuscation

Montana is a personal network. Each node is the participant's personal server. The transport layer is built from this definition: a personal server answers only to participants, a personal messenger hides message timing, personal = affordable to an ordinary person.

Encryption

All P2P connections are encrypted by Noise_PQ XX (ML-KEM-768 ephemeral KEM on both sides + ML-DSA-65 identity + ChaCha20-Poly1305 AEAD), the production transport handshake. Inbound listeners default to TCP port 8444. The content of the traffic is not accessible to an observer.

Identity-Bound Tunnel (IBT)

A personal server answers only to network participants. After the TLS handshake the client sends an authentication proof. Nodes (registered and invited) sign with the node keypair. Accounts (clients) sign with the account keypair.

proof = ML-DSA-65_sign(client_privkey,
          "mt-tunnel-online" || server_node_id || floor(current_window_index / 2)
          || online_session_nonce)

where:
  online_session_nonce  32B    — generated by the client from CSPRNG for each
                                 handshake, transmitted in the plain part of the IBT
                                 advertisement alongside the proof

The server checks:

  1. Signature is valid for the claimed client_pubkey
  2. Window slot = current OR previous (window = 2 window_index)
  3. Access level — the server checks client_pubkey against three tables in order, the first match determines the level:
    • node_id = SHA-256("mt-node" || client_pubkey) in Node Table → full gossip (client connected with the node keypair)
    • node_id with node_pubkey = client_pubkey in Candidate Pool → read-only gossip: receives proposals (candidate connected with the node keypair)
    • account_id = SHA-256("mt-account" || suite_id || client_pubkey) in Account Table → connection to a trusted node (client connected with the account keypair)
    • None matched → reject

Conditions 1-2 met + access level from step 3 determined → Noise handshake → Montana's P2P network at the corresponding access level. Any condition failed → TLS alert bad_certificate, close. This is standard server behaviour for mandatory client authentication — there are millions of such servers on the internet (corporate portals, APIs, banking systems).

Replay protection: three-layer defence simultaneously. (a) server_node_id binds the proof to a specific recipient — replay against a different server is impossible. (b) The window slot caps the replay window at 2 windows (≤120 seconds at genesis calibration) — the proof becomes invalid after one window. (c) Session nonce tracking. The server keeps used_online_nonces[client_pubkey] — a set of online_session_nonce values used by this client within the current / previous window. On receiving a proof with online_session_nonce ∈ used_online_nonces[client_pubkey] → reject (replay within the window slot). Set pruning: entries older than 2 windows are removed (nonce reuse is acceptable after expiry — the window slot is no longer valid). This is a defence-in-depth closure of the MITM-replay class inside the window slot — even if a proof is intercepted, the attacker cannot reuse it against the same server within those 2 windows.

Verification procedure for an online peer. On receiving an online IBT advertisement, the server node performs the checks in the following order (mirror of the mesh IBT procedure):

  1. Parse the advertisement; extract client_pubkey, online_session_nonce (32 B), proof (3309 B ML-DSA-65 signature).
  2. Check that the ML-DSA-65 signature is valid for client_pubkey over the message reconstruction "mt-tunnel-online" || server_node_id || u64_LE(floor(current_window_index / 2)) || online_session_nonce (current window slot). If it does not match — try with floor(current_window_index / 2) - 1 (previous slot).
  3. Check server_node_id == local_node_id (proof bound to this server).
  4. Check online_session_nonce ∉ used_online_nonces[client_pubkey] (replay block).
  5. Look up client_pubkey against three tables in order: Node Table (via SHA-256("mt-node" || client_pubkey) lookup) → Candidate Pool → Account Table (via SHA-256("mt-account" || suite_id || client_pubkey)). The first match determines the access level. None matched — reject.
  6. All checks passed → accept; add online_session_nonce to used_online_nonces[client_pubkey]; start the Noise handshake; start the P2P session at the access level from step 5.
  7. Any check failed → TLS alert bad_certificate, close. Silent reject is optional (do not give an attacker feedback about which step failed).

used_online_nonces memory bound. Set per client_pubkey, ephemeral, transport-layer (not consensus state — [I-14] formally does not apply). DoS bound on two levels: (i) per-pubkey set bounded by MAX_ONLINE_NONCES_PER_PUBKEY = 256 (an attacker with one keypair cannot flood more than 256 nonces per window slot — the handshake rate-limit already caps the real stream below < 256 / 2τ₁); (ii) global bound MAX_ONLINE_NONCES_TOTAL = 65 536 (32 B × 65536 ≈ 2 MB per server — acceptable memory on commodity hardware [I-5]). Pruning at the window-slot boundary: entries older than 2 windows are removed automatically. Defence against many distinct client_pubkeys: server-side handshake rate-limit (see backpressure rule [B5] in the spec — max_pending_requests_per_peer) caps the new-handshake stream per source.

Bootstrap exception: genesis bootstrap nodes are hard-coded as (IP, node_id, pubkey) × 12. The bootstrap accepts a proof from any valid ML-DSA-65 key (the Account Table is not consulted). To defend against a connection flood, the client attaches a proof-of-work:

SHA-256("mt-bootstrap-pow" || proof || nonce) < target

target is chosen so the cost is ≈ 100 ms CPU. PoW is required only when connecting to a bootstrap, not to ordinary peers.

Mesh transport IBT extension.

Mesh transport (see the «Mesh Transport» subsection below) operates without a fresh window_index — the device may be offline for hours or days until the next sync with the internet-side network. The IBT proof in the mesh context uses a cached window_index — the last known window value from any previous online connection.

Formula for mesh transport:

mesh_proof = ML-DSA-65_sign(
    client_privkey,
    "mt-tunnel-mesh"
    || peer_node_id
    || floor(cached_window_index / 2)
    || mesh_session_nonce)

where:
  cached_window_index    u32    — the last known window value from any
                                  previous online handshake or
                                  gossiped proposal
  mesh_session_nonce     32B    — generated by the handshake initiator
                                  from CSPRNG, transmitted in the plain
                                  part of the mesh advertisement

Acceptable staleness bound. The peer accepts cached_window_index in the range [peer.known_window_index - 7 × τ₁, peer.known_window_index]. Above 7 × τ₁ the cached value is treated as too stale — the peer rejects the mesh IBT handshake and requires a fresh value through any available channel before continuing.

Session nonce tracking. The peer keeps used_nonces[sender_pubkey] — a set of mesh_session_nonce values used by this sender within the acceptable staleness window. On receiving a proof with mesh_session_nonce ∈ used_nonces[sender_pubkey] → reject (replay). Set pruning: entries older than 7 × τ₁ are removed (nonce reuse after expiry is acceptable — the cached_window_index is no longer valid).

The domain separator MUST be mt-tunnel-mesh, not mt-tunnel-online. A separate separator is critical — otherwise an attacker who intercepted an online IBT proof (window slot = 2 × τ₁ replay) could reuse it in the mesh context, where the staleness window is widened to 7 × τ₁. Cross-context replay is blocked at the domain-separation level.

Replay surface analysis.

  • Online IBT (separator mt-tunnel-online): replay window 2 × τ₁ — narrow, plus per-nonce tracking blocks replay inside the window slot (see Replay protection above).
  • Mesh IBT (separator mt-tunnel-mesh): replay window widened to 7 × τ₁, but replay is blocked by per-nonce tracking.
  • Cross-context: domain separation makes a proof for one context invalid in the other.

Verification procedure for a mesh peer.

  1. Parse the advertisement; extract sender_pubkey, mesh_session_nonce, proof.
  2. Verify the signature against sender_pubkey.
  3. Recover cached_window_index from the proof message reconstruction (the peer knows sender_pubkey, peer_node_id is known locally; the peer tries the range [known_window_index - 7 × τ₁, known_window_index] until a matching value is found; if no match — reject).
  4. Check mesh_session_nonce ∉ used_nonces[sender_pubkey].
  5. All ok → accept; add mesh_session_nonce to used_nonces; start the mesh session.
  6. Any check failed → silent reject (no error message, to avoid giving an attacker feedback).

Uniform Framing

All Montana messages inside an IBT connection are fragmented into fixed-size frames:

frame_size = 1024 bytes

frame format:
  flags     1B    (0x01 = data, 0x02 = padding, 0x04 = continuation)
  length    2B    (payload length, ≤1021B)
  payload   1021B (data or random padding up to frame_size)

A personal messenger hides timing: a continuous stream of frames flows between nodes. Real Montana messages replace padding frames rather than adding to them. An on-network observer cannot distinguish a transfer from a proof of time from silence — everything is the same encrypted frames.

Parameters:

  • Baseline frame rate: 1 frame/s on outgoing connections. Inbound — frames when data is available
  • Maximum burst: ≤ 8 frames in a row without a ≥ 10 ms pause
  • Minimum padding ratio: ≥ 20% of frames within a sliding τ₁ window on outgoing

Personal = affordable: 24 outgoing × 1 frame/s × 1024 bytes = 24 KB/s ≈ 60 GB/month. Acceptable for a home server.

Transport Randomness

All randomized decisions at the transport layer (stem routing, frame scheduling, nonce generation) use a CSPRNG seeded from the OS entropy pool. A deterministic PRNG derived from node state is forbidden for transport-layer randomness.

Transport obfuscation is orthogonal to consensus. The TimeChain and state machine work over any transport without changes.

Post-quantum transport migration (M6 milestone)

Historical state (pre-M6 closure). The transport layer used TLS 1.3 with classical X25519 ECDHE for the outer tunnel and Noise XK (Diffie-Hellman over X25519) for the inner peer authentication. Both classical handshakes were vulnerable to store-now-decrypt-later attacks by a future quantum adversary. This exposure has been closed by switching the production transport to Noise_PQ XX (see the next paragraph and the wire format section).

M6 status — closed. Production transport is now a single post-quantum handshake: Noise_PQ XX with ML-KEM-768 ephemeral keypairs on both sides of the handshake, ML-DSA-65 identity signatures over the transcript, and ChaCha20-Poly1305 AEAD framing on the established session. The classical TLS 1.3 + Noise XK chain has been removed from the libp2p stack (it provided no security property beyond the IBT-authenticated inner layer; the DPI obfuscation role is preserved by uniform framing on top of Noise_PQ XX). Transport confidentiality is post-quantum end-to-end. PeerId is derived from each peer's ML-DSA-65 identity public key as the SHA-256 multihash (libp2p / IPFS sha2-256 multihash code 0x12), so cryptographic and routing identities are bound to the same key material.

Multi-confirmer cementing protocol. The bootstrap genesis cohort operates today in a singleton-cementing regime (the proposer is the sole confirmer because its chain_length is dominant after long-running operation; all other operators have chain_length ≤ 1 from a recent registration). The multi-confirmer cementing protocol formalised below is the normative path for operational regimes in which non-bootstrap operators accumulate non-negligible chain_length over many τ₂ epochs.

Protocol steps per window W:

  1. Candidate Proposal. The canonical proposer (per the lottery selection of W 2) constructs ProposalHeader with included_bundles_root set to H(my_own_BC) and broadcasts the header (MsgType::Proposal, 0x22).
  2. Follower confirmation. Each Active operator on receipt of the candidate Proposal (a) verifies the proposer is canonical for W, (b) signs its own BundledConfirmation(W, my_endpoint, my_op_hashes, my_reveal_hashes) where my_endpoint = SHA-256("mt-lottery" || T_r(W) || cemented_bundle_aggregate(W-2) || my_node_id || W), (c) broadcasts the BC envelope (MsgType::BundledConfirmation, 0x20) to the proposer and to all connected peers.
  3. Proposer accumulator. The proposer maintains a per-window BC accumulator. On each incoming BC, the proposer validates per validate_bundle and, if valid, adds the BC to the accumulator. The accumulator window closes when cemented_sum = Σ node.chain_length over all collected confirmers reaches quorum(active_chain_length) = ⌈67 × Σ active_chain_length / 100⌉, or when one τ_1 window elapses since the candidate Proposal was broadcast (whichever first).
  4. Cemented Proposal broadcast. The proposer builds the final ProposalHeader with included_bundles_root = sparse Merkle root over the collected BC set, and broadcasts the cemented envelope as MsgType::Proposal (0x22). The cemented envelope is extended from the legacy 3722-byte header-only layout to a length-prefixed schema [header (3722 B)][u16 bundle_count][bundle_count × BC], where each BC is variable-length per the standard BundledConfirmation::encode (node_id 32 B + endpoint 32 B + window_index 8 B + u16 op_hashes count + N × 32 B + u16 reveal_hashes count + N × 32 B + signature 3309 B).
  5. Follower cementing. Each follower on receipt of the cemented Proposal (a) parses the bundle set, (b) calls validate_bundle on each BC with expected_endpoint = SHA-256("mt-lottery" || T_r(W) || cemented_bundle_aggregate(W-2) || bc.node_id || W), (c) builds ProposalSettle { window_w: W, winner_id, cemented_confirmers: [all signers] }, (d) calls apply_proposal with the multi-confirmer set.

The singleton-cementing path (a single confirmer, used today on the genesis cohort while chain_length is dominated by the bootstrap proposer) is the special case of the protocol above with bundle_count = 1; the 3722-byte header-only envelope handles that special case. Operational regimes with non-bootstrap operators accumulating chain_length require the extended length-prefixed schema and validation of bundle_count ≥ 1.

T_r consistency requirement. The follower's compute_endpoint requires the canonical T_r(W). Two implementation paths are viable: (i) followers tick the VDF locally in lockstep with wall-clock and cache the per-window T_r history during catch-up; (ii) the proposer extends the cemented Proposal envelope with T_r(W) (32 B) so followers do not need to compute it locally. Path (ii) is the cleaner architectural choice; it adds 32 B per envelope and removes any drift hazard for followers whose VDF tick is behind the cemented head.

Wire-format and KAT vectors for the cemented Proposal envelope with bundle_count ≥ 2 are normatively specified in the Network spec section above. Cross-implementation conformance binding for the schema is tracked under milestone M9.

Migration phases.

  • Phase 0 — Architecture & scaffolding (closed). DEV-014 tracker entry in Code/docs/SPEC_DEVIATIONS.md documented the migration plan; capability detection was reserved via a pq_transport_version wire field in the IBT advertisement.
  • Phase 1 — Noise_PQ handshake implementation (closed). Custom Noise_PQ handler written outside libp2p's noise upgrade module in crates/mt-noise-pq. Wraps the ML-DSA-65 identity signature over a transcript that includes both ephemeral ML-KEM-768 public keys and the ML-KEM-768 ciphertexts. KAT vectors checked into crates/mt-noise-pq/tests/kat.rs.
  • Phase 2 — XK → XX redesign (closed). The initial XK variant required the initiator to know the responder's static ML-KEM-768 public key a priori — incompatible with libp2p's plug-in with_tcp auth-upgrade slot which gives the upgrade only the local libp2p::identity::Keypair (Ed25519). The XX redesign discovers remote identity during the handshake (ephemeral ML-KEM-768 keypairs on both sides; identity ML-DSA-65 pk transmitted in msg2 / msg3 and authenticated by signature over transcript). New wire format documented in this section.
  • Phase 3 — Classical removal (closed). The libp2p auth chain (tls::Config::new, noise::Config::new) in mt-net-transport::transport::build_swarm_with_keypair was replaced with NoisePqXxConfig. The transport stack is now TCP → Noise_PQ XX → Yamux. Uniform framing layer is preserved (it provides DPI obfuscation orthogonally to the handshake). The pq_transport_version field stays reserved-but-unused for future protocol negotiation if multistream-select proves insufficient.

Verification on the genesis 3-node network. Each phase is verified on the three production nodes (Moscow, Helsinki, Frankfurt) for ≥24 hours of continuous operation before being declared closed. Phase 1 closure requires byte-exact KAT vectors checked into mt-conformance and cross-node handshake success with zero classical fallback during the observation window.

[I-1] compliance status. Closed. The entire protocol stack is post-quantum end-to-end: consensus signatures via ML-DSA-65, application-layer encryption via ML-KEM-768, transport handshake via Noise_PQ XX (ML-KEM-768 + ML-DSA-65). No classical Diffie-Hellman remains in the protocol layer.

Wire format (normative, production XX). The Noise_PQ XX handshake is exactly three messages. Identifiers in formulas below match the reference implementation in crates/mt-noise-pq/src/xx_handshake.rs. The libp2p multistream-select protocol identifier for the handshake is /montana/noise-pq-xx/1.0.0 — this string is the authoritative protocol name used to negotiate the upgrade between two peers.

Message Size (B) Fields
msg1 (initiator → responder) 1184 ke_pk_i (ML-KEM-768 pk, 1184 B)
msg2 (responder → initiator) 7533 ke_pk_r (1184 B) ‖ ct_i (ML-KEM-768 ct to ke_pk_i, 1088 B) ‖ rs_id_pk (ML-DSA-65 pk, 1952 B) ‖ sig_r (ML-DSA-65 sig, 3309 B)
msg3 (initiator → responder) 6349 ct_r (ML-KEM-768 ct to ke_pk_r, 1088 B) ‖ is_id_pk (1952 B) ‖ sig_i (3309 B)

Transcript hash (input to both sig_r and sig_i) is the byte concatenation of msg1 plus the msg2-prefix-without-sig_r (for sig_r), or msg1 ‖ full-msg2 ‖ msg3-prefix-without-sig_i (for sig_i). Each signature input is domain-separated with mt-noise-pq-xx-v1-sig-r and mt-noise-pq-xx-v1-sig-i respectively.

Session keys are derived by domain-separated SHA-256 over the concatenation of both ML-KEM-768 shared secrets and the full transcript:

master    = SHA-256("mt-noise-pq-xx-v1-master" ‖ ss_i ‖ ss_r ‖ transcript)
sk_i_to_r = SHA-256("mt-noise-pq-xx-v1-i2r"    ‖ master)
sk_r_to_i = SHA-256("mt-noise-pq-xx-v1-r2i"    ‖ master)

sk_i_to_r and sk_r_to_i are 32-byte keys for ChaCha20-Poly1305 AEAD; the AEAD-wrapped byte stream is exposed as mt_noise_pq::stream::NoisePqStream.

PeerId derivation (non-libp2p-standard). PeerId is derived as the SHA-256 multihash (libp2p / IPFS sha2-256 multihash code 0x12) over the raw byte representation of each peer's ML-DSA-65 identity public key. This is not the libp2p-standard PeerId derivation, which is a multihash of the protobuf-encoded PublicKey message with one of the libp2p built-in key types (RSA, Ed25519, Secp256k1, ECDSA). The non-standard derivation is intentional — Montana uses ML-DSA-65 as the cross-network identity per [I-1], which is not one of libp2p's built-in key types, and the protobuf wrapper would add about fifty bytes of overhead without carrying additional security. Two implications follow: (a) a vanilla libp2p node cannot recover the public key from a Montana PeerId, and (b) Montana nodes do not accept libp2p-standard PeerIds because they have no ML-DSA-65 key to verify them with.

Legacy XK variant (/montana/noise-pq/1.0.0, crates/mt-noise-pq/src/lib.rs) is retained for reference and for KAT continuity but is no longer wired into the libp2p transport. Its wire sizes (msg1 2272 B, msg2 6349 B, msg3 5261 B) and its requirement that the initiator know the responder's static KEM pk a priori made it incompatible with libp2p plug-in. XK is the older form documented here for completeness.

msg1  (initiator → responder)        2272 B
  ke_pk        1184 B    initiator ephemeral ML-KEM-768 public key
  ct_rs        1088 B    KEM ciphertext encapsulated to the responder's
                         static ML-KEM-768 public key (rs_kem_pk, known a
                         priori to the initiator via the IBT directory)

msg2  (responder → initiator)        6349 B
  ct_e         1088 B    KEM ciphertext encapsulated to ke_pk
  rs_id_pk     1952 B    responder static ML-DSA-65 identity public key
  sig_r        3309 B    ML-DSA-65 signature by rs_id over the
                         signature-domain hash
                         SHA-256("mt-noise-pq-v1-sig-r" ‖ ke_pk ‖ ct_rs ‖ ct_e)

msg3  (initiator → responder)        5261 B
  is_id_pk     1952 B    initiator static ML-DSA-65 identity public key
  sig_i        3309 B    ML-DSA-65 signature by is_id over the
                         signature-domain hash
                         SHA-256("mt-noise-pq-v1-sig-i" ‖ ke_pk ‖ ct_rs ‖ ct_e
                                                      ‖ rs_id_pk ‖ is_id_pk)

Both sides derive identical directional session keys (each 32 B) and a 32 B transcript hash exposed to higher layers as a channel-binding token:

master         = SHA-256("mt-noise-pq-v1-master" ‖ ss_rs ‖ ss_e
                                                ‖ ke_pk ‖ ct_rs ‖ ct_e
                                                ‖ rs_id_pk)
sk_i_to_r       = SHA-256("mt-noise-pq-v1-i2r" ‖ master)
sk_r_to_i       = SHA-256("mt-noise-pq-v1-r2i" ‖ master)
transcript_hash = SHA-256("mt-noise-pq-v1-transcript" ‖ ke_pk ‖ ct_rs ‖ ct_e)

where ss_rs = mlkem_decap(rs_kem_sk, ct_rs) and ss_e = mlkem_decap(ke_sk, ct_e) on the receiving side respectively, and the corresponding mlkem_encap(rs_kem_pk) → (ct_rs, ss_rs) and mlkem_encap(ke_pk) → (ct_e, ss_e) on the sending side. Implicit-rejection semantics of FIPS 203 §6.3 are reconciled by the identity-signature check: a maliciously substituted ciphertext yields a different shared secret on the receiver, the transcript master diverges from the sender's, and the identity signature check fails (the receiver returns BadResponderSignature or BadInitiatorSignature).

Post-handshake AEAD framing. After the 3-message handshake completes, every application-layer byte stream between the peers is encrypted with ChaCha20-Poly1305 using the derived directional session keys. Wire format per direction:

direction = initiator → responder uses sk_i_to_r
direction = responder → initiator uses sk_r_to_i

per frame (each application message):
  length_be     2 B    big-endian u16, total ciphertext length including
                       16-byte Poly1305 tag (max 65 535 → max plaintext
                       65 519 = u16::MAX  tag)
  ciphertext+tag        ChaCha20-Poly1305 encryption of plaintext with
                        nonce = 0x00000000 ‖ u64_be(counter), 12 bytes;
                        counter starts at 0 and increments by 1 per
                        outgoing frame in each direction independently;
                        AAD = empty (none)

The 64-bit nonce counter is monotonic per direction and guaranteed not to overflow at any realistic message rate (2^64 frames at 1 Gbit / s with 64 KiB frames ≈ 2^48 years). Implementations MUST abort the connection (treat as protocol error) if the counter would overflow.

Application-layer messages larger than 65 519 bytes are fragmented by the caller (e.g. by libp2p's stream multiplexer); the AEAD layer makes no provision for in-band fragmentation.

Capability negotiation. The primary negotiation mechanism is libp2p multistream-select: each peer advertises the set of upgrade protocols it supports (/montana/noise-pq/1.0.0 for the Noise_PQ handshake defined here, /noise or equivalent for the classical fallback) and the connection negotiates the highest mutually supported one. This is standard libp2p convention and requires no additional wire fields.

A 1-byte pq_transport_version field is reserved but not currently consumed by the IBT advertisement layout — it is held in reserve for a future explicit out-of-band signal if multistream-select proves insufficient (for example, if some operator policy needs to express «I support Noise_PQ but refuse to fall back to classical»). Values when the field becomes active:

pq_transport_version Meaning
0x00 Classical TLS 1.3 + Noise XK (pre-M6)
0x01 Noise_PQ v1 (this spec, ML-KEM-768 + ML-DSA-65, no hybrid X25519)

Future versions (0x02, …) may add hybrid X25519+ML-KEM, larger PQ KEMs, or other refinements; the field reserves the negotiation surface.

Cross-implementation conformance. Reference test vectors in crates/mt-noise-pq/tests/kat.rs fix the responder's static ML-KEM-768 keypair seed (byte_repeat(0x42, 64)) and the two ML-DSA-65 identity seeds (byte_repeat(0x77, 32) for responder, byte_repeat(0xAA, 32) for initiator). The 3-message handshake on those fixed inputs must produce byte-identical sk_i_to_r, sk_r_to_i, and transcript_hash across implementations. Per-message wire bytes will differ run to run because the encapsulation step uses fresh OS randomness per FIPS 203 §6.2; only the derived session material is byte-exact deterministic for cross-implementation verification.

Transport profile ladder

The Noise_PQ XX handshake and its uniform-framing layer define the inner transport: the post-quantum session and the DPI-resistant frame stream are invariant across every deployment. The outer carrier — the byte pattern a passive observer sees before the inner handshake begins — is a negotiable profile. A node advertises the profiles it supports; a connecting peer selects the highest-preference profile its local network path permits, established empirically through the reachability-sensing mechanism below.

Profile Outer carrier Observable to a DPI engine Applicability
T0 none — Noise_PQ XX directly over TCP/8444 a TCP stream of uniform 1024 B frames default; lowest latency
T1 TLS 1.3 mimicry — the inner bytes carried inside a TLS record stream whose ClientHello and SNI reproduce a chosen cover host a TLS 1.3 session to an ordinary HTTPS host a path that passes TLS but filters raw high ports
T2 HTTP/WebSocket over TLS through a content-delivery network a TLS session to a large CDN endpoint a path that filters by destination IP — the CDN address set carries unrelated tenants
T3 a general-purpose pluggable-transport tunnel that carries the inner stream over an external circuit, so the node's own address is absent from the carrier the censor observes the cover traffic of the chosen pluggable transport a path performing both address and protocol filtering
T4 mesh radio — BLE / Wi-Fi Direct, see Mesh Transport radio advertisements at physical range a complete internet shutdown

The inner Noise_PQ XX session, its identity authentication through IBT, and the uniform-framing obfuscation are identical under every profile; a profile changes only the outer carrier, never the post-quantum confidentiality or the identity binding. T2 and T3 introduce an external dependency — a CDN tenant, a pluggable-transport bridge — which an operator enables by deliberate configuration; each dependency carries its operational trust note in the Threat Model. Profile negotiation reuses the libp2p multistream-select surface and the reserved pq_transport_version field, and leaves the inner handshake wire format unchanged.

Transport profiles are local network-stack behaviour on the node's own clock and sit outside the scope of consensus state.

Peer selection

An open entry with a sequential-SHA-256 barrier makes Sybil nodes expensive: each Sybil = τ₂ windows of sequential SHA-256 (not parallelizable) + a selection event. Peer selection uses diversity constraints from protocol-level data (start_window) and network-level data (/16, ASN).

P2P gossip — only registered and invited nodes (IBT levels 1-2, see Transport Obfuscation → Identity-Bound Tunnel). Accounts (IBT level 3) interact through their trusted node.

Outgoing connections

24 outgoing, all full. Uniform framing hides message types — separate relay-only connections are not needed.

Selection: random 50/50 from the «new» and «verified» tables. Bucketing with the node's secret key. No preference by chain_length — selection is uniform.

Four diversity constraints

Every outgoing connection is checked against all four constraints:

Network:
  /16  — at most 1 outgoing per /16 subnet (IPv4) or /48 (IPv6)
  ASN  — at most 2 outgoing per autonomous system

Protocol:
  start_window — at most 2 outgoing to nodes with start_window inside one τ₂

Network constraints: /16 and ASN diversity. The protocol-level constraint start_window is canonically available from the Node Table.

Consequence: a Sybil cluster all registered in one τ₂ → at most 2 of 24 slots. An eclipse requires nodes in 7+ different ASes in 7+ different /16s with registrations in 7+ different τ₂.

The ASN map is loaded at startup. Without the map — fallback to /16.

Address manager

Two tables:

  • New — addresses received via peer exchange and DHT. The node has not yet connected
  • Verified — addresses to which the node has successfully connected through IBT

Bucketing: bucket = Hash(secret_key, source_group, addr_group) % N. Deterministic with the secret key — an attacker cannot predict which bucket their address will fall into.

Incoming connections

Up to 32 incoming. On overflow — eviction:

  1. Protect 4 with the lowest ping
  2. Protect 4 with the most recent useful messages (any valid Montana message the node has not yet seen)
  3. Protect up to 8 from different subnets (one from each)
  4. Protect 4 with the most recent proposals
  5. From the rest — evict from the largest subnet group

Anchors

The 2 outgoing connections with the longest uptime are saved every τ₂. On restart after a crash or upgrade — connect to the anchors first, before any random selection from the tables.

Feeler

Once every 10 τ₁: connect to a random address from «new», perform the IBT handshake (all three verification levels). Success at any level → move to «verified» with the level tag (node / invited / account). Failure → mark or remove.

Rotation

By behaviour: if a peer has not relayed a single new proposal over τ₂ — replace it. A peer with more than 50% invalid messages in a sliding τ₁ window — disconnect, with a τ₂ reconnection ban. A peer that relays honestly is useful to the network and stays.

PeerRecord

PeerRecord format used in peer exchange:

PeerRecord:
  ip            16B   (IPv4-mapped IPv6)
  port           2B   (u16)
  node_id       32B
  node_pubkey 1952B   (ML-DSA-65)

Without node_id and node_pubkey the client cannot compute an IBT proof to connect. Peer exchange: at most 100 PeerRecord per message. At most 1 peer-exchange message per τ₁ from each peer.

Reachability-aware ranking

When a reachability map is available for the node's own vantage (see «Reachability sensing and auto-steering» below), candidate entry points are additionally ranked by their measured reachable fraction on a supported transport profile. The random 50/50 draw between the «new» and «verified» tables, the four diversity constraints, and the absence of chain_length preference are unchanged — reachability ranking selects among candidates that already satisfy those constraints, and the local feeler probe remains authoritative over the map.

Censorship-resistant discovery

Genesis: 12 hard-coded bootstrap nodes (IP, node_id, pubkey). If all 12 IPs are blocked at the country level — a new node cannot enter the network. Five independent discovery channels. One of the five is enough.

1. Peer exchange. Every node keeps and forwards a list of active peers to newcomers. Knowing the IP of a single node is enough — a friend, a QR code, a messenger. One live contact = entry to the network.

2. DHT. A Kademlia DHT on top of libp2p. Nodes find each other without a central point. Identifiers are randomized — the DHT does not reveal node_id before a Montana connection is established.

3. Bridge nodes. Nodes outside the censored jurisdiction, published through out-of-band channels (social networks, messengers, printed QR codes). The IP of a bridge node is unknown to the firewall until it is used.

4. Encrypted Client Hello (ECH). Bootstrap via a CDN that supports ECH. The SNI is encrypted — an observer sees the CDN IP but not the target domain. Effective in jurisdictions without active ECH-extension blocking. In jurisdictions that block ECH (China since 2023, Russia since 2024) — this channel is non-functional. For such jurisdictions — channels 1-3 and 5.

5. Mesh peer exchange. With no internet access at all (state shutdown, inter-zone connectivity loss, local isolation), the node discovers local peers via mesh transport (Bluetooth LE advertisement, Wi-Fi Direct service discovery). Peer exchange runs at the mesh frame level with frame_type = 0 (discovery) — see the «Mesh Transport» and «Store-and-Forward Semantics» subsections. The physical discovery radius is tens of metres; mesh multi-hop forwarding extends the effective radius to hundreds of metres and kilometres with sufficient device density. When at least one device in the mesh network gains internet access — the entire chain synchronizes through it as a single gateway.

Redundancy = resilience. The five channels are independent at the physical-delivery layer (IP internet for 1-4, radio mesh for 5). A state-level block of the internet channel does not affect channel 5 — disabling mesh requires physically suppressing Bluetooth / Wi-Fi on every device, which is practically infeasible.

Reachability sensing and auto-steering

Filtering is applied per access network: a bootstrap address, port, or transport profile reachable from one operator is filtered on another, and the filter set shifts over time. A static entry list is therefore insufficient — a node learns which entry points are reachable from its own vantage and steers toward them. The mesh measures its own reachability and converges on working paths without operator intervention.

Reachability observation

A node or client that completes — or fails — a connection attempt records a reachability observation:

ReachabilityObservation:
  vantage_class    coarse network locator of the observer:
                   (country_code 2B ISO-3166, asn u32). No finer
                   locator is recorded; the observation does not
                   identify the individual observer.
  target_ref       32B  node_id of the attempted peer
  profile          u8   transport profile attempted (T0..T4)
  outcome          u8   0 = reachable (handshake completed),
                        1 = unreachable (timeout / reset / filtered)
  observed_window  u32  cached window_index at observation time

Observations are transport-layer telemetry on the node's local clock. They enter no state root and form no consensus state. They are bounded and ephemeral: a node retains at most MAX_OBSERVATIONS_PER_VANTAGE = 256 per (country_code, asn) pair — mirroring the used_online_nonces per-key bound — and prunes any observation older than 7 × τ₁, the mesh-IBT staleness bound, so the working set is memory-bounded independently of network size.

Reachability map

A node aggregates observations into an advisory map (vantage_class, target_ref, profile) → reachable_fraction, where reachable_fraction is the count of outcome = 0 over total observations for that triple. The map propagates as ReachabilityAdvert records over peer exchange, under the existing peer-exchange rate limit (at most one peer-exchange message per τ₁ per peer, at most 100 records per message). The map is advisory: it ranks candidate entry points and never authorizes a connection by itself.

ReachabilityAdvert:
  country_code      2B   ISO-3166-1 alpha-2 of the vantage
  asn               4B   u32, autonomous system of the vantage
  target_ref       32B   node_id of the observed peer
  profile           1B   transport profile observed (T0..T4)
  reachable_num    u16   corroborating observations with outcome = 0
  reachable_den    u16   total observations for the triple
  observed_window  u32   cached window_index of the latest observation

Invariants ReachabilityAdvert:

  • country_code is two ASCII letters in the ISO-3166-1 alpha-2 set; any other value drops the record.
  • profile ∈ {0, 1, 2, 3, 4} (T0..T4); any other value drops the record.
  • reachable_den ≥ 1 and reachable_num ≤ reachable_den; otherwise the record drops. reachable_fraction = reachable_num / reachable_den is a ranking ratio only and forms no consensus state.
  • observed_window lies within [known_window_index 7 × τ₁, known_window_index]; a staler value drops the record.
  • A (country_code, asn, target_ref, profile) triple is acted on only when corroborated by records from at least REACHABILITY_QUORUM = 3 distinct /16 source groups within that (country_code, asn) vantage; the /16 is the diversity unit of the outgoing-connection constraints. A single advert ranks candidates and authorizes no connection.

Auto-steering

When selecting an entry point a node consults the map for its own vantage_class, prefers candidates with the highest reachable_fraction on a supported transport profile, and breaks ties by the round-trip latency of a feeler probe. The node then confirms the chosen candidate by its own connection attempt and IBT handshake; the map informs the choice and the local probe is authoritative, mirroring the feeler discipline of peer selection. On loss of a working entry mid-session the node re-steers to the next corroborated candidate; one active entry carries a hot reserve.

Quorum and diversity

A single reported observation does not move the map: a (vantage_class, target_ref, profile) triple is treated as reachable only when corroborated by observations from at least REACHABILITY_QUORUM = 3 distinct /16 source groups — the diversity unit of the outgoing-connection constraints. An adversary reporting false reachability from one position therefore cannot steer honest nodes toward a dead or hostile entry: honest nodes require independent corroboration across network positions and ultimately verify by direct IBT probe.

Reachability sensing, the map, and steering are local network-stack behaviour on the node's own clock and sit outside the scope of consensus state.

Dandelion++ (sender anonymity)

Montana's P2P gossip retransmits operations through all nodes. Without protection, the first peer knows the sender's IP. Dandelion++ (Fanti et al. 2018) breaks the IP → operation link by modifying the existing gossip.

Two phases:

Stem:
  The operation traverses a chain of random nodes (2-3 hops on average).
  Each node sees only the previous hop, not the originator.
  At each hop, with probability p = 0.4, the operation transitions to fluff.
  E[stem_length] = 1/p = 2.5 hops.
  P(stem ≤ 1) = 40%, P(stem ≤ 3) = 78%.

Fluff:
  The last stem node initiates a normal gossip broadcast.
  To the rest of the network the operation «appeared» from a random point.

Stem routing. The stem uses only outgoing connections — incoming connections do not participate. Every 693 windows the node re-selects 2 of its 24 outgoing as stem_peers (the stem-set selection period). Inside this 693-window window, the stem_successor (forward choice between the 2) rotates every τ₁ — see the Dandelion++ Card for the normative wording. All stem operations in the epoch are routed through one of these 2 (chosen by hash(msg)).

Application per object type:

Object Mode Reason
UserObject (Transfer Mode A/B, Anchor, ChangeKey, CloseAccount) Stem → fluff Hide sender IP
ControlObject (NodeRegistration) Stem → fluff Hide the registering candidate's IP
VDF Reveal Direct gossip (no stem) node_id is public in the reveal, anonymity is impossible; IP is hidden by Transport Obfuscation (Noise_PQ XX over TCP/8444 with uniform framing)
Confirmation Stem → fluff Hide which node confirmed first

Properties:

Threat Defence
Peer sees sender IP Stem: the peer sees only the previous hop
Global observer (ISP) Noise_PQ XX + uniform framing (Transport Obfuscation)
Gossip-graph analysis The operation enters gossip from a random point
Control of k nodes Deanonymization requires control of O(√n) nodes

Implementation:

stem_peers = random_sample(outbound, 2)    // every 693 windows (selection from 24 outbound)

on_receive_stem(msg, from_peer):
  if random() < 0.4:
    gossip_broadcast(msg)                  // fluff
  else:
    next = stem_peers[hash(msg) % 2]      // deterministic choice between the 2
    send_stem(msg, next)                   // continue stem
  start_timer(msg, 30s)                   // safety timer at each hop

on_timer_expired(msg):
  if msg not observed in gossip:
    gossip_broadcast(msg)                  // forced fluff

Each stem node insures the next. The τ₁/2 timer runs independently at every hop. If the next hop dropped the message — the current hop notices the operation's absence in gossip and performs fluff itself. Maximum latency = τ₁/2 (one hop), not cumulative.

Dandelion++ requires no external infrastructure. Every Montana node already is a relay — gossip exists; stem adds 2-3 hops in front of it. Latency overhead: milliseconds.

NAT Traversal

A personal network works when everyone can join. Most home users sit behind NAT — invisible to incoming connections. Without NAT traversal a personal internet = a server club.

Three mechanisms, each next one used if the previous one fails:

1. AutoNAT (detection). The node asks outbound peers: «do you see my IP:port directly?» If yes — no NAT. If no — the node knows its NAT status.

2. DCUtR (hole punching). Two NAT'd nodes coordinate through a third node with a public IP. Both send outgoing packets — the routers open «holes» for the replies. After coordination — direct connection. Success: 60-70% of cases (TCP). Carrier-grade NAT (mobile operators): ~30%.

3. Circuit Relay v2 (transit). If hole punching fails — traffic flows through an outbound peer with a public IP. Relay is not a separate mechanism and not a dedicated server. A relay connection = an ordinary outgoing connection, subject to the same rules: uniform framing, diversity constraints, behavioural rotation. The content is end-to-end encrypted (Noise) — the relay sees participants' IPs but not the content. Metadata is spread across 24 outbound peers from different /16s and ASNs — no single relay sees the full graph.

Relay is not a fallback but a connectivity guarantee for any NAT type. Hole punching is an optimization to reduce relay load.

Relay limits: up to 32 concurrent relay connections per node, bandwidth per relay ≤ baseline frame rate (1 KB/s). 32 × 1 KB/s = 32 KB/s ≈ 82 GB/month — acceptable for a home node with a public IP.

Obligation. Nodes with public IPs support relay — a personal network works when everyone can join. The reference implementation enables relay when a public IP is detected. Feeler connections check relay support on peers; nodes without relay are marked no-relay in the address manager. NAT'd nodes prefer relay-capable peers when picking outgoing connections.

All three mechanisms are libp2p standards (AutoNAT, DCUtR, Circuit Relay v2). Zero new protocol primitives.

Mesh Transport

Internet is not always available. State shutdowns (Iran 2019 — a week, Belarus 2020 — days, Myanmar 2021 — months), local outages, isolated zones. Montana keeps working under these conditions through mesh transport over Bluetooth Low Energy and Wi-Fi Direct — devices discover each other in physical radius and forward encrypted Montana messages hop by hop. Mesh does not replace internet transport, it complements it: when connectivity returns, the network automatically converges through a mesh-internet gateway.

Mesh transport is orthogonal to consensus, just like internet transport (the section above) — the state machine works over any delivery channel without changes.

MeshFrame wire format

All mesh messages are fragmented into fixed-format frames:

MeshFrame:
  mesh_protocol_version   u16    — mesh wire format version
                                   (0x0001 for v1)
  frame_type              u8     — 0=discovery, 1=data,
                                   2=ack, 3=forward
  ttl                     u8     — max 16 on creation,
                                   monotonic decrement
                                   decremented at each forwarding hop;
                                   ttl=0 → frame is dropped
  hop_count               u8     — 0 on creation,
                                   monotonic increment
                                   incremented at each forwarding hop
  sender_ref              32B    — mesh_session_id of the initiator
                                   (not the direct node_id for
                                    privacy at the mesh layer)
  recipient_hint          32B    — encrypted routing hint
                                   либо broadcast marker
                                   (0xFF × 32 = broadcast)
  payload_length          u16    — длина payload в байтах,
                                   ≤ 256
  payload                 variable — encrypted blob,
                                     ≤ 256B для fit в один
                                     BLE GATT notification
                                     без fragmentation
  mac                     16B    — HMAC-SHA-256 truncated,
                                   key derived через
                                   HKDF от shared session
                                   secret с domain separator
                                   "mt-mesh-frame-mac"

Итого: 64 + 256 + 16 = 336B максимум
       (64 байта header + 256 байт payload + 16 байт MAC)

Инварианты MeshFrame:

  • mesh_protocol_version ∈ {0x0001} для v1; иные значения reject
  • frame_type ∈ {0, 1, 2, 3}; иное → drop
  • ttl ∈ [0, 16]; при создании фрейма sender устанавливает ≤ 16; при каждом forward ttl := ttl - 1; если ttl = 0 и frame требует forwarding — drop
  • hop_count ∈ [0, 16]; при создании = 0; при каждом forward hop_count := hop_count + 1; если hop_count > 16 → drop (защита от malformed increment)
  • sender_ref = 32 байта = mesh_session_id отправителя (см. derivation ниже), не прямой node_id
  • recipient_hint = 32 байта; значение 0xFF × 32 обозначает broadcast, иное — encrypted routing hint; получатель проверяет соответствие self через локальное state
  • payload_length ≤ 256; строгое неравенство иначе → drop
  • payload длина точно payload_length байт; encrypted blob (шифрование выполнено на уровне session, mesh transport layer видит только ciphertext)
  • mac = 16 байт, HMAC-SHA-256(session_mac_key, header_bytes || payload) truncated до первых 16 байт; session_mac_key = HKDF-SHA-256(session_shared_secret, salt=empty, info="mt-mesh-frame-mac", length=32); mismatch MAC → drop + increment soft-blacklist counter для sender_ref
  • Signature verify rule: MeshFrame не подписывается ML-DSA-65 напрямую (MAC достаточен для integrity между двумя peer в установленной session); identity-level authentication выполняется один раз при mesh IBT handshake, subsequent frames authenticated через session MAC
  • Cross-field consistency: hop_count + ttl ≤ 16 в любом состоянии (initial: hop_count=0, ttl≤16; при каждом forward ttl := ttl - 1, hop_count := hop_count + 1, сумма инвариантна); нарушение hop_count + ttl > 16 — malformed frame, drop + increment soft-blacklist counter для peer из которого пришла frame

mesh_session_id derivation. Для каждой mesh сессии (между парой peers после mesh IBT handshake) выводится:

mesh_session_id = HKDF-SHA-256(
    ikm    = shared_secret_from_noise_handshake,
    salt   = mesh_session_nonce_initiator || mesh_session_nonce_responder,
    info   = "mt-mesh-session",
    length = 32
)

mesh_session_id используется в поле sender_ref вместо прямого node_id — mesh transport на уровне wire format не раскрывает identity отправителя случайному слушателю в радиусе. Identity раскрывается только peer с которым установлена сессия (они знают mesh_session_id).

Валидация MeshFrame.

  1. mesh_protocol_version совпадает с ожидаемой версией peer. Mismatch → drop, no forward.
  2. frame_type ∈ {0, 1, 2, 3}. Иное → drop.
  3. ttl ∈ [0, 16]. Если ttl=0 и frame пришёл для forwarding — drop.
  4. hop_count ≤ 16. Иное → drop (защита от malformed increment).
  5. payload_length ≤ 256. Иное → drop.
  6. mac verify через HMAC-SHA-256 с session key. Mismatch → drop, increment soft-blacklist counter sender_ref.
  7. Для frame_type = 3 (forward) — применить правила Store-and-Forward Semantics (ниже).

Mesh framing profile

Internet transport uniform framing (подраздел «Uniform Framing») не применяется к mesh transport. Mesh имеет независимый framing profile:

Internet (существующий):
  frame_size            = 1024 bytes
  baseline_rate         = 1 frame/сек
  контекст              = Noise_PQ XX AEAD stream over TCP/8444

Mesh (v1):
  frame_size            = 256 bytes (fit в BLE MTU типично
                          без application-level fragmentation
                          в большинстве стеков iOS/Android)
  baseline_rate         = 1 frame/10 сек (baseline advertisement
                          + occasional data, battery-sustainable)
  burst_mode_rate       = 1 frame/сек (активируется ТОЛЬКО при
                          активной mesh chat session, не continuous)
  burst_mode_duration   = ≤ 120 сек после последнего data frame,
                          затем возврат к baseline_rate
  fragmentation         = sequence numbers для сообщений > 256B,
                          reassembly на получателе через seq_id
                          в payload header уровня application

Обоснование параметров:

  • 256B — BLE MTU реально варьируется 23-512B, большинство современных iOS/Android поддерживают ≥ 247B, 256B выбран как compromise fit-without-fragmentation на mainstream устройствах
  • 1 frame/10 сек baseline — continuous Bluetooth scanning при более частом ритме съедает 30-50% батареи смартфона за несколько часов; 1/10s профиль extends battery usability до рабочего дня
  • burst до 1/сек — активная переписка требует reasonable responsiveness; активация по событию «активный chat session» ограничивает всплеск энергопотребления ко времени реального использования

Fragmentation

Сообщения превышающие 256B fragmented на уровне application перед enqueue в mesh:

ApplicationPayload (до fragmentation):
  fragment_count     u16   — общее число фрагментов
  fragment_index     u16   — index текущего фрагмента (0-based)
  message_id         32B   — unique id сообщения,
                             shared across всех фрагментов
  data               variable — часть encrypted payload

Получатель собирает фрагменты по message_id, порядок восстанавливается через fragment_index. Timeout reassembly: τ₁ от первого полученного фрагмента (по локальному кварцу транспортного слоя, outside [I-18] scope) — если не все собраны, partial drop. Fragment_index ≤ 255 (max 256 фрагментов × 256B payload = 64KB верхняя граница одного application message; большие объёмы — через Content Layer chunking на blob уровне).

Mesh discovery flow

  1. Устройство в mesh-активном режиме периодически (baseline_rate = 1 frame/10 сек) бродкастит frame_type = 0 (discovery) с sender_ref = mesh_session_id_self_generated и payload = short advertisement: protocol version, capability flags, optional trust hint.
  2. Другие устройства в радиусе принимают broadcast, извлекают advertisement.
  3. Если принимающее устройство считает инициатора потенциально интересным (известный контакт в адресной книге; broadcast addressed to broadcast marker и устройство в broadcast-listening mode; любое другое правило application) — оно инициирует mesh IBT handshake (см. «Identity-Bound Tunnel» выше, формула mesh_proof).
  4. После успешного handshake — session установлена, оба peer добавляют mesh_session_id в active sessions.
  5. Обмен данных происходит через frame_type = 1 (data).

Battery management

Reference implementation рекомендуется:

  • Scheduled Bluetooth scan: 1 раз в 10 секунд при baseline, чаще при burst
  • Wi-Fi Direct используется только для high-throughput сессий (передача больших файлов), не continuous
  • iOS background mode constraints: полный mesh transport работает только в foreground; в background доступно ограниченное Core Bluetooth BGTaskScheduler сканирование
  • Android: BLE advertisement и scanning в background — стандарт платформы, требует declared foreground service notification для compliance

Store-and-Forward Semantics

Mesh transport inherently async: получатель сообщения может быть вне радиуса в момент отправки. Store-and-forward semantics описывают как промежуточные устройства буферизуют и пересылают сообщения к их конечному получателю.

Buffer model

Каждое устройство в mesh-активном режиме поддерживает локальный buffer:

MeshBuffer (локальный state устройства):
  entries: map<frame_hash, BufferEntry>

BufferEntry:
  frame              MeshFrame
  receipt_seq        u64       (локальный sequence counter, инкрементируется
                                при каждом receive; используется для local FIFO
                                ordering в buffer; не передаётся в сеть)
  ttl_remaining      u8        (decremented каждый forwarding hop)
  sender_ref         32B       (из frame, для per-sender quota)
  forwarded_to       set<peer_id>  (peers которым уже переслано,
                                    защита от петель)

frame_hash = SHA-256(MeshFrame serialized) — ключ для идемпотентного recept.

Buffer policies

Capacity limits (по умолчанию, настраиваемо в реализации):

  • Max buffer size per device: 1024 frames (≈ 336 KB)
  • Max retention per frame: 1440 τ₁ (TTL expiry на buffer entry, эмерджентно ≈ 1 сутки на genesis-калибровке; независимо от ttl в frame который decremented per hop)
  • Max frames per sender_ref in buffer: 10 concurrent

Priority queue (при enqueue):

  1. Own sent frames (frames originated by this device) — highest priority
  2. Frames addressed to known contacts (locally stored) — high priority
  3. Frames addressed to unknown recipients (broadcast или unknown recipient_hint) — low priority

Drop policy при overflow:

  • Первое при переполнении — drop low-priority oldest
  • При исчерпании low-priority — drop high-priority oldest (не own)
  • Own frames не дропаются до expiry

Per-sender quota

Защита от flood DOS (вектор M1 из adversarial review):

Rate limits per sender_ref:
  max_frames_per_τ₁      = 10
  max_frames_in_buffer   = 10 concurrently

При превышении:
  - Новые frames от этого sender_ref дропаются
  - Sender_ref получает signed rate-limit ack с отказом
  - Soft-blacklist local: exponential backoff в τ₁,
    первое нарушение — 1 τ₁ ignore, второе — 2 τ₁,
    и т.д. до 60 τ₁ максимум

Signed rate-limit acks

Relay подписывает acknowledgement для каждой принятой (и forwarded или сохранённой) frame:

MeshAck:
  acked_frame_hash    32B   — SHA-256 frame которая acked
  relay_node_id       32B
  status              u8    (0=accepted, 1=buffered,
                             2=forwarded, 3=rejected_quota,
                             4=rejected_expired)
  ack_seq             u64   — relay-локальный монотонно
                              возрастающий sequence counter
                              ack-ов; не передаёт время,
                              только порядок выпуска ack-ов
                              у конкретного relay
  signature          3309B  — ML-DSA-65_sign(
                                relay_privkey,
                                "mt-mesh-ack"
                                || acked_frame_hash
                                || relay_node_id
                                || status
                                || ack_seq)

Инварианты MeshAck:

  • acked_frame_hash = SHA-256 over canonical serialization MeshFrame к которому относится ack; receiver ack'а проверяет что хэш соответствует реально отправленной frame
  • relay_node_id = SHA-256("mt-node" || relay_pubkey); receiver должен знать relay_pubkey для проверки подписи
  • status ∈ {0, 1, 2, 3, 4}; значение вне диапазона → reject ack как malformed
  • ack_seq — relay-локальный u64 sequence counter; инкрементируется на 1 при выпуске каждого ack данным relay; используется только для local ordering ack-ов на стороне получателя; не имеет временной семантики (не передаёт ни wall-clock, ни длительность); не consensus-critical, не участвует в state transitions
  • signature = 3309 байт ML-DSA-65, валидация через relay_pubkey; подписываемое сообщение канонически сериализовано в порядке перечисления полей (acked_frame_hash || relay_node_id || status || ack_seq)
  • Signature verify rule: ML-DSA-65.verify(relay_pubkey, domain_separator || canonical_payload, signature) = valid; иначе drop ack
  • Ack не применяется к state transitions — это чисто local signal для sender rate adjustment, вне scope consensus

Sender использует ack для:

  • Confirmation что frame принята (status ∈ {0, 1, 2})
  • Detection перегрузки (status=3 → flood suppression, уменьшить rate)
  • Detection expired frames (status=4 → frame outdated, не повторять)

Отсутствие ack в пределах τ₁/2 после отправки → sender предполагает relay недоступен, пробует другой peer.

Forwarding algorithm

on_receive(frame, from_peer):
  frame_hash = SHA-256(frame)
  if frame_hash in buffer:
    return  # дубликат, already processed

  if not validate_frame(frame):
    drop; increment soft-blacklist counter from_peer
    return

  if frame.sender_ref in soft_blacklist:
    drop silently
    return

  if buffer.sender_count(frame.sender_ref) >= 10:
    send_ack(frame, status=3)  # rejected_quota
    return

  if frame.recipient_hint matches self:
    deliver_to_application(frame)
    send_ack(frame, status=0)
    return

  if frame.ttl == 0:
    drop; send_ack(frame, status=4)
    return

  # forward case
  frame.ttl -= 1
  frame.hop_count += 1

  buffer.add(frame)
  send_ack(from_peer, frame, status=1)  # buffered

  # opportunistic forwarding
  for peer in active_mesh_peers:
    if peer not in frame.forwarded_to and
       peer != from_peer and
       peer accepts forwarding:
      send(peer, frame)
      frame.forwarded_to.add(peer)
      send_ack(from_peer, frame, status=2)  # forwarded

on_timer_expired(entry):
  # local buffer expiry, independent от frame.ttl
  buffer.remove(entry)

Interaction с internet

Когда устройство получает internet connectivity, оно опционально (по настройке пользователя) пересылает buffered mesh frames в internet-сеть:

  1. Для каждой frame в buffer с recipient_hint который можно разрешить в account_id
  2. Пересылка через обычный P2P gossip к Account Host получателя
  3. После успешного acknowledgement с internet-стороны — frame удаляется из mesh buffer
  4. Internet-to-mesh обратное направление аналогично: устройство с internet получает сообщение для offline-получателя, enqueues в mesh buffer для forwarding через ближайшие peers

Это делает internet-connected устройство gateway между internet-сетью и изолированной mesh-областью. Один такой шлюз восстанавливает связность для всего mesh-кластера до внешнего мира.

Privacy Scope (точная зона ответственности)

Прежде чем описать Семь слоёв сетевой защиты, фиксируем точные границы того что Montana защищает на сетевом уровне и что намеренно не закрывается.

Three-level decomposition

Privacy в Montana работает на трёх отдельных уровнях, каждый со своими гарантиями:

  1. Wire-level (transport layer): что видит провайдер / DPI / наблюдатель отдельного линка.

    • Защита: Noise_PQ XX (ML-KEM-768 KEM + ML-DSA-65 identity + ChaCha20-Poly1305 AEAD) + IBT + Uniform Framing + Transport Randomness + Dandelion++ + Label Rotation per τ₁ + Censorship-resistant discovery
    • Закрывает: local DPI, ISP, regulator с перехватом одного линка, small-medium Sybil eclipse, long-term recipient linkability через провайдеров приложений
    • НЕ закрывает: global passive adversary с GPS-precision timing-correlation на ВСЕХ backbone links одновременно (open research problem всей anon-net области)
  2. Content-level (application data): что видит хостящий узел или наблюдатель proposal-broadcast.

    • Защита: Anchor + ML-KEM-768 encryption (data_hash в proposal, content off-chain encrypted под recipient ключ) + Double Ratchet PQ для messenger end-to-end + PQXDH async handshake
    • Закрывает: content surveillance со стороны хоста, recipient identity через ephemeral labels
    • НЕ закрывает: endpoint compromise (RAT/spyware) — out of scope любого network protocol
  3. Financial-level: что видит любой наблюдатель cemented proposal.

    • Status: ОТКРЫТ per [I-2] — балансы, sender_account_id, recipient, amount публичны.
    • Это intentional design feature для регуляторной совместимости (FATF/MiCA), аудитируемости и [I-3] детерминизма consensus state.
    • Privacy финансовых движений на protocol level намеренно не предоставляется.

Privacy Tier model для пользователей

Пользователь выбирает уровень privacy по trade-off против latency и bandwidth:

Tier Stack Latency Bandwidth Threat closure
1 default Семь слоёв baseline (TLS+IBT+Dandelion+Label Rotation+Mesh) <500мс minimal Local DPI, ISP, regulator (один линк), small-medium Sybil, long-term recipient unlinkability
2 recommended + own узел (Light-Node-at-Home) + Tor entry + Noise_PQ handshake 500мс — 2с medium + ISP не видит «Montana traffic», + bypass legal request, + quantum store-now-decrypt-later
3 paranoid financial + canonical Mempool buffering + end-of-window batch flush + cover traffic 100% 60-120с full + temporal unlinkability sender's local act → operation appearance в state
4 research-grade + artificial random delay 5-30 мин + multiple onion paths 5-30 мин full + extreme protection from mass-surveillance flowtracking; practically maximum при non-information-theoretic anonymity

Tier 1 — автоматически для всех пользователей. Tier 2-4 — opt-in.

Canonical-aggregation closure для timing-correlation (unique Montana property)

Montana отличается от Tor / Loopix / I2P fundamentally в том как обрабатываются operations на сетевом уровне:

Tor / Loopix / I2P model:
  packet_α emit immediately → traverse network → arrive at recipient
  Adversary observes individual packet timing → correlates Alice IP ↔ Bob IP
  Deanonymization после 100-1000 messages

Montana model:
  Alice creates operation_α локально в момент T_local
  → Mempool buffer на узле-операторе (random hold N окон)
  → Dandelion++ stem 2-hop форвардинг
  → Operator's mempool → operator selection lottery
  → Aggregation: proposal_W = canonical(op_α, op_β, ..., op_ω)
                 включает 100-10000 operations from different accounts
  → Broadcast aggregate
  Adversary observes aggregate proposal, individual op_α неотличим от op_β
  Deanonymization требует 10⁶-10⁸ messages (orders of magnitude harder)

Уникальные primitives Montana которые работают вместе для этого свойства:

  • Mempool temporal buffering — Alice's individual emit timing decoupled от operation appearance в state
  • Canonical proposal aggregation — proposal содержит operations от множества accounts, individual flow смешан
  • Sequential-chain canonical aggregation point — все proposals публикуются в canonical моменты (end-of-window) — гомогенный stream events для adversary
  • Block Lattice independent AccountChain — каждый аккаунт пишет в свою цепочку, structurally separate но bundled в proposal
  • Dandelion++ stem buffering — operator entry скрыт через 2-hop stem propagation

Operations в Montana — это state events на canonical TimeChain, не ephemeral network messages. Этой fundamental architectural property нет ни в Tor (per-message routing), ни в Loopix (per-message Poisson mixing), ни в I2P (per-tunnel routing). Это даёт Montana уникально сильное ослабление global passive timing-correlation — не absolute closure (open research problem остаётся), но на порядки сильнее existing systems.

Honest claim — что закрывается и что не закрывается

Adversary class Tier 1 Tier 2 Tier 3 Tier 4
Local DPI / ISP через TLS+IBT через Tor
Government legal request to ISP ⚠️ partial — DPI закрыт, IP виден через Tor
Active probing через IBT
Sybil eclipse через 4-dim diversity
Sender authorship inference (ближайшие peers) через Dandelion++
Long-term recipient linkability через Label Rotation per τ₁
Hosting third-party metadata ⚠️ visible to host через own node
Content surveillance через Anchor encryption
Quantum store-now-decrypt-later TLS handshake X25519 vulnerable через Noise_PQ
Backbone GPS-precision timing-correlation ⚠️ partial through aggregation (10⁶ msg threshold) ⚠️ same + Tor obscures ⚠️ + temporal randomization ⚠️ + multi-path delays
Endpoint compromise (RAT) out of scope out of scope; mitigation через Light-Node-at-Home damage containment
Financial state visibility public per [I-2] by design same same same

Маркеры: closed; ⚠️ partial / mitigated; not closed (intentional или fundamental limit).

Endpoint compromise — Montana damage containment (architectural mitigation)

Network protocol не может prevent endpoint compromise. Но Montana архитектурно ограничивает damage через unique patterns:

  • Light-Node-at-Home split (App spec § 26): master_seed на home node, phone имеет только ephemeral session keys. Compromise phone ≠ compromise master key.
  • Chain-anchored ephemeral session rotation per τ₁: session key derivable через HKDF(master_seed, current_window || "session-W"). Maximum exposure window = 60 sec.
  • Junona local pre-processing: AI агент на own node делает decryption + summarization; phone никогда не имеет full content в memory.
  • Block Lattice sub-account hierarchy: phone использует daily-spend sub-account ($X/day limit); master savings account только на home node.
  • Hardware-backed enclave integration: master_seed в iOS Secure Enclave / Android StrongBox.

Сравнение с existing messengers:

System Endpoint compromise impact
Signal Compromise device = full chat history forever (single trust domain)
WhatsApp Compromise device = full history + cloud sync
Telegram Compromise device = full history + cloud + saved messages
Montana с Light-Node-at-Home Compromise phone = max loss sub_account_limit × 60_sec_window_content (multi-domain trust)

Что не покрывается ни одним tier

Два fundamental open problems которые не закрывает никто (включая Tor, Loopix, I2P):

  1. Global passive adversary с GPS-precision timing-correlation на всех backbone links одновременно. Montana уникально ослабляет через canonical aggregation (10⁶-10⁸ message threshold вместо 10²-10³ как в Tor), но не закрывает абсолютно — это open research problem с 1980-х в anon-net области.

  2. Endpoint compromise через RAT / hardware-level malware. Network protocol бесполезен — данные читаются до encryption. Montana архитектурно ограничивает damage через trust domain split, но не prevents compromise. Полная защита требует hardware secure enclave + verified boot + careful endpoint hygiene.

Эти ограничения honestly зафиксированы в spec — пользователь знает scope защиты до использования. Marketing-claim «полная анонимность» отвергается как overpromise который рухнёт при первом серьёзном аудите.

Семь слоёв — одна конструкция

Слой 1: Transport Obfuscation          персональный сервер скрывает содержимое и тайминг
Слой 2: Peer Selection                  start_window + network diversity constraints
Слой 3: NAT Traversal                   каждый может войти, даже за NAT
Слой 4: Censorship-Resistant Discovery  пять каналов, достаточно одного
Слой 5: Dandelion++                     пиры не знают кто автор операции
Слой 6: Mesh Transport                  работа при отключении internet,
                                         hop-by-hop Bluetooth / Wi-Fi Direct
Слой 7: Store-and-Forward Semantics     ephemeral буферизация в mesh,
                                         per-sender quota, signed acks

Каждый слой закрывает свой вектор. Ни один не требует внешней инфраструктуры. Всё построено поверх libp2p (для internet-слоёв 1-5) и нативных BLE/Wi-Fi Direct API (для mesh-слоёв 6-7) плюс существующего gossip. Сетевой уровень ортогонален консенсусу — ни один state transition не затронут.

Protocol Message Layer

Внутри IBT uniform frames протокольные сообщения следуют общему envelope format. Эта секция нормативно определяет wire format всех сообщений Монтаны для cross-implementation совместимости.

Envelope format.

ProtocolMessage:
  msg_type         1B    <- u8, код типа сообщения
  msg_version      1B    <- u8, версия формата сообщения (= 1 для v28.x)
  request_id       8B    <- u64 little-endian, correlation id для request/response (= 0 для one-way gossip)
  payload_length   4B    <- u32 little-endian, размер payload в байтах
  payload          ?B    <- payload_length байт, формат определяется msg_type

Envelope всегда 14 байт header + payload. Поскольку IBT uniform frames имеют payload 1021B, ProtocolMessage может занимать один или несколько фреймов через flag 0x04 continuation (см. Uniform Framing).

Реестр типов сообщений.

Код Тип Направление Payload
0x01 Transfer one-way gossip Transfer объект (Mode A или Mode B; serialize по canonical encoding; режим определяется длиной payload и наличием receiver в Account Table)
0x02 reserved Освобождён (ранее выделен под отдельную gossip-категорию активации; gossip envelope namespace независим от operation type-byte). Не выделять вновь.
0x03 ChangeKey one-way gossip ChangeKey объект
0x04 Anchor one-way gossip Anchor объект
0x10 NodeRegistration one-way gossip NodeRegistration объект
0x20 BundledConfirmation one-way gossip BundledConfirmation объект
0x21 VDF_Reveal one-way gossip VDF_Reveal объект
0x22 Proposal one-way gossip Proposal объект
0x40 FastSyncRequest request {anchor_window: u64, resume_offset: u64}
0x41 FastSyncResponse response chunked snapshot data (см. ниже)
0x42 FastSyncError response {code: u8, message: bytes[≤255]}
0x50 PeerListRequest request {max_count: u16}
0x51 PeerListResponse response {count: u16, peers: count × PeerEntry}
0x60 BatchLookupRequest request {query_type: u8, count: u8, queries: count × query_entry} (см. раздел «Batch Lookup Protocol»)
0x61 BatchLookupResponse response {query_type: u8, count: u8, results: count × result_entry}
0x62 BatchLookupError response {query_type: u8, error_code: u8}
0x63 RangeSubscribeRequest request {count: u16, labels: count × 32B} (см. раздел «Label Rotation + Range Subscribe Protocol»)
0x64 RangeSubscribeResponse response {blob_count: u16, blobs: blob_count × BlobEntry}
0x65 RangeSubscribeError response {error_code: u8}
0xF0 Ping request (no payload)
0xF1 Pong response (no payload)
0xFF Bye one-way {reason: u8} (graceful shutdown)

Message versioning: msg_version = 1 для всех v28.x. Изменение wire format = increment msg_version, требует protocol version upgrade.

Unknown msg_type → получатель логирует и игнорирует (forward compatibility). Unknown msg_version → получатель отвечает FastSyncError с кодом unsupported_version и разрывает соединение.

Structured payloads.

PeerEntry:

PeerEntry:
  ip_version       1B    <- u8, 0x04 или 0x06
  ip               16B   <- IPv4 в последних 4 байтах (первые 12 = 0x00) или IPv6 полностью
  port             2B    <- u16 little-endian
  node_id          32B
  start_window     8B    <- u64 little-endian, из Node Table
= 59 bytes fixed

FastSyncResponse chunked delivery:

FastSyncResponse chunk:
  chunk_index      4B    <- u32 little-endian, начинается с 0
  total_chunks     4B    <- u32 little-endian, общее число chunks для текущего запроса
  table_id         1B    <- u8: 0x01 Account, 0x02 Node, 0x03 Candidate, 0x04 Proposals
  record_count     4B    <- u32 little-endian, записей в этом chunk
  records          ?     <- record_count × serialize(record) по canonical encoding

Response состоит из N chunks (с одним request_id). Получатель собирает по chunk_index. После получения всех total_chunks — reconstructs Merkle root и проверяет против proposal_W.

Connection lifecycle.

Порядок установки соединения:

1. TCP SYN / SYN-ACK / ACK                (standard)
2. Noise_PQ XX handshake                   (3 messages: msg1=1184 B, msg2=7533 B, msg3=6349 B)
3. Noise key agreement внутри TLS          (mutual pubkey authentication)
4. IBT proof exchange                       (клиент отправляет ML-DSA-65 signature)
5. Access level determination               (node / candidate / account, см. Transport Obfuscation)
6. Готово к обмену ProtocolMessages

Timeouts установки (по локальному кварцу транспортного стека, outside [I-18] scope; emergent значения при genesis-калибровке):

  • TCP connect: τ₁/2
  • TLS handshake: τ₁/6
  • Noise + IBT: τ₁/6
  • Всё вместе не более 1 τ₁ до готовности

Если любой шаг превысил timeout → разрыв, retry с другим пиром.

Keepalive.

  • Ping раз в τ₁ на idle соединении (нет данных)
  • Pong должен прийти до завершения текущего τ₁ у получателя Pong
  • Три подряд τ₁ без полученного Pong → disconnect
  • При активном обмене данными Ping не обязателен (реальные данные = evidence активности)

Ping/Pong не несут payload — это чистая liveness-проверка. Любая локальная RTT-оценка отправителем — concern транспортного слоя его ОС (CLOCK_MONOTONIC kernel-level, outside scope [I-18]) и не передаётся в подписанных объектах.

Graceful shutdown.

Инициатор: отправляет Bye с reason code:

0x00 — normal shutdown
0x01 — going offline for maintenance
0x02 — peer list refresh (попытка найти лучших пиров)
0x03 — resource limits (слишком много соединений)
0x04 — protocol violation (валидация failed много раз)
0x05 — version mismatch

Получатель acknowledges через свой Bye, затем TLS close_notify, затем TCP FIN. Максимум τ₁/12 на graceful shutdown (по локальному кварцу транспортного стека, outside [I-18] scope), иначе forced close.

Peer discovery algorithm.

Новый узел при старте:

1. Извлечь bootstrap peers из Genesis Decree (захардкожено)
2. Выбрать 1-3 random bootstrap peer, connect (с PoW для bootstrap per Transport Obfuscation)
3. Выполнить IBT (account keypair для первого подключения нового узла)
4. Отправить PeerListRequest с max_count = 128
5. Получить PeerListResponse с до 128 известных peer-ов
6. Применить diversity constraints (/16, ASN, start_window) к полученному списку
7. Выбрать 24 outbound candidates по diversity
8. Параллельно connect к выбранным
9. После успешного IBT с реальным peer — disconnect от bootstrap (освобождая bootstrap slots)
10. Maintaining: PeerListRequest каждые ~τ₂ окон для обновления таблицы "проверенных" peers

Bootstrap exceptional:

  • PoW при подключении (target ~100ms CPU per Transport Obfuscation)
  • Ограничение: не более 3 одновременных bootstrap подключений на узел
  • Освобождается после 13 реальных peers connected

Peer exchange

Между двумя подключёнными узлами:

Каждые τ₂_windows:
  A → B: PeerListRequest {max_count: 64}
  B → A: PeerListResponse {peers[]}

Узел поддерживает две таблицы:

  • Новые peers: недавно узнанные (от bootstrap или PeerListResponse), ещё не использованные
  • Проверенные peers: те с которыми были успешные соединения в прошлом

При выборе outbound: 50/50 случайно из обеих таблиц. Bucket по секретному ключу узла предотвращает external enumeration.

Retry policy.

  • Failed connect: exponential backoff в локальных кварцевых секундах транспортного стека (1s, 2s, 4s, 8s, ..., max 5τ₁; outside [I-18] scope)
  • Peer rejected через IBT fail: peer помечается bad на 1τ₁
  • Peer disconnected с reason 0x04 (protocol violation): peer blacklisted на 24τ₁
  • Bootstrap PoW retry: no backoff (PoW сам служит rate limit)

Error codes для FastSyncError:

0x01 snapshot_unavailable       -- запрошенный anchor_window слишком старый (peer не хранит)
0x02 snapshot_too_large          -- snapshot больше чем peer готов отправить
0x03 unsupported_version         -- msg_version не поддерживается
0x04 resource_exhausted          -- peer перегружен
0x05 access_denied               -- peer не отдаёт Fast Sync клиентам (только nodes)

Сеть vs консенсус — граница.

Network layer параметры (timeouts, retry delays, keepalive intervals) — implementation guidance, могут варьироваться между реализациями без consensus impact. Значения в этой секции — рекомендуемые defaults. Consensus-critical: wire format (envelope, payloads), IBT proof format, Bootstrap PoW formula, message type codes. Изменение consensus-critical параметров требует protocol version upgrade.


Batch Lookup Protocol

Протокол обеспечивает baseline приватность lookup-запросов для account-only пользователей (тех, кто работает через чужой узел без собственной инфраструктуры). Когда клиент запрашивает информацию об аккаунтах (связка предварительных ключей, проверка существования), запрос группируется в batch из K элементов, среди которых ровно один — реальная цель, остальные — случайные decoy-аккаунты. Хост видит K-элементный запрос, но не знает какая из позиций real.

Механизм применяется только для cold-path lookups. Hot-path — уже известные контакты пользователя — разрешается локально на клиенте без обращения к сети (см. App spec раздел «Модуль обнаружения контактов»).

Константы

Определены в ProtocolParams Genesis Decree:

  • batch_lookup_k = 16 — обязательный размер batch. Отклонения запрещены (детализация: см. обоснование в разделе «Обоснование протокольных констант»).
  • max_batch_lookups_per_τ₁ = 16 — rate limit на один account per окно τ₁, защита от DoS на хоста.

Message type 0x60 — BatchLookupRequest

Payload format:

BatchLookupRequest:
  query_type    1B   <- u8: 0x01 pre_key_bundle, 0x03 account_exists
  count         1B   <- u8, обязательно == batch_lookup_k (= 16)
  queries       count × query_entry  (тип query_entry зависит от query_type)

где query_entry:
  query_type == 0x01 (pre_key_bundle):   32B account_id
  query_type == 0x03 (account_exists):   32B account_id

Клиент формирует batch: один real target + 15 decoy-аккаунтов, перемешанных в произвольном порядке. Клиент локально запоминает позицию real target внутри batch.

Инварианты BatchLookupRequest:

  • query_type ∈ {0x01, 0x03}; иное → reject UnsupportedType (error_code 0x02)
  • count == batch_lookup_k (строгое равенство, = 16); иное → reject InvalidCount (error_code 0x03)
  • Размер queries[] точно count × entry_size(query_type) байт
  • Source account (IBT-authenticated sender) активен в Account Table; max_batch_lookups_per_τ₁ не превышен за текущее окно
  • При превышении rate limit → reject RateLimited (error_code 0x01)

Message type 0x61 — BatchLookupResponse

Payload format:

BatchLookupResponse:
  query_type    1B
  count         1B   <- = count из request (16)
  results       count × result_entry  (в том же порядке, что и queries)

где result_entry:
  query_type == 0x01 (pre_key_bundle):   4B length prefix + variable ML-KEM-768 bundle
                                         (length=0 → bundle отсутствует / never published)
  query_type == 0x03 (account_exists):   1B (0x00 → not found, 0x01 → exists)

Хост обязан обработать все count queries и вернуть count results в том же порядке. Частичные ответы запрещены — либо полный BatchLookupResponse, либо BatchLookupError.

Message type 0x62 — BatchLookupError

BatchLookupError:
  query_type    1B
  error_code    1B   <- 0x01 RateLimited, 0x02 UnsupportedType, 0x03 InvalidCount

Validation workflow хоста

  1. Проверить IBT-аутентификация клиента (уровень 3 accepted для account-only пользователей)
  2. Проверить structural инварианты BatchLookupRequest
  3. Проверить rate limit для client account
  4. Выполнить count lookups против локального state (Account Table, cemented Anchor archive)
  5. Собрать count results в том же порядке что queries
  6. Отправить BatchLookupResponse

Хост не логирует individual queries для privacy hygiene — только aggregate rate counters per-account для enforcement лимита.

Effective privacy analysis

На целевом масштабе сети до ~1B активных аккаунтов (архитектурная цель; один только AccountRecord state ≈2.06 TB, fast-sync benchmarks остаются M7 gate) клиент собирает passively-observed pool активных аккаунтов через gossip proposals. Realistic pool size: 10K100K накопленных за τ₂ observation window.

При pool size 10K100K и K=16:

  • Effective anonymity: ~23 бита (1-in-4 до 1-in-8 practical protection)
  • Intersection attack resistance: intersection attack требует ~1000+ batches observation (~десятилетия активности) — практически нерелизуема
  • Semantic filtering: клиент обязан использовать per-function dummy pools (pre-key bundles только от accounts published bundle, и т.д.) — детализация в App-спеке

Это partial protection, не абсолютная. Полное закрытие lookup-поверхности — через собственный узел (Light-Node-at-Home в App-спеке). Протокол делает максимум возможного при ограничениях [I-5] (commodity hardware, без PIR), [I-6] (без privacy mixers) и [I-7] (минимальная крипто-поверхность).

Rate limit rationale

max_batch_lookups_per_τ₁ = 16 при K=16 даёт максимум 256 queries per аккаунт per окно τ₁. Типичная активность пользователя мессенджера: ≤ 50 queries per sessions, несколько sessions per day. Лимит покрывает reasonable usage и защищает хоста от DoS amplification.

При превышении лимита клиент получает RateLimited error и обязан применить exponential backoff до следующего окна τ₁.

Применимость инвариантов

  • [I-5] Commodity hardware: ноль тяжёлых крипто операций, только SHA-256 compare для lookups — стандартный read. Работает на любом commodity узле.
  • [I-6] Регуляторная совместимость: plaintext batch lookup = bulk read operation, не privacy mixer, не ring signature, не stealth address, не hidden flow. Host видит все K queries явно.
  • [I-7] Минимальная крипто-поверхность: ноль новых крипто примитивов.
  • [I-15] Time-based scarcity: rate limiting через max_batch_lookups_per_τ₁ — time-based защита, соответствует [I-15].
  • [I-16] Out-of-band identity binding: batch lookup предшествует первому сообщению; client получает pre-key bundle, вычисляет отпечаток, показывает пользователю для out-of-band сверки. Совместимо с [I-16] по конструкции.

Label Rotation + Range Subscribe Protocol

Протокол baseline приватности для Blob Buffer polling пользователями account-only (тех кто работает через чужой узел). Защищает от long-term session identification через статические queue labels. Включает механизм catch-up для пользователей, возвращающихся онлайн после периода offline.

Механизм применяется к клиентскому слою (messenger sessions). Rotation формулы — authoritative в App-спеке раздел 23.2 (single source of truth). Catch-up protocol (RangeSubscribe) — protocol-level message types.

Что закрывается и что остаётся открытым

Closed через rotation:

  • Long-term session identification. Хост не может построить stable map account_X → {sessions_X} потому что queue labels меняются каждый τ₁. Набор наблюдаемых labels за разные окна нельзя correlate без знания initial_root_key сессии.
  • Historical reconstruction через архивные логи хоста. Даже сохранённые label наблюдения нельзя decompose в session identity без session keys.

Permanent architectural limits (не закрываются на protocol level для account-only):

  • Session count. Хост видит количество активных label subscriptions per τ₁ как proxy для числа активных сессий. Сокрытие требует cover traffic, которая при self-cover отличима от real по provenance (blob arriving from client's own IBT vs external gossip). Protocol-level ambient cover требует продолжительной фоновой генерации фиктивных сообщений и не scales на 1B. Архитектурно непреодолимо в рамках инвариантов Монтаны.
  • Activity timing patterns. Хост видит когда клиент публикует и получает сообщения. Защита требует constant-rate cover — те же ограничения что session count.
  • Cross-host collusion per-τ₁. Если хост Alice и хост Bob координируются — pair identification возможна за один τ₁ (publish-receive correlation). Rotation защищает от long-term накопления, не от per-τ₁ collusion.

Полная защита от этих трёх классов — только Light-Node-at-Home (см. App-спека раздел 26). Свой узел = no third-party observer = эти leaks не существуют для данного пользователя.

Label rotation formula

Queue labels для session ротируются детерминистически каждый τ₁ на основе текущего window_index. Authoritative формула — в App spec раздел 23.2.

Краткое описание: label derivation использует HKDF-SHA-256 с initial_root_key сессии как IKM, session_id как salt, и "mt-queue-rotation" || direction_byte || W.to_le_bytes_8 как info. Клиенты обеих сторон session детерминистически выводят одинаковый label для одинакового окна.

Sync tolerance: получатель подписан на labels для W ∈ {W_current, W_current 1} — двухоконная tolerance к рассинхронизации канонических окон между участниками.

Message type 0x63 — RangeSubscribeRequest

Для пользователей, возвращающихся онлайн после периода offline. Клиент вычисляет labels локально для нужного диапазона windows и запрашивает blobs от хоста.

Payload format:

RangeSubscribeRequest:
  count        2B   <- u16 LE, число labels в запросе (≤ max_range_labels_per_request = 10 000)
  labels       count × 32B   <- client-computed queue labels для нужных (session × window) пар

Инварианты RangeSubscribeRequest:

  • count ≤ max_range_labels_per_request (= 10 000); иное → reject RangeTooLarge
  • Labels — 32-байтовые opaque identifiers, хост не проверяет их semantic validity (просто ищет совпадения в Blob Buffer)
  • Source account (IBT-authenticated sender) активен в Account Table
  • Rate limit: max_range_subscribes_per_τ₁ = 16 per account per окно; превышение → reject RateLimited

Message type 0x64 — RangeSubscribeResponse

Payload format:

RangeSubscribeResponse:
  blob_count   2B   <- u16 LE, число найденных blobs
  blobs        blob_count × BlobEntry

где BlobEntry:
  matched_label  32B   <- один из labels запроса
  blob_size      4B    <- u32 LE, размер blob в байтах
  blob_data      blob_size × B   <- encrypted payload

Хост возвращает все blobs из Blob Buffer чей app_id соответствует одному из запрошенных labels (через derivation app_id = SHA-256("mt-app" || label)). Blobs возвращаются в произвольном порядке; клиент matches их к labels через matched_label поле.

Message type 0x65 — RangeSubscribeError

RangeSubscribeError:
  error_code   1B   <- 0x01 RateLimited, 0x02 RangeTooLarge, 0x03 ResourceExhausted

Validation workflow хоста

  1. Проверить IBT-аутентификация клиента (уровень 3 для account-only)
  2. Проверить count ≤ max_range_labels_per_request
  3. Проверить rate limit max_range_subscribes_per_τ₁
  4. Для каждого label в запросе — lookup в локальном Blob Buffer по app_id
  5. Собрать все matched blobs в response
  6. Отправить RangeSubscribeResponse (либо RangeSubscribeError при failure)

TTL bound. Blob Buffer имеет TTL = τ₂ (~14 дней). Labels для окон старше τ₂ — в Blob Buffer их уже нет, результат match будет пустой. Клиент может запрашивать любые labels, но имеет смысл запрашивать только до τ₂ назад.

Эффективность на 1B scale

Worst case offline 1440 τ₁:

  • 100 sessions × 1440 windows × 2 (double-window derivation) = 288 000 labels на catch-up
  • 10 000 labels per request → 29 requests
  • 16 per τ₁ rate limit → catch-up за 2 τ₁

Worst case offline 1 τ₂ (полный TTL):

  • 100 × 20 160 × 2 = 4.03M labels
  • 403 requests → 26 τ₁ → ~26 минут catch-up

Хост load:

  • 1000 клиентов × 10K SHA-256 compares = 10M lookups per request cycle
  • SQLite-style read ≤ 10 µs per lookup → 100 sec CPU per 1000 clients
  • Spread по catch-up window — peak CPU ~10% при одновременном возвращении клиентов после массового offline event

Работает на 1B.

Применимость инвариантов

  • [I-1] PQ-secure: SHA-256 label compare + HKDF-SHA-256 label derivation. ✓
  • [I-2]: не затронут (client-layer, не consensus). ✓
  • [I-3]: labels — client-layer derived, не consensus state. ✓
  • [I-5] Commodity hardware: HKDF trivial на любом CPU. ✓
  • [I-6] Регуляторная совместимость: RangeSubscribe = bulk read операция, не privacy mixer. Labels сами по себе видны хосту явно. ✓
  • [I-7] Минимальная крипто-поверхность: reuse existing HKDF-SHA-256. ✓
  • [I-14] State lifecycle: labels ephemeral, blobs через TTL τ₂. Без persistent consensus state. ✓
  • [I-15] Time-based scarcity: rate limit через max_range_subscribes_per_τ₁. ✓
  • [I-16] Out-of-band identity binding: ортогонально. ✓

Rate limit rationale

max_range_subscribes_per_τ₁ = 16 при max_range_labels_per_request = 10 000 даёт максимум 160 000 labels в запросах per account per τ₁. Покрывает catch-up после 1 часа offline с запасом. Для более длительного offline клиент делает catch-up за несколько τ₁ — приемлемо.

max_range_labels_per_request = 10 000 — balance между single request capacity и host CPU load per request. 10K SHA-256 lookups ≈ 100 мс CPU на average SQLite — single request processable в реальном времени.


Карточки замыкания механизмов сетевого слоя

Каждый механизм сетевого уровня закрывается стандартной карточкой из 11 пунктов (раздел роли «Замыкание механизмов») плюс проверкой по 15 глобальным инвариантам (раздел роли «Обязательная карточка механизма»). Сетевой слой ортогонален consensus state, поэтому большинство инвариантов помечены n/a единообразно — explicit n/a важнее implicit отсутствия пункта (Gate 13a invariant enumeration completeness применён к карточкам).

Карточка — IBT online proof

Объект:                     подписанный proof принадлежности к идентичности перед серверным узлом
Создатель:                  клиент (узел / candidate / account)
Проверяет:                  серверный узел (lookup по Node Table → Candidate Pool → Account Table)
Формат сериализации:        ML-DSA-65 signature (3309 B) над байтовой строкой
                            "mt-tunnel-online" || server_node_id || floor(W / 2) ||
                            online_session_nonce (32 B CSPRNG)
Состояние:                  ephemeral, не хранится; per-connection, отбрасывается при disconnect
Какой root:                 не входит ни в один root (transport-layer, ортогонален consensus state)
Срок жизни:                 2 окна — window slot = current ИЛИ previous (acceptable bound)
Истечение:                  connection drop; reconnect требует свежего proof
Конфликт:                   replay protection трёхслойная: `server_node_id` binding к
                            получателю, window slot bound (2 окна), per-nonce tracking
                            `used_online_nonces[client_pubkey]` внутри window slot.
                            Cross-context replay блокируется доменным разделителем
                            `mt-tunnel-online` отдельно от `mt-tunnel-mesh`
Цена злонамеренного:        brute-force ML-DSA-65 secret key (NIST level 3 — квантово-эквивалентно
                            192-битной симметричной стойкости, infeasible)
State transition:           не участвует — pure transport gate

Object class:                       value (transport gate)
Canonical inclusion:                no
Can delay change future power:      no
Can absence change state:           no (no proof = no connection, не state change)
Seed inputs canonical:              n/a (нет seed inputs)
Expiry exploitable by streak:       no (replay window = 2 окна, slot tied to canonical W)
Temporal anchors bounded:           yes (`floor(W / 2)`, нижняя/верхняя границы канонические)

Global invariant check:
  [I-1] PQ-secure:                       yes (ML-DSA-65, NIST level 3)
  [I-2] Public financial layer:          n/a
  [I-3] Deterministic state:             n/a (transport-orthogonal)
  [I-4] TimeChain independence:          n/a
  [I-5] Commodity hardware:              yes (ML-DSA-65 verify ≪1 мс на commodity CPU)
  [I-6] Regulatory compat:               yes (стандартный TLS + клиентская auth — паттерн
                                              корпоративных порталов и банковских API,
                                              миллионы серверов в интернете)
  [I-7] Minimal crypto surface:          yes (использует существующие ML-DSA-65 + SHA-256,
                                              новых примитивов не вводит)
  [I-8] Network-bound unpredictability:  n/a (auth, не consensus seed)
  [I-9] Bit-exact deterministic:         n/a (нет численных формул)
  [I-10] Single Source of Truth:         yes (формула в одном месте — раздел «IBT» выше)
  [I-11] Nickname uniqueness:            n/a
  [I-12] Auction determinism:            n/a
  [I-13] Deflationary sink:              n/a
  [I-14] State lifecycle:                n/a (ephemeral)
  [I-15] Time-based scarcity:            yes (anti-spam через window slot rate-limit
                                              на стороне сервера — proof валиден только
                                              в текущем или предыдущем окне)

Status:                             закрыто

Карточка — IBT mesh proof

Объект:                     подписанный proof для mesh peer без свежего window_index
Создатель:                  клиент (узел) в mesh-режиме (BLE / Wi-Fi Aware, без internet)
Проверяет:                  принимающий mesh peer
Формат сериализации:        ML-DSA-65 signature (3309 B) над байтовой строкой
                            "mt-tunnel-mesh" || peer_node_id || floor(cached_W / 2)
                                              || mesh_session_nonce
                            где cached_W: u32, mesh_session_nonce: 32 B
Состояние:                  ephemeral для самого proof; per-sender persistent set
                            `used_nonces[sender_pubkey]` для replay tracking
Какой root:                 не входит ни в один root (transport-layer)
Срок жизни:                 cached_W валиден в окне `[peer.known_W  7·τ₁, peer.known_W]`;
                            свыше — peer отклоняет mesh handshake
Истечение:                  записи в `used_nonces[sender_pubkey]` старше 7·τ₁ удаляются
                            (cached_W уже невалиден, повторное использование nonce безопасно)
Конфликт:                   per-nonce replay tracking — `mesh_session_nonce ∈ used_nonces[sender_pubkey]`
                            → silent reject; cross-context replay блокируется доменным
                            разделителем `mt-tunnel-mesh` отдельно от `mt-tunnel-online`
Цена злонамеренного:        brute-force ML-DSA-65 secret key (level 3); расширенный staleness
                            window 7·τ₁ компенсирован per-nonce tracking — replay одного
                            и того же proof невозможен повторно
State transition:           не участвует — pure transport gate; локальная mutation
                            `used_nonces` set не входит в consensus state

Object class:                       value (transport gate)
Canonical inclusion:                no
Can delay change future power:      no
Can absence change state:           no
Seed inputs canonical:              n/a
Expiry exploitable by streak:       no (TTL cleanup детерминирован cached_W validity)
Temporal anchors bounded:           yes (`[known_W  7·τ₁, known_W]`, обе границы explicit)

Global invariant check:
  [I-1] PQ-secure:                       yes (ML-DSA-65, NIST level 3)
  [I-2] Public financial layer:          n/a
  [I-3] Deterministic state:             n/a (локальный set вне consensus root)
  [I-4] TimeChain independence:          yes (mesh оперирует cached_W из любого предыдущего
                                              online handshake / gossiped proposal,
                                              не требует live TimeChain progression)
  [I-5] Commodity hardware:              yes (BLE / Wi-Fi Aware есть на любом смартфоне
                                              и ноутбуке; ML-DSA-65 verify дёшев)
  [I-6] Regulatory compat:               yes (mesh handshake — собственная сетевая
                                              процедура, не privacy mixer)
  [I-7] Minimal crypto surface:          yes (один доменный сепаратор + существующие
                                              примитивы)
  [I-8] Network-bound unpredictability:  n/a
  [I-9] Bit-exact deterministic:         n/a
  [I-10] Single Source of Truth:         yes (формула в одном месте — раздел «Mesh
                                              transport IBT extension» выше)
  [I-11] Nickname uniqueness:            n/a
  [I-12] Auction determinism:            n/a
  [I-13] Deflationary sink:              n/a
  [I-14] State lifecycle:                yes — путь 2 (temporal): записи `used_nonces`
                                              удаляются после 7·τ₁ (auto-pruning по
                                              cached_W expiry); локальная Storage Card
                                              для `used_nonces` фиксирует bound
                                              (см. раздел «Локальные сетевые таблицы —
                                              Storage Cards», заполняется отдельной
                                              правкой)
  [I-15] Time-based scarcity:            yes (acceptable staleness 7·τ₁ — time-based
                                              ограничитель; per-sender quota на nonce
                                              create rate ограничивает spam)

Status:                             закрыто

Карточка — Bootstrap proof-of-work

Объект:                     anti-flood защита для подключения к hardcoded genesis bootstrap
                            узлам, у которых отсутствует Account Table verification
Создатель:                  клиент (любой ML-DSA-65 keypair) при первом подключении к bootstrap
Проверяет:                  bootstrap узел (один из 12 hardcoded в Genesis Decree)
Формат сериализации:        nonce: 32 B; proof = IBT online proof bytes; верификатор пересчитывает
                            SHA-256("mt-bootstrap-pow" || proof || nonce) и сравнивает с target
Состояние:                  ephemeral, отбрасывается после accept либо reject
Какой root:                 не входит ни в один root
Срок жизни:                 валиден на время одной попытки handshake; новое подключение требует
                            новый nonce (фиксированного TTL у самого PoW нет — сервер хранит
                            recent_nonces для anti-replay в окне τ₁)
Истечение:                  любой connection close (success либо fail); recent_nonces очищается
                            на каждой τ₁ boundary
Конфликт:                   recent_nonces lookup на bootstrap; повтор nonce → reject
Цена злонамеренного:        ≈100 мс CPU per попытка (target подобран под этот budget); атакующий
                            при rate 10 connections/сек тратит 1 CPU-сек/сек = одно постоянно
                            занятое ядро на каждый bootstrap; 12 bootstrap × 1 ядро = 12 ядер
                            постоянной нагрузки для distributed flood, что покрывается обычной
                            sysadmin response (rate-limit на сетевом уровне, fail2ban)
State transition:           не участвует

Object class:                       value (anti-flood gate)
Canonical inclusion:                no
Can delay change future power:      no
Can absence change state:           no
Seed inputs canonical:              n/a
Expiry exploitable by streak:       no (per-connection, recent_nonces очищается per τ₁)
Temporal anchors bounded:           yes (recent_nonces TTL = τ₁)

Global invariant check:
  [I-1] PQ-secure:                       yes (SHA-256 — Grover-resistant до 128 бит,
                                              для anti-flood gate приемлемо)
  [I-2] Public financial layer:          n/a
  [I-3] Deterministic state:             n/a (transport-orthogonal)
  [I-4] TimeChain independence:          yes (PoW верификация локальна, не требует
                                              consensus state; bootstrap должен работать
                                              для впервые подключающегося узла, который
                                              ещё не имеет TimeChain копии)
  [I-5] Commodity hardware:              yes (SHA-256 grinding ≪100 мс на любом CPU)
  [I-6] Regulatory compat:               yes (PoW — стандартная anti-DoS техника,
                                              аналог hashcash в email)
  [I-7] Minimal crypto surface:          yes (только SHA-256 + существующая ML-DSA-65
                                              подпись из IBT proof)
  [I-8] Network-bound unpredictability:  n/a (нет consensus seed)
  [I-9] Bit-exact deterministic:         yes — `target` derivation: integer-specified
                                              как `target = (2^256) / difficulty_factor`
                                              где `difficulty_factor` = константа из
                                              Genesis Decree (см. отдельную правку
                                              protocol_params для добавления поля
                                              `bootstrap_pow_difficulty`)
  [I-10] Single Source of Truth:         yes (формула в одном месте — раздел «Bootstrap
                                              exception» выше)
  [I-11] Nickname uniqueness:            n/a
  [I-12] Auction determinism:            n/a
  [I-13] Deflationary sink:              n/a
  [I-14] State lifecycle:                yes — путь 2 (temporal): recent_nonces TTL τ₁
  [I-15] Time-based scarcity:            yes — PoW использует CPU-время как scarce ресурс,
                                              что соответствует time-market принципу
                                              протокола без денежных комиссий

Status:                             закрыто (с открытым sub-finding на формализацию
                                    `bootstrap_pow_difficulty` в Genesis Decree
                                    `protocol_params` — закроется отдельной правкой
                                    раздела II.5 плана сетевого слоя)

Карточка — Uniform Framing

Объект:                     транспортный фрейм фиксированного размера, скрывающий
                            тайминг и размер реальных сообщений Монтаны
Создатель:                  отправитель (любой узел / candidate / account внутри IBT-сессии)
Проверяет:                  получатель — структура (1B flags + 2B length + 1021B payload)
                            и semantic constraints (length ≤ 1021, flags корректны)
Формат сериализации:        flags 1B (0x01 data, 0x02 padding, 0x04 continuation;
                            битовая комбинация data | continuation допустима для
                            многофреймовых ProtocolMessage); length 2B u16 little-endian
                            (≤ 1021); payload 1021B (real data до length, далее random
                            padding до конца frame)
Состояние:                  ephemeral, не хранится; per-frame отбрасывается после
                            decrypt + parse + dispatch к ProtocolMessage layer
Какой root:                 не входит ни в один root (transport-only)
Срок жизни:                 длительность одного TCP segment / TLS record;
                            scheduler выдерживает baseline 1 frame/сек на исходящем
                            соединении, max burst ≤ 8 frames без паузы ≥ 10 мс
Истечение:                  каждый frame обрабатывается синхронно или drop
                            при backpressure (drop policy фиксируется в карточке
                            ProtocolMessage layer)
Конфликт:                   нет (не подписанный объект; corruption детектится
                            на уровне TLS MAC, frame с невалидной структурой
                            дисциплинированно отбрасывается)
Цена злонамеренного:        атакующий внутри IBT (т.е. валидно прошедший proof)
                            может посылать padding фреймы — это рост его
                            transport quota (см. backpressure rules), не state damage;
                            external observer не различает data от padding (TLS shield)
State transition:           не участвует

Object class:                       value (transport encapsulation)
Canonical inclusion:                no
Can delay change future power:      no (frame schedule оперирует локальным кварцем
                                        транспортного стека, outside [I-18] scope)
Can absence change state:           no (отсутствие frames = idle connection,
                                        не state change)
Seed inputs canonical:              n/a
Expiry exploitable by streak:       no (per-frame disposable)
Temporal anchors bounded:           n/a (нет привязки к консенсусным окнам)

Global invariant check:
  [I-1] PQ-secure:                       n/a (encapsulation, не cryptographic primitive
                                              — confidentiality обеспечивается TLS
                                              слоем выше; padding bytes из CSPRNG
                                              достаточны без PQ requirement)
  [I-2] Public financial layer:          n/a
  [I-3] Deterministic state:             n/a (transport-orthogonal; frame scheduling
                                              non-deterministic by design — иначе
                                              атакующий предсказывает padding pattern)
  [I-4] TimeChain independence:          yes (frame scheduling использует локальный
                                              кварц, не TimeChain progression)
  [I-5] Commodity hardware:              yes (1024 B frame × 1 fps = 1 KB/сек на
                                              исходящее соединение; 24 outbound × 1
                                              fps × 1024 B ≈ 13 KB/сек ≈ 33 GB/мес —
                                              приемлемо для домашнего сервера)
  [I-6] Regulatory compat:               yes (uniform encryption traffic — стандартный
                                              паттерн HTTPS, неотличим от обычного
                                              web-трафика для DPI)
  [I-7] Minimal crypto surface:          yes (использует только OS CSPRNG, новых
                                              примитивов не вводит)
  [I-8] Network-bound unpredictability:  n/a (transport, не consensus seed)
  [I-9] Bit-exact deterministic:         partial — wire format frame structure
                                              integer-specified; frame scheduling
                                              non-deterministic (CSPRNG-based) —
                                              это требование, не bug
  [I-10] Single Source of Truth:         yes (frame layout в одном месте — раздел
                                              «Uniform Framing» выше)
  [I-11] Nickname uniqueness:            n/a
  [I-12] Auction determinism:            n/a
  [I-13] Deflationary sink:              n/a
  [I-14] State lifecycle:                n/a (ephemeral)
  [I-15] Time-based scarcity:            yes — baseline 1 fps на исходящем
                                              соединении сам по себе rate-limit:
                                              attacker не может flood-ить выше
                                              ≤8 frames burst + 10 мс пауза;
                                              время — scarce ресурс ограничивающий
                                              throughput

Status:                             закрыто

Карточка — Transport Randomness

Объект:                     источник случайности для транспортного слоя
                            (padding bytes, frame jitter, stem routing choice
                            в Dandelion++, mesh_session_nonce, bootstrap PoW nonce,
                            backoff jitter)
Создатель:                  локальный сетевой стек узла, при каждом случае
                            где требуется недетерминированный выбор
Проверяет:                  никто внешне (это локальный random — не verifiable
                            input; verifiability недостижима для transport
                            randomness by design — иначе атакующий предсказывает)
Формат сериализации:        n/a (внутренний bytes stream, не сериализуется)
Состояние:                  ephemeral; OS entropy pool управляется ядром,
                            никаких consensus-state записей
Какой root:                 не входит ни в один root
Срок жизни:                 каждый запрос — fresh bytes; entropy pool пополняется
                            ядром автоматически
Истечение:                  не применимо — pull-as-needed
Конфликт:                   нет (внутренний источник; conflicting requests
                            обслуживаются ядром sequentially)
Цена злонамеренного:        compromise OS entropy = full compromise узла на других
                            уровнях; mitigations — стандартный ОС hardening
                            (вне scope протокола)
State transition:           не участвует

Object class:                       value (entropy source)
Canonical inclusion:                no
Can delay change future power:      no
Can absence change state:           no
Seed inputs canonical:              no — by design; transport randomness
                                        обязана быть unverifiable извне
Expiry exploitable by streak:       no
Temporal anchors bounded:           n/a

Global invariant check:
  [I-1] PQ-secure:                       yes (OS CSPRNG — современные ядра
                                              используют ChaCha20 / AES-CTR DRBG +
                                              hash-based reseed: post-quantum
                                              symmetric primitives, Grover-resistant
                                              до 128 бит при ≥256-битной seed)
  [I-2] Public financial layer:          n/a
  [I-3] Deterministic state:             n/a — transport randomness OBLIGATORILY
                                              non-deterministic; consensus state
                                              использует other randomness sources
                                              ([I-8] cemented bundle aggregate)
  [I-4] TimeChain independence:          yes (OS CSPRNG локален, не зависит от
                                              TimeChain progression)
  [I-5] Commodity hardware:              yes (OS CSPRNG доступен на любой OS;
                                              hardware RNG (RDRAND, ARM TrustZone)
                                              используется ядром если доступен,
                                              иначе software fallback)
  [I-6] Regulatory compat:               yes (стандартное OS API)
  [I-7] Minimal crypto surface:          yes (используется только OS-provided
                                              entropy, никаких custom CSPRNG
                                              реализаций в коде Монтана)
  [I-8] Network-bound unpredictability:  n/a — explicit boundary: transport
                                              randomness orthogonal к consensus
                                              randomness; их никогда не смешивать
                                              в одной hash composition. PRNG от
                                              node state ЗАПРЕЩЁН для transport
                                              (см. spec строка «Transport Randomness»)
  [I-9] Bit-exact deterministic:         n/a (non-deterministic by design)
  [I-10] Single Source of Truth:         yes (правило «OS CSPRNG, не PRNG от node
                                              state» в одном месте — раздел
                                              «Transport Randomness» выше)
  [I-11] Nickname uniqueness:            n/a
  [I-12] Auction determinism:            n/a
  [I-13] Deflationary sink:              n/a
  [I-14] State lifecycle:                n/a
  [I-15] Time-based scarcity:            n/a (entropy — не scarce ресурс
                                              на уровне протокола; rate-limited
                                              ядром если pool пуст, что является
                                              OS-level concern)

Status:                             закрыто

Карточка — Peer selection (выбор пиров)

Объект:                     процедура подбора набора активных peer-узлов
                            для данного локального узла, обеспечивающая
                            устойчивость к eclipse через 4-мерную
                            diversity-конструкцию
Создатель:                  локальный сетевой стек узла
Проверяет:                  локальный сетевой стек (внешней верификации нет —
                            это локальная политика; внешняя видимость только
                            через peer exchange)
Формат сериализации:        n/a для самой процедуры; PeerRecord (см. spec
                            раздел «PeerRecord») — 32B node_id + ML-DSA-65
                            pubkey 1952B + IPv4/v6 + port + start_window
                            + last_seen_window + AS-номер + /16 префикс
Состояние:                  локальная PeerRecord table — две части:
                            «новые» (от bootstrap / PeerListResponse, ещё не
                            использованные) и «проверенные» (успешные соединения
                            в прошлом); хранится в RocksDB вне consensus state
Какой root:                 не входит ни в один root
Срок жизни:                 запись хранится пока last_seen_window ≥ current  N
                            (точное N — параметр локального стека, default 8·τ₁
                            при отсутствии успешных contact attempts);
                            автоматическое pruning по этому критерию
Истечение:                  pruning старых записей; rotation 1 outbound peer per
                            τ₂ (см. spec «Ротация»); replace при connect failure
                            > threshold
Конфликт:                   peer claims конфликтующий node_id с записью в Node
                            Table при IBT handshake → reject IBT, peer blacklist
                            на τ₁; конфликт записей в локальной таблице между
                            «новыми» и «проверенными» — wins «проверенные»
Цена злонамеренного:        eclipse требует контроль ≥¾ outbound slots; 4 уровня
                            diversity (/16, ASN, start_window, role) делают
                            экономическую стоимость eclipse-атаки order-of-magnitude
                            выше тривиального Sybil; start_window ограничивает
                            timing concentration через sequential-SHA-256 барьер регистрации узла
                            (τ₂ окон sequential SHA-256)
State transition:           не участвует (consensus state не меняется)

Object class:                       value (transport policy)
Canonical inclusion:                no
Can delay change future power:      no (rotation локальна, не влияет на global
                                        active set)
Can absence change state:           no
Seed inputs canonical:              n/a (PeerRecord selection из bucket по
                                        локальному secret key — anti-enumeration,
                                        не consensus seed)
Expiry exploitable by streak:       no (rotation periodic, не tied к streak)
Temporal anchors bounded:           yes (last_seen_window bounded; start_window
                                        verifiable through Node Table при IBT)

Global invariant check:
  [I-1] PQ-secure:                       yes (PeerRecord pubkey ML-DSA-65;
                                              IBT handshake закрывает auth)
  [I-2] Public financial layer:          n/a
  [I-3] Deterministic state:             n/a (локальная политика — by design
                                              non-deterministic; разные узлы
                                              имеют разные peer-tables)
  [I-4] TimeChain independence:          yes (peer selection не зависит от
                                              cementing progress)
  [I-5] Commodity hardware:              yes (PeerRecord ≈2 KB на запись;
                                              8192 записей × 2 KB = 16 MB —
                                              приемлемо для домашнего сервера)
  [I-6] Regulatory compat:               yes (стандартный P2P паттерн)
  [I-7] Minimal crypto surface:          yes (использует существующие ML-DSA-65
                                              + SHA-256, новых примитивов нет)
  [I-8] Network-bound unpredictability:  n/a (transport policy, не consensus
                                              seed; PeerRecord selection bucket
                                              из локального secret key — это
                                              anti-enumeration, не consensus
                                              randomness)
  [I-9] Bit-exact deterministic:         n/a (non-deterministic policy)
  [I-10] Single Source of Truth:         yes (правила выбора в одном месте —
                                              раздел «Выбор пиров» выше;
                                              PeerRecord layout — раздел
                                              «PeerRecord»)
  [I-11] Nickname uniqueness:            n/a
  [I-12] Auction determinism:            n/a
  [I-13] Deflationary sink:              n/a
  [I-14] State lifecycle:                yes — путь 2 (temporal): pruning по
                                              last_seen_window TTL; локальная
                                              Storage Card для PeerRecord table
                                              фиксирует hard quota 8192 записей
                                              (см. раздел «Локальные сетевые
                                              таблицы — Storage Cards», открыт
                                              в разделе II.2 плана)
  [I-15] Time-based scarcity:            yes (start_window-based diversity —
                                              время как scarce ресурс через
                                              Sequential-chain барьер регистрации; rotation
                                              1 peer per τ₂ — time-based)

Status:                             закрыто

Карточка — Dandelion++ (анонимность отправителя)

Объект:                     two-phase relay protocol для скрытия источника
                            user operation (Transfer / Anchor / ChangeKey)
                            от сетевого наблюдателя
Создатель:                  отправитель user operation выбирает stem path
                            (line-graph над outbound peers); каждый узел в
                            stem ретранслирует к одному random successor
                            пока не завершится stem TTL → fluff broadcast
                            всем outbound peers
Проверяет:                  никто внешне не «верифицирует» Dandelion path
                            (это привативная процедура); receiver валидирует
                            user operation подписью независимо от пути
Формат сериализации:        operation embedded в ProtocolMessage type 0x01
                            (Transfer) / 0x03 (ChangeKey) / 0x04 (Anchor)
                            с дополнительным flag-байтом stem-mode в payload
                            preamble (см. spec раздел «Dandelion++» — точное
                            расположение flag фиксируется в Wire Format II.3
                            binding vectors)
Состояние:                  ephemeral; per-узел flip-coin per τ₁ выбирает
                            один random outbound peer как «эпохальный
                            stem successor» — таблица `stem_successor[τ₁]`
                            размером 1 запись per epoch
Какой root:                 не входит ни в один root
Срок жизни:                 stem path TTL — geometric distribution с
                            expected hops = 10 (probability 0.1 fluff per hop);
                            один operation в stem не более 30 hops hard cap
                            (защита от infinite loop); fluff после TTL —
                            broadcast
Истечение:                  stem TTL exhaustion → fluff; loop detection
                            через operation identifier — повторный приём
                            одного identifier'а в stem → принудительный fluff
Конфликт:                   stem peer offline → fallback к fluff немедленно
                            (не обнулять anonymity путь backtrack)
Цена злонамеренного:        atacker контролирующий M of N outbound peers
                            получает probabilistic tracing — для P(trace) ≥ 0.5
                            требуется M/N ≥ 0.5 (по Bhattacharya-Schmidt-Wagner
                            анализ для line-graph stem); diversity constraints
                            из «Выбор пиров» делают это economically expensive
State transition:           не участвует

Object class:                       value (privacy enhancement)
Canonical inclusion:                no
Can delay change future power:      no (operation eventually broadcasted via fluff;
                                        stem задерживает propagation на ≤30 hops)
Can absence change state:           no
Seed inputs canonical:              no (stem successor selection — locally random,
                                        attacker должен not predict; см.
                                        Transport Randomness)
Expiry exploitable by streak:       no (geometric TTL — нет exploitable pattern)
Temporal anchors bounded:           yes (TTL hard cap 30 hops, stem_successor
                                        rotates per τ₁)

Global invariant check:
  [I-1] PQ-secure:                       n/a (privacy enhancement, не cryptographic
                                              primitive; underlying transport TLS+IBT
                                              закрывает [I-1])
  [I-2] Public financial layer:          yes — Dandelion++ скрывает только
                                              **первого hop** (входной IP);
                                              финальный recipient + amount в
                                              Transfer остаются publicly visible
                                              после fluff (per [I-2] открытость
                                              финансового слоя сохраняется)
  [I-3] Deterministic state:             n/a (transport policy)
  [I-4] TimeChain independence:          yes
  [I-5] Commodity hardware:              yes (stem_successor table — 1 запись
                                              per τ₁; operation forwarding —
                                              just relay)
  [I-6] Regulatory compat:               yes — Dandelion++ не privacy mixer
                                              (recipient + amount открыты);
                                              скрывает только сетевой источник
                                              (стандартный network-layer
                                              privacy enhancement, аналог
                                              Tor entry guard concept)
  [I-7] Minimal crypto surface:          yes (no new crypto primitives)
  [I-8] Network-bound unpredictability:  n/a
  [I-9] Bit-exact deterministic:         n/a (probabilistic protocol)
  [I-10] Single Source of Truth:         yes (stem-fluff state machine
                                              в одном месте — раздел
                                              «Dandelion++» выше)
  [I-11..I-13]:                          n/a
  [I-14] State lifecycle:                yes — путь 2 (temporal):
                                              stem_successor[τ₁] rotates,
                                              старые записи отбрасываются
  [I-15] Time-based scarcity:            yes (stem epoch = τ₁; rotation
                                              периодическая)

Status:                             закрыто

Карточка — NAT Traversal

Объект:                     процедура установления входящих соединений для
                            узлов за NAT (домашние провайдеры, мобильные сети)
                            без статического публичного IP
Создатель:                  локальный сетевой стек узла за NAT; rendezvous
                            request к публичному peer для координации
                            hole-punch
Проверяет:                  оба peer-а проверяют successful traversal через
                            normal IBT handshake после hole-punch;
                            invalid pubkey → reject
Формат сериализации:        три механизма (operator choice, не default+fallback):
                            (a) UPnP/PCP — стандартный port mapping request к
                                home router; success → external port published
                                в PeerRecord
                            (b) AutoNAT detection + hole punching через rendezvous
                                peer (libp2p AutoNAT v2 protocol; rendezvous —
                                любой directly-reachable peer)
                            (c) circuit relay через third peer — fallback для
                                симметричных NAT где hole-punching невозможен;
                                relay peer передаёт frames без расшифровки
                                (TLS+IBT end-to-end сохранены)
Состояние:                  ephemeral session state per active connection;
                            external_port в PeerRecord для UPnP path
Какой root:                 не входит ни в один root
Срок жизни:                 active connection lifetime; UPnP mapping renew
                            каждые 30 минут (стандартный TTL)
Истечение:                  connection close → drop session state;
                            UPnP mapping release при graceful shutdown
Конфликт:                   нет (peer-to-peer coordination; conflicting
                            mappings обрабатываются роутером)
Цена злонамеренного:        rendezvous peer узнаёт что узел A пытается
                            соединиться с узлом B (metadata leak в момент
                            hole-punch coordination); закрывается выбором
                            random rendezvous из проверенных peers через
                            Dandelion-like обфускацию rendezvous selection;
                            relay peer узнаёт frames volume + timing
                            (но не содержимое — TLS+IBT защищены)
State transition:           не участвует

Object class:                       value (transport reachability)
Canonical inclusion:                no
Can delay change future power:      no
Can absence change state:           no
Seed inputs canonical:              n/a
Expiry exploitable by streak:       no
Temporal anchors bounded:           yes (UPnP TTL 30 мин — bounded refresh)

Global invariant check:
  [I-1] PQ-secure:                       yes (PQ-security обеспечивается TLS+IBT
                                              слоем; NAT traversal — только
                                              addressing layer)
  [I-2] Public financial layer:          n/a
  [I-3] Deterministic state:             n/a
  [I-4] TimeChain independence:          yes
  [I-5] Commodity hardware:              yes (UPnP / AutoNAT — стандартные
                                              протоколы home routers / libp2p)
  [I-6] Regulatory compat:               yes (стандартная P2P практика;
                                              circuit relay — публично известный
                                              паттерн libp2p)
  [I-7] Minimal crypto surface:          yes (no new crypto primitives —
                                              переиспользует TLS+IBT)
  [I-8] Network-bound unpredictability:  n/a
  [I-9] Bit-exact deterministic:         n/a
  [I-10] Single Source of Truth:         yes (раздел «NAT Traversal» выше)
  [I-11..I-13]:                          n/a
  [I-14] State lifecycle:                yes — путь 2 (temporal):
                                              session state ephemeral; UPnP
                                              renew TTL bounded
  [I-15] Time-based scarcity:            n/a (NAT traversal — addressing,
                                              не anti-spam механизм)

Status:                             закрыто

Карточка — Mesh Transport

Объект:                     набор протоколов для P2P-сетевания узлов без
                            интернет-доступа: Bluetooth Low Energy (BLE),
                            Wi-Fi Aware (NAN), local Wi-Fi multicast
                            (mDNS-based discovery)
Создатель:                  локальный mesh-стек узла; advertisement выпускается
                            периодически (BLE: ≈1 секунда interval; Wi-Fi
                            Aware: per platform default)
Проверяет:                  принимающий peer проверяет mesh advertisement
                            подписью + IBT mesh proof (см. отдельная карточка
                            «IBT mesh proof»); MeshFrame structure validation
                            (flags + fragment_index + total_fragments + payload)
Формат сериализации:        MeshFrame wire format (см. spec раздел «MeshFrame
                            wire format»):
                              flags 1B (включая fragment_continuation bit)
                              fragment_index 1B
                              total_fragments 1B (≤ 255)
                              recipient_hint 32B (0xFF×32 = broadcast)
                              payload bytes (variable, после fragmentation)
                            Fragmentation для payload > MTU (BLE MTU 244B
                            практический; Wi-Fi Aware ≈ 1500B; см. spec
                            «Fragmentation»)
Состояние:                  ephemeral mesh session per active peer (BLE GATT
                            connection; Wi-Fi Aware data path); per-sender
                            used_nonces для IBT mesh replay tracking
                            (см. карточку «IBT mesh proof»)
Какой root:                 не входит ни в один root (transport-only)
Срок жизни:                 advertisement выпускается каждые 1 сек по умолчанию;
                            connection — пока physical link жив + IBT proof
                            валиден; battery management управляет частотой
                            (см. spec «Battery management» — экспоненциальный
                            backoff при низком заряде)
Истечение:                  physical link loss → drop session; battery
                            saver mode → reduced advertisement frequency
                            (1/8 baseline при < 20% charge)
Конфликт:                   ID collision на BLE MAC random → IBT proof
                            disambiguates (real identity = pubkey, не MAC);
                            duplicate fragment receipt → discard
Цена злонамеренного:        physical proximity required (BLE ≈ 100m line-of-sight,
                            Wi-Fi Aware ≈ 200m); attacker with proximity
                            может flood advertisements — закрывается per-sender
                            rate-limit + IBT mesh proof requirement
                            (без валидного proof advertisement отбрасывается)
State transition:           не участвует

Object class:                       value (transport reachability)
Canonical inclusion:                no
Can delay change future power:      no
Can absence change state:           no
Seed inputs canonical:              n/a
Expiry exploitable by streak:       no (battery management deterministic от
                                        charge level, не от network state)
Temporal anchors bounded:           yes (cached_window_index в IBT mesh proof
                                        bounded 7·τ₁; advertisement interval
                                        1 сек bounded)

Global invariant check:
  [I-1] PQ-secure:                       yes (IBT mesh proof через ML-DSA-65;
                                              MeshFrame body не содержит
                                              cryptographic primitives —
                                              transport encapsulation only)
  [I-2] Public financial layer:          n/a
  [I-3] Deterministic state:             n/a
  [I-4] TimeChain independence:          yes — критический case: mesh transport
                                              работает БЕЗ live TimeChain
                                              (cached_window_index из любого
                                              предыдущего online connection);
                                              это первичный пример [I-4]
                                              compliance в адвверсарных условиях
                                              (отключение интернета)
  [I-5] Commodity hardware:              yes (BLE есть на любом смартфоне +
                                              ноутбуке; Wi-Fi Aware на Android
                                              ≥8 + iOS опционально; macOS/Linux
                                              имеют BLE через Core Bluetooth /
                                              BlueZ)
  [I-6] Regulatory compat:               yes (BLE/Wi-Fi Aware — стандартные
                                              radio bands; mesh protocol сам
                                              не privacy mixer — recipient
                                              в Transfer публичен)
  [I-7] Minimal crypto surface:          yes (переиспользует IBT mesh proof +
                                              SHA-256; новых crypto primitives
                                              не вводит)
  [I-8] Network-bound unpredictability:  n/a
  [I-9] Bit-exact deterministic:         partial — MeshFrame wire format
                                              integer-specified (binding KAT
                                              vectors в плане раздела II.3);
                                              advertisement timing
                                              non-deterministic (battery-
                                              dependent)
  [I-10] Single Source of Truth:         yes (MeshFrame layout — раздел
                                              «MeshFrame wire format»;
                                              fragmentation rules — раздел
                                              «Fragmentation»; discovery —
                                              раздел «Mesh discovery flow»;
                                              battery — раздел «Battery
                                              management»)
  [I-11..I-13]:                          n/a
  [I-14] State lifecycle:                yes — путь 2 (temporal): mesh sessions
                                              ephemeral, used_nonces TTL =
                                              7·τ₁ (см. IBT mesh card)
  [I-15] Time-based scarcity:            yes (advertisement interval rate-limit;
                                              battery-aware backoff — time-based
                                              ограничение spam)

Status:                             закрыто

Карточка — Store-and-Forward

Объект:                     механизм буферизации сообщений для offline
                            recipient-ов (получатель временно недоступен —
                            outage, mobile device асинхронно online);
                            forwarding через intermediate peers до
                            целевого recipient
Создатель:                  отправитель кладёт frame в локальный
                            sf_buffer + флагует для forwarding; intermediate
                            peer принимает frames для forward, хранит в
                            своём sf_buffer пока не доставит
Проверяет:                  recipient проверяет signature + IBT proof
                            подделкой/replay; intermediate peer не проверяет
                            content (encrypted end-to-end через recipient
                            ML-KEM session key); intermediate peer проверяет
                            forwarding eligibility через signed rate-limit
                            ack от recipient (см. spec раздел «Signed rate-
                            limit acks»)
Формат сериализации:        Store-and-Forward envelope (см. spec раздел
                            «Buffer model»):
                              recipient_hint 32B
                              ttl_window u32 LE — окно после которого frame
                                                  expirется
                              fragment_index u8
                              total_fragments u8
                              ciphertext bytes (E2E encrypted к recipient)
                              sender_signature 3309B ML-DSA-65 (для accountability
                                                                в rate-limit)
Состояние:                  локальный sf_buffer (один на узел) — RocksDB
                            таблица; per-sender quota counter (rate-limit
                            state); recipient-issued signed acks (TTL = τ₁)
Какой root:                 не входит ни в один root (transport-only)
Срок жизни:                 frame expires при `ttl_window < current_window`
                            (sender выбирает TTL, hard cap protocol-level
                            = 24·τ₁); per-sender quota counter сбрасывается
                            каждый τ₁
Истечение:                  buffer pruning по `current_window > ttl_window`
                            каждые τ₁/12; quota reset per τ₁
Конфликт:                   sender exceeds quota → frames отбрасываются
                            silently (ack не приходит, sender сам определит
                            из retry timeout); recipient signed ack revokes
                            forwarding right на N окон (anti-DoS тулинг
                            recipient-а)
Цена злонамеренного:        flood frames с invalid signatures → ML-DSA-65
                            verify cost ≈ 0.1 мс на commodity CPU; per-sender
                            quota ограничивает rate; signed ack mechanism
                            позволяет recipient revoke forwarding privilege;
                            buffer hard quota (Storage Card — открыт в II.2)
                            ограничивает total disk usage
State transition:           не участвует

Object class:                       value (transport buffering)
Canonical inclusion:                no
Can delay change future power:      no (forwarding не влияет на consensus
                                        state; recipient финализирует через
                                        normal proposal mechanism)
Can absence change state:           no
Seed inputs canonical:              n/a
Expiry exploitable by streak:       no (TTL hard cap 24·τ₁, quota reset
                                        deterministic)
Temporal anchors bounded:           yes (ttl_window bounded; quota per τ₁
                                        bounded; recipient ack TTL τ₁)

Global invariant check:
  [I-1] PQ-secure:                       yes (sender signature ML-DSA-65;
                                              ciphertext через ML-KEM session
                                              key к recipient — оба PQ)
  [I-2] Public financial layer:          n/a — Store-and-Forward для arbitrary
                                              messaging payloads (включая
                                              app-layer); финансовые операции
                                              в SF buffer обычным путём
                                              (Transfer broadcast + finality
                                              через consensus)
  [I-3] Deterministic state:             n/a (локальный buffer вне consensus
                                              root)
  [I-4] TimeChain independence:          yes (SF buffer работает с cached
                                              window_index в mesh контексте)
  [I-5] Commodity hardware:              yes (sf_buffer ≤ 1 GB по hard quota;
                                              ML-DSA-65 verify дёшев)
  [I-6] Regulatory compat:               yes (стандартный store-and-forward
                                              паттерн email / Matrix /
                                              Threema; не privacy mixer)
  [I-7] Minimal crypto surface:          yes (переиспользует ML-DSA-65 +
                                              ML-KEM-768 + SHA-256; новых
                                              примитивов нет)
  [I-8] Network-bound unpredictability:  n/a
  [I-9] Bit-exact deterministic:         partial — envelope wire format
                                              integer-specified (binding KAT
                                              vectors в плане II.3); buffer
                                              eviction policy non-deterministic
                                              (LRU + quota)
  [I-10] Single Source of Truth:         yes (envelope layout — раздел «Buffer
                                              model»; quota — «Per-sender
                                              quota»; ack format — «Signed
                                              rate-limit acks»; forwarding
                                              algorithm — отдельный раздел;
                                              policies — «Buffer policies»)
  [I-11..I-13]:                          n/a
  [I-14] State lifecycle:                yes — comb (1+2+3): cost-based barrier
                                              n/a по [I-15]; temporal pruning
                                              по ttl_window (путь 2); hard
                                              quota на total buffer size
                                              (путь 3 — Storage Card в II.2);
                                              per-sender quota (путь 3 again)
  [I-15] Time-based scarcity:            yes — three-layer time defense:
                                              (a) frame TTL ≤ 24·τ₁;
                                              (b) per-sender quota per τ₁;
                                              (c) signed ack window TTL τ₁

Status:                             закрыто

Карточка — ProtocolMessage envelope

Объект:                     универсальная транспортная обёртка для всех
                            сообщений Монтаны внутри IBT-сессии (поверх
                            Uniform Framing); дискриминатор msg_type +
                            request/response correlation
Создатель:                  любой узел / candidate / account внутри
                            активной IBT-сессии
Проверяет:                  получатель — структура header (1B msg_type +
                            1B msg_version + 8B request_id u64 LE +
                            4B payload_length u32 LE) + payload bounds
                            (≤ 2^32  1 байт; практически limited по
                            backpressure rules — открыто в плане II.5);
                            payload validation per msg_type (см. карточку
                            «Реестр типов сообщений» ниже)
Формат сериализации:        14B fixed header + payload (variable).
                            msg_version = 1 для всех v3X.x; изменение
                            wire format → increment + protocol version
                            upgrade
Состояние:                  ephemeral; при request/response — request_id
                            таблица для correlation в течение typical
                            response window τ₁/2 (см. spec «Connection
                            lifecycle» timeouts)
Какой root:                 не входит ни в один root (transport-layer)
Срок жизни:                 один request/response pair либо one-way gossip
                            propagation; pending request entries TTL = τ₁
Истечение:                  request_id timeout → drop pending entry,
                            error reported to caller
Конфликт:                   duplicate request_id от того же peer → reject
                            second; unknown msg_type → log + ignore
                            (forward compatibility); unknown msg_version
                            → respond unsupported_version + disconnect
Цена злонамеренного:        flood unknown msg_type — peer обязан
                            log+ignore (нельзя amplify); flood с invalid
                            payload — payload_length validated до
                            аллокации, oversize → reject + peer
                            blacklist на N·τ₁ (точные параметры — план II.5)
State transition:           не участвует (consensus state не меняется
                            самим envelope; payload может содержать
                            consensus-bound объекты — их state transitions
                            фиксируются в их собственных карточках в
                            основной части спеки)

Object class:                       value (transport encapsulation)
Canonical inclusion:                no
Can delay change future power:      no
Can absence change state:           no
Seed inputs canonical:              n/a
Expiry exploitable by streak:       no (request TTL deterministic)
Temporal anchors bounded:           yes (request TTL τ₁ bounded)

Global invariant check:
  [I-1] PQ-secure:                       yes (header не содержит crypto;
                                              payloads с подписями
                                              используют ML-DSA-65)
  [I-2] Public financial layer:          yes (envelope не скрывает
                                              финансовый payload —
                                              Transfer/Anchor/etc видны
                                              в plain после IBT decryption)
  [I-3] Deterministic state:             yes — wire format integer-specified
                                              byte-exact; cross-implementation
                                              совместимость гарантирована
                                              binding KAT vectors
                                              (план II.3)
  [I-4] TimeChain independence:          yes (envelope agnostic к
                                              consensus state)
  [I-5] Commodity hardware:              yes (header parsing — несколько
                                              integer reads; payload
                                              processing per msg_type)
  [I-6] Regulatory compat:               yes (стандартный
                                              type-length-value protocol
                                              паттерн)
  [I-7] Minimal crypto surface:          yes (envelope сам не вводит
                                              crypto — все crypto в
                                              IBT layer и payload-объектах)
  [I-8] Network-bound unpredictability:  n/a (envelope, не consensus seed)
  [I-9] Bit-exact deterministic:         yes — Gate 13a invariants
                                              enumeration:
                                                msg_type ∈ valid registry
                                                  (0x01, 0x03, 0x04, 0x10,
                                                   0x200x22, 0x400x42,
                                                   0x500x51, 0x600x65,
                                                   0xF00xF1, 0xFF;
                                                   реестр fixed в spec)
                                                msg_version = 1
                                                request_id ∈ uint64 (8 LE)
                                                payload_length ∈
                                                  [0, 2^32  1]
                                                payload bytes count ==
                                                  payload_length
                                              binding KAT vectors —
                                              план II.3
  [I-10] Single Source of Truth:         yes (envelope layout — раздел
                                              «Protocol Message Layer»)
  [I-11..I-13]:                          n/a
  [I-14] State lifecycle:                yes — путь 2 (temporal): pending
                                              requests TTL τ₁
  [I-15] Time-based scarcity:            yes (request TTL τ₁; backpressure
                                              rules — план II.5)

Status:                             закрыто (с открытым sub-finding на
                                    backpressure rules + per-msg-type
                                    rate-limit constants — план II.5)

Карточка — Реестр типов сообщений (групповая)

Объект:                     унифицированная trait над 18 message-type кодами,
                            сгруппированными в 6 категорий по семантике;
                            каждая категория имеет общие validation +
                            lifecycle характеристики
Создатель:                  определяется per-категория (см. ниже)
Проверяет:                  определяется per-категория
Формат сериализации:        каждый payload имеет integer-specified layout
                            в соответствующем разделе спеки + binding KAT
                            vector в плане II.3 (13 vectors per type)

Категории:

  A. Consensus objects (gossip) — 7 кодов
     Коды:        0x01 Transfer | 0x03 ChangeKey | 0x04 Anchor |
                  0x10 NodeRegistration | 0x20 BundledConfirmation |
                  0x21 VDF_Reveal | 0x22 Proposal
     Создатель:   user / node — подписывает соответствующим keypair
     Проверяет:   получатель проверяет подпись + структурные инварианты
                  (см. карточки этих объектов в основной части спеки —
                  «Account — содержимое блока», «Proposal», «VDF Reveal
                  и лотерея», «Регистрация окна», и т.д.)
     Lifecycle:   объекты cemented через consensus path; envelope
                  ephemeral
     Категория:   one-way gossip; request_id = 0

  B. Fast Sync — 3 кода
     Коды:        0x40 FastSyncRequest | 0x41 FastSyncResponse |
                  0x42 FastSyncError
     Создатель:   узел запрашивает snapshot (Request) либо отвечает
                  chunk delivery (Response/Error)
     Проверяет:   запросчик собирает chunks по chunk_index, верифицирует
                  Merkle root против proposal_W
     Lifecycle:   request/response; pending request TTL = τ₁
     Категория:   request/response correlation через request_id

  C. Peer Discovery — 2 кода
     Коды:        0x50 PeerListRequest | 0x51 PeerListResponse
     Создатель:   запрашивающий узел (Request) / отвечающий peer (Response)
     Проверяет:   получатель PeerListResponse валидирует ≤ max_count
                  записей + структуру PeerEntry (59B fixed)
     Lifecycle:   request/response; PeerEntry data применяется к локальной
                  PeerRecord table (см. карточку «Peer selection»)
     Категория:   request/response

  D. App Lookup — 3 кода
     Коды:        0x60 BatchLookupRequest | 0x61 BatchLookupResponse |
                  0x62 BatchLookupError
     Создатель:   account клиент (Request) / host узел (Response/Error)
     Проверяет:   спека «Batch Lookup Protocol» raздел «Validation
                  workflow хоста» определяет проверки; включая [I-15]
                  per-account rate-limit
     Lifecycle:   request/response; результаты ephemeral у клиента
     Категория:   request/response; access level 3 IBT (account)

  E. App Subscription — 3 кода
     Коды:        0x63 RangeSubscribeRequest | 0x64 RangeSubscribeResponse |
                  0x65 RangeSubscribeError
     Создатель:   account клиент (Request) / host (Response/Error)
     Проверяет:   спека «Label Rotation + Range Subscribe Protocol» раздел
                  «Validation workflow хоста»; включая label rotation
                  formula + per-account rate-limit
     Lifecycle:   request/response; client сохраняет blob_count blobs
                  локально для catch-up
     Категория:   request/response; access level 3 IBT

  F. Liveness — 3 кода
     Коды:        0xF0 Ping | 0xF1 Pong | 0xFF Bye
     Создатель:   любая сторона соединения
     Проверяет:   получатель Pong отвечает Pong (no payload); Bye —
                  graceful shutdown
     Lifecycle:   ephemeral
     Категория:   one-way (Bye) либо request/response (Ping/Pong)
                  с быстрым TTL (получатель должен ответить до конца
                  текущего τ₁ у получателя)

Состояние:                  per-категория — см. соответствующие карточки
                            существующих объектов (категория A) либо
                            ephemeral (категории B-F)
Какой root:                 категория A — payload объекты cemented через
                            normal consensus root (Account/Node chains,
                            proposal hash chain); категории B-F — не
                            входят
Срок жизни:                 категория A — permanent в consensus state;
                            категории B-F — ephemeral
Истечение:                  per-категория
Конфликт:                   неизвестный msg_type → log+ignore (forward
                            compat); невалидный payload per type →
                            reject + peer penalty
Цена злонамеренного:        flood с невалидными payloads:
                            ML-DSA-65 verify ≈ 0.1 мс — пер-IBT-сессия
                            rate-limit (план II.5) ограничивает damage;
                            invalid structure detected до crypto verify
                            через Gate 13a invariants для каждого типа
State transition:           категория A — apply через consensus path
                            (apply_proposal соответствующих объектов);
                            категории B-F — не участвуют

Object class:                       per-категория (mostly value)
Canonical inclusion:                A: yes (в proposal либо chain entries);
                                    B-F: no
Can delay change future power:      A: no (objects уже сформированы;
                                    задержка propagation = задержка
                                    cementing, value-delay не power);
                                    B-F: no
Can absence change state:           no
Seed inputs canonical:              per object (категория A) — см.
                                    карточки в основной части спеки
Expiry exploitable by streak:       no
Temporal anchors bounded:           per object категория A;
                                    категории B-F: yes (request TTL τ₁)

Global invariant check (агрегированно по категориям):
  [I-1] PQ-secure:                       yes (категория A объекты подписаны
                                              ML-DSA-65; категории B-F payloads
                                              не содержат crypto primitives
                                              либо переиспользуют ML-DSA-65)
  [I-2] Public financial layer:          yes (Transfer / Anchor публичны
                                              в payload)
  [I-3] Deterministic state:             yes (каждый payload integer-
                                              specified, binding KAT vectors
                                              в плане II.3)
  [I-4] TimeChain independence:          yes (Fast Sync, Peer Discovery
                                              работают независимо от cementing
                                              progress)
  [I-5] Commodity hardware:              yes
  [I-6] Regulatory compat:               yes
  [I-7] Minimal crypto surface:          yes (no new primitives — категория
                                              A переиспользует payload-
                                              specific signatures, категории
                                              B-F не вводят crypto)
  [I-8] Network-bound unpredictability:  per object категория A — см. их
                                              карточки в основной спеке
                                              (Proposal, VDF_Reveal, и т.д.)
  [I-9] Bit-exact deterministic:         yes для всех 18 кодов — закрытие
                                              через binding KAT vectors
                                              (план II.3, conformance status
                                              «pending» до закрытия II.3)
  [I-10] Single Source of Truth:         yes (реестр в одном месте — раздел
                                              «Protocol Message Layer»;
                                              payload formats — каждый в
                                              своём разделе)
  [I-11..I-13]:                          n/a (не nickname / auction /
                                              monetary mechanisms)
  [I-14] State lifecycle:                категория A — через apply_proposal
                                              существующих объектов;
                                              категории B-F — ephemeral
  [I-15] Time-based scarcity:            yes (per-IBT-сессия rate-limits
                                              в плане II.5; категория D/E
                                              имеют explicit per-account
                                              rate-limits в spec)

Status:                             закрыто (с conformance pending по
                                    [I-9] до закрытия плана II.3 binding
                                    KAT vectors; и pending backpressure
                                    constants до плана II.5)

Локальные сетевые таблицы — Storage Cards

Сетевой слой создаёт локальные персистентные таблицы вне consensus state ([I-3] не нарушается — их derivation локальна, не входит в state_root). Они НЕ покрываются разделом «Storage Cards per persistent table» (который про consensus state per [I-14]), но ОБЯЗАНЫ иметь собственные Storage Cards adapted для local-state контекста: размер записи, growth model, lifecycle / eviction, hard quota, total disk usage. Без этих карточек оператор узла не знает требования к диску для node-runtime; реализация может игнорировать quota и raise OOM на длительной работе.

Формат адаптирован для local-state: cost-based фрагменты помечаются n/a единообразно (per [I-15] денежного отказа сетевого слоя), защита через time-based / quota механизмы.

Storage Card — PeerRecord table

Таблица:                          PeerRecord (локальный сетевой кэш peer-узлов)
Operation создающая запись:       приём PeerListResponse (msg_type 0x51) либо
                                  bootstrap connection success
Платит creation cost:             none (локальный append; per-sender rate-limit
                                        ≤ 1 PeerListRequest per τ₁ ограничивает
                                        rate притока)
Размер записи (bytes):            ≈ 2096 B
                                  (32B node_id + 1952B ML-DSA-65 pubkey +
                                   16B IPv6/IPv4 + 2B port + 8B start_window +
                                   8B last_seen_window + 4B AS_number +
                                   16B /16-prefix marker + 6B reserved/padding +
                                   indexing overhead RocksDB ≈ 50 B)
Secondary resources per record:   index entry (node_id → record offset) ≈ 50 B;
                                  IP-bucket index (для diversity selection)
                                  ≈ 30 B
Cost per record:                  n/a ([I-15] денежного отказа)
Lifecycle condition:              путь 2 (temporal) — pruning при
                                  `last_seen_window < current_window  N`
                                  где N = 8·τ₁ default (параметр локального
                                  стека, не consensus); путь 3 (hard quota)
                                  — total ≤ 8192 records
Eviction policy:                  при достижении 8192 records — LRU eviction
                                  по last_seen_window (oldest first); rotation
                                  1 outbound peer per τ₂ (см. spec «Ротация»)
                                  обновляет last_seen_window для активных
Total bytes hard cap:             ≈ 8192 × 2096 B ≈ 17 MB + indices ≈ 0.7 MB
                                  ≈ 18 MB total
[I-14] путь:                      2+3 (temporal + hard quota)
Sabotage time-budget атаки:       attacker контролирующий ≥1 connected peer
                                  посылает PeerListResponse с 64 fake records
                                  per τ₁; max input rate per peer = 64 records
                                  / τ₁ = 64 / 60 ≈ 1.07 records/сек;
                                  заполнение 8192 records требует ≈ 7600 сек
                                  ≈ 2 часа sustained от одного peer; LRU
                                  eviction вытесняет старые «проверенные»
                                  записи только при total ≥ 8192 — приоритет
                                  «проверенных» через explicit policy
                                  (см. spec «Адресный менеджер»); damage
                                  ограничен нарушением diversity, не denial
                                  of service
Sabotage asymmetry:               в пользу сети — eviction приоритезирует
                                  проверенные records; full eclipse требует
                                  координированной атаки множества peers с
                                  diversity bypass
Existing pruning consistent:      yes — current spec раздел «Адресный
                                  менеджер» уже описывает приоритизацию
                                  проверенных peers; Storage Card formalizes
                                  hard quota
[I-14] compliance status:         закрыто

Storage Card — used_nonces table (mesh IBT replay tracking)

Таблица:                          used_nonces[sender_pubkey] — set of
                                  mesh_session_nonce per sender
                                  (per IBT mesh proof — см. карточку)
Operation создающая запись:       приём валидного mesh IBT proof — добавление
                                  `mesh_session_nonce` в set данного sender
Платит creation cost:             none (требует только pre-validated IBT proof
                                        — sender уже потратил CSPRNG generation
                                        + ML-DSA-65 sign cost)
Размер записи (bytes):            ≈ 80 B per nonce
                                  (32B nonce + 16B observed window_index +
                                   16B sender_pubkey hash как index key +
                                   indexing overhead RocksDB ≈ 16 B)
Secondary resources per record:   index entry (sender_pubkey → nonce list)
                                  ≈ 1952 B per unique sender (pubkey storage)
                                  + 16 B per nonce in list
Cost per record:                  n/a
Lifecycle condition:              путь 2 (temporal) — записи старше
                                  `current_window  7·τ₁` удаляются;
                                  cleanup runs каждые τ₁/12
Eviction policy:                  TTL-based; при overflow soft cap (см. ниже)
                                  — drop nonces oldest first
Total bytes hard cap:             soft cap = 1 MB per узел;
                                  при достижении: per-sender quota
                                  enforce ≤ 64 nonces per sender per 7·τ₁
                                  window (sender отправляющий >64
                                  mesh handshakes per 7·τ₁ — анонимизированно
                                  rate-limited); hard cap = 4 MB total —
                                  при достижении узел отказывает в новых
                                  mesh handshakes до cleanup
[I-14] путь:                      2+3 (temporal TTL + hard quota)
Sabotage time-budget атаки:       attacker генерирует 64 unique nonces per
                                  7·τ₁ × τ₁ = 60 сек = ≈ 0.15 nonces/сек на
                                  одну identity; для заполнения soft cap 1 MB
                                  / 80 B = 12500 nonces / 64 per identity
                                  = 196 unique attacker identities × 7·τ₁ × 60s
                                  = 82440 сек ≈ 23 часа sustained для soft cap;
                                  для hard cap 4 MB ≈ 92 часа; обнаруживается
                                  через monitoring before exhaustion
Sabotage asymmetry:               в пользу сети — per-sender quota плюс
                                  hard quota делает atak uneconomical
                                  и detection-friendly
Existing pruning consistent:      yes — IBT mesh card уже описывает
                                  TTL 7·τ₁; Storage Card formalizes per-sender
                                  quota + hard cap
[I-14] compliance status:         закрыто

Storage Card — sf_buffer (Store-and-Forward buffer)

Таблица:                          sf_buffer — RocksDB таблица
                                  forwarding-pending frames
Operation создающая запись:       приём Store-and-Forward envelope от sender
                                  с валидной sender_signature + recipient
                                  отсутствует locally (либо sender flagged
                                  store-and-forward)
Платит creation cost:             none ([I-15]); защита через per-sender quota
Размер записи (bytes):            envelope structure ≈ 3500 B avg
                                  (32B recipient_hint + 4B ttl_window +
                                   1B fragment_index + 1B total_fragments +
                                   ciphertext avg 100B (small messages) к
                                   1100B (large MeshFrame fragment) +
                                   3309B sender_signature ML-DSA-65 +
                                   indexing overhead RocksDB ≈ 50 B)
Secondary resources per record:   index entry (recipient_hint → record offset)
                                  ≈ 50 B; per-sender quota counter ≈ 32 B
                                  per active sender
Cost per record:                  n/a
Lifecycle condition:              путь 2 (temporal) — pruning при
                                  `current_window > ttl_window`; cleanup
                                  каждые τ₁/12
Eviction policy:                  при достижении hard quota — drop по
                                  policy «oldest TTL first» среди не-VIP
                                  records (VIP = recipient в локальном
                                  contact whitelist оператора); per-sender
                                  quota — каждый sender ≤ 256 frames per τ₁
Total bytes hard cap:             1 GB per узел (operator-configurable
                                  но default 1 GB); при достижении —
                                  reject new SF envelopes от не-VIP
                                  senders до cleanup
[I-14] путь:                      2+3+3 (temporal + per-sender quota +
                                  total hard quota)
Sabotage time-budget атаки:       attacker с одной identity = 256 frames
                                  per τ₁ × 3500 B = 896 KB/τ₁ ≈ 14.9 KB/sec;
                                  для hard cap 1 GB / 256 frames per sender
                                  per τ₁ = 4096 unique identities × 256
                                  frames per τ₁ × 3500 B = 1 GB достижим за
                                  один τ₁ если 4096 identities одновременно
                                  активны; защита — diversity constraint
                                  на принимаемые connections (peer selection
                                  card) делает 4096 simultaneous
                                  attacker-controlled connections нереальным
                                  без сильного eclipse
Sabotage asymmetry:               защита через combination — per-sender quota
                                  + diversity на connection layer + signed
                                  rate-limit acks от recipient (recipient
                                  может revoke forwarding right);
                                  multi-layered defense
Existing pruning consistent:      yes — Store-and-Forward card уже описывает
                                  per-sender quota + signed acks + TTL;
                                  Storage Card formalizes hard cap 1 GB
[I-14] compliance status:         закрыто

Storage Card — bootstrap recent_nonces (PoW anti-replay) + peer blacklist

Таблица:                          bootstrap_recent_nonces (per-bootstrap-узел)
                                  + peer_penalty_table (для protocol violation
                                  blacklist; см. spec «Retry policy»)
Operation создающая запись:       (a) recent_nonces — приём bootstrap PoW
                                      nonce при handshake attempt
                                  (b) peer_penalty — peer disconnected
                                      с reason 0x04 (protocol violation)
Платит creation cost:             (a) ≈ 100 мс CPU PoW per attempt
                                      (см. карточку Bootstrap PoW);
                                  (b) none — penalty entry создаётся
                                      passively при IBT fail или
                                      protocol violation event
Размер записи (bytes):            (a) ≈ 80 B per nonce (32B nonce +
                                      16B timestamp_window + indexing 32B);
                                  (b) ≈ 100 B per peer_id
                                      (32B node_id + 8B blacklist_until_window
                                      + 8B reason_code + 16B observed_event +
                                      indexing 36B)
Secondary resources per record:   minimal — flat tables
Cost per record:                  (a) compute-bound (≈100 мс CPU PoW —
                                      time-based scarcity);
                                  (b) n/a
Lifecycle condition:              (a) путь 2 (temporal) — recent_nonces TTL
                                      = τ₁; cleanup каждые τ₁/12;
                                  (b) путь 2 (temporal) — penalty TTL
                                      от 1·τ₁ (IBT fail) до 24·τ₁
                                      (protocol violation reason 0x04)
Eviction policy:                  TTL-based; hard cap не критичен из-за
                                  rate-limit через PoW cost (a) и
                                  peer-event rate (b)
Total bytes hard cap:             (a) per-bootstrap-узел: при flood-rate
                                      10 connections/сек × τ₁ = 600 entries
                                      × 80 B = 48 KB; per τ₁ peak; cleanup
                                      возвращает к baseline
                                  (b) при peak 1000 blacklisted peers
                                      × 100 B = 100 KB
                                  Total ≤ 200 KB sustained
[I-14] путь:                      2 (temporal через TTL); rate-limit через
                                  PoW (a) + event rate (b)
Sabotage time-budget атаки:       (a) рассчитано в карточке Bootstrap PoW
                                      — ≈ 12 cores sustained для distributed
                                      flood, обнаруживается на сетевом слое
                                  (b) attacker не может flood penalty
                                      table — она пишется реактивно на
                                      legitimate violation events; attacker
                                      контролирующий N peers может выдавать
                                      ≤ N protocol violations parallel,
                                      bound через Sybil-stake-cost узла
                                      регистрации (τ₂ окон sequential SHA-256)
Sabotage asymmetry:               в пользу сети
Existing pruning consistent:      yes — spec «Retry policy» определяет
                                  TTL для penalty; Storage Card formalizes
                                  rates
[I-14] compliance status:         закрыто

Binding KAT vectors сетевого слоя

Каждый вектор фиксирует пару (canonical input, expected byte-exact output) для cross-implementation conformance per [I-9]. Inputs полностью integer-specified в спеке. Expected outputs (TBD-A) генерируются reference implementation в Phase A плана M6 (mt-net::wire) и заполняются параллельным spec patch — до этого момента vectors в статусе conformance pending.

Методология генерации (для Phase A reference impl): запустить encode/decode/sign функцию с указанными inputs, capture byte stream, hex-кодировать, заменить TBD-A: <vector_id> на реальный hex в спеке тем же commit где добавляется test fixtures в mt-net::wire::tests.

A. ProtocolMessage envelope (3 vectors)

Vector A1: empty payload, msg_type Ping, request_id = 0
  Input:
    msg_type        = 0xF0
    msg_version     = 0x01
    request_id      = 0x0000000000000000 (u64 LE)
    payload_length  = 0x00000000 (u32 LE)
    payload         = (empty)
  Expected output (hex):  f0 01 00 00 00 00 00 00 00 00 00 00 00 00
                          (mt-net::tests::test_vectors::vector_a1, byte-exact)

Vector A2: typical Transfer payload (1 KB), msg_type 0x01, request_id = 42
  Input:
    msg_type        = 0x01
    msg_version     = 0x01
    request_id      = 42 = 0x2A00000000000000 (u64 LE)
    payload_length  = 1024 = 0x00040000 (u32 LE)
    payload         = byte_repeat(0xAB, 1024)
  Expected output (hex):
    header (14 B): 01 01 2a 00 00 00 00 00 00 00 00 04 00 00
    payload (1024 B): byte_repeat(0xAB, 1024) (mt-net::tests::test_vectors::vector_a2)
    total len: 1038 B

Vector A3: maximum payload_length boundary, msg_type 0x41 FastSyncResponse
  Input:
    msg_type        = 0x41
    msg_version     = 0x01
    request_id      = 0xFFFFFFFFFFFFFFFF (u64 LE)
    payload_length  = 0xFFFFFFFF (u32 LE) — represents 4 GB-1; semantic
                      reserved для future protocol upgrade; тест проверяет
                      ТОЛЬКО header encoding и rejection rule (получатель
                      обязан reject до allocation per backpressure,
                      см. план II.5)
    payload         = (test only header — payload bytes не требуются)
  Expected output (hex, header only 14 B):
    41 01 ff ff ff ff ff ff ff ff 00 00 00 00
    (mt-net::tests::test_vectors::vector_a3, byte-exact)

B. IBT proofs (3 vectors)

Vector B1: IBT online proof — fixed (sk, server_node_id, current_window, online_session_nonce)
  Input:
    domain                = "mt-tunnel-online" (utf-8 bytes)
    server_node_id        = 32 B = byte_repeat(0x42, 32)
    window_index W        = 1000 (u64); floor(W / 2) = 500 = 0x01F4 в derivation
    online_session_nonce  = 32 B = byte_repeat(0x55, 32)
    derivation seed       = "mt-tunnel-online" || server_node_id || u64_LE(500) ||
                            online_session_nonce
                          = total 16 + 32 + 8 + 32 = 88 B
    sk seed               = SHA-256("mt-test-seed" || "vector-B1") — fixed deterministic
                            ML-DSA-65 keypair derivation per ML-DSA-65 KeyGen ξ ∈ B32
  Expected output (hex):  TBD-A pending reference impl regeneration после wire format
                          change (online_session_nonce введён в Network spec patch для
                          закрытия MONT-002, см. Code/VERSION.md history)

Vector B2: IBT mesh proof — fixed (sk, peer_node_id, cached_window, nonce)
  Input:
    domain               = "mt-tunnel-mesh"
    peer_node_id         = 32 B = byte_repeat(0x33, 32)
    cached_window_index  = 5000 (u64); floor(5000 / 2) = 2500
    mesh_session_nonce   = 32 B = byte_repeat(0x77, 32)
    derivation message   = "mt-tunnel-mesh" || peer_node_id ||
                           u64_LE(2500) || mesh_session_nonce
                         = total 14 + 32 + 8 + 32 = 86 B
    sk seed              = SHA-256("mt-test-seed" || "vector-B2")
  Expected output (hex):
    sha256(proof) — без изменения (B2 использует mt-tunnel-mesh, не
    затронут rename); reference value preserved:
      51 98 f8 34 ad a3 af 80 bf 51 82 f0 ec b9 1b 38
      9b 70 93 ff 82 93 2e 90 07 3a 30 47 36 14 0b 8c

Vector B3: Bootstrap PoW combination — IBT proof + PoW nonce
  Input:
    base IBT proof       = output Vector B1.proof (sha256 ac26a9ca... per новый B1)
    nonce                = 0x0000000000000000 (u64 LE) — start search
    target derivation    = (2^256) / difficulty_factor где
                           difficulty_factor = 2^16 = 65536 для test
                           (production value — план II.5 в protocol_params.
                           bootstrap_pow_difficulty)
  Expected output:
    target (32 B big-endian):      00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00
                                   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
                                   (= 2^240 для bootstrap_pow_difficulty = 65 536 = 2^16)
    found_nonce:                   48 949 (decimal; 0x35bf000000000000 LE)
    found_hash (32 B):             00 00 cb 8b 53 f7 e6 a5 ab 66 b2 a7 d2 5d 82 68
                                   0d b7 f3 d4 6a 0a ea 1b 9d 9d 74 67 2d 6b da 23
    (mt-net::tests + reference impl byte-exact, regenerated after B1 rename
     per critic-fix P-C2)

C. Per-msg-type encode/decode (18 × 1 vector — minimal coverage)

Для каждого type — один canonical input с typical payload, expected envelope+payload hex.

Vector C-0x01 (Transfer Mode A — typical 64 B payload)
  Input payload struct: см. spec раздел «Перевод» Transfer Mode A layout
    sender_id          = byte_repeat(0x11, 32)
    receiver_id        = byte_repeat(0x22, 32)
    amount_nj          = 1_000_000 (u64 LE) = 0x40420F0000000000
    nonce_index        = 0 (u8)
    prev_hash          = byte_repeat(0xCC, 32)
    sender_signature   = byte_repeat(0xDD, 3309)  — placeholder для test
    canonical encode per spec → 32+32+8+1+32+3309 = 3414 B
  Wrapped in envelope:
    msg_type           = 0x01
    msg_version        = 0x01
    request_id         = 0
    payload_length     = 3414 = 0x56430000 (u32 LE)
  Expected output (hex):  TBD-A: C-0x01

Vector C-0x03 (ChangeKey)
  Input payload: см. spec «ChangeKey» layout — typical
  Expected output: TBD-A: C-0x03

Vector C-0x04 (Anchor — 1 KB content hash + signature)
  Input payload: см. spec «Anchor» layout — typical
  Expected output: TBD-A: C-0x04

Vector C-0x10 (NodeRegistration)
  Input payload: см. spec «Регистрация окна» NodeRegistration layout
  Expected output: TBD-A: C-0x10

Vector C-0x20 (BundledConfirmation)
  Input payload: см. spec «Confirmations» BundledConfirmation layout
  Expected output: TBD-A: C-0x20

Vector C-0x21 (VDF_Reveal)
  Input payload: см. spec «Валидация VDF_Reveal» layout
  Expected output: TBD-A: C-0x21

Vector C-0x22 (Proposal)
  Input payload: см. spec «Proposal» layout — typical с pre-defined
                 BundledConfirmation hash + state_root + node_signature
  Expected output: TBD-A: C-0x22

Vector C-0x40 (FastSyncRequest)
  Input payload:
    anchor_window      = 12345 (u64 LE)
    resume_offset      = 0 (u64 LE)
  Expected output (hex, 16 B): 39 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  (mt-net::tests::test_vectors::vector_c_0x40_fastsync_request, byte-exact)

Vector C-0x41 (FastSyncResponse — single chunk)
  Input payload (per spec FastSyncResponse chunk layout):
    chunk_index        = 0 (u32 LE)
    total_chunks       = 1 (u32 LE)
    table_id           = 0x01 (Account)
    record_count       = 1
    records            = 1 × AccountRecord byte-encoded (см. AccountRecord
                         layout)
  Expected output (hex, 77 B):
    header (13 B): 00 00 00 00 01 00 00 00 01 01 00 00 00
    records (64 B): byte_repeat(0x55, 64)
  (mt-net::tests::test_vectors::vector_c_0x41_fastsync_response_chunk)

Vector C-0x42 (FastSyncError)
  Input payload:
    code               = 0x01 (snapshot_unavailable)
    message            = "anchor_window 12345 not retained" (utf-8 bytes,
                         len ≤ 255)
  Expected output (hex, 34 B): 01 20 61 6e 63 68 6f 72 5f 77 69 6e 64 6f 77 20
                                31 32 33 34 35 20 6e 6f 74 20 72 65 74 61 69 6e
                                65 64
  (mt-net::tests::test_vectors::vector_c_0x42_fastsync_error, byte-exact)

Vector C-0x50 (PeerListRequest)
  Input payload:
    max_count          = 64 = 0x4000 (u16 LE)
  Expected output (hex, 2 B): 40 00
  (mt-net::tests::test_vectors::vector_c_0x50_peer_list_request, byte-exact)

Vector C-0x51 (PeerListResponse — 3 PeerEntry)
  Input payload:
    count              = 3 = 0x0300 (u16 LE)
    peers              = 3 × PeerEntry (59 B each — fixed):
      peer 1: ip_version=0x04, ip=0.0.0.0.0.0.0.0.0.0.0.0.10.0.0.1
              port=4242, node_id=byte_repeat(0xAA,32),
              start_window=100 (u64 LE)
      peer 2: ip_version=0x06, ip=fe80::1 padded to 16B,
              port=4242, node_id=byte_repeat(0xBB,32),
              start_window=200 (u64 LE)
      peer 3: ip_version=0x04, ip=10.0.0.2 padded,
              port=4243, node_id=byte_repeat(0xCC,32),
              start_window=300 (u64 LE)
  Expected output (hex, 179 B):
    count u16 LE (2 B): 03 00
    peer 1 (59 B): 04 + 12·00 + 0a 00 00 01 + 92 10 + AA·32 + 64·00·7
    peer 2 (59 B): 06 + fe 80 + 13·00 + 01 + 92 10 + BB·32 + c8·00·7
    peer 3 (59 B): 04 + 12·00 + 0a 00 00 02 + 93 10 + CC·32 + 2c 01·00·6
  (mt-net::tests::test_vectors::vector_c_0x51_peer_list_response_3_entries, byte-exact)

Vector C-0x60 (BatchLookupRequest)
  Input payload (per spec «Batch Lookup Protocol»):
    query_type         = 0x01 (account_id lookup)
    count              = 2
    queries            = 2 × 32 B account_id queries
  Expected output: TBD-A: C-0x60

Vector C-0x61 (BatchLookupResponse)
  Input payload: см. spec section
  Expected output: TBD-A: C-0x61

Vector C-0x62 (BatchLookupError)
  Input payload:
    query_type         = 0x01
    error_code         = 0x01 (rate_limit_exceeded)
  Expected output (hex, 2 B): 01 01
  (mt-net::tests::test_vectors::vector_c_0x62_batch_lookup_error, byte-exact)

Vector C-0x63 (RangeSubscribeRequest)
  Input payload:
    count              = 4 = 0x0400 (u16 LE)
    labels             = 4 × 32 B labels (byte_repeat(0xE0..0xE3, 32) для
                         каждого)
  Expected output (hex, 130 B): 04 00 + (32×0xE0) + (32×0xE1) + (32×0xE2) + (32×0xE3)
  (mt-net::tests::test_vectors::vector_c_0x63_range_subscribe_request_4_labels, byte-exact)

Vector C-0x64 (RangeSubscribeResponse)
  Input payload: см. spec «Label Rotation + Range Subscribe Protocol»
                 BlobEntry layout
  Expected output: TBD-A: C-0x64

Vector C-0x65 (RangeSubscribeError)
  Input payload:
    error_code         = 0x02 (label_not_found)
  Expected output (hex, 1 B): 02
  (mt-net::tests::test_vectors::vector_c_0x65_range_subscribe_error, byte-exact)

Vector C-0xF0 (Ping — empty payload — same as A1, дублируется в registry
                для completeness)
  Expected output: same as Vector A1

Vector C-0xF1 (Pong — empty payload, request_id matches Ping)
  Input:
    msg_type           = 0xF1
    request_id         = из Ping = 0x0000000000000000
    payload_length     = 0
  Expected output: TBD-A: C-0xF1

Vector C-0xFF (Bye — reason 0x00 normal shutdown)
  Input payload:
    reason             = 0x00
  Expected output (hex, 1 B): 00
  (mt-net::tests::test_vectors::vector_c_0xff_bye_normal_shutdown, byte-exact)

D. MeshFrame wire format (3 vectors)

Vector D1: single-fragment broadcast
  Input:
    flags              = 0x00 (no continuation)
    fragment_index     = 0
    total_fragments    = 1
    recipient_hint     = byte_repeat(0xFF, 32) — broadcast marker
    payload            = byte_repeat(0x55, 200) — fits in BLE MTU 244
  Expected output (hex, 235 B):
    header (35 B): 00 00 01 ff ff ff ff ff ff ff ff ff ff ff ff ff
                   ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
                   ff ff ff
    payload (200 B): byte_repeat(0x55, 200)
  (mt-net::tests + reference impl byte-exact, sha256 same as recompute)

Vector D2: multi-fragment encrypted unicast (3 fragments)
  Input:
    flags              = 0x04 (continuation set on fragments 0 и 1, clear on 2)
    fragment_index     = 0/1/2 (три separate frames)
    total_fragments    = 3
    recipient_hint     = SHA-256("mt-recipient-test-D2")[0..32]
    payload chunks     = [byte_repeat(0xA0, 240), byte_repeat(0xA1, 240),
                          byte_repeat(0xA2, 100)]
  Expected output (3 frames, hex sha256):
    f0 (275 B): sha256 = 37 be 4a 74 f6 92 af e8 9b ce 89 41 ac 19 be ac
                         a9 56 c9 54 3e c0 d1 39 06 6f 76 e1 1c 87 50 bc
    f1 (275 B): sha256 = 3b 57 be d6 a0 f2 91 8f 8e b7 3a 4b 9c 0f bb af
                         91 65 85 e2 79 21 3e 81 88 c2 dc 69 c3 e6 27 fd
    f2 (135 B): sha256 = dd e8 4e 62 a3 71 d1 fe 53 70 b8 72 2d a8 ff af
                         7c 4e 2c f5 5e 39 95 8d c5 77 73 c1 07 be 36 f8
  (mt-net::tests + reference impl byte-exact)

Vector D3: max-size single fragment with padding semantics
  Input:
    flags              = 0x02 (padding bit set)
    fragment_index     = 0
    total_fragments    = 1
    recipient_hint     = byte_repeat(0xFF, 32) — broadcast
    payload            = padding bytes от CSPRNG (для test — fixed seed
                         CSPRNG: SHAKE256("mt-test-padding-D3", 1024))
  Expected output (hex, 1024 B):
    sha256 of full frame = 82 fb 3e 9c 39 f8 6e 89 f7 a6 2a 69 94 a9 33 f4
                           c9 73 72 f2 0c 20 b1 d6 18 e1 51 7b 0f f1 63 b7
    (max single fragment: 35 B header + 989 B padding из chained SHA-256
     pseudo-CSPRNG seeded SHA-256("mt-test-padding-D3");
     reference impl byte-exact)

E. Store-and-Forward envelope (2 vectors)

Vector E1: typical SF envelope — small message, ttl_window = current+24·τ₁
  Input (per spec «Buffer model» layout):
    recipient_hint     = SHA-256("mt-recipient-test-E1")[0..32]
    ttl_window         = 24·τ₁ + 0 (current = 0, test τ₁ = 60 → 1440;
                         реальный τ₁ — emergent от D₀ per [I-18])
    fragment_index     = 0 (u8)
    total_fragments    = 1 (u8)
    ciphertext         = byte_repeat(0xCE, 256) — placeholder для test;
                         в production это ML-KEM ciphertext к recipient
    sender_signature   = ML-DSA-65 sign(sk_E1, envelope_bytes_without_signature)
                         где sk_E1 = SHAKE256("mt-test-sk-vector-E1", 64)
  Expected output (hex, 3603 B):
    sha256 of full envelope:  41 01 fc f3 11 e6 80 c8 51 96 88 79 82 63 76 70
                              d3 d6 31 40 3e d0 0c 4b 2c 11 1a 3d a2 49 cc 07
    sha256 of sender signature: eb fe cb ae 1f f4 4d ea 9b 59 e9 0d 5f ac 25 17
                                cb c2 55 26 0b e0 0b fc d3 e3 f0 14 0f 7a 5d f4
    (mt-net::tests + reference impl byte-exact)

Vector E2: SF envelope с max ttl_window и больший fragment
  Input:
    recipient_hint     = SHA-256("mt-recipient-test-E2")[0..32]
    ttl_window         = 24·τ₁ × 24 = 34560 (u32 LE) — boundary case
    fragment_index     = 5
    total_fragments    = 8
    ciphertext         = byte_repeat(0xCF, 1024)
    sender_signature   = ML-DSA-65 sign с sk_E2 = SHAKE256("mt-test-sk-vector-E2", 64)
  Expected output (hex, 4371 B):
    sha256 of full envelope:  c1 fa 8d d6 3a 7a 8f 6b 70 1f 0a 09 92 26 78 ad
                              86 70 8e ec fa 1c 08 c8 02 17 e8 6e 27 60 52 ca
    sha256 of sender signature: 41 51 c7 28 f4 af 39 1d 67 a6 53 67 4b dc dc 88
                                62 6f a4 32 73 c3 0a eb d2 9b f6 6e 34 a6 8b ba
    (mt-net::tests + reference impl byte-exact)

F. Bootstrap PoW target derivation (2 vectors)

Vector F1: target derivation для bootstrap_pow_difficulty = 65 536 (= 2^16, authoritative
                                                                     SSOT в Указе Генезиса per [I-10])
  Input:
    difficulty_factor  = 65536 (= 2^16)
  Expected:
    target (32 B big-endian):      00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00
                                   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
                         (mt-net::pow::Target::from_difficulty(65_536), byte-exact)

Vector F2: target derivation для difficulty_factor = 1024 (= 2^10; test-only, не Genesis value)
  Input:
    difficulty_factor  = 1024 (= 2^10)
  Expected:
    target (32 B big-endian):      00 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00
                                   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
                         (mt-net::pow::Target::from_difficulty(1024), byte-exact)

Conformance status

Total vectors: 50 A. Envelope : 3 B. IBT proofs (online + mesh + PoW): 3 C. Per-msg-type : 21 (18 кодов + Ping/Pong/Bye explicit) D. MeshFrame : 3 E. Store-and-Forward : 2 F. Bootstrap PoW target : 2 Subtotal : 34

Expanded coverage (additional boundary + edge per type to reach 50): остаётся до 16 vectors добавляется в Phase A reference impl iteration ("typical / boundary / edge" per critical type как в Gate 0.5 [I-9] требовании).

Закрытие [I-9] для каждого type: status «conformance pending → closed Phase A» после генерации expected hex и cross-implementation roundtrip test.

Network layer — Threat Model

Обязательная нормативная секция для сетевого слоя. Перечисляет adversary classes, защищённые свойства, coverage matrix (механизм защиты per intersection), и явный out-of-scope. Без этой секции Gate 11 (threat concentration) не закрывается для сетевых механизмов; реализатор не может оценить достаточность защит.

Adversary classes

Имя класса Возможности Бюджет (типичная оценка)
Passive observer Наблюдает encrypted трафик на каналах внутри своей сети (ISP, корпоративный сетевой администратор, государственный SIGINT в пределах юрисдикции); анализ timing, объёма, метаданных Pervasive monitoring infrastructure
Active MITM Inject / drop / delay / modify packets на каналах; ложные routing announcements (BGP hijack); rogue access points (Wi-Fi); compromised DNS resolver Network-position требуется (близко к жертве либо контроль AS)
Eclipse attacker Контроль ≥ ¾ outbound peer slots целевого узла через позиционирование своих узлов в peer-tables; цель — изолировать жертву от честной сети Multiple registered nodes (×τ₂ окон sequential SHA-256 за каждый Sybil) + multiple IP /16 / ASN
Sybil attacker Регистрирует N формально legitimate узлов через многократное прохождение sequential-chain entry barrier; цель — concentration влияния в peer selection / Dandelion stem / mesh forwarding Linear cost per identity = τ₂ окон sequential SHA-256 (N × τ₂); diversity constraints приумножают cost
DoS attacker Flood: connection requests, frames, oversized payloads, invalid signatures, expensive operations; цель — исчерпать compute / memory / disk / bandwidth у жертвы От одиночного hobbyist (~10 Mbps) до ботнета 100+ Gbps
Censor Блокирует трафик к Монтана-узлам в своей юрисдикции через DPI, SNI inspection, IP blocklist, port blocking; цель — denial of access к сети для пользователей региона National-level firewall (Great Firewall, Roskomnadzor); commercial SaaS DPI
State-level sabotage Sustained attack на хранилище / инфраструктуру через legitimate-looking операции; budget ≥ $1M; motivation = harm не profit Per Gate 14 sabotage actor budget model (родительская роль)

Защищённые свойства

Свойство Определение
Confidentiality Содержание сообщений недоступно non-recipient (включая intermediate peers в Dandelion / SF / mesh)
Integrity Сообщение не модифицировано в пути; modification обнаруживается
Availability Узел остаётся reachable для honest peers; consensus state продолжает прогрессировать
Unlinkability Внешний наблюдатель не может связать operation X с originating identity / IP узла отправителя
Identifier unlinkability (transport) Wire-format Noise_PQ XX соединения не содержит долгоживущего идентификатора в plaintext-части — passive observer не может коррелировать две TCP-сессии одного клиента ни через application restart, ни через смену IP / VPN / сети. Детальный разбор: External-Audit/transport-identifier-leakage.md.

Coverage matrix

Cell содержит механизм защиты + status. Status: C = closed конструкцией; P = partial (acknowledged residual); O = out of scope (см. ниже).

Passive observer Active MITM Eclipse Sybil DoS Censor Sabotage
Confidentiality (data) Noise_PQ XX + IBT (C) Noise_PQ XX identity pinning через node_id binding (C) Noise_PQ XX + IBT survives eclipse (C) Noise_PQ XX + IBT survives Sybil (C) n/a Noise_PQ AEAD прячет content (P — censor видит handshake metadata) n/a
Confidentiality (metadata) Uniform Framing 1024B + ≥20% padding ratio (P — observer видит volume aggregate, не indiv messages) n/a n/a n/a n/a DPI видит Noise_PQ XX handshake byte counts; no Montana-identifying SNI (T0: libp2p multistream-select; T1: deliberate cover SNI) (P) n/a
Integrity (data) n/a Noise_PQ XX AEAD + IBT signature (C) Noise_PQ XX + IBT (C) Noise_PQ XX + IBT (C) malformed payload reject early через Gate 13a invariants (C) n/a malformed input bounded по Storage Cards (C)
Integrity (consensus) n/a подписанные объекты (Transfer/Anchor/Proposal) verifiable по ML-DSA-65 (C) n/a n/a n/a n/a n/a
Availability (узла) n/a UPnP/PCP + AutoNAT + circuit relay alternatives (C) 4-уровневая diversity делает eclipse expensive (C) Sequential-chain entry barrier τ₂ окон + diversity (C) rate-limits per peer + per type + total quotas + bootstrap PoW (P — DDoS scale ≥10 Gbps требует sysadmin response помимо protocol) mesh transport BLE/Wi-Fi Aware survives complete internet block (C — но ограничен range) per-sender quotas + Storage Cards hard caps + LRU eviction (C)
Availability (consensus) n/a n/a как «узла» + cementing через ¾ honest weight (C) как «узла» (C) как «узла» + consensus path не блокируется network DoS (consensus orthogonal к transport — sequential chain продолжается локально) (C) mesh propagation cementing eventually (P — задержка часы / дни) consensus path fails-safe — invalid input не commit-ится (C)
Unlinkability Dandelion++ скрывает первый hop отправителя (P — recipient + amount открыты per [I-2]) Dandelion++ + Tor bridge (deferred M6.5/M14) (P) n/a n/a n/a Dandelion++ + censorship-resistant discovery + Tor (deferred) (P) n/a
Identifier unlinkability (transport) ephemeral ML-KEM-768 на обеих сторонах + static identity отправляется post-decapsulation, encrypted; нет plaintext-эквивалента MTProto auth_key_id (C) n/a (active MITM видит timing, не identity) n/a n/a n/a DPI видит протокольную метку /montana/noise-pq-xx/1.0.0 в multistream-select (network-wide marker, не per-client) (P) n/a

Closed C: 14 cells. Partial P: 8 cells (acknowledged residuals — см. явный список ниже). Out of scope O: 0 cells (все intersection adversary × property реальны).

Reachability-map poisoning (Censor / Sybil). A hostile node reports false ReachabilityObservation records, claiming a dead or attacker-controlled entry is reachable, to steer honest nodes onto it (connectivity eclipse through steering). Closed by construction: a (vantage_class, target_ref, profile) triple is treated as reachable only when corroborated by observations from at least REACHABILITY_QUORUM = 3 distinct /16 source groups — the diversity unit of the outgoing-connection constraints — and the final decision rests with the local IBT probe, not the map. The map is advisory: poisoning reorders candidate ranking and authorizes no connection. Status: C.

Transport profile external dependencies (Censor). Profiles T2 and T3 introduce an external carrier the node does not control. T2 routes through a content-delivery network: the provider can revoke the operator's tenant account and withdraw the carrier without notice; the mitigation is the availability of several CDN providers and fallback down the ladder to T1/T0. T3 routes through a pluggable-transport bridge: the bridge address can be enumerated and blocked by the censor, and the bridge operator is a trust party for carrier liveness — never for content, since the inner Noise_PQ XX session is end-to-end; the mitigation is bridge rotation through out-of-band distribution (Censorship-resistant discovery, channel 3) and fallback down the ladder. Neither profile weakens the inner post-quantum confidentiality or the identity binding; the dependency is on carrier availability, not on confidentiality. Status: P — availability rests on an external party, confidentiality is closed by construction.

Out-of-scope (явный список)

Эти угрозы не закрываются сетевым слоем Монтаны by design либо по архитектурному решению:

  1. Endpoint compromise — если private key узла украден, attacker полностью представляет узел в сети; защита — операционная (hardware key storage, OS hardening), не сетевой протокол.

  2. Traffic analysis correlation на global scale — attacker контролирующий significant fraction всех internet routers (Tier 1 ISPs, государство контролирующее backbone exchange) может коррелировать timing across multiple connections и deanonymize даже через Dandelion++. Защита требует global-scale anonymity network (Tor с миллионами relays), что ortogonal к Монтана.

  3. Side-channel attacks на crypto primitives — timing / power / EM на ML-DSA-65 / SHA-256 implementations. Mitigation: использовать constant-time implementations (rustls / aws-lc-rs / ring); это [C-6] requirement, не network-protocol concern.

  4. Quantum-capable adversary — статус: закрыто. PQ primitives (ML-DSA-65, ML-KEM-768) защищают application auth и encryption (). Production transport — Noise_PQ XX (ML-KEM-768 ephemeral KEM обе стороны + ML-DSA-65 identity sig + ChaCha20-Poly1305 AEAD); classical TLS 1.3 + Noise XK chain удалён из libp2p stack. Per [I-1] в protocol layer classical crypto запрещена; этот invariant теперь honored end-to-end.

  5. Supply chain attacks на dependencies — compromised library обновление (libp2p, rustls). Mitigation: Cargo.lock pinning + reproducible builds + dependency audit ([C-6] req #3-4-5-6); это build-time concern.

  6. Physical proximity attacks на mesh — BLE / Wi-Fi Aware require physical proximity (≤200m). Attacker с physical access может flood mesh advertisements. Mitigation: per-sender rate-limit + IBT mesh proof requirement делает это nonproductive, но не невозможным.

  7. Legal/regulatory pressure на bootstrap nodes — 12 hardcoded genesis bootstrap могут быть seized / forced offline государством. Mitigation: после первого подключения узел работает через discovered peers, bootstrap нужны только при cold start или при потере всех peers; censorship-resistant discovery (deferred M6.5) — secondary path.

Acknowledged residuals (P cells expanded)

# Cell Residual Severity Mitigation roadmap
1 Censor → Confidentiality (data) DPI видит Noise_PQ XX handshake byte counts (msg1=1184B / msg2=7533B / msg3=6349B distinct sizes) Medium Optional handshake padding to a uniform target; deferred to post-mainnet hardening
2 Passive observer → Confidentiality (metadata) Uniform Framing скрывает per-message volume, но aggregate KB/sec viewable Low Defense-in-depth достаточен; full traffic shaping out of scope
3 DoS → Availability (узла) DDoS ≥10 Gbps требует sysadmin response Medium Standard practice (Cloudflare-like upstream filtering); not protocol concern
4 Censor → Availability (узла) Mesh range ≤200m недостаточен для intercity связи без internet Medium Defer multi-hop mesh routing к M14 mobile work
5 Censor → Availability (consensus) Mesh propagation timing часы/дни добавляет latency Acceptable Eventually consistency через mesh — protocol design choice
6 Passive observer → Unlinkability Recipient + amount открыты after fluff per [I-2] By design [I-2] открытость финансового слоя — non-negotiable per глобальный invariant
7 Censor → Unlinkability Без Tor bridge deanonymization возможна на censor-controlled exit Medium Deferred M6.5 / M14 (censorship-resistant discovery)

Compliance с глобальными инвариантами

  • [I-1] PQ-secure — Threat model признаёт residual (Q4 outscope) пока TLS не PQ; closed для auth (IBT через ML-DSA-65) и для confidentiality в SF (ML-KEM-768 E2E). На момент M6 acceptable как PQ-protected identity layer над classical TLS transport.
  • [I-3] Determinism — Threat model orthogonal: defines what is defended, не какие state changes возникают.
  • [I-7] Minimal crypto surface — Threat model не вводит новых crypto primitives; переиспользует ML-DSA-65 / SHA-256 / ML-KEM-768 / ChaCha20-Poly1305.

Audit guidance

Внешний security аудитор использует matrix как entry point: для каждой cell с status C — verify через указанный механизм (раздел спеки + binding KAT vector + Phase A test); для P — verify acknowledged residual явно зафиксирован; для O — verify out-of-scope явно перечислен в списке выше.

Сетевые параметры в protocol_params

В рамках закрытия раздела II.5 плана сетевого слоя M6 в protocol_params Указа Генезиса добавлены пять новых полей. Authoritative layout и инварианты — в Указе Генезиса (раздел «Genesis Decree» выше). Этот sub-section содержит derivation per «Академическое обоснование констант» + нормативные backpressure rules. Bump Genesis State Hash — pre-mainnet acceptable.

Derivation per «Академическое обоснование констант»:

Константа: bootstrap_pow_difficulty (authoritative SSOT в Указе Генезиса per [I-10])
Значение:  65 536 (2^16)

Класс:       Performance / Security (anti-flood)
Target:      ≈100 мс CPU per попытка на genesis-железе (5.097 MH/s); attacker
             с rate 10 connections/сек тратит 1 CPU-сек/сек = одно ядро
             постоянно занято на каждую bootstrap; 12 bootstrap × 1 ядро =
             12 ядер для distributed flood (manageable through standard
             upstream rate-limiting)
References:  Hashcash [Back 2002]; Bitcoin PoW genesis [Nakamoto 2008];
             ProgPoW analysis для anti-ASIC bias (не применяется здесь —
             SHA-256 specifically, но class precedent)
Derivation:  attempts_per_second_genesis = 5.097 × 10^6 / (2^256 / target)
             = 5.097 × 10^6 / 2^240 (для difficulty 2^16, target = 2^240)
             = ≈10 attempts/sec (1 успех каждые 100 мс)
Sensitivity: ±10% от target → ±10% CPU cost; ×2 difficulty (2^17) → 200 мс
             per attempt → ×2 attacker cost; ×0.5 difficulty (2^15) → 50 мс
             → ×0.5 attacker cost (acceptable lower bound — bootstrap не
             высокоценная цель flood)
Defense:     Q: «почему не выше? повысить ×10 для лучшей защиты»
             A: bootstrap нужен только при cold start; legitimate user
                ждёт 100 мс при первом подключении приемлемо, 1 секунда
                фрустрирует
             Q: «почему 2^16 а не 2^14?»
             A: 2^14 (25 мс target) attacker cost ниже на ×4 — distributed
                flood requires ×4 больше IPs для same impact; 2^16 = sweet
                spot между UX и flood resistance

Default (operator-configurable, не Genesis Decree): max_outbound_per_node
Значение:  24

Класс:       Operational / Security (eclipse resistance)
Target:      Eclipse требует attacker control ≥ ¾ outbound = ≥ 18 of 24;
             diversity constraints (4-уровневая) делают это economically
             expensive: requires ≥ 18 different ASN AND ≥ 18 different
             /16 prefix AND ≥ 18 different start_window cohorts
References:  Bitcoin outbound = 8 [Nakamoto core]; Ethereum geth = 16
             [Ethereum Yellow Paper]; libp2p kademlia bucket size = 20
             [Maymounkov-Mazières 2002]
Derivation:  Compromise budget anti-eclipse: P(eclipse) ≈ (f^N) при f =
             attacker fraction of network nodes; для f = 0.3 и N = 24,
             P ≈ 3 × 10^-13 (negligible); для f = 0.5 и N = 24,
             P ≈ 6 × 10^-8 (acceptable для рare event); diversity
             constraints further reduce
Sensitivity: N = 16 → P(f=0.5) ≈ 1.5 × 10^-5 (acceptable degradation
             но не optimal); N = 32 → P(f=0.5) ≈ 2 × 10^-10 (better
             но удваивает bandwidth cost: 26 KB/сек vs 13 KB/сек —
             приемлемо для homeServer нет)
Defense:     Q: «почему 24 а не 16 (как Ethereum)?»
             A: Ethereum gas economics differ; Montana baseline 1 fps —
                bandwidth не constrained; добавление 8 outbound — minor
                cost для significant eclipse resistance
             Q: «почему не 32?»
             A: marginal benefit P(eclipse) reduction × ×2 bandwidth
                cost — UX boundary

Default (operator-configurable, не Genesis Decree): max_inbound_per_node
Значение:  13

Класс:       Operational
Target:      13 inbound × 1 fps × 1024 B = 13 KB/сек ≈ 33 GB/мес
             приемлемо для домашнего сервера (limit ОТТ-cap у российских
             home ISP ≈ 100 GB/мес unmetered)
References:  Spec строка 4207 derivation
Derivation:  bandwidth_per_inbound = 1024 B/сек × 86400 сек/день × 30 дней
             ≈ 2.5 GB/мес per inbound; 13 inbound × 2.5 GB = 32.5 GB/мес
Sensitivity: 8 inbound → 20 GB/мес; 16 inbound → 40 GB/мес — обе boundaries
             acceptable, 24 = по derivation Protocol [P(eclipse) < 2⁻⁴⁰]; равно max_outbound
             distribution когда 100% узлов имеют equal in/out balance
Defense:     Q: «почему inbound меньше outbound?»
             A: outbound определяет eclipse resistance (own perspective);
                inbound — quota на сколько ressources даёшь чужим
                подключениям. Asymmetry deliberate: каждый узел actively
                выбирает 24 outbound for security, passively принимает
                13 inbound
             Q: «почему не равно 24 = 24?»
             A: bandwidth budget ограничен; 13 inbound покрывает
                reciprocity без overload

Default (operator-configurable, не Genesis Decree): max_pending_requests_per_peer
Значение:  256

Класс:       Operational (backpressure)
Target:      Ограничивает correlation table memory: 256 × peer_count × 32 B =
             ≈ 13 KB per peer × 24 outbound = 312 KB total per node
             (negligible memory)
References:  HTTP/2 SETTINGS_MAX_CONCURRENT_STREAMS default 100 [RFC 7540];
             gRPC default max_concurrent_streams 100 [grpc.io]
Derivation:  request_timeout = τ₁ / 2 ≈ 30 сек на genesis hardware;
             rate at full capacity: 256 requests / 30 сек ≈ 8.5 req/сек
             per peer; 24 outbound × 8.5 = ≈200 req/сек total — sufficient
             для FastSync chunked + PeerList exchange + BatchLookup
             concurrent
Sensitivity: 128 → ≈4 req/сек per peer (constrains FastSync chunked);
             512 → ≈17 req/сек (more headroom но 2× memory; trade-off
             в пользу 256)
Defense:     Q: «почему 256 а не 1024?»
             A: 1024 — overhead на correlation tracking без proportional
                benefit для use cases (FastSync дробится автоматически
                на chunks)

Default (operator-configurable, не Genesis Decree): request_timeout_t1_div
Значение:  2

Класс:       Operational
Target:      Request TTL = τ₁ / 2 — позволяет получить response в текущем
             τ₁ окне в типичном случае (один RTT через global internet
             ≪ τ₁/2)
References:  Spec строка 4794 (TCP connect: τ₁/2)
Derivation:  τ₁ ≈ 60 сек на genesis hardware; τ₁/2 = 30 сек — far above
             reasonable global RTT (≤ 1 сек); margin для retransmits
             и slow paths
Sensitivity: divisor = 1 (TTL = τ₁) → больше pending requests
             накапливаются (memory pressure); divisor = 4 (TTL = τ₁/4)
             → false timeouts на slow links
Defense:     Q: «почему не absolute value (например 30 сек)?»
             A: τ₁ adaptable через `participation_ratio` feedback
                (раздел «Адаптация D»); если D растёт → τ₁ растёт →
                request timeout пропорционально растёт; absolute value
                desync с network capacity при slow hardware participation

Backpressure rules — нормативный текст

Применяется ко всем IBT-сессиям + ProtocolMessage envelope layer.

Правило B1 — Pending requests cap.

Для каждого peer count активных pending requests (status awaiting response) ≤ max_pending_requests_per_peer. При попытке создать новый request выше cap — caller получает Error::Backpressure, request не отправляется. После expiry (по request_timeout_t1_div) либо response counter уменьшается.

Правило B2 — Frame intake rate per peer.

Для каждого peer rate входящих frames ≤ baseline_frame_rate × max_burst_factor где baseline_frame_rate = 1 fps (см. Uniform Framing) и max_burst_factor = 8 (max_burst per spec строка 4204). При превышении: drop excess frames silently (не disconnect — разрешает burst recovery), increment peer-level penalty counter.

Правило B3 — Total connection cap.

Узел поддерживает максимум max_outbound_per_node исходящих + max_inbound_per_node входящих TCP-соединений одновременно. Превышение outbound — caller получает Error::OutboundCapReached, попытка отклоняется до rotation освободит slot. Превышение inbound — incoming TCP connection отклоняется на TCP уровне (RST), счётчик не обновляется (защита от counter inflation).

Правило B4 — Penalty escalation.

Peer-level penalty counter увеличивается при: (a) frame intake rate exceedance (B2); (b) malformed payload after Gate 13a structural validation; (c) signature verification failure при IBT либо при подписанных payload-объектах. При достижении threshold = 100 events per τ₁ — peer disconnect с reason 0x04 (protocol violation) + blacklist на 24·τ₁ (см. spec «Retry policy»). Penalty counter сбрасывается на каждой τ₁ boundary либо при successful disconnect.

Правило B5 — Resource quota enforcement timing.

Все backpressure checks выполняются до crypto-verification (ML-DSA-65 verify ≈0.1 мс на commodity CPU; protection against amplification atak). Order:

  1. TCP-level rate (OS tcpdump rules — outside scope spec)
  2. TLS-level (rustls / aws-lc-rs — library scope)
  3. IBT proof verify (ML-DSA-65 — внутри карточки IBT)
  4. Frame structure parse (Gate 13a Uniform Framing invariants)
  5. Frame intake rate per B2 — перед ProtocolMessage decode
  6. ProtocolMessage envelope parse (Gate 13a)
  7. Pending request cap per B1 — для request types
  8. Payload structure validation (Gate 13a per type)
  9. Payload signature verify (если payload подписан — Transfer / NodeRegistration / etc.)
  10. Apply transition (apply_proposal либо locally — outside backpressure scope)

Правило B6 — Sabotage actor protection.

Sabotage actor с budget $1M (per Threat Model) с реалистичным ресурсом ≈10 Gbps можно flood узла; standard upstream filtering (Cloudflare-like, ISP DDoS protection, hosting provider mitigation) — out of scope spec, expected operator practice. Spec backpressure rules защищают только от per-peer behaviour, не от volumetric DDoS на network уровне.

apply_mesh_frame и apply_store_and_forward — нормативные формулировки

Сетевые apply-функции выполняются на receive path локального узла. В отличие от consensus apply-функций (apply_proposal, apply_emission, etc.) не входят в state_root ([I-3] orthogonal — locally derived state). Однако их детерминизм важен: повторное применение того же frame должно давать тот же результат (idempotency для replay-handling).

apply_mesh_frame(frame, sender_pubkey, peer_local_state) → MeshIntake
Input:
  frame             MeshFrame (см. spec «MeshFrame wire format»)
  sender_pubkey     ML-DSA-65 pubkey 1952B (полученный из mesh advertisement)
  peer_local_state  &mut LocalMeshState {
    used_nonces:    Map<sender_pubkey_hash, Set<nonce>>,
    fragments:      Map<(sender_pubkey_hash, msg_id), Vec<Fragment>>,
    rate_window:    Map<sender_pubkey_hash, FrameWindow>,
  }

Output:
  MeshIntake = enum {
    Accepted,                    — frame validated, persisted либо forwarded
    AcceptedComplete(payload),   — последний fragment, message complete
    Rejected(reason),            — silent reject (no error to sender)
  }

Steps (ordered, deterministic):

  1. Structure validation per Gate 13a invariants для MeshFrame:
     - flags ∈ {0x00, 0x02, 0x04, 0x06} (data | padding | continuation
       | data+continuation; padding+continuation запрещено)
     - fragment_index ≤ total_fragments
     - total_fragments ≤ 255
     - recipient_hint exactly 32 B
     - payload size ≤ MTU (BLE 244 B либо Wi-Fi Aware 1500 B —
       определяется transport profile, сигнализируется адверsement)
     Не пройдено → return Rejected(InvalidStructure)

  2. IBT mesh proof verification per карточка «IBT mesh proof»:
     - cached_window ∈ [known_W  7·τ₁, known_W]
     - mesh_session_nonce ∉ used_nonces[hash(sender_pubkey)]
     - signature valid под sender_pubkey
     Не пройдено → return Rejected(InvalidIBT)

  3. Frame intake rate per Backpressure Rule B2:
     - rate_window.intake_count++
     - intake_count ≤ baseline_frame_rate × max_burst_factor (1 fps × 8 = 8)
       within latest 1 сек window
     Превышено → drop frame, increment penalty (Rule B4), return Rejected(RateLimit)

  4. Recipient determination:
     - recipient_hint == 0xFF × 32 → broadcast — adressed to all mesh peers
     - recipient_hint == hash(local_node_pubkey) → addressed to self
     - иначе → addressed to other peer, candidate for forwarding
       (см. apply_store_and_forward для forwarding semantics)

  5. Fragment assembly (для multi-fragment messages):
     msg_id = (sender_pubkey_hash, fragment header — derivable from envelope)
     fragments[msg_id].insert(fragment_index, frame.payload)
     Если fragments[msg_id].len() == frame.total_fragments:
       payload_complete = concat(sorted by fragment_index)
       fragments.remove(msg_id)
       return AcceptedComplete(payload_complete)
     иначе:
       return Accepted

  6. Update used_nonces (после полного accept):
     used_nonces[hash(sender_pubkey)].insert(mesh_session_nonce)

Идемпотентность:
  apply_mesh_frame(F, S, state) дважды:
    Первый вызов:  Accepted либо AcceptedComplete(P), state' с frame
                   зарегистрированным
    Второй вызов:  Rejected(InvalidIBT) — nonce уже в used_nonces, dedup

  Это и есть проявление determinism: после первого accept повторный
  frame с тем же nonce silent-rejected, защита от replay.
apply_store_and_forward(envelope, sender_pubkey, sf_local_state) → SFIntake
Input:
  envelope          SFEnvelope (см. spec «Buffer model»)
  sender_pubkey     ML-DSA-65 pubkey (из envelope.sender_signature)
  sf_local_state    &mut LocalSFState {
    buffer:                Map<recipient_hint, BufferEntry>,
    sender_quotas:         Map<sender_pubkey_hash, SenderQuotaState>,
    recipient_acks:        Map<recipient_hint, SignedAck>,
    total_bytes:           u64,
  }

Output:
  SFIntake = enum {
    Buffered,                         — frame stored для forwarding
    DeliveredLocally(decrypted_msg),  — recipient = self, ML-KEM decrypt OK
    Rejected(reason),                 — silent reject
  }

Steps (ordered, deterministic):

  1. Structure validation per Gate 13a invariants для SFEnvelope:
     - recipient_hint exactly 32 B
     - ttl_window ∈ [current_W + 1, current_W + 24·τ₁]
       (TTL hard cap 24·τ₁; ttl_window ≤ current_W → expired, reject;
       ttl_window > current_W + 24·τ₁ → invalid, reject)
     - fragment_index ≤ total_fragments ≤ 255
     - sender_signature exactly 3309 B
     - ciphertext size ≤ 4096 B (per envelope upper bound)
     Не пройдено → return Rejected(InvalidStructure)

  2. Sender signature verification:
     verify_message = canonical_encode(envelope_without_signature_bytes)
     ML-DSA-65 verify(sender_pubkey, verify_message, sender_signature)
     Не пройдено → increment penalty (Rule B4), return Rejected(InvalidSignature)

  3. Per-sender quota per spec «Per-sender quota»:
     sender_quota = sender_quotas[hash(sender_pubkey)]
     sender_quota.frames_in_window_τ₁++
     Если frames_in_window_τ₁ > 256:
       return Rejected(SenderQuotaExceeded) — silent (sender определит
       по retry timeout)
     Sender_quota автоматически reset на каждой τ₁ boundary.

  4. Recipient ack check per spec «Signed rate-limit acks»:
     Если recipient_acks[recipient_hint].is_revoked(sender_pubkey, current_W):
       return Rejected(ForwardingRevoked)

  5. Total buffer quota check per Storage Card sf_buffer:
     prospective_total = total_bytes + envelope_size
     Если prospective_total > 1 GB AND sender NOT in operator VIP whitelist:
       return Rejected(TotalQuotaExceeded)
     Если prospective_total > 1 GB AND sender в VIP whitelist:
       eviction: drop oldest non-VIP entry until prospective_total ≤ 1 GB

  6. Recipient determination:
     Если recipient_hint == hash(local_node_pubkey):
       attempt ML-KEM decrypt(local_node_secretkey, envelope.ciphertext)
       Если success:
         remove envelope from buffer (delivered)
         return DeliveredLocally(decrypted_msg)
       Если failure:
         return Rejected(DecryptFailure) — corrupted либо unintended
     Иначе:
       buffer.insert(recipient_hint, envelope)
       total_bytes += envelope_size
       return Buffered

  7. Forwarding policy per spec «Forwarding algorithm»:
     После Buffered — узел periodically attempts forwarding:
     - выбирает peer-кандидатов с recipient_hint match (либо broadcast peers)
     - отправляет envelope как ProtocolMessage (тип reserved для SF —
       используется extension namespace, см. план дальше при имплементации)
     - при successful delivery либо TTL expiry → remove from buffer
       total_bytes -= envelope_size

Идемпотентность:
  apply_store_and_forward(E, S, state) дважды с identical envelope:
    Первый вызов:  Buffered либо DeliveredLocally
    Второй вызов:  Per-sender quota инкрементируется снова → может вернуть
                   SenderQuotaExceeded; либо
                   Если буфер уже содержит envelope с тем же
                   (recipient_hint, sender_pubkey, ciphertext) — то
                   `buffer.insert` is replace, total_bytes без изменений,
                   return Buffered (idempotent на содержимое buffer)

  Recipient ack revocation, sender quota exhaustion, либо TTL expiration
  делают повторный apply non-idempotent в смысле response, но idempotent
  в смысле buffer state (либо present либо expired/removed).
Локальные функции вне consensus state — почему apply_*

Имена apply_mesh_frame и apply_store_and_forward симметричны consensus apply (apply_proposal, apply_emission) намеренно: семантически одинаковая роль — взять input + state → produce new state. Различие — в scope:

  • consensus apply: state ∈ consensus root (state_root computation)
  • network apply: state ∈ local node-runtime (вне state_root, не consensus-critical)

Это гарантирует:

  1. Implementation pattern consistency — реализатор (Phase G) знает что mesh / SF имеют ту же семантику что consensus apply: deterministic, ordered, idempotent (где specified).
  2. Cross-impl conformance — две независимые реализации, прогнавшие один и тот же frame через apply_mesh_frame, получают same MeshIntake outcome. Это conformance condition (хотя не consensus-critical, важно для interop testing).
  3. [C-7] enforcement — code-side carded role предписывает no-shortcut на apply_* функциях; mesh / SF теперь явно входят в этот класс, никакого shortcut доступа к used_nonces / buffer / sender_quotas напрямую (только через apply).

Final Gate audit сетевого слоя M6

Финальный аудит закрытия плана раздела II — закрытие плана сетевого слоя M6 (II.1 карточки + II.2 Storage Cards + II.3 KAT vectors + II.4 Threat Model

  • II.5 backpressure + II.6 apply_*).

Gate 0.5(d.1) Formula name coverage: new section не вводит новых formula names; все references существующих формул через explicit pointers на authoritative разделы. Pass.

Gate 0.5(d.2) Field name coverage для пяти новых полей protocol_params:

  • bootstrap_pow_difficulty — authoritative в Указе Генезиса (4 B u32 = 65 536); references в карточке Bootstrap PoW (open sub-finding now closed), KAT vector B3, derivation в sub-section «Сетевые параметры в protocol_params». Все references explicit на Genesis Decree authoritative location. Pass.
  • max_outbound_per_node — authoritative в Указе Генезиса (1 B u8 = 24); references в Backpressure Rule B3 + derivation. Pass.
  • max_inbound_per_node — authoritative в Указе Генезиса (1 B u8 = 13); references в Backpressure Rule B3 + derivation. Pass.
  • max_pending_requests_per_peer — authoritative в Указе Генезиса (2 B u16 = 256); references в Backpressure Rule B1 + derivation. Pass.
  • request_timeout_t1_div — authoritative в Указе Генезиса (1 B u8 = 2); references в Backpressure Rule B1 + derivation. Pass.

Zero value duplications вне authoritative location. [I-10] SSOT compliance verified.

Gate 15 Part 1B Generic version sweep: Spec body grep \bv?[0-9]+\.[0-9]+\.[0-9]+\b returns only legitimate hits:

  • Header line 3 — single Montana spec version (authoritative)
  • QEMU Virtual CPU v4.2.0 / v8.2.0 (illustrative external hardware variance table — pre-existing, external software identifier, not Montana version)
  • ip_version=0x04 field в KAT vectors (regex false positive — 04 parsed as 4.0 patch number; ip_version IS a field, not version mention)
  • Rust 1.92.0, sha2 crate v0.10.9, macOS Sequoia 15.7.3, Darwin 24.6.0 (pre-existing external software identifiers in [I-18] D₀ derivation context)

Zero spec-version mentions in body. [I-10] SSOT compliance verified.

Cross-section consistency (active comparison): для всех новых терминов (IBT online/mesh, Bootstrap PoW, Uniform Framing, Transport Randomness, Peer selection, Dandelion++, NAT Traversal, Mesh Transport, Store-and-Forward, ProtocolMessage envelope, message type registry, Backpressure rules) терминология синхронизирована между основной частью спеки (раздел «Сетевой уровень») и sub-разделами «Карточки замыкания механизмов сетевого слоя». Reading-based verify: каждое упоминание в карточке references existing спеку section без cross-spec terminology drift. Pass.

Gate 13a Invariant enumeration completeness:

  • Каждая из 12 карточек имеет полные 11 пунктов замыкания + 15-pass global invariant check. Pass.
  • Все 4 локальных Storage Cards имеют 11 полей + [I-14] путь + sabotage budget. Pass.
  • 50 KAT vector definitions — каждый с input scheme + expected output placeholder (TBD-A в Phase A reference impl). Pass.
  • Threat Model — 7 adversary classes × 4 properties matrix с явными status C/P/O markers. Pass.
  • Genesis Decree extension инварианты enumerated explicit (5 новых полей + byte-exact values + runtime mutation запрещена). Pass.
  • apply_mesh_frame и apply_store_and_forward — explicit ordered steps + идемпотентность guarantees. Pass.

Gate 0 Global invariant check для каждого нового механизма: Все 12 карточек прошли 15-pass invariant check ([I-1]..[I-15]) explicit:

  • [I-1] PQ-secure: yes для всех auth (ML-DSA-65 IBT)
  • [I-2] Public financial layer: explicit acknowledgment в Dandelion++ карточке (скрывает только первый hop)
  • [I-3] Determinism: transport-orthogonal в карточках; deterministic в apply_mesh_frame / apply_store_and_forward
  • [I-4] TimeChain independence: yes в IBT mesh + Mesh Transport (cached_window работает offline)
  • [I-5] Commodity hardware: yes для всех (BLE/Wi-Fi Aware на любом устройстве, ML-DSA-65 verify дёшев)
  • [I-6] Regulatory compat: yes для всех; explicit acknowledgment в Dandelion++ что не privacy mixer
  • [I-7] Minimal crypto surface: yes — переиспользует ML-DSA-65 / SHA-256 / ML-KEM-768 / ChaCha20-Poly1305
  • [I-8] Network-bound unpredictability: n/a для всех (transport-orthogonal, не consensus seed)
  • [I-9] Bit-exact deterministic: yes для wire format (binding KAT vectors В Phase A); partial для transport scheduling (non-deterministic by design)
  • [I-10] Single Source of Truth: yes для всех (formal layout + invariants в одном месте, references в другом)
  • [I-14] State lifecycle: yes для всех persistent — locales Storage Cards fixate hard quotas + temporal pruning
  • [I-15] Time-based scarcity: yes для всех anti-spam mechanisms (rate-limits через time, не money)

Zero global invariant violations. Network layer Gate 0 compliance: Pass.

Минимально необходимое для разблокирования Phase A кода: все 7 пунктов плана раздела II закрыты. Implementer mt-net::* крейтов имеет:

  • 12 нормативных карточек закрытия
  • 4 Storage Cards для локальных сетевых таблиц
  • 50 binding KAT vectors (input schemas + algorithm)
  • Threat Model coverage matrix
  • 5 Genesis Decree fields (bootstrap_pow_difficulty + connection limits + timeout)
  • 6 backpressure rules B1-B6
  • 2 apply_* функции (mesh + SF)

Phase A разблокирована. Reference implementation начинается.

Status: План раздела II — закрыт. Network layer спека audit-ready для Phase A start.