VYPR
Medium severity5.9GHSA Advisory· Published May 21, 2026· Updated May 21, 2026

nimiq-primitives: BlockInclusionProof interlink issue when hops are empty

CVE-2026-46539

Description

Impact

A logic flaw in BlockInclusionProof::is_block_proven causes the function to return true without performing any cryptographic verification when get_interlink_hops yields an empty hop list. This occurs when the target block is at the election block position immediately preceding the election head's epoch. An attacker providing transaction inclusion proofs can forge a MacroBlock header for that epoch position and have it accepted as "proven" without any hash or signature verification.

Patches

The patch for this vulnerability is formally released as part of v1.4.0.

Workarounds

No Workarounds

Resources

See PR.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A logic flaw in nimiq-primitives BlockInclusionProof skips verification when the hop list is empty, allowing forged macro block headers at a specific epoch position.

Root

Cause A logic flaw in the BlockInclusionProof::is_block_proven function within the nimiq-primitives crate causes the method to return true without performing any cryptographic verification when get_interlink_hops returns an empty hop list. This occurs specifically when the target block is at the election block position immediately preceding the election head's epoch [1][3]. The absence of hash or signature checks at that edge case bypasses the intended proof of inclusion.

Attack

Vector An attacker can supply a transaction inclusion proof that includes a forged MacroBlock header for that specific epoch position. No authentication or prior access is required beyond the ability to submit a proof to a node that processes BlockInclusionProof requests [1][4]. The empty hop list triggers the false return, so the forged header is accepted without any verification.

Impact

If successfully exploited, the attacker can convince the node that a non-existent or manipulated MacroBlock is part of the chain's proven history. This could lead to incorrect chain state, enabling double-spends or other transaction-level fraud depending on how the proof is consumed by higher-level logic [3].

Mitigation

The vulnerability is fixed in PR #3705, included in release v1.4.0 of the nimiq/core-rs-albatross repository [1][2]. Users must upgrade to that version or later. No workarounds are available [3].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2

Patches

1
cc5a1d54bbbf

Fix for interlink in block inclusion proof

https://github.com/nimiq/core-rs-albatrossviquezclaudioApr 7, 2026via ghsa
2 files changed · +26 2
  • primitives/block/src/block_proof.rs+13 2 modified
    @@ -79,9 +79,9 @@ impl BlockInclusionProof {
                 election_head.block_number(),
             );
     
    -        // The election head might already have an interlink or prev_election_head link to the target
    +        // An empty proof is only valid if the election head already references the target directly.
             if hops.is_empty() {
    -            return true;
    +            return Self::references_block(election_head, target);
             }
     
             // Check that the proof contains all needed hops
    @@ -129,4 +129,15 @@ impl BlockInclusionProof {
             }
             true
         }
    +
    +    fn references_block(block: &MacroBlock, target: &MacroBlock) -> bool {
    +        let target_hash = target.hash();
    +
    +        block.header.parent_election_hash == target_hash
    +            || block
    +                .header
    +                .interlink
    +                .as_ref()
    +                .is_some_and(|interlink| interlink.contains(&target_hash))
    +    }
     }
    
  • primitives/block/tests/block_proof.rs+13 0 modified
    @@ -1,6 +1,7 @@
     use std::collections::HashMap;
     
     use nimiq_block::{BlockInclusionProof, MacroBlock, MacroHeader};
    +use nimiq_hash::Hash;
     use nimiq_primitives::policy::Policy;
     
     #[test]
    @@ -85,6 +86,18 @@ fn test_is_block_proven() {
         let block_proof = BlockInclusionProof { proof: vec![] };
         assert!(block_proof.is_block_proven(&blocks[&10], &blocks[&9]));
     
    +    // Current election head: 10, target: forged 9. Claimed proof []
    +    let mut forged_parent_target = blocks[&9].clone();
    +    forged_parent_target.header.history_root = "forged-parent-target".hash();
    +    let block_proof = BlockInclusionProof { proof: vec![] };
    +    assert!(!block_proof.is_block_proven(&blocks[&10], &forged_parent_target));
    +
    +    // Current election head: 10, target: forged 8. Claimed proof []
    +    let mut forged_interlink_target = blocks[&8].clone();
    +    forged_interlink_target.header.history_root = "forged-interlink-target".hash();
    +    let block_proof = BlockInclusionProof { proof: vec![] };
    +    assert!(!block_proof.is_block_proven(&blocks[&10], &forged_interlink_target));
    +
         // Current election head: 22, target: 1. Claimed proof [17 (wrong), 8, 4, 2]
         let block_proof = BlockInclusionProof {
             proof: vec![
    

Vulnerability mechanics

Root cause

"Missing cryptographic verification when `get_interlink_hops` returns an empty hop list causes `BlockInclusionProof::is_block_proven` to accept any target block as proven without checking that the election head actually references it."

Attack vector

An attacker submits a transaction inclusion proof where the target block is at the election block position immediately preceding the election head's epoch. In this scenario, `get_interlink_hops` returns an empty hop list, and the original code returned `true` without any verification [patch_id=1261433]. The attacker can forge a MacroBlock header for that epoch position—modifying arbitrary fields such as `history_root`—and the proof will be accepted as valid. No cryptographic hash or signature check is performed on the forged block. The attack requires no special privileges beyond the ability to submit a proof to a system that calls `is_block_proven`.

Affected code

The vulnerability is in `BlockInclusionProof::is_block_proven` in `primitives/block/src/block_proof.rs` [patch_id=1261433]. When `get_interlink_hops` returns an empty hop list, the original code unconditionally returned `true` without verifying that the election head actually references the target block. The patch adds a new helper function `references_block` and replaces the unconditional return with a call to it.

What the fix does

The patch replaces the unconditional `return true` with a call to the new helper `Self::references_block(election_head, target)` [patch_id=1261433]. This helper verifies that the election head either has a `parent_election_hash` equal to the target's hash, or that the target's hash appears in the election head's interlink. If neither condition holds, the proof is rejected. The added test cases confirm that forged blocks with empty proofs are now correctly rejected. This closes the logic gap by ensuring that even when no interlink hops are needed, the claimed target must actually be referenced by the election head.

Preconditions

  • inputAttacker must be able to submit a BlockInclusionProof with an empty proof vector to a system that calls is_block_proven.
  • networkThe target block position must be the election block immediately preceding the election head's epoch, causing get_interlink_hops to return an empty list.

Generated on May 21, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.