`IndexedTxGraph::apply_block` does not check merkle root

Open Beginner friendly
#2,278 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
78/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
rust
Domain
blockchain

Research direction

Start at the IndexedTxGraph::apply_block_relevant and IndexedTxGraph::apply_block entry points and run the reproduction test from the issue. Check how each method handles block txdata versus the header merkle root; done means the documented trust requirement is clear and debug builds fail for the fabricated-body case without changing the public error behavior.

Written by the indexing model from the issue text.

Description

bug

Describe the bug

IndexedTxGraph::apply_block_relevant and IndexedTxGraph::apply_block anchor transactions without verifying that the block’s txdata matches its header Merkle root.

A caller that accepts an unvalidated block body can attach fabricated transactions to a genuine block hash, causing them to appear confirmed.

To Reproduce

#[test]
fn apply_block_relevant_confirms_txs_not_in_merkle_root() {
    use bdk_chain::{
        local_chain::LocalChain, spk_txout::SpkTxOutIndex, BlockId, IndexedTxGraph,
    };
    use bitcoin::{
        absolute, hashes::Hash, transaction, Amount, Network, OutPoint, ScriptBuf,
        Transaction, TxIn, TxOut, Txid,
    };

    let script = ScriptBuf::new();
    let mut graph = IndexedTxGraph::<BlockId, SpkTxOutIndex<()>>::default();
    assert!(graph.index.insert_spk((), script.clone()));

    let mut block = bitcoin::constants::genesis_block(Network::Bitcoin);
    let genuine_hash = block.block_hash();

    block.txdata = vec![Transaction {
        version: transaction::Version::TWO,
        lock_time: absolute::LockTime::ZERO,
        input: vec![TxIn {
            previous_output: OutPoint::new(Txid::from_byte_array([1; 32]), 0),
            ..Default::default()
        }],
        output: vec![TxOut {
            value: Amount::from_sat(50_000),
            script_pubkey: script,
        }],
    }];

    assert_eq!(block.block_hash(), genuine_hash);
    assert!(!block.check_merkle_root());

    let chain = LocalChain::from_blocks([(0, genuine_hash)].into_iter().collect()).unwrap();

    let _ = graph.apply_block_relevant(&block, 0);

    let view = chain.canonical_view(
        graph.graph(),
        chain.tip().block_id(),
        Default::default(),
    );
    let balance = view.balance(
        graph.index.outpoints().iter().cloned(),
        |_, _| true,
        0,
    );

    assert_eq!(balance.confirmed, Amount::from_sat(50_000));
}

Expected behavior

Document that these methods trust txdata matches the header merkle root. Callers must check that before calling. Add debug_assert!(block.check_merkle_root()) so the fake-body case fails in debug.

Alternative: Reject on merkle mismatch before updating graph/index. That would be a breaking change (ChangeSetResult).

Build environment

  • BDK tag/commit: acc06e53220960caa89efd5984d7b43914640dd4

Which backend(s) are relevant (if any)?

  • None / not backend-related (e.g. bdk_chain, bdk_core)

Is this blocking production use?

  • No

Additional context

The block header remains genuine, so block.block_hash() is unchanged; only its transaction list is replaced. LocalChain therefore accepts the anchor as canonical even though the transactions were never committed by the block header.

Dominant language
Rust
Stars
1.1k
Forks
491
Avg merge
1d 5h
Merged PRs (30d)
1

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from bitcoindevkit/bdk

All issues in bitcoindevkit/bdk

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.