bitcoindevkit / bitcoindevkit/bdk

`IndexedTxGraph::apply_block` does not check merkle root

Open Beginner friendly
#2,278 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Rust
Stars
1.1k
Forks
483
Avg merge
20d 3h
Merged PRs (30d)
3

Description

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

```rust
#[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::>::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 (`ChangeSet` → `Result`).

## Build environment

- BDK tag/commit: acc06e53220960caa89efd5984d7b43914640dd4

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

- [x] None / not backend-related (e.g. `bdk_chain`, `bdk_core`)

## Is this blocking production use?

- [x] 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.

Contributor guide

Open the contributing guide

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.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.