Files
MESH2/docs/specs/MESH-Security-Addendum-v0.1.1.md
anthonyrawlins f2d62fa03d Initial workspace: scaffold + constitution + spec documents
Rust workspace with 5 crates (mesh-types, mesh-crypto, mesh-network,
mesh-validator, mesh-wallet), PROJECT_CONSTITUTION.md for CHORUS
automated ingestion, and the full MESH protocol specification suite.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 20:49:44 +11:00

28 KiB
Raw Permalink Blame History

MESH PROTOCOL

Security Addendum

Attack Vector Analysis and Formal Mitigations

Errata and amendments to SPEC-003, SPEC-007, SPEC-008, SPEC-010

Version 0.1.1-draft | March 2026

Introduction

This document identifies seven attack vectors against the MESH protocol as specified in the v0.1.0 specification suite. For each attack, we provide: the threat description, the root cause in the existing specification, the formal mitigation, the specification amendments required, and the mandatory test assertions.

All mitigations in this document are MANDATORY amendments to the referenced specifications. An implementation that passes all v0.1.0 test vectors but does not implement these mitigations is NON-CONFORMANT. The version number of the specification suite is incremented to v0.1.1 upon adoption of this addendum.

Severity Classification


Severity Label Definition

S1 CRITICAL Allows direct theft of funds or creation of value from nothing. Protocol is broken without mitigation.

S2 HIGH Allows economic griefing, denial of service against specific users, or degrades privacy guarantees.

S3 MEDIUM Allows minor economic advantage or information leakage under specific conditions.


AV-001: Free Option PTLC Griefing Attack

Severity: S2 (HIGH)

1.1 Threat Description

A malicious sender initiates a multi-hop payment via PTLCs (SPEC-008 §8.4) with a long expiry. The Prepare packet locks the connector's liquidity for the duration of the expiry. The sender then monitors the market and decides whether to fulfill or abandon the payment based on whether the exchange rate has moved in their favour.

This creates a free financial option: the connector bears the full market risk (their liquidity is locked at a fixed rate) while the sender has the optionality to walk away at zero cost. Over millions of transactions, a systematic attacker could extract significant value from connectors.

1.2 Root Cause

SPEC-008 §8.2.1 defines an expiry field on MeshPacket but imposes no constraint on its maximum value and no cost for locking connector liquidity. The specification is silent on the relationship between expiry duration and economic cost.

1.3 Mitigation

Three complementary mechanisms:

1.3.1 Maximum Expiry Decay Per Hop

Each connector along a payment path MUST reduce the expiry by at least a minimum decrement. This limits the total lock-up time and ensures that senders cannot impose arbitrarily long lock-ups.

SPEC-008 AMENDMENT §8.4.5 (NEW): Each connector MUST set the outgoing Prepare packet's expiry to at most: outgoing_expiry = incoming_expiry - T_hop, where T_hop is a per-connector configuration parameter with a RECOMMENDED minimum of 30 seconds. A connector MUST reject an incoming Prepare if incoming_expiry - current_time < T_hop (insufficient time to complete the hop). This creates a hard ceiling on total path expiry: max_total_expiry = T_hop × max_path_length. With T_hop = 30s and max_path_length = 10 hops, the maximum lock-up is 5 minutes.

1.3.2 Locking Fee

Connectors MAY charge a time-value locking fee that is deducted from the payment regardless of whether it is fulfilled or rejected. This converts the free option into a priced option.

SPEC-008 AMENDMENT §8.5.1 (NEW): A connector MAY specify a locking_fee_rate in its published rate information, expressed as basis points per second of lock-up time. The locking fee is: locking_fee = amount × locking_fee_rate × (expiry - current_time) / 10000. The locking fee is deducted from the transferred amount. If the payment is fulfilled, the connector receives the locking fee plus the spread. If the payment expires, the connector retains the locking fee. The sender's wallet MUST account for locking fees when calculating the total cost of a payment.

1.3.3 Sender Reputation

