Skip to content

Commit 33a01c0

Browse files
committed
Merge rust-bitcoin#5356: Remove alloc feature gate for MerkleNode
cc0da4c Remove alloc feature gate for MerkleNode (Mitchell Bagot) 4c65ce7 Add pop to ArrayVec (Mitchell Bagot) Pull request description: The MerkleNode::calculate_root function currently relies on the alloc feature to be enabled in order to provide functionality. Since the calculate_root function generates a balanced tree, it only requires a stack of size log2(N), where N is the number of transactions in the input iterator. The ArrayVec struct in internals can replace the Vec for this function in the case where the transaction count is bounded. NOTE: This PR has `unsafe` logic in 7a5c157. Follow up to rust-bitcoin#5346 ACKs for top commit: apoelstra: ACK cc0da4c; successfully ran local tests tcharding: ACK cc0da4c Tree-SHA512: e4042c350770f36adfe9d88f9c399ca950a0b055c62b148754368b67e859ee94496adca855cf5fca6482ccae678a105e0f8e883a02dc541f299e76b36d527c77
2 parents 4f44b1d + cc0da4c commit 33a01c0

File tree

7 files changed

+60
-24
lines changed

7 files changed

+60
-24
lines changed

.cargo/mutants.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ exclude_re = [
5151
"primitives/.* <impl Decoder for WitnessDecoder>::push_bytes", # Replacing == with != causes an infinite loop
5252
"primitives/.* WitnessDecoder::resize_if_needed", # Replacing *= with += still resizes the buffer making the mutant untestable.
5353
"primitives/.* replace \\+ with \\* in MerkleNode::calculate_root", # Replacing + with * causes an infinite loop
54+
"primitives/.* replace == with != in MerkleNode::calculate_root", # Replacing == with != isn't caught unless alloc is disabled.
5455

5556
# consensus_encoding - most of these are for mutations in the logic used to determine when to stop encoding or decoding.
5657
"consensus_encoding/.* <impl Decoder for ArrayDecoder<N>>::push_bytes", # Mutations cause an infinite loop

api/primitives/no-features.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,12 +514,15 @@ pub fn bitcoin_primitives::TxMerkleNode::as_ref(&self) -> &[u8; 32]
514514
pub fn bitcoin_primitives::TxMerkleNode::as_ref(&self) -> &[u8]
515515
pub fn bitcoin_primitives::TxMerkleNode::borrow(&self) -> &[u8; 32]
516516
pub fn bitcoin_primitives::TxMerkleNode::borrow(&self) -> &[u8]
517+
pub fn bitcoin_primitives::TxMerkleNode::calculate_root<I: core::iter::traits::iterator::Iterator<Item = bitcoin_primitives::Txid>>(iter: I) -> core::option::Option<Self>
517518
pub fn bitcoin_primitives::TxMerkleNode::clone(&self) -> bitcoin_primitives::TxMerkleNode
518519
pub fn bitcoin_primitives::TxMerkleNode::cmp(&self, other: &bitcoin_primitives::TxMerkleNode) -> core::cmp::Ordering
520+
pub fn bitcoin_primitives::TxMerkleNode::combine(&self, other: &Self) -> Self
519521
pub fn bitcoin_primitives::TxMerkleNode::decoder() -> Self::Decoder
520522
pub fn bitcoin_primitives::TxMerkleNode::encoder(&self) -> Self::Encoder
521523
pub fn bitcoin_primitives::TxMerkleNode::eq(&self, other: &bitcoin_primitives::TxMerkleNode) -> bool
522524
pub fn bitcoin_primitives::TxMerkleNode::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
525+
pub fn bitcoin_primitives::TxMerkleNode::from_leaf(leaf: bitcoin_primitives::Txid) -> Self
523526
pub fn bitcoin_primitives::TxMerkleNode::hash<__H: core::hash::Hasher>(&self, state: &mut __H)
524527
pub fn bitcoin_primitives::TxMerkleNode::partial_cmp(&self, other: &bitcoin_primitives::TxMerkleNode) -> core::option::Option<core::cmp::Ordering>
525528
pub fn bitcoin_primitives::Txid::as_ref(&self) -> &[u8; 32]
@@ -546,12 +549,15 @@ pub fn bitcoin_primitives::WitnessMerkleNode::as_ref(&self) -> &[u8; 32]
546549
pub fn bitcoin_primitives::WitnessMerkleNode::as_ref(&self) -> &[u8]
547550
pub fn bitcoin_primitives::WitnessMerkleNode::borrow(&self) -> &[u8; 32]
548551
pub fn bitcoin_primitives::WitnessMerkleNode::borrow(&self) -> &[u8]
552+
pub fn bitcoin_primitives::WitnessMerkleNode::calculate_root<I: core::iter::traits::iterator::Iterator<Item = bitcoin_primitives::Wtxid>>(iter: I) -> core::option::Option<Self>
549553
pub fn bitcoin_primitives::WitnessMerkleNode::clone(&self) -> bitcoin_primitives::WitnessMerkleNode
550554
pub fn bitcoin_primitives::WitnessMerkleNode::cmp(&self, other: &bitcoin_primitives::WitnessMerkleNode) -> core::cmp::Ordering
555+
pub fn bitcoin_primitives::WitnessMerkleNode::combine(&self, other: &Self) -> Self
551556
pub fn bitcoin_primitives::WitnessMerkleNode::decoder() -> Self::Decoder
552557
pub fn bitcoin_primitives::WitnessMerkleNode::encoder(&self) -> Self::Encoder
553558
pub fn bitcoin_primitives::WitnessMerkleNode::eq(&self, other: &bitcoin_primitives::WitnessMerkleNode) -> bool
554559
pub fn bitcoin_primitives::WitnessMerkleNode::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
560+
pub fn bitcoin_primitives::WitnessMerkleNode::from_leaf(leaf: bitcoin_primitives::Wtxid) -> Self
555561
pub fn bitcoin_primitives::WitnessMerkleNode::hash<__H: core::hash::Hasher>(&self, state: &mut __H)
556562
pub fn bitcoin_primitives::WitnessMerkleNode::partial_cmp(&self, other: &bitcoin_primitives::WitnessMerkleNode) -> core::option::Option<core::cmp::Ordering>
557563
pub fn bitcoin_primitives::Wtxid::as_ref(&self) -> &[u8; 32]

internals/src/array_vec.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,23 @@ mod safety_boundary {
6969
self.len += 1;
7070
}
7171

72+
/// Removes the last element, returning it.
73+
///
74+
/// # Returns
75+
///
76+
/// None if the ArrayVec is empty.
77+
pub fn pop(&mut self) -> Option<T> {
78+
if self.len > 0 {
79+
self.len -= 1;
80+
// SAFETY: All elements in 0..len are initialized
81+
let res = self.data[self.len];
82+
self.data[self.len] = MaybeUninit::uninit();
83+
Some(unsafe { res.assume_init() })
84+
} else {
85+
None
86+
}
87+
}
88+
7289
/// Copies and appends all elements from `slice` into `self`.
7390
///
7491
/// # Panics

primitives/src/hash_types/transaction_merkle_node.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ use arbitrary::{Arbitrary, Unstructured};
1212
use hashes::sha256d;
1313
use internals::write_err;
1414

15-
#[cfg(feature = "alloc")]
1615
use crate::merkle_tree::MerkleNode;
17-
#[cfg(feature = "alloc")]
1816
use crate::Txid;
1917

2018
/// A hash of the Merkle tree branch or root for transactions.
@@ -28,7 +26,6 @@ type Inner = sha256d::Hash;
2826

2927
include!("./generic.rs");
3028

31-
#[cfg(feature = "alloc")]
3229
impl TxMerkleNode {
3330
/// Convert a [`Txid`] hash to a leaf node of the tree.
3431
pub fn from_leaf(leaf: Txid) -> Self { MerkleNode::from_leaf(leaf) }

primitives/src/hash_types/witness_merkle_node.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ use arbitrary::{Arbitrary, Unstructured};
1212
use hashes::sha256d;
1313
use internals::write_err;
1414

15-
#[cfg(feature = "alloc")]
1615
use crate::merkle_tree::MerkleNode;
17-
#[cfg(feature = "alloc")]
1816
use crate::Wtxid;
1917

2018
/// A hash corresponding to the Merkle tree root for witness data.
@@ -28,7 +26,6 @@ type Inner = sha256d::Hash;
2826

2927
include!("./generic.rs");
3028

31-
#[cfg(feature = "alloc")]
3229
impl WitnessMerkleNode {
3330
/// Convert a [`Wtxid`] hash to a leaf node of the tree.
3431
pub fn from_leaf(leaf: Wtxid) -> Self { MerkleNode::from_leaf(leaf) }

primitives/src/merkle_tree.rs

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@
1111
// C'est la vie.
1212
#[cfg(feature = "alloc")]
1313
use alloc::vec::Vec;
14+
#[cfg(not(feature = "alloc"))]
15+
use internals::array_vec::ArrayVec;
1416

15-
#[cfg(feature = "alloc")]
1617
use hashes::{HashEngine, sha256d};
1718

18-
#[cfg(feature = "alloc")]
1919
use crate::hash_types::{Txid, Wtxid};
20-
#[cfg(feature = "alloc")]
2120
use crate::transaction::TxIdentifier;
2221

2322
#[doc(inline)]
@@ -32,7 +31,6 @@ pub use crate::hash_types::{TxMerkleNode, TxMerkleNodeEncoder, WitnessMerkleNode
3231
///
3332
/// Other Merkle trees in Bitcoin, such as those used in Taproot commitments,
3433
/// do not use this algorithm and cannot use this trait.
35-
#[cfg(feature = "alloc")]
3634
pub(crate) trait MerkleNode: Copy + PartialEq {
3735
/// The hash (TXID or WTXID) of a transaction in the tree.
3836
type Leaf: TxIdentifier;
@@ -50,13 +48,23 @@ pub(crate) trait MerkleNode: Copy + PartialEq {
5048
/// transactions will always be invalid, so there is no harm in us refusing to
5149
/// compute their merkle roots.
5250
///
51+
/// Also returns `None` if the `alloc` feature is disabled and `iter` has more than
52+
/// 32,767 transactions.
53+
///
5354
/// Unless you are certain your transaction list is nonempty and has no duplicates,
5455
/// you should not unwrap the `Option` returned by this method!
5556
fn calculate_root<I: Iterator<Item = Self::Leaf>>(iter: I) -> Option<Self> {
5657
{
58+
#[cfg(feature = "alloc")]
5759
let mut stack = Vec::<(usize, Self)>::with_capacity(32);
60+
#[cfg(not(feature = "alloc"))]
61+
let mut stack = ArrayVec::<(usize, Self), 15>::new();
62+
5863
// Start with a standard Merkle tree root computation...
5964
for (mut n, leaf) in iter.enumerate() {
65+
#[cfg(not(feature = "alloc"))]
66+
// This is the only time that the stack actually grows, rather than being combined.
67+
if stack.len() == 15 { return None; }
6068
stack.push((0, Self::from_leaf(leaf)));
6169

6270
while n & 1 == 1 {
@@ -106,7 +114,6 @@ pub(crate) trait MerkleNode: Copy + PartialEq {
106114
// our hash traits, it should be possible to put bounds on `MerkleNode`
107115
// and `MerkleNode::Leaf` which are sufficient to turn both methods into
108116
// provided methods in the trait definition.
109-
#[cfg(feature = "alloc")]
110117
impl MerkleNode for TxMerkleNode {
111118
type Leaf = Txid;
112119
fn from_leaf(leaf: Self::Leaf) -> Self { Self::from_byte_array(leaf.to_byte_array()) }
@@ -118,7 +125,6 @@ impl MerkleNode for TxMerkleNode {
118125
Self::from_byte_array(sha256d::Hash::from_engine(encoder).to_byte_array())
119126
}
120127
}
121-
#[cfg(feature = "alloc")]
122128
impl MerkleNode for WitnessMerkleNode {
123129
type Leaf = Wtxid;
124130
fn from_leaf(leaf: Self::Leaf) -> Self { Self::from_byte_array(leaf.to_byte_array()) }
@@ -133,19 +139,16 @@ impl MerkleNode for WitnessMerkleNode {
133139

134140
#[cfg(test)]
135141
mod tests {
136-
#[cfg(feature = "alloc")]
137142
use crate::hash_types::*;
138143

139144
// Helper to make a Txid, TxMerkleNode pair with a single number byte array
140-
#[cfg(feature = "alloc")]
141145
fn make_leaf_node(byte: u8) -> (Txid, TxMerkleNode) {
142146
let leaf = Txid::from_byte_array([byte; 32]);
143147
let node = TxMerkleNode::from_leaf(leaf);
144148
(leaf, node)
145149
}
146150

147151
#[test]
148-
#[cfg(feature = "alloc")]
149152
fn tx_merkle_node_single_leaf() {
150153
let (leaf, node) = make_leaf_node(1);
151154
let root = TxMerkleNode::calculate_root([leaf].into_iter());
@@ -154,7 +157,6 @@ mod tests {
154157
}
155158

156159
#[test]
157-
#[cfg(feature = "alloc")]
158160
fn tx_merkle_node_two_leaves() {
159161
let (leaf1, node1) = make_leaf_node(1);
160162
let (leaf2, node2) = make_leaf_node(2);
@@ -169,7 +171,6 @@ mod tests {
169171
}
170172

171173
#[test]
172-
#[cfg(feature = "alloc")]
173174
fn tx_merkle_node_duplicate_leaves() {
174175
let leaf = Txid::from_byte_array([3; 32]);
175176
// Duplicate transaction list should be rejected (CVE 2012‑2459).
@@ -178,13 +179,11 @@ mod tests {
178179
}
179180

180181
#[test]
181-
#[cfg(feature = "alloc")]
182182
fn tx_merkle_node_empty() {
183183
assert!(TxMerkleNode::calculate_root([].into_iter()).is_none(), "Empty iterator should return None");
184184
}
185185

186186
#[test]
187-
#[cfg(feature = "alloc")]
188187
fn tx_merkle_node_2n_minus_1_unbalanced_tree() {
189188
// Test a tree with 2^n - 1 unique nodes and at least 3 layers deep.
190189
let (leaf1, node1) = make_leaf_node(1);
@@ -243,7 +242,30 @@ mod tests {
243242
}
244243

245244
#[test]
246-
#[cfg(feature = "alloc")]
245+
fn tx_merkle_node_oversize_tree() {
246+
// Confirm that with no-alloc, we return None for iter length >= 32768
247+
let root = TxMerkleNode::calculate_root((0..32768u32).map(|i| {
248+
let mut buf = [0u8; 32];
249+
buf[..4].copy_from_slice(&i.to_le_bytes());
250+
Txid::from_byte_array(buf)
251+
}));
252+
253+
// We just want to confirm that we return None at the 32768 element boundary.
254+
#[cfg(feature = "alloc")]
255+
assert_ne!(root, None);
256+
#[cfg(not(feature = "alloc"))]
257+
assert_eq!(root, None);
258+
259+
// Check just under the boundary
260+
let root = TxMerkleNode::calculate_root((0..32767u32).map(|i| {
261+
let mut buf = [0u8; 32];
262+
buf[..4].copy_from_slice(&i.to_le_bytes());
263+
Txid::from_byte_array(buf)
264+
}));
265+
assert_ne!(root, None);
266+
}
267+
268+
#[test]
247269
fn witness_merkle_node_single_leaf() {
248270
let leaf = Wtxid::from_byte_array([1; 32]);
249271
let root = WitnessMerkleNode::calculate_root([leaf].into_iter());
@@ -253,7 +275,6 @@ mod tests {
253275
}
254276

255277
#[test]
256-
#[cfg(feature = "alloc")]
257278
fn witness_merkle_node_duplicate_leaves() {
258279
let leaf = Wtxid::from_byte_array([2; 32]);
259280
let root = WitnessMerkleNode::calculate_root([leaf, leaf].into_iter());

primitives/src/transaction.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,12 +250,9 @@ impl From<&Transaction> for Wtxid {
250250
}
251251

252252
/// Trait that abstracts over a transaction identifier i.e., `Txid` and `Wtxid`.
253-
#[cfg(feature = "alloc")]
254253
pub(crate) trait TxIdentifier: AsRef<[u8]> {}
255254

256-
#[cfg(feature = "alloc")]
257255
impl TxIdentifier for Txid {}
258-
#[cfg(feature = "alloc")]
259256
impl TxIdentifier for Wtxid {}
260257

261258
// Duplicated in `bitcoin`.

0 commit comments

Comments
 (0)