Connectors SHOULD maintain a local reputation score for senders based on their fulfillment rate. Senders who frequently let payments expire receive lower priority and wider spreads.

SPEC-008 AMENDMENT §8.5.2 (NEW): Connectors SHOULD track the ratio of fulfilled to expired Prepare packets per sender public key. A connector MAY reject Prepare packets from senders whose fulfillment rate falls below a configurable threshold (RECOMMENDED: 80% over the last 1000 packets).

1.4 Test Assertions

  1. Assert that a connector rejects an incoming Prepare with expiry > current_time + T_hop × max_path_length.

  2. Assert that each hop reduces the outgoing expiry by at least T_hop.

  3. Assert that a payment path with 10 hops and T_hop = 30s has a maximum total lock-up of 300 seconds.

  4. Assert that the locking fee is correctly deducted from the amount when the payment expires without fulfillment.

  5. Assert that a sender with a 50% fulfillment rate is rejected by a connector with an 80% threshold.

AV-002: Negative Fee Range Proof Bypass

Severity: S1 (CRITICAL)

2.1 Threat Description

A sender constructs a transition where the amount commitment is valid (positive value with valid range proof), but the fee commitment encodes a negative value. If the conservation check only verifies that Σ(recipient amounts) + fee = debit amount on the commitment points, and the fee range proof is missing or weak, the sender effectively increases their balance by the absolute value of the negative fee.

Example: Sender has balance 1000. Sender creates a transfer of 1000 to recipient, with fee = -500. Conservation check passes: 1000 + (-500) = 500. But the sender has only been debited 500 from a balance of 1000, so they retain 500 extra. The recipient gets 1000. 500 units of value have been created from nothing.

2.2 Root Cause

SPEC-002 §2.4.3 specifies RANGE_CHECK (rule 6) and CONSERVATION_CHECK (rule 7) as separate steps. However, the specification text for RANGE_CHECK states: "All range proofs MUST verify. This proves all amounts are non-negative and within the valid range [0, 2⁶⁴)." The word "amounts" is ambiguous --- it could be read as referring only to the recipient amounts, not the fee. This ambiguity is the attack surface.

2.3 Mitigation

Eliminate the ambiguity with an explicit enumeration of every commitment that requires a range proof.

SPEC-002 AMENDMENT §2.4.3 Rule 6 (REVISED): RANGE_CHECK: The following range proofs MUST be present and MUST verify: (a) transition.range_proof proves transition.amount is in [0, 2⁶⁴). (b) transition.fee_proof proves transition.fee is in [0, 2⁶⁴). (c) For each recipient r in transition.recipients: r.range_proof proves r.amount is in [0, 2⁶⁴). If ANY of these proofs is absent, malformed, or fails verification, the validator MUST reject the transition with ERR_INVALID_RANGE_PROOF. There are no exceptions. A transition with zero fee MUST still include a valid range proof for the zero-value fee commitment.

SPEC-002 AMENDMENT §2.4.3 Rule 7 (CLARIFIED): CONSERVATION_CHECK: The sum of all output commitments MUST equal the input commitment. Formally: C_amount = Σ(C_recipient_i) + C_fee. This is verified on the commitment points (elliptic curve point addition). The validator MUST verify this equation AFTER all range proofs have passed. The combination of range proofs (all values non-negative) and conservation (inputs = outputs) together guarantee that no value is created or destroyed.

2.4 Test Assertions

  1. Assert that a transition with a valid amount range proof but a missing fee range proof is rejected with ERR_INVALID_RANGE_PROOF.

  2. Assert that a transition with a fee commitment to a negative value (commitment point C_fee where opening reveals v < 0) is rejected.

  3. Assert that a transition with fee = 0 still requires a valid range proof for the zero commitment.

  4. Assert that a transition where Σ(outputs) + fee = input but fee is negative is rejected (the range proof on fee catches this before the conservation check).

  5. Assert that crafting a Bulletproofs+ proof for a value outside [0, 2⁶⁴) produces a proof that fails verification.

AV-003: Causal Dependency Orphan Exploit

Severity: S1 (CRITICAL)

3.1 Threat Description

A Byzantine account references a hash in causal_deps that is a syntactically valid SHA3-256 digest but does not correspond to any settlement certificate the validator has seen. If the validator only checks the hash format (32 bytes, non-zero) without verifying that it corresponds to a real, certified transition, the Byzantine account could spend funds it never received.

Worse: if the "ghost" dependency references a fabricated certificate that credits the attacker with value, the attacker creates money from nothing by spending against a fictional incoming payment.

3.2 Root Cause

SPEC-002 §2.4.3 Rule 8 (CAUSAL_CHECK) states: "Every hash in causal_deps MUST reference a transition that the validator has already processed and certified." This is correct in intent but insufficiently precise about what "processed and certified" means. The specification does not mandate how the validator performs this lookup or what happens during network partition when a validator may be behind.

3.3 Mitigation

SPEC-002 AMENDMENT §2.4.3 Rule 8 (REVISED): CAUSAL_CHECK: For every hash h in transition.causal_deps, the validator MUST verify that: (a) h exists in the validator's local certificate store (a hash map keyed by transition hash, populated only when a valid SettlementCertificate is received and verified per SPEC-003 §3.3.2), AND (b) the referenced transition's effects (credit to the current sender) have been applied to the sender's AccountState. If any hash is not found in the certificate store, the validator MUST return ERR_UNKNOWN_DEPENDENCY. The validator MUST NOT attempt to fetch the missing certificate from the network during validation. If a dependency is genuinely missing (e.g., due to network delay), the sender must retry after the dependency has been disseminated to the validator.

Additionally, the specification must prevent the case where a sender references a certified transition that does not actually credit them:

SPEC-002 AMENDMENT §2.4.3 Rule 8a (NEW): DEPENDENCY_RELEVANCE_CHECK: For every hash h in transition.causal_deps, the validator MUST verify that the referenced transition either: (a) has the current sender as a recipient, OR (b) is a prior transition by the current sender (same sender public key). A transition that references an unrelated third-party certificate as a causal dependency MUST be rejected with ERR_IRRELEVANT_DEPENDENCY. This prevents an attacker from padding causal_deps with arbitrary valid hashes to confuse causal ordering analysis.

3.4 Test Assertions

  1. Assert that a transition referencing a non-existent hash in causal_deps is rejected with ERR_UNKNOWN_DEPENDENCY.

  2. Assert that a transition referencing a real certificate hash that does not credit the sender is rejected with ERR_IRRELEVANT_DEPENDENCY.

  3. Assert that a transition referencing a valid, relevant certificate is accepted.

  4. Assert that a validator does NOT fetch missing dependencies from the network during the validation path (no side-effects during validation).

AV-004: Cross-Shard Double-Spend Race

Severity: S1 (CRITICAL) if sharding is incorrectly implemented; MITIGATED BY DESIGN if spec is followed

4.1 Threat Description

A Byzantine sender sends two conflicting transitions (same sequence number, different recipients) to two different validator shards simultaneously, hoping that each shard independently certifies a different transition before the equivocation is detected.

4.2 Root Cause

SPEC-003 §3.6 defines the sharding function as shard_id = hash(account_pubkey) mod num_shards. This is correct and ensures all transitions for a given account go to the same shard. However, the specification does not explicitly state that this is a SAFETY-CRITICAL invariant, and does not specify what happens if an implementation uses a different sharding function.

4.3 Mitigation

This attack is already mitigated by design in the specification, but the safety-critical nature of the sharding invariant must be made explicit:

SPEC-003 AMENDMENT §3.6 (REVISED): SHARDING SAFETY INVARIANT: All transitions for a given account MUST be validated by the same set of validators/shards. The shard assignment function is: shard_id = SHA3-256(account_pubkey)[0..4] mod num_shards, where [0..4] takes the first 4 bytes of the hash as a little-endian u32. This function MUST be deterministic, stateless, and identical across all validators. A validator MUST reject a VOTE_REQUEST if the sender's account does not map to a shard it is responsible for. An implementation that uses a different sharding function, or that allows a transition to be processed by a non-responsible shard, is NON-CONFORMANT and UNSAFE.

SPEC-003 AMENDMENT §3.6.1 (NEW): CROSS-SHARD VERIFICATION: When a validator receives a SettlementCertificate for a recipient that maps to a different shard, it MUST verify the certificate (signatures, quorum) before crediting the recipient. The certificate provides proof that the sender's shard has validated and certified the debit. The receiving shard does not re-validate the sender's balance or sequence number --- it trusts the certificate as proof that the sender's shard has done so. This is safe because the sender's shard is the sole authority for the sender's account state.

4.4 Test Assertions

  1. Assert that a validator rejects a VOTE_REQUEST if hash(sender_pubkey) mod num_shards does not match the validator's shard_id.

  2. Assert that two conflicting transitions from the same sender (same sequence, different content) sent to the correct shard produce an equivocation proof.

  3. Assert that a conflicting transition sent to the WRONG shard is rejected before equivocation detection (at the shard routing check).

  4. Assert that the sharding function is deterministic: the same public key always produces the same shard_id regardless of when or where it is computed.

AV-005: Multi-Hop Dust Shaving

Severity: S3 (MEDIUM)

5.1 Threat Description

A malicious connector in a multi-hop payment path exploits integer division rounding to pocket small amounts of value ("dust") from each transaction. When converting between asset types, the exchange calculation amount_out = amount_in × rate.numerator / rate.denominator produces a non-integer result that must be truncated. The connector keeps the fractional remainder.

At 1 cent per transaction and 1 million transactions per day, this yields $10,000/day in stolen dust.

5.2 Root Cause

SPEC-002 §2.7 defines the Value Semiring's exchange operation as (a, id₁) ⊗ rate = (a × rate.numerator / rate.denominator, id₂) but does not specify the rounding rule or what happens to the remainder.

5.3 Mitigation

SPEC-002 AMENDMENT §2.7.1 (NEW): EXCHANGE ROUNDING RULE: All exchange calculations MUST use the following formula: amount_out = floor(amount_in × rate.numerator / rate.denominator). The remainder is: remainder = amount_in × rate.numerator mod rate.denominator. The remainder MUST be returned to the sender (not retained by the connector). The sender's wallet MUST verify that amount_out + remainder (converted back to the source asset) exactly equals the original amount_in. This is enforced at the PTLC fulfillment stage: the sender only fulfills the PTLC if the received amount matches expectations within a sender-specified tolerance (max_slippage).

SPEC-008 AMENDMENT §8.2.1 (REVISED): The MeshPacket Prepare message MUST include a new field: min_destination_amount (u128). The recipient (or the recipient-side connector) MUST reject the Prepare with ERR_BELOW_MINIMUM if the actual delivered amount is less than min_destination_amount. This shifts control to the sender: the sender specifies the minimum acceptable output, and if any connector along the path shaves more than the sender is willing to tolerate, the payment fails rather than succeeding with missing value.

5.4 Test Assertions

  1. Assert that for an exchange with rate 3/7 and amount_in = 100, the result is amount_out = floor(300/7) = 42, remainder = 300 mod 7 = 6.

  2. Assert that the remainder is returned to the sender, not retained by the connector.

  3. Assert that a Prepare packet with min_destination_amount = 42 and actual delivery of 41 is rejected with ERR_BELOW_MINIMUM.

  4. Assert that integer overflow in the multiplication amount_in × rate.numerator is handled (use u256 intermediate or checked arithmetic). If overflow would occur, the operation MUST fail with ERR_OVERFLOW rather than silently truncating.

AV-006: Epoch-Boundary Replay Attack

Severity: S1 (CRITICAL)

6.1 Threat Description

A user collects VOTE_RESPONSE messages from validators in Epoch N but does not broadcast the SettlementCertificate until Epoch N+1 (or later). If validators in the new epoch accept certificates signed by the old epoch's validators, the user could exploit a situation where: (a) a validator was removed from the set in Epoch N+1 (perhaps for misbehaviour), and its votes are now suspect, or (b) the validator set has changed and the "2f+1 quorum" calculated against the new set may not hold against the old set.

6.2 Root Cause

SPEC-002 §2.5.1 defines the SettlementCertificate with an epoch field and states votes must be from validators in that epoch's set. However, the specification does not define a grace period or cutoff for cross-epoch certificate acceptance. The specification is silent on what happens when a certificate arrives after an epoch boundary.

6.3 Mitigation

SPEC-003 AMENDMENT §3.3.2 (REVISED): EPOCH VALIDATION FOR CERTIFICATES: When a validator receives a SettlementCertificate, it MUST perform the following epoch check: (a) If certificate.epoch == current_epoch: accept and verify votes against the current validator set. (b) If certificate.epoch == current_epoch - 1: accept and verify votes against the PREVIOUS epoch's validator set. This provides a one-epoch grace period for certificates that were assembled just before an epoch transition. (c) If certificate.epoch < current_epoch - 1: REJECT with ERR_CERTIFICATE_EXPIRED. Certificates more than one epoch old are not accepted. (d) If certificate.epoch > current_epoch: REJECT with ERR_FUTURE_EPOCH. The validator does not accept certificates from future epochs.

SPEC-010 AMENDMENT §10.2.1 (REVISED): EPOCH STATE RETENTION: Validators MUST retain the validator set (public keys) of the immediately previous epoch until the end of the current epoch. This means at any given time, a validator stores exactly two validator sets: the current epoch's and the previous epoch's. Upon transitioning to epoch N+1, the validator set for epoch N-1 is discarded.

6.4 Test Assertions

  1. Assert that a certificate from the current epoch is accepted.

  2. Assert that a certificate from epoch N-1 is accepted during epoch N (grace period).

  3. Assert that a certificate from epoch N-2 is rejected with ERR_CERTIFICATE_EXPIRED during epoch N.

  4. Assert that a certificate from epoch N+1 is rejected with ERR_FUTURE_EPOCH during epoch N.

  5. Assert that votes are verified against the CORRECT epoch's validator set, not the current epoch's set, when the certificate's epoch differs from the current epoch.

  6. Assert that a certificate assembled with votes from a validator that was present in epoch N but removed in epoch N+1 is still accepted during epoch N+1 (grace period) because the votes are valid for epoch N.

AV-007: Stealth Address Unlinkability Breach via Ephemeral Key Reuse

Severity: S2 (HIGH)

7.1 Threat Description

If a sender reuses an ephemeral key r when constructing stealth addresses for two different payments to the same recipient, the resulting stealth addresses P_stealth_1 and P_stealth_2 will be computed from the same shared secret S, making them linkable. An observer who sees the same ephemeral public key R in two different transaction memos can conclude that both payments went to the same recipient.

More subtly, even if the ephemeral key is not reused, patterns in the memo field (identical encrypted payload sizes, timing correlations, or metadata leakage) could allow statistical linkability analysis.

7.2 Root Cause

SPEC-007 §7.4.1 step 1 states "Sender generates an ephemeral key pair: (r, R = r·G)" but does not explicitly mandate that r MUST be fresh randomness for every payment. The specification assumes correct implementation but does not defend against implementation errors or deliberate shortcuts.

7.3 Mitigation

SPEC-007 AMENDMENT §7.4.1 Step 1 (REVISED): Sender generates an ephemeral key pair: r = CSPRNG(32 bytes), R = r·G. The value r MUST be generated from a cryptographically secure random number generator for EVERY payment. The value r MUST NOT be derived deterministically from the payment amount, recipient, or any other predictable input. After the Fulfill is received (or the payment expires), the value r MUST be securely erased from memory using zeroize. An implementation MUST NOT cache, reuse, or persist ephemeral keys across payments.

SPEC-007 AMENDMENT §7.4.2 (NEW): EPHEMERAL KEY UNIQUENESS ENFORCEMENT: The sender's wallet MUST maintain a Bloom filter (or equivalent probabilistic set) of recently used ephemeral public keys R. Before using a new R, the wallet MUST check that R is not in the filter. If a collision is detected (which should only occur due to a CSPRNG failure), the wallet MUST generate a new r and retry. The Bloom filter SHOULD retain entries for at least 10,000 recent payments. This provides defence-in-depth against CSPRNG failures.

SPEC-007 AMENDMENT §7.4.3 (NEW): MEMO PADDING: The encrypted memo field MUST be padded to a fixed length before encryption, regardless of the actual payload size. The padded length MUST be one of: 64, 128, 256, 512, 1024, or 2048 bytes (the smallest size that fits the payload). This prevents an observer from correlating transactions by memo ciphertext length. Padding MUST use random bytes (not zero-padding) to prevent distinguishing padding from payload without the decryption key.

7.4 Test Assertions

  1. Assert that two payments to the same recipient produce different stealth addresses (P_stealth_1 ≠ P_stealth_2).

  2. Assert that two payments to the same recipient produce different ephemeral public keys (R_1 ≠ R_2).

  3. Assert that an observer with access to all SettlementCertificates but without any view_key cannot determine if two stealth addresses belong to the same recipient, with advantage ≤ negligible (formal: advantage < 2⁻¹²⁸).

  4. Assert that the Bloom filter detects a deliberately reused ephemeral key and forces regeneration.

  5. Assert that the encrypted memo is padded to a fixed size: a 10-byte payload and a 100-byte payload to the same recipient produce memos of the same ciphertext length.

  6. Assert that the ephemeral secret r is zeroed from memory after use (test via memory inspection in debug mode).

Summary of Specification Amendments


Attack Severity Amended Spec Key Change

AV-001 S2 SPEC-008 §8.4.5, Maximum expiry decay per hop, §8.5.1, §8.5.2 locking fee, sender reputation scoring.

AV-002 S1 SPEC-002 §2.4.3 Rules Explicit enumeration of all 6--7 commitments requiring range proofs, including fee. Zero-value fee requires proof.

AV-003 S1 SPEC-002 §2.4.3 Rules Certificate store lookup 8--8a mandatory; new relevance check prevents referencing unrelated certificates.

AV-004 S1* SPEC-003 §3.6, §3.6.1 Sharding invariant made explicit as safety-critical. Cross-shard credit via certificate verification.

AV-005 S3 SPEC-002 §2.7.1, Rounding rule: floor + SPEC-008 §8.2.1 remainder to sender. min_destination_amount field in Prepare packets.

AV-006 S1 SPEC-003 §3.3.2, One-epoch grace period for SPEC-010 §10.2.1 certificates. Strict rejection of certificates more than one epoch old.

AV-007 S2 SPEC-007 §7.4.1--7.4.3 Mandatory fresh CSPRNG ephemeral key, Bloom filter uniqueness check, random-padded memo to fixed lengths.


* AV-004 is mitigated by design if the specification is followed. The severity rating reflects the consequence of non-conformant implementation, not a flaw in the protocol.

Formal Verification Additions

The following properties MUST be added to the formal verification requirements in SPEC-010 §10.6:


Property Tool Scope

Fee commitment range TLA+ Verify that no state is (AV-002) reachable where a transition with negative fee commitment passes all validation rules.

Causal dependency TLA+ Verify that no state is integrity (AV-003) reachable where an account spends value credited by an uncertified or irrelevant transition.

Sharding safety (AV-004) TLA+ Verify that no state is reachable where two conflicting transitions for the same account are certified by different shards.

Epoch-boundary TLA+ Verify that a certificate certificate validity from epoch N-2 cannot be (AV-006) applied in epoch N under any message ordering.

Stealth address ProVerif Verify the PRIVACY-2 unlinkability (AV-007) property: an observer without the view_key has negligible advantage in linking two stealth addresses to the same recipient.


Document Control


Version Date Author Description

0.1.1 2026-03-11 MESH Foundation Security addendum. Mitigations for 7 identified attack vectors (AV-001 through AV-007).