From 846a6d1df026f731aa357d9bf1de231f100a5d20 Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Wed, 8 Jul 2026 10:52:38 -0300 Subject: [PATCH 1/5] chore: bump `edition` to 2021 and update `Cargo.toml` * Bump the Rust edition to 2021 (released on Rust 1.56.0) * Add the `tools` field to `[package.metadata.rbmt]` such that CI and local `cargo-audit` and Zizmor versions are in sync * Update the Zizmor CI job to set up rbmt's tools * Update the justfile and README --- .github/workflows/audit.yml | 18 +++++++---- Cargo.toml | 60 +++++++++++++++---------------------- README.md | 3 ++ justfile | 13 ++++++++ 4 files changed, 53 insertions(+), 41 deletions(-) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 88867a4..f22e6a3 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -69,12 +69,20 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: - persist-credentials: false + persist-credentials: false + + - name: Setup Rust Cache + uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + + - name: Setup cargo-rbmt + uses: ./.github/actions/setup-rbmt - - name: Install uv - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + - name: Install cargo-rbmt Tools + shell: bash + run: cargo rbmt tools - name: Run Zizmor - run: uvx zizmor . + shell: bash + run: zizmor . diff --git a/Cargo.toml b/Cargo.toml index ba90e62..d3dc104 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ authors = ["Calvin Kim ", "Davidson Souza /dev/null 2>&1 || { echo "shellcheck was not found on \$PATH" && exit 1; } + find . -name '*.sh' -print -exec shellcheck {} + + +[doc: "Run Zizmor Static Analysis"] +zizmor: + zizmor . + [doc: "Run pre-push suite: lock, fmt, check, test, and test-no-std"] pre-push: @just check-sigs From a6368d729e6ebfc6f1da721665bc3790c8476165 Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Wed, 8 Jul 2026 17:09:28 -0300 Subject: [PATCH 2/5] feat!: implement `new_hash_map` and `new_hash_set` for the `std` prelude The `default_trait_access` lint complained that we were instantiating a `HashMap` with `HashMap::with_hasher(Default::default())`. I've mirrored the helpers on the `std` prelude such that we may keep the two in sync. --- src/lib.rs | 18 ++++++++++++++++++ src/mem_forest/mod.rs | 8 ++++---- src/pollard/mod.rs | 4 ++-- src/util/mod.rs | 2 +- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 556748d..151a776 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,10 +71,18 @@ pub mod prelude { pub type HashMap = hashbrown::HashMap; pub type HashSet = hashbrown::HashSet; + /// Create a new [`HashMap`]. + /// + /// On `#[no_std]!` targets, we must be explicit regarding the undelying hasher that the [`HashMap`] + /// uses. In this case, `foldhash`'s [`FixedState`] hasher backs the `#![no_std]` [`HashMap`]. pub fn new_hash_map() -> HashMap { HashMap::with_hasher(FixedState::default()) } + /// Create a new [`HashSet`]. + /// + /// On `#[no_std]!` targets, we must be explicit regarding the undelying hasher that the [`HashSet`] + /// uses. In this case, `foldhash`'s [`FixedState`] hasher backs the `#![no_std]` [`HashSet`]. pub fn new_hash_set() -> HashSet { HashSet::with_hasher(FixedState::default()) } @@ -89,6 +97,16 @@ pub mod prelude { pub use std::io; pub use std::io::Read; pub use std::io::Write; + + /// Create a new [`HashMap`]. + pub fn new_hash_map() -> HashMap { + HashMap::new() + } + + /// Create a new [`HashSet`]. + pub fn new_hash_set() -> HashSet { + HashSet::new() + } } pub mod mem_forest; diff --git a/src/mem_forest/mod.rs b/src/mem_forest/mod.rs index a5bf1b6..e71da71 100644 --- a/src/mem_forest/mod.rs +++ b/src/mem_forest/mod.rs @@ -178,7 +178,7 @@ impl Node { Ok(node) } - let mut index = HashMap::with_hasher(Default::default()); + let mut index = new_hash_map(); let root = _read_one(None, reader, &mut index)?; Ok((root, index)) } @@ -216,7 +216,7 @@ impl MemForest { /// ``` pub fn new() -> Self { Self { - map: HashMap::with_hasher(Default::default()), + map: new_hash_map(), roots: Vec::new(), leaves: 0, } @@ -233,7 +233,7 @@ impl MemForest { /// ``` pub fn new_with_hash() -> Self { Self { - map: HashMap::with_hasher(Default::default()), + map: new_hash_map(), roots: Vec::new(), leaves: 0, } @@ -286,7 +286,7 @@ impl MemForest { let leaves = read_u64(&mut reader)?; let roots_len = read_u64(&mut reader)?; let mut roots = Vec::new(); - let mut map = HashMap::with_hasher(Default::default()); + let mut map = new_hash_map(); for _ in 0..roots_len { let (root, _map) = Node::read_one(&mut reader)?; map.extend(_map); diff --git a/src/pollard/mod.rs b/src/pollard/mod.rs index 1858e3b..38890f6 100644 --- a/src/pollard/mod.rs +++ b/src/pollard/mod.rs @@ -779,7 +779,7 @@ impl Pollard { Self { roots, leaves: 0, - leaf_map: HashMap::with_hasher(Default::default()), + leaf_map: new_hash_map(), } } @@ -807,7 +807,7 @@ impl Pollard { Self { roots, leaves, - leaf_map: HashMap::with_hasher(Default::default()), + leaf_map: new_hash_map(), } } diff --git a/src/util/mod.rs b/src/util/mod.rs index 34459d7..d5070c5 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -342,7 +342,7 @@ fn is_sibling(a: u64, b: u64) -> bool { /// whose hashes will be calculated to reach a root pub fn get_proof_positions(targets: &[u64], num_leaves: u64, forest_rows: u8) -> Vec { let mut proof_positions = BTreeSet::new(); - let mut map = HashSet::with_hasher(foldhash::quality::FixedState::default()); + let mut map = new_hash_set(); let mut computed_positions = targets.to_vec(); computed_positions.reserve(targets.len() * 2); From b5237179f5ec9844d6a021efb106867e11075b1e Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Wed, 8 Jul 2026 18:25:25 -0300 Subject: [PATCH 3/5] fix: make `cargo-rbmt` run `#![no_std]` tests Import the `std` crate, feature gated on the `std` feature, and add the `#![no_std]` attribute such that `cargo-rbmt` also tests the crate in a no-std target (`thumbv7m-none-eabi`). --- .github/workflows/rust.yml | 21 --------------------- README.md | 1 - justfile | 10 +--------- src/lib.rs | 12 ++++++++++-- 4 files changed, 11 insertions(+), 33 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index fb6f971..9a58de1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -70,24 +70,3 @@ jobs: - name: Run Tests run: cargo rbmt test --toolchain ${{ matrix.toolchain }} --lockfile ${{ matrix.lockfile }} - - test-no-std: - name: Test - no-std, MSRV toolchain, Minimal Lockfile - runs-on: ubuntu-latest - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Setup Build Cache - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - - - name: Setup cargo-rbmt - uses: ./.github/actions/setup-rbmt - - - name: Add no_std Target - run: rustup target add thumbv7m-none-eabi --toolchain 1.74.0 - - - name: Run no_std Tests - run: cargo rbmt test --toolchain msrv --lockfile minimal diff --git a/README.md b/README.md index 8eaa224..4bf497a 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,6 @@ Available recipes: pre-push # Run pre-push suite: lock, fmt, check, test, and test-no-std [alias: p] shellcheck # Run ShellCheck test # Run tests across all toolchains and lockfiles [alias: t] - test-no-std # Run no_std build check with the MSRV toolchain (1.74.0) [alias: tns] tools # Install cargo-rbmt tools zizmor # Run Zizmor Static Analysis ``` diff --git a/justfile b/justfile index 2c1f66c..d764d0b 100644 --- a/justfile +++ b/justfile @@ -5,7 +5,6 @@ alias do := doc-open alias f := fmt alias l := lock alias t := test -alias tns := test-no-std alias p := pre-push _default: @@ -45,15 +44,9 @@ lock: [doc: "Run tests across all toolchains and lockfiles"] test: - rustup target add thumbv7m-none-eabi cargo rbmt test --toolchain stable --lockfile recent cargo rbmt test --toolchain stable --lockfile minimal -[doc: "Run no_std build check with the MSRV toolchain (1.74.0)"] -test-no-std: - rustup target add thumbv7m-none-eabi --toolchain 1.74.0 - cargo rbmt test --toolchain msrv --lockfile minimal - [doc: "Install cargo-rbmt tools"] tools: RBMT_LOG_LEVEL=progress cargo rbmt tools @@ -67,11 +60,10 @@ shellcheck: zizmor: zizmor . -[doc: "Run pre-push suite: lock, fmt, check, test, and test-no-std"] +[doc: "Run pre-push suite: lock, fmt, check, and test"] pre-push: @just check-sigs @just lock @just fmt @just check @just test - @just test-no-std diff --git a/src/lib.rs b/src/lib.rs index 151a776..2a6dc37 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,10 +23,13 @@ //! * [`mem_forest`]: an in-memory forest accumulator. It keeps track of every leaf in the forest. It can both verify and //! generate inclusion proofs for any leaf in the forest. -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] extern crate alloc; +#[cfg(feature = "std")] +extern crate std; + /// This is the maximum size the forest is ever allowed to have, this caps how big `num_leaves` can /// be (we use a [`u64`]) and is also used by the [`util::translate`] logic. /// @@ -91,12 +94,17 @@ pub mod prelude { #[cfg(feature = "std")] /// Re-exports `std` basics plus HashMap/HashSet and IO traits. pub mod prelude { - extern crate std; + pub use std::borrow::ToOwned; pub use std::collections::HashMap; pub use std::collections::HashSet; + pub use std::format; pub use std::io; pub use std::io::Read; pub use std::io::Write; + pub use std::string::String; + pub use std::string::ToString; + pub use std::vec; + pub use std::vec::Vec; /// Create a new [`HashMap`]. pub fn new_hash_map() -> HashMap { From 0ecf725fb1a1e4f268b3f588c632bfca1d2f2b1c Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Wed, 8 Jul 2026 17:46:33 -0300 Subject: [PATCH 4/5] chore!: add and apply most of `rust-bitcoin`'s lints Add and apply all `rust-bitcoin` lints, except for documentation lints. --- Cargo.toml | 135 ++++++++++++++++++++++++++++++ benches/accumulator.rs | 11 ++- benches/proof.rs | 9 +- benches/stump.rs | 8 +- examples/custom_hash.rs | 8 +- examples/proof_update.rs | 6 +- src/mem_forest/mod.rs | 77 +++++++++-------- src/node_hash/mod.rs | 12 +-- src/pollard/mod.rs | 176 +++++++++++++++++++-------------------- src/proof/mod.rs | 123 ++++++++++++++------------- src/stump/mod.rs | 46 +++++----- src/util/mod.rs | 107 ++++++++++++------------ 12 files changed, 435 insertions(+), 283 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d3dc104..9dff593 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,6 +84,141 @@ examples = [ ] [lints.rust] +# TODO(@luisschwab): redocument the whole crate +#missing_docs = "warn" +unreachable_pub = "warn" +unused_qualifications = "warn" +# Copied over from rust-bitcoin [lints.clippy] +# Exclude lints we don't think are valuable. +needless_question_mark = "allow" # https://github.com/rust-bitcoin/rust-bitcoin/pull/2134 +manual_range_contains = "allow" # More readable than clippy's format. +# Exhaustive list of pedantic clippy lints +assigning_clones = "warn" +bool_to_int_with_if = "warn" +borrow_as_ptr = "warn" +case_sensitive_file_extension_comparisons = "warn" +cast_lossless = "warn" +cast_possible_truncation = "allow" # All casts should include a code comment (except test code). +cast_possible_wrap = "allow" # Same as above re code comment. +cast_precision_loss = "warn" +cast_ptr_alignment = "warn" +cast_sign_loss = "allow" # All casts should include a code comment (except in test code). +checked_conversions = "warn" +cloned_instead_of_copied = "warn" +copy_iterator = "warn" +default_trait_access = "warn" +doc_link_with_quotes = "warn" +doc_markdown = "warn" +empty_enums = "warn" +enum_glob_use = "warn" +expl_impl_clone_on_copy = "warn" +explicit_deref_methods = "warn" +explicit_into_iter_loop = "warn" +explicit_iter_loop = "warn" +filter_map_next = "warn" +flat_map_option = "warn" +float_cmp = "allow" # Bitcoin floats are typically limited to 8 decimal places and we want them exact. +fn_params_excessive_bools = "warn" +if_not_else = "warn" +ignored_unit_patterns = "warn" +implicit_clone = "warn" +implicit_hasher = "warn" +inconsistent_struct_constructor = "warn" +index_refutable_slice = "warn" +inefficient_to_string = "warn" +inline_always = "warn" +into_iter_without_iter = "warn" +invalid_upcast_comparisons = "warn" +items_after_statements = "warn" +iter_filter_is_ok = "warn" +iter_filter_is_some = "warn" +iter_not_returning_iterator = "warn" +iter_without_into_iter = "warn" +large_digit_groups = "warn" +large_futures = "warn" +large_stack_arrays = "warn" +large_types_passed_by_value = "warn" +linkedlist = "warn" +macro_use_imports = "warn" +manual_assert = "warn" +manual_instant_elapsed = "warn" +manual_is_power_of_two = "warn" +manual_is_variant_and = "warn" +manual_let_else = "warn" +manual_ok_or = "warn" +manual_string_new = "warn" +many_single_char_names = "warn" +map_unwrap_or = "warn" +match_bool = "allow" # Adds extra indentation and LOC. +match_same_arms = "allow" # Collapses things that are conceptually unrelated to each other. +match_wild_err_arm = "warn" +match_wildcard_for_single_variants = "warn" +maybe_infinite_iter = "warn" +mismatching_type_param_order = "warn" +# TODO(@luisschwab): redocument the whole crate +#missing_errors_doc = "warn" +missing_fields_in_debug = "warn" +# TODO(@luisschwab): redocument the whole crate +#missing_panics_doc = "warn" +missing_safety_doc = "warn" +# TODO(@luisschwab): redocument the whole crate +#missing_docs_in_private_items = "warn" +must_use_candidate = "allow" # Useful for audit but many false positives. +mut_mut = "warn" +naive_bytecount = "warn" +needless_bitwise_bool = "warn" +needless_continue = "warn" +needless_for_each = "warn" +needless_pass_by_value = "warn" +needless_raw_string_hashes = "warn" +no_effect_underscore_binding = "warn" +no_mangle_with_rust_abi = "warn" +option_as_ref_cloned = "warn" +option_option = "warn" +ptr_as_ptr = "warn" +ptr_cast_constness = "warn" +pub_underscore_fields = "warn" +range_minus_one = "warn" +range_plus_one = "warn" +redundant_clone = "warn" +redundant_closure_for_method_calls = "warn" +redundant_else = "warn" +ref_as_ptr = "warn" +ref_binding_to_reference = "warn" +ref_option = "warn" +ref_option_ref = "warn" +return_self_not_must_use = "warn" +same_functions_in_if_condition = "warn" +semicolon_if_nothing_returned = "warn" +should_panic_without_expect = "warn" +similar_names = "allow" # Too many (subjectively) false positives. +single_char_pattern = "warn" +single_match_else = "warn" +stable_sort_primitive = "warn" +str_split_at_newline = "warn" +string_add_assign = "warn" +struct_excessive_bools = "warn" +struct_field_names = "allow" # dumb +too_many_lines = "warn" +transmute_ptr_to_ptr = "warn" +trivially_copy_pass_by_ref = "warn" +unchecked_time_subtraction = "warn" +unicode_not_nfc = "warn" +uninlined_format_args = "allow" # This is a subjective style choice. +unnecessary_box_returns = "warn" +unnecessary_join = "warn" +unnecessary_literal_bound = "warn" +unnecessary_wraps = "warn" +unnested_or_patterns = "warn" +unreadable_literal = "warn" +unsafe_derive_deserialize = "warn" +unused_async = "warn" +unused_self = "warn" use_self = "warn" +used_underscore_binding = "warn" +used_underscore_items = "warn" +verbose_bit_mask = "warn" +wildcard_imports = "warn" +zero_sized_map_values = "warn" diff --git a/benches/accumulator.rs b/benches/accumulator.rs index feafae3..6c547c5 100644 --- a/benches/accumulator.rs +++ b/benches/accumulator.rs @@ -1,3 +1,7 @@ +//! # Accumulator Benchmarks +//! +//! Benchmarks for the [`Pollard`] and [`MemForest`] Utreexo accumulators. + use std::hint::black_box; use criterion::criterion_group; @@ -31,7 +35,7 @@ fn memforest_proof_generation(c: &mut Criterion) { let mut forest = MemForest::new(); forest.modify(&hashes, &[]).unwrap(); - for target_count in [1, 10].iter() { + for target_count in &[1, 10] { let targets = &hashes[..*target_count]; group.throughput(Throughput::Elements(*target_count as u64)); @@ -57,7 +61,7 @@ fn memforest_verification(c: &mut Criterion) { let mut forest = MemForest::new(); forest.modify(&hashes, &[]).unwrap(); - for target_count in [1, 10].iter() { + for target_count in &[1, 10] { let targets = &hashes[..*target_count]; let proof = forest.prove(targets).unwrap(); @@ -82,11 +86,10 @@ fn pollard_operations(c: &mut Criterion) { let base_size = 100; let hashes = generate_test_hashes(base_size, 42); let roots = vec![hashes[0]]; // Simplified root structure - let pollard = Pollard::from_roots(roots, base_size as u64); + let pollard = Pollard::from_roots(&roots, base_size as u64); { let batch_size = &10; - let _del_hashes = &hashes[..*batch_size / 2]; group.throughput(Throughput::Elements(*batch_size as u64)); group.bench_with_input( diff --git a/benches/proof.rs b/benches/proof.rs index 5309d88..17fdbbf 100644 --- a/benches/proof.rs +++ b/benches/proof.rs @@ -1,3 +1,7 @@ +//! # Proof Benchmarks +//! +//! Benchmarks for the [`Proof`] data structure. + use std::hint::black_box; use criterion::criterion_group; @@ -27,7 +31,7 @@ fn generate_test_hashes(count: usize, seed: u64) -> Vec { fn proof_creation(c: &mut Criterion) { let mut group = c.benchmark_group("proof_creation"); - for target_count in [1, 10].iter() { + for target_count in &[1, 10] { let targets: Vec = (0..*target_count).collect(); let proof_hashes = generate_test_hashes((*target_count * 3) as usize, 42); // Approximate proof size @@ -47,6 +51,7 @@ fn proof_creation(c: &mut Criterion) { group.finish(); } +#[allow(clippy::cast_precision_loss)] #[allow(clippy::unnecessary_cast)] fn proof_verification(c: &mut Criterion) { let mut group = c.benchmark_group("proof_verification"); @@ -57,7 +62,7 @@ fn proof_verification(c: &mut Criterion) { let stump = Stump::new(); let (stump, _) = stump.modify(&hashes, &[], &Proof::default()).unwrap(); - for target_count in [1, 10].iter() { + for target_count in &[1, 10] { let del_hashes = hashes[..*target_count].to_vec(); let targets: Vec = (0..*target_count as u64).collect(); diff --git a/benches/stump.rs b/benches/stump.rs index 4c4bc58..95027dd 100644 --- a/benches/stump.rs +++ b/benches/stump.rs @@ -1,3 +1,7 @@ +//! # Stump Benchmarks +//! +//! Benchmarks for the [`Stump`] accumulator. + use std::hint::black_box; use criterion::criterion_group; @@ -26,7 +30,7 @@ fn generate_test_hashes(count: usize, seed: u64) -> Vec { fn stump_modify_add_only(c: &mut Criterion) { let mut group = c.benchmark_group("stump_modify_add_only"); - for size in [10, 100].iter() { + for size in &[10, 100] { group.throughput(Throughput::Elements(*size as u64)); group.bench_with_input(BenchmarkId::new("add_elements", size), size, |b, &size| { let hashes = generate_test_hashes(size, 42); @@ -52,7 +56,7 @@ fn stump_verify(c: &mut Criterion) { let stump = Stump::new(); let (stump, _) = stump.modify(&hashes, &[], &Proof::default()).unwrap(); - for proof_size in [1, 10, 100].iter() { + for proof_size in &[1, 10, 100] { let del_hashes = hashes[..*proof_size].to_vec(); let proof = Proof::new( (0..*proof_size as u64).collect(), diff --git a/examples/custom_hash.rs b/examples/custom_hash.rs index 53f6251..1aa952a 100644 --- a/examples/custom_hash.rs +++ b/examples/custom_hash.rs @@ -6,7 +6,7 @@ //! //! This example shows how to use a custom hash type based on the Poseidon hash function. The //! [Poseidon Hash](https://eprint.iacr.org/2019/458.pdf) is a hash function that is optmized -//! for zero-knowledge proofs, and is used in projects like ZCash and StarkNet. +//! for zero-knowledge proofs, and is used in projects like `ZCash` and `StarkNet`. //! If you want to work with utreexo proofs in zero-knowledge you may want to use this instead //! of our usual sha512-256 that we use by default, since that will give you smaller circuits. //! This example shows how to use both the [`MemForest`] and proofs with a custom hash type. @@ -28,8 +28,8 @@ enum CustomHash { Hash([u8; 32]), /// Placeholder is a value that haven't been deleted, but we don't have the actual value. /// The only thing that matters about it is that it's not empty. You can implement this - /// the way you want, just make sure that [NodeHash::is_placeholder] and [NodeHash::placeholder] - /// returns sane values (that is, if we call [NodeHash::placeholder] calling [NodeHash::is_placeholder] + /// the way you want, just make sure that [`NodeHash::is_placeholder`] and [`NodeHash::placeholder`] + /// returns sane values (that is, if we call [`NodeHash::placeholder`] calling [`NodeHash::is_placeholder`] /// on the result should return true). Placeholder, @@ -37,7 +37,7 @@ enum CustomHash { /// This is an empty value, it represents a node that was deleted from the accumulator. /// /// Same as the placeholder, you can implement this the way you want, just make sure that - /// [NodeHash::is_empty] and [NodeHash::empty] returns sane values. + /// [`NodeHash::is_empty`] and [`NodeHash::empty`] returns sane values. Empty, } diff --git a/examples/proof_update.rs b/examples/proof_update.rs index 42209c2..f03a456 100644 --- a/examples/proof_update.rs +++ b/examples/proof_update.rs @@ -33,7 +33,7 @@ fn main() { // update_data is the data we got from the accumulator update, and contains multiple intermediate // data we'll need. let (p, cached_hashes) = p - .update(vec![], utxos.clone(), vec![], vec![0, 1], update_data) + .update(vec![], &utxos, &[], vec![0, 1], update_data) .unwrap(); // This should be a valid proof over 0 and 1. assert_eq!(p.n_targets(), 2); @@ -53,8 +53,8 @@ fn main() { let (p2, cached_hashes) = p .update( cached_hashes, - new_utxos, - vec![0], + &new_utxos, + &[0], vec![1, 2, 3, 4, 5, 6, 7], update_data, ) diff --git a/src/mem_forest/mod.rs b/src/mem_forest/mod.rs index e71da71..6b8beb6 100644 --- a/src/mem_forest/mod.rs +++ b/src/mem_forest/mod.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 -//! A full MemForest accumulator implementation. This is a simple version of the forest, +//! A full [`MemForest`] accumulator implementation. This is a simple version of the forest, //! that keeps every node in memory. This is may require more memory, but is faster //! to update, prove and verify. //! @@ -129,7 +129,7 @@ impl Node { /// many roots there are. #[allow(clippy::type_complexity)] pub fn read_one(reader: &mut R) -> io::Result<(Rc, HashMap>)> { - fn _read_one( + fn read_one_inner( ancestor: Option>>, reader: &mut R, index: &mut HashMap>>, @@ -162,8 +162,8 @@ impl Node { right: RefCell::new(None), }); if !data.is_empty() { - let left = _read_one(Some(node.clone()), reader, index)?; - let right = _read_one(Some(node.clone()), reader, index)?; + let left = read_one_inner(Some(node.clone()), reader, index)?; + let right = read_one_inner(Some(node.clone()), reader, index)?; node.left.replace(Some(left)); node.right.replace(Some(right)); } @@ -179,7 +179,7 @@ impl Node { Ok(node) } let mut index = new_hash_map(); - let root = _read_one(None, reader, &mut index)?; + let root = read_one_inner(None, reader, &mut index)?; Ok((root, index)) } @@ -189,7 +189,7 @@ impl Node { } } -/// The actual MemForest accumulator, it implements all methods required to update the forest +/// The actual [`MemForest`] accumulator, it implements all methods required to update the forest /// and to prove/verify membership. #[derive(Default, Clone)] pub struct MemForest { @@ -205,10 +205,10 @@ pub struct MemForest { } impl MemForest { - /// Creates a new empty [MemForest] with the default hash function. + /// Creates a new empty [`MemForest`] with the default hash function. /// - /// This will create an empty MemForest, using [BitcoinNodeHash] as the hash function. If you - /// want to use a different hash function, you can use [MemForest::new_with_hash]. + /// This will create an empty [`MemForest`], using [`BitcoinNodeHash`] as the hash function. If you + /// want to use a different hash function, you can use [`MemForest::new_with_hash`]. /// # Example /// ``` /// use rustreexo::mem_forest::MemForest; @@ -224,7 +224,7 @@ impl MemForest { } impl MemForest { - /// Creates a new empty [MemForest] with a custom hash function. + /// Creates a new empty [`MemForest`] with a custom hash function. /// # Example /// ``` /// use rustreexo::mem_forest::MemForest; @@ -239,7 +239,7 @@ impl MemForest { } } - /// Writes the MemForest to a writer. Used to send the accumulator over the wire + /// Writes the [`MemForest`] to a writer. Used to send the accumulator over the wire /// or to disk. /// # Example /// ``` @@ -266,7 +266,7 @@ impl MemForest { Ok(()) } - /// Deserializes a MemForest from a reader. + /// Deserializes a [`MemForest`] from a reader. /// # Example /// ``` /// use rustreexo::mem_forest::MemForest; @@ -288,8 +288,8 @@ impl MemForest { let mut roots = Vec::new(); let mut map = new_hash_map(); for _ in 0..roots_len { - let (root, _map) = Node::read_one(&mut reader)?; - map.extend(_map); + let (root, node_map) = Node::read_one(&mut reader)?; + map.extend(node_map); roots.push(root); } Ok(Self { roots, leaves, map }) @@ -341,13 +341,13 @@ impl MemForest { Ok(Proof::new_with_hash(translated_targets, proof)) } - /// Returns a reference to the roots in this MemForest. + /// Returns a reference to the roots in this [`MemForest`]. pub fn get_roots(&self) -> &[Rc>] { &self.roots } - /// Modify is the main API to a [MemForest]. Because order matters, you can only `modify` - /// a [MemForest], and internally it'll add and delete, in the correct order. + /// Modify is the main API to a [`MemForest`]. Because order matters, you can only `modify` + /// a [`MemForest`], and internally it'll add and delete, in the correct order. /// /// This method accepts two vectors as parameter, a vec of [Hash] and a vec of [u64]. The /// first one is a vec of leaf hashes for the newly created UTXOs. The second one is the position @@ -405,7 +405,7 @@ impl MemForest { #[allow(clippy::assigning_clones)] if let Some(node) = n { - if is_left_niece(niece_pos as u64) { + if is_left_niece(u64::from(niece_pos)) { n = node.right.borrow().clone(); sibling.clone_from(&*node.left.borrow()); } else { @@ -527,19 +527,18 @@ impl MemForest { fn del_single(&mut self, node: &Node) -> Option<()> { let parent = node.parent.borrow(); // Deleting a root - let parent = match *parent { - Some(ref node) => node.upgrade()?, - None => { - let pos = self.roots.iter().position(|x| x.data == node.data).unwrap(); - self.roots[pos] = Rc::new(Node { - ty: NodeType::Branch, - parent: RefCell::new(None), - data: Cell::new(Hash::empty()), - left: RefCell::new(None), - right: RefCell::new(None), - }); - return None; - } + let parent = if let Some(ref node) = *parent { + node.upgrade()? + } else { + let pos = self.roots.iter().position(|x| x.data == node.data).unwrap(); + self.roots[pos] = Rc::new(Node { + ty: NodeType::Branch, + parent: RefCell::new(None), + data: Cell::new(Hash::empty()), + left: RefCell::new(None), + right: RefCell::new(None), + }); + return None; }; let me = parent.left.borrow(); @@ -615,7 +614,7 @@ impl MemForest { } } - /// to_string returns the full MemForest in a string for all forests less than 6 rows. + /// `to_string` returns the full [`MemForest`] in a string for all forests less than 6 rows. fn string(&self) -> String { if self.leaves == 0 { return "empty".to_owned(); @@ -629,14 +628,14 @@ impl MemForest { a }); } - let mut output = vec!["".to_string(); (fh as usize * 2) + 1]; + let mut output = vec![String::new(); (fh as usize * 2) + 1]; let mut pos: u8 = 0; for h in 0..=fh { let row_len = 1 << (fh - h); for _ in 0..row_len { let max = max_position_at_row(h, fh, self.leaves).unwrap(); - if max >= pos as u64 { - match self.get_hash(pos as u64) { + if max >= u64::from(pos) { + match self.get_hash(u64::from(pos)) { Ok(val) => { if pos >= 100 { output[h as usize * 2].push_str( @@ -873,7 +872,7 @@ mod test { expected_roots: Vec, } - fn run_single_addition_case(case: TestCase) { + fn run_single_addition_case(case: &TestCase) { let hashes = case .leaf_preimages .iter() @@ -895,7 +894,7 @@ mod test { assert_eq!(expected_roots, roots, "Test case failed {case:?}"); } - fn run_case_with_deletion(case: TestCase) { + fn run_case_with_deletion(case: &TestCase) { let hashes = case .leaf_preimages .iter() @@ -940,10 +939,10 @@ mod test { serde_json::from_str::(contents).expect("JSON deserialization error"); for i in tests.insertion_tests { - run_single_addition_case(i); + run_single_addition_case(&i); } for i in tests.deletion_tests { - run_case_with_deletion(i); + run_case_with_deletion(&i); } } diff --git a/src/node_hash/mod.rs b/src/node_hash/mod.rs index e7e93ee..ec609c3 100644 --- a/src/node_hash/mod.rs +++ b/src/node_hash/mod.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 -//! [AccumulatorHash] is an internal type for representing Hashes in an utreexo accumulator. It's +//! [`AccumulatorHash`] is an internal type for representing Hashes in an utreexo accumulator. It's //! just a wrapper around [[u8; 32]] but with some useful methods. //! # Examples //! Building from a str @@ -84,7 +84,7 @@ pub trait AccumulatorHash: Copy + Clone + Ord + Debug + Display + Hash + Default #[derive(Eq, PartialEq, Copy, Clone, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] -/// AccumulatorHash is a wrapper around a 32 byte array that represents a hash of a node in the tree. +/// `AccumulatorHash` is a wrapper around a 32 byte array that represents a hash of a node in the tree. /// # Example /// ``` /// use rustreexo::node_hash::BitcoinNodeHash; @@ -120,7 +120,7 @@ impl Display for BitcoinNodeHash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { if let Self::Some(ref inner) = self { let mut s = String::new(); - for byte in inner.iter() { + for byte in inner { s.push_str(&format!("{byte:02x}")); } write!(f, "{s}") @@ -137,7 +137,7 @@ impl Debug for BitcoinNodeHash { Self::Placeholder => write!(f, "placeholder"), Self::Some(ref inner) => { let mut s = String::new(); - for byte in inner.iter() { + for byte in inner { s.push_str(&format!("{byte:02x}")); } write!(f, "{s}") @@ -215,7 +215,7 @@ impl FromStr for BitcoinNodeHash { } impl BitcoinNodeHash { - /// Creates a new AccumulatorHash from a 32 byte array. + /// Creates a new `AccumulatorHash` from a 32 byte array. /// # Example /// ``` /// use rustreexo::node_hash::BitcoinNodeHash; @@ -249,7 +249,7 @@ impl AccumulatorHash for BitcoinNodeHash { Self::Empty } - /// parent_hash return the merkle parent of the two passed in nodes. + /// `parent_hash` return the merkle parent of the two passed in nodes. /// # Example /// ``` /// use std::str::FromStr; diff --git a/src/pollard/mod.rs b/src/pollard/mod.rs index 38890f6..6f2a8ff 100644 --- a/src/pollard/mod.rs +++ b/src/pollard/mod.rs @@ -10,7 +10,7 @@ //! //! This implementation is close to the one in `MemForest`, but it is specialized in keeping track //! of subsets of the whole tree, allowing you to cache and uncache elements as needed. While the -//! MemForest keeps everything in the accumulator, and may take a lot of memory. +//! [`MemForest`](crate::mem_forest::MemForest) keeps everything in the accumulator, and may take a lot of memory. //! //! Nodes are kept in memory, and they hold their hashes, a reference to their **aunt** (not //! parent!), and their nieces (not children!). We do this to allow for proof generation, while @@ -25,12 +25,12 @@ //! except for the owner of the node. Things are kept in a [Rc] to allow for multiple references to //! the same node, as we may need to operate on it, and also to allow the nieces to have a reference //! to their aunt. It could be done with pointers, but it would be more complex and error-prone. The -//! [Rc]s live inside a [RefCell], to allow for interior mutability, as we may need to change the -//! values inside a node. Make sure to avoid leaking a reference to the inner [RefCell] to the outside +//! [`Rc`]s live inside a [`RefCell`], to allow for interior mutability, as we may need to change the +//! values inside a node. Make sure to avoid leaking a reference to the inner [`RefCell`] to the outside //! world, as it may cause race conditions and panics. Every time we use a reference to the inner -//! [RefCell], we make sure to drop it as soon as possible, and that we are the only ones operating -//! on it at that time. For this reason, a [Pollard] is not [Sync], and you'll need to use a `Mutex` -//! or something similar to share it between threads. But it is [Send], as it is safe to send it to +//! [`RefCell`], we make sure to drop it as soon as possible, and that we are the only ones operating +//! on it at that time. For this reason, a [`Pollard`] is not [`Sync`], and you'll need to use a `Mutex` +//! or something similar to share it between threads. But it is [`Send`], as it is safe to send it to //! another thread - everything is owned by the Pollard and lives on the heap. //! //! ## Usage @@ -80,30 +80,30 @@ struct PollardNode { /// /// This is the hash used in the merkle proof. For leaves, this is the hash of the value /// committed to. For internal nodes, this is the hash of the concatenation of the hashes of - /// the children. This value is stored in a [Cell] to allow for interior mutability, as we may + /// the children. This value is stored in a [`Cell`] to allow for interior mutability, as we may /// need to change it if some descendant is deleted. hash: Cell, /// This node's aunt /// /// The aunt is the sibling of the parent. This is the only node that is not owned by this - /// node, as it is owned by some ancestor. This is a [Weak] reference to avoid cycles in the tree. + /// node, as it is owned by some ancestor. This is a [`Weak`] reference to avoid cycles in the tree. /// If a node is a root, this value is `None`, as it doesn't have an aunt. If this node's /// parent is a root, then it actually points to its parent, as the parent is a root, and /// there's no aunt. aunt: RefCell>>, /// This node's left niece /// - /// The left niece is the left child of this node's sibling. We use an actual [Rc] here, to - /// make this node own the niece. This is the only place where an [Rc] can be stored past some - /// function's scope, as it may create cycles in the tree. This is a [RefCell] because we may + /// The left niece is the left child of this node's sibling. We use an actual [`Rc`] here, to + /// make this node own the niece. This is the only place where an [`Rc`] can be stored past some + /// function's scope, as it may create cycles in the tree. This is a [`RefCell`] because we may /// need to either prune the nieces, or swap them if this node is a root. If this node is a /// leaf, this value is `None`, as it doesn't have any descendants. left_niece: RefCell>>, /// This node's right niece /// - /// The right niece is the right child of this node's sibling. We use an actual [Rc] here, to - /// make this node own the niece. This is the only place where an [Rc] can be stored past some - /// function's scope, as it may create cycles in the tree. This is a [RefCell] because we may + /// The right niece is the right child of this node's sibling. We use an actual [`Rc`] here, to + /// make this node own the niece. This is the only place where an [`Rc`] can be stored past some + /// function's scope, as it may create cycles in the tree. This is a [`RefCell`] because we may /// need to either prune the nieces, or swap them if this node is a root. If this node is a /// leaf, this value is `None`, as it doesn't have any descendants. right_niece: RefCell>>, @@ -205,7 +205,7 @@ impl Debug for PollardError { } } -impl fmt::Display for PollardError { +impl Display for PollardError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self:?}") } @@ -227,7 +227,7 @@ impl PartialEq for PollardNode { impl Eq for PollardNode {} impl PollardNode { - /// Creates a new PollardNode with the given hash and remember value + /// Creates a new `PollardNode` with the given hash and remember value fn new(hash: Hash, remember: bool) -> Rc { Rc::new(Self { remember, @@ -239,7 +239,7 @@ impl PollardNode { } fn serialize(&self, writer: &mut W) -> Result<(), PollardError> { - let is_leaf = self.left_niece().is_none() as u8; + let is_leaf = u8::from(self.left_niece().is_none()); writer.write_all(&is_leaf.to_be_bytes())?; let self_hash = self.hash(); @@ -388,7 +388,7 @@ impl PollardNode { return parent.recompute_hashes(); } - aunt.recompute_hashes() + aunt.recompute_hashes(); } } @@ -433,7 +433,7 @@ impl PollardNode { let left_niece = aunt.left_niece().ok_or(PollardError::NieceNotFound)?; - let _self = if left_niece.hash() == self.hash() { + let this_node = if left_niece.hash() == self.hash() { aunt.left_niece().ok_or(PollardError::NieceNotFound)? } else { aunt.right_niece().ok_or(PollardError::NieceNotFound)? @@ -447,12 +447,12 @@ impl PollardNode { let left_niece = grandparent .left_niece() .ok_or(PollardError::NieceNotFound)?; - (left_niece, _self.clone()) + (left_niece, this_node.clone()) } else { let right_niece = grandparent .right_niece() .ok_or(PollardError::NieceNotFound)?; - (_self.clone(), right_niece) + (this_node.clone(), right_niece) }; // place myself and my parent's sibling as my grandancestor's nieces @@ -465,16 +465,16 @@ impl PollardNode { // I'm now my aunt's sibling, so I should have their children. // Update my nieces's aunt to be me if let Some(x) = parent.left_niece() { - x.set_aunt(Rc::downgrade(&_self)) + x.set_aunt(Rc::downgrade(&this_node)); }; if let Some(x) = parent.right_niece() { - x.set_aunt(Rc::downgrade(&_self)) + x.set_aunt(Rc::downgrade(&this_node)); } // take my parent's nieces, as they are still needed self.swap_nieces(&parent); - _self.recompute_hashes(); + this_node.recompute_hashes(); Ok(()) } @@ -610,7 +610,7 @@ impl Pollard { /// error. pub fn ingest_proof( &mut self, - proof: Proof, + proof: &Proof, del_hashes: &[Hash], remembers: &[u64], ) -> Result<(), PollardError> { @@ -630,7 +630,7 @@ impl Pollard { pub fn verify_and_ingest( &mut self, - proof: Proof, + proof: &Proof, del_hashes: &[Hash], remembers: &[u64], ) -> Result<(), PollardError> { @@ -695,7 +695,7 @@ impl Pollard { get_proof_positions(&target_positions, self.leaves, tree_rows(self.leaves)); let mut proof_hashes = Vec::new(); - for pos in proof_positions.iter() { + for pos in &proof_positions { let hash = self .grab_position(*pos) .ok_or(PollardError::PositionNotFound(*pos))? @@ -727,14 +727,14 @@ impl Pollard { let hashes = self.prove_single_inner(pos)?; let targets = vec![pos]; - Ok(Proof { hashes, targets }) + Ok(Proof { targets, hashes }) } /// Applies the changes to the [Pollard] for a new block /// /// Since the order of the operations is important, the API can't expose adding and deleting /// directly. Instead, the user should call this function with the additions and deletions they - /// want to make. You should pass in the additions as [PollardAddition]s, telling what should + /// want to make. You should pass in the additions as [`PollardAddition`]s, telling what should /// be added to the accumulator, and whether it should be remembered or not. /// The deletions should be passed as a list of target positions, telling which nodes should be /// deleted from the accumulator. Positions that are not cached will be ignored. You should check @@ -744,7 +744,7 @@ impl Pollard { &mut self, adds: &[PollardAddition], del_hashes: &[Hash], - proof: Proof, + proof: &Proof, ) -> Result<(), PollardError> { let targets = proof.targets.clone(); self.ingest_proof(proof, del_hashes, &targets)?; @@ -759,15 +759,15 @@ impl Pollard { .collect::>(); for del in targets { - self.delete_single(del?.0)? + self.delete_single(&del?.0)?; } let mut add_nodes = Vec::new(); let mut roots_destroyed = Vec::new(); for node in adds { - let (_new_nodes, _roots_destroyed) = self.add_single(*node)?; - add_nodes.extend(_new_nodes); - roots_destroyed.extend(_roots_destroyed); + let (new_nodes, destroyed_roots) = self.add_single(*node); + add_nodes.extend(new_nodes); + roots_destroyed.extend(destroyed_roots); } Ok(()) @@ -789,7 +789,7 @@ impl Pollard { /// keep track of a subset of the whole tree. Instead of remputing the pollard from genesis for /// every flavor of accumulator you have, you can sync-up using the [Stump] and then use its /// roots to create a up-to-date pollard. - pub fn from_roots(roots: Vec, leaves: u64) -> Self { + pub fn from_roots(roots: &[Hash], leaves: u64) -> Self { let mut pollard = Self::new(); pollard.leaves = leaves; @@ -821,17 +821,14 @@ impl Pollard { pub fn serialize(&self, writer: &mut W) -> Result<(), PollardError> { writer.write_all(&self.leaves.to_be_bytes())?; - for root in self.roots.iter() { - match root { - Some(root) => { - let marker = 1u8; - writer.write_all(&marker.to_be_bytes())?; - root.serialize(writer)? - } - None => { - let marker = 0u8; - writer.write_all(&marker.to_be_bytes())?; - } + for root in &self.roots { + if let Some(root) = root { + let marker = 1u8; + writer.write_all(&marker.to_be_bytes())?; + root.serialize(writer)?; + } else { + let marker = 0u8; + writer.write_all(&marker.to_be_bytes())?; } } @@ -850,7 +847,7 @@ impl Pollard { let mut pollard = Self::new(); pollard.leaves = leaves; - for root in pollard.roots.iter_mut() { + for root in &mut pollard.roots { let mut marker = [0u8; 1]; reader.read_exact(&mut marker)?; let marker = marker[0]; @@ -870,7 +867,7 @@ impl Pollard { // private methods -/// The result from add_single +/// The result from `add_single` type AddSingleResult = (Vec<(u64, T)>, Vec); type ChildrenTuple = (Rc>, Rc>); @@ -953,7 +950,7 @@ impl Pollard { fn do_ingest_proof( &mut self, - proof: Proof, + proof: &Proof, del_hashes: &[Hash], remembers: &[u64], recompute: bool, @@ -965,7 +962,11 @@ impl Pollard { let proof_positions = get_proof_positions(&proof.targets, self.leaves, forest_rows); - all_nodes.extend(proof_positions.into_iter().zip(proof.hashes.clone())); + all_nodes.extend( + proof_positions + .into_iter() + .zip(proof.hashes.iter().copied()), + ); all_nodes.sort(); let iter = all_nodes.into_iter().rev(); self.ingest_positions(iter, remembers)?; @@ -1012,7 +1013,7 @@ impl Pollard { } } - /// to_string returns the full mem_forest in a string for all forests less than 6 rows. + /// `to_string` returns the full `mem_forest` in a string for all forests less than 6 rows. fn string(&self) -> String { if self.leaves == 0 { return "empty".to_owned(); @@ -1027,14 +1028,14 @@ impl Pollard { }); } - let mut output = vec!["".to_string(); (fh as usize * 2) + 1]; + let mut output = vec![String::new(); (fh as usize * 2) + 1]; let mut pos: u8 = 0; for h in 0..=fh { let row_len = 1 << (fh - h); for _ in 0..row_len { let max = max_position_at_row(h, fh, self.leaves).unwrap(); - if max >= pos as u64 { - match self.get_hash(pos as u64) { + if max >= u64::from(pos) { + match self.get_hash(u64::from(pos)) { Ok(val) => { if pos >= 100 { output[h as usize * 2].push_str( @@ -1097,10 +1098,7 @@ impl Pollard { Ok(proof) } - fn add_single( - &mut self, - node: PollardAddition, - ) -> Result, PollardError> { + fn add_single(&mut self, node: PollardAddition) -> AddSingleResult { let mut row = 0; let mut new_node = PollardNode::new(node.hash, node.remember); self.leaf_map.insert(node.hash, Rc::downgrade(&new_node)); @@ -1136,17 +1134,17 @@ impl Pollard { //FIXME: This should be a method in PollardNode if let Some(x) = new_node.left_niece() { - x.set_aunt(Rc::downgrade(&new_node)) + x.set_aunt(Rc::downgrade(&new_node)); } if let Some(x) = new_node.right_niece() { - x.set_aunt(Rc::downgrade(&new_node)) + x.set_aunt(Rc::downgrade(&new_node)); } if let Some(x) = old_root.left_niece() { - x.set_aunt(Rc::downgrade(&old_root)) + x.set_aunt(Rc::downgrade(&old_root)); } if let Some(x) = old_root.right_niece() { - x.set_aunt(Rc::downgrade(&old_root)) + x.set_aunt(Rc::downgrade(&old_root)); } // update aunts for the old nodes @@ -1172,15 +1170,15 @@ impl Pollard { self.roots[row as usize] = Some(new_node); self.leaves += 1; - Ok((add_positions, roots_to_destroy)) + (add_positions, roots_to_destroy) } - fn delete_single(&mut self, node: Rc>) -> Result<(), PollardError> { + fn delete_single(&mut self, node: &Rc>) -> Result<(), PollardError> { self.leaf_map.remove(&node.hash()); // we are deleting a root, just write an empty hash where it was if node.aunt.borrow().is_none() { for i in 0..64 { - if self.roots[i].eq(&Some(node.clone())) { + if self.roots[i].as_ref().is_some_and(|root| root == node) { self.roots[i] = Some(Rc::new(PollardNode::default())); return Ok(()); } @@ -1196,9 +1194,7 @@ impl Pollard { for i in 0..64 { let aunt = node.aunt().ok_or(PollardError::AuntNotFound)?; - let root = if let Some(root) = self.roots[i].as_ref() { - root - } else { + let Some(root) = self.roots[i].as_ref() else { continue; }; @@ -1272,7 +1268,7 @@ impl Pollard { impl From> for Pollard { fn from(stump: Stump) -> Self { - Self::from_roots(stump.roots, stump.leaves) + Self::from_roots(&stump.roots, stump.leaves) } } @@ -1355,7 +1351,7 @@ mod tests { let proof = Proof::default(); let dels = Vec::new(); - p.modify(&adds, &dels, proof.clone()).unwrap(); + p.modify(&adds, &dels, &proof).unwrap(); p.prune(&[0, 1, 6, 7, 10]).unwrap(); let mut buffer = Vec::new(); @@ -1376,7 +1372,7 @@ mod tests { let leaves = 15; - let p = Pollard::::from_roots(roots.clone(), leaves); + let p = Pollard::::from_roots(&roots, leaves); assert_eq!(roots, p.roots()); assert_eq!(leaves, p.leaves()); } @@ -1391,7 +1387,7 @@ mod tests { ]; let leaves = 15; - let stump = Stump { roots, leaves }; + let stump = Stump { leaves, roots }; let p: Pollard = stump.clone().into(); assert_eq!(stump.roots, p.roots()); @@ -1413,7 +1409,7 @@ mod tests { .collect::>(); let mut acc = Pollard::::new(); - acc.modify(&hashes, &[], Proof::default()).unwrap(); + acc.modify(&hashes, &[], &Proof::default()).unwrap(); assert_eq!( "b151a956139bb821d4effa34ea95c17560e0135d1e4661fc23cedc3af49dac42", @@ -1455,15 +1451,15 @@ mod tests { serde_json::from_str::(contents).expect("JSON deserialization error"); for i in tests.insertion_tests { - run_single_addition_case(i); + run_single_addition_case(&i); } for i in tests.deletion_tests { - run_case_with_deletion(i); + run_case_with_deletion(&i); } } - fn run_single_addition_case(case: TestCase) { + fn run_single_addition_case(case: &TestCase) { let hashes = case .leaf_preimages .iter() @@ -1477,7 +1473,7 @@ mod tests { .collect::>(); let mut p = Pollard::::new(); - p.modify(&hashes, &[], Proof::default()).unwrap(); + p.modify(&hashes, &[], &Proof::default()).unwrap(); let expected_roots = case .expected_roots @@ -1490,7 +1486,7 @@ mod tests { assert_eq!(expected_roots, roots, "Test case failed {case:?}"); } - fn run_case_with_deletion(case: TestCase) { + fn run_case_with_deletion(case: &TestCase) { let hashes = case .leaf_preimages .iter() @@ -1524,8 +1520,8 @@ mod tests { let proof = Proof::new(case.target_values.clone().unwrap(), proof_hashes); let mut p = Pollard::::new(); - p.modify(&hashes, &[], Proof::default()).unwrap(); - p.modify(&[], &target_hashes, proof).unwrap(); + p.modify(&hashes, &[], &Proof::default()).unwrap(); + p.modify(&[], &target_hashes, &proof).unwrap(); let expected_roots = case .expected_roots @@ -1559,8 +1555,8 @@ mod tests { .collect(); let mut p = Pollard::::new(); - p.modify(&hashes, &[], Proof::default()).unwrap(); - p.delete_single(p.grab_position(1).unwrap().0) + p.modify(&hashes, &[], &Proof::default()).unwrap(); + p.delete_single(&p.grab_position(1).unwrap().0) .expect("Failed to delete"); let root = p.roots[1].clone(); @@ -1584,7 +1580,7 @@ mod tests { .collect(); let mut acc = Pollard::::new(); - acc.modify(&hashes, &[], Proof::default()).unwrap(); + acc.modify(&hashes, &[], &Proof::default()).unwrap(); let del_hashes = [ hash_from_u8(2), @@ -1595,7 +1591,8 @@ mod tests { let proof = acc.batch_proof(&del_hashes).unwrap(); acc.prune(&[0, 1, 2, 3, 4, 5, 6, 7]).unwrap(); - acc.ingest_proof(proof, &del_hashes, &[2, 1, 4, 6]).unwrap(); + acc.ingest_proof(&proof, &del_hashes, &[2, 1, 4, 6]) + .unwrap(); let del_hashes = [0, 1, 4, 5, 6, 7] .iter() @@ -1619,7 +1616,7 @@ mod tests { .collect(); let mut acc = Pollard::::new(); - acc.modify(&hashes, &[], Proof::default()).unwrap(); + acc.modify(&hashes, &[], &Proof::default()).unwrap(); let del_hashes = [ hash_from_u8(2), hash_from_u8(1), @@ -1662,7 +1659,7 @@ mod tests { .collect(); let mut acc = Pollard::::new(); - acc.modify(&hashes, &[], Proof::default()).unwrap(); + acc.modify(&hashes, &[], &Proof::default()).unwrap(); let proof = acc.prove_single(hashes[3].hash).unwrap(); let expected_hashes = [ @@ -1710,7 +1707,7 @@ mod tests { let hashes = get_hashes_of(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); let mut p = Pollard::new(); - p.modify(&hashes, &[], Proof::default()) + p.modify(&hashes, &[], &Proof::default()) .expect("Test mem_forests are valid"); test_get_pos!(p, 0); test_get_pos!(p, 1); @@ -1761,9 +1758,8 @@ mod tests { }; let mut acc = Pollard::::new(); - acc.modify(&values, &[], Proof::default()).unwrap(); - acc.ingest_proof(proof.clone(), &[hash_from_u8(3)], &[3]) - .unwrap(); + acc.modify(&values, &[], &Proof::default()).unwrap(); + acc.ingest_proof(&proof, &[hash_from_u8(3)], &[3]).unwrap(); let new_proof = acc.prove_single(values[3].hash).unwrap(); assert_eq!(new_proof, proof); diff --git a/src/proof/mod.rs b/src/proof/mod.rs index 4187c1c..e16c77c 100644 --- a/src/proof/mod.rs +++ b/src/proof/mod.rs @@ -71,9 +71,17 @@ use serde::Serialize; use super::node_hash::AccumulatorHash; use super::node_hash::BitcoinNodeHash; use super::stump::UpdateData; -use super::util; +use super::util::calc_next_pos; +use super::util::detect_offset; +use super::util::detect_row; +use super::util::detwin; use super::util::get_proof_positions; +use super::util::is_ancestor; +use super::util::is_root_position; +use super::util::num_roots; +use super::util::parent; use super::util::read_u64; +use super::util::start_position_at_row; use super::util::tree_rows; use crate::prelude::*; use crate::util::translate; @@ -503,11 +511,10 @@ impl Proof { } // Where all the root hashes that we've calculated will go to. - let total_rows = util::tree_rows(num_leaves); + let total_rows = tree_rows(num_leaves); // Where all the parent hashes we've calculated in a given row will go to. - let mut calculated_root_hashes = - Vec::<(Hash, Hash)>::with_capacity(util::num_roots(num_leaves)); + let mut calculated_root_hashes = Vec::<(Hash, Hash)>::with_capacity(num_roots(num_leaves)); // the positions that should be passed as a proof let translated: Vec<_> = self @@ -541,7 +548,7 @@ impl Proof { while let Some((next_pos, (next_hash_old, next_hash_new))) = Self::get_next(&computed, &nodes, &mut computed_index, &mut provided_index) { - if util::is_root_position(next_pos, num_leaves, total_rows) { + if is_root_position(next_pos, num_leaves, total_rows) { calculated_root_hashes.push((next_hash_old, next_hash_new)); continue; } @@ -562,7 +569,7 @@ impl Proof { (false, false) => AccumulatorHash::parent_hash(&next_hash_new, &sibling_hash_new), }; - let parent = util::parent(next_pos, total_rows); + let parent = parent(next_pos, total_rows); let old_parent_hash = AccumulatorHash::parent_hash(&next_hash_old, &sibling_hash_old); computed.push((parent, (old_parent_hash, parent_hash))); } @@ -597,10 +604,10 @@ impl Proof { } // Where all the root hashes that we've calculated will go to. - let total_rows = util::tree_rows(num_leaves); + let total_rows = tree_rows(num_leaves); // Where all the parent hashes we've calculated in a given row will go to. - let mut calculated_root_hashes = Vec::::with_capacity(util::num_roots(num_leaves)); + let mut calculated_root_hashes = Vec::::with_capacity(num_roots(num_leaves)); // the positions that should be passed as a proof let translated: Vec<_> = self @@ -636,7 +643,7 @@ impl Proof { while let Some((next_pos, next_hash)) = Self::get_next(&computed, &nodes, &mut computed_index, &mut provided_index) { - if util::is_root_position(next_pos, num_leaves, total_rows) { + if is_root_position(next_pos, num_leaves, total_rows) { calculated_root_hashes.push(next_hash); continue; } @@ -651,7 +658,7 @@ impl Proof { } let parent_hash = AccumulatorHash::parent_hash(&next_hash, &sibling_hash); - let parent = util::parent(next_pos, total_rows); + let parent = parent(next_pos, total_rows); computed.push((parent, parent_hash)); } @@ -699,15 +706,15 @@ impl Proof { pub fn update( self, cached_hashes: Vec, - add_hashes: Vec, - block_targets: Vec, + add_hashes: &[Hash], + block_targets: &[u64], remembers: Vec, update_data: UpdateData, ) -> Result<(Self, Vec), ProofError> { let (proof_after_deletion, cached_hashes) = self.update_proof_remove( block_targets, cached_hashes, - update_data.new_del, + &update_data.new_del, update_data.prev_num_leaves, )?; @@ -715,7 +722,7 @@ impl Proof { add_hashes, cached_hashes, remembers, - update_data.new_add, + &update_data.new_add, update_data.prev_num_leaves, update_data.to_destroy, )?; @@ -725,10 +732,10 @@ impl Proof { fn update_proof_add( self, - adds: Vec, + adds: &[Hash], cached_del_hashes: Vec, remembers: Vec, - new_nodes: Vec<(u64, Hash)>, + new_nodes: &[(u64, Hash)], before_num_leaves: u64, to_destroy: Vec, ) -> Result<(Self, Vec), ProofError> { @@ -744,7 +751,7 @@ impl Proof { let proof_pos = get_proof_positions( &self.targets, before_num_leaves, - util::tree_rows(before_num_leaves), + tree_rows(before_num_leaves), ); let proof_with_pos = proof_pos.into_iter().zip(self.hashes).collect(); @@ -760,10 +767,8 @@ impl Proof { // Move up positions that need to be moved up due to the empty roots // being written over. for node in to_destroy { - final_targets = - Self::calc_next_positions(&vec![node], &final_targets, num_leaves, true)?; - proof_with_pos = - Self::calc_next_positions(&vec![node], &proof_with_pos, num_leaves, true)?; + final_targets = Self::calc_next_positions(&[node], &final_targets, num_leaves, true)?; + proof_with_pos = Self::calc_next_positions(&[node], &proof_with_pos, num_leaves, true)?; } // remembers is an index telling what newly created UTXO should be cached @@ -783,8 +788,8 @@ impl Proof { final_targets.clone().into_iter().unzip(); // Grab all the new nodes after this add. let mut needed_proof_positions = - util::get_proof_positions(&new_target_pos, num_leaves, util::tree_rows(num_leaves)); - needed_proof_positions.sort(); + get_proof_positions(&new_target_pos, num_leaves, tree_rows(num_leaves)); + needed_proof_positions.sort_unstable(); // We'll use all elements from the old proof, as addition only creates new nodes // in our proof (except for root destruction). But before using it, we have to @@ -817,23 +822,23 @@ impl Proof { )) } - /// maybe_remap remaps the passed in hash and pos if the tree_rows increase after + /// `maybe_remap` remaps the passed in hash and pos if the `tree_rows` increase after /// adding the new nodes. fn maybe_remap( num_leaves: u64, num_adds: u64, positions: Vec<(u64, Hash)>, ) -> Vec<(u64, Hash)> { - let new_forest_rows = util::tree_rows(num_leaves + num_adds); - let old_forest_rows = util::tree_rows(num_leaves); - let tree_rows = util::tree_rows(num_leaves); + let new_forest_rows = tree_rows(num_leaves + num_adds); + let old_forest_rows = tree_rows(num_leaves); + let tree_rows = tree_rows(num_leaves); let mut new_proofs = vec![]; if new_forest_rows > old_forest_rows { - for (pos, hash) in positions.iter() { - let row = util::detect_row(*pos, tree_rows); + for (pos, hash) in &positions { + let row = detect_row(*pos, tree_rows); - let old_start_pos = util::start_position_at_row(row, old_forest_rows); - let new_start_pos = util::start_position_at_row(row, new_forest_rows); + let old_start_pos = start_position_at_row(row, old_forest_rows); + let new_start_pos = start_position_at_row(row, new_forest_rows); let offset = pos - old_start_pos; let new_pos = offset + new_start_pos; @@ -845,34 +850,33 @@ impl Proof { positions } - /// update_proof_remove modifies the cached proof with the deletions that happen in the block proof. + /// `update_proof_remove` modifies the cached proof with the deletions that happen in the block proof. /// It updates the necessary proof hashes and un-caches the targets that are being deleted. fn update_proof_remove( self, - block_targets: Vec, + block_targets: &[u64], cached_hashes: Vec, - updated: Vec<(u64, Hash)>, + updated: &[(u64, Hash)], num_leaves: u64, ) -> Result<(Self, Vec), ProofError> { - let total_rows = util::tree_rows(num_leaves); + let total_rows = tree_rows(num_leaves); let targets_with_hash: Vec<(u64, Hash)> = self .targets .iter() - .cloned() + .copied() .zip(cached_hashes) .filter(|(pos, _)| !block_targets.contains(pos)) .collect(); - let (targets, _): (Vec<_>, Vec<_>) = targets_with_hash.iter().cloned().unzip(); - let proof_positions = - util::get_proof_positions(&self.targets, num_leaves, util::tree_rows(num_leaves)); + let (targets, _): (Vec<_>, Vec<_>) = targets_with_hash.iter().copied().unzip(); + let proof_positions = get_proof_positions(&self.targets, num_leaves, tree_rows(num_leaves)); let old_proof: Vec<_> = proof_positions.iter().zip(self.hashes.iter()).collect(); let mut new_proof = vec![]; // Grab all the positions of the needed proof hashes. - let needed_pos = util::get_proof_positions(&targets, num_leaves, total_rows); + let needed_pos = get_proof_positions(&targets, num_leaves, total_rows); let old_proof_iter = old_proof.iter(); // Loop through old_proofs and only add the needed proof hashes. @@ -916,58 +920,57 @@ impl Proof { // element. If so we move it to its new position. After that the vector is probably unsorted, so we sort it. let mut proof_elements: Vec<_> = - Self::calc_next_positions(&block_targets, &new_proof, num_leaves, true)?; + Self::calc_next_positions(block_targets, &new_proof, num_leaves, true)?; proof_elements.sort(); // Grab the hashes for the proof let (_, hashes): (Vec, Vec) = proof_elements.into_iter().unzip(); // Gets all proof targets, but with their new positions after delete let (targets, target_hashes) = - Self::calc_next_positions(&block_targets, &targets_with_hash, num_leaves, true)? + Self::calc_next_positions(block_targets, &targets_with_hash, num_leaves, true)? .into_iter() .unzip(); - Ok((Self { hashes, targets }, target_hashes)) + Ok((Self { targets, hashes }, target_hashes)) } fn calc_next_positions( - block_targets: &Vec, + block_targets: &[u64], old_positions: &Vec<(u64, Hash)>, num_leaves: u64, append_roots: bool, ) -> Result, ProofError> { - let total_rows = util::tree_rows(num_leaves); + let total_rows = tree_rows(num_leaves); let mut new_positions = vec![]; - let block_targets = util::detwin(block_targets.to_owned(), total_rows); + let block_targets = detwin(block_targets.to_owned(), total_rows); for (position, hash) in old_positions { if hash.is_empty() { continue; } let mut next_pos = *position; - for target in block_targets.iter() { - if util::is_root_position(next_pos, num_leaves, total_rows) { + for target in &block_targets { + if is_root_position(next_pos, num_leaves, total_rows) { break; } // If these positions are in different subtrees, continue. - let (sub_tree, _, _) = util::detect_offset(*target, num_leaves); - let (sub_tree1, _, _) = util::detect_offset(next_pos, num_leaves); + let (sub_tree, _, _) = detect_offset(*target, num_leaves); + let (sub_tree1, _, _) = detect_offset(next_pos, num_leaves); if sub_tree != sub_tree1 { continue; } - let is_ancestor = - util::is_ancestor(util::parent(*target, total_rows), next_pos, total_rows) - .map_err(|_| ProofError::MissingSibling(next_pos))?; + let is_ancestor = is_ancestor(parent(*target, total_rows), next_pos, total_rows) + .map_err(|_| ProofError::MissingSibling(next_pos))?; if is_ancestor { - next_pos = util::calc_next_pos(next_pos, *target, total_rows) + next_pos = calc_next_pos(next_pos, *target, total_rows) .map_err(|_| ProofError::MissingSibling(next_pos))?; } } - if append_roots || !util::is_root_position(next_pos, num_leaves, total_rows) { + if append_roots || !is_root_position(next_pos, num_leaves, total_rows) { new_positions.push((next_pos, *hash)); } } @@ -1004,7 +1007,7 @@ mod tests { /// but for this test, block is just random data. For each block we update our Stump and /// our proof as well, after that, our proof **must** still be valid for the latest Stump. /// - /// Fix-me: Using derive for deserialize, when also using AccumulatorHash leads to an odd + /// Fix-me: Using derive for deserialize, when also using `AccumulatorHash` leads to an odd /// error that can't be easily fixed. Even bumping version doesn't appear to help. /// Deriving hashes directly reduces the amount of boilerplate code used, and makes everything /// more clearer, hence, it's preferable. @@ -1100,8 +1103,8 @@ mod tests { let (cached_proof, cached_hashes) = cached_proof .update( cached_hashes.clone(), - utxos, - case_values.update.proof.targets, + &utxos, + &case_values.update.proof.targets, case_values.remembers.clone(), updated.clone(), ) @@ -1319,9 +1322,9 @@ mod tests { .unwrap(); let (new_proof, _) = cached_proof .update_proof_remove( - vec![1, 2, 6], + &[1, 2, 6], vec![hash_from_u8(0), hash_from_u8(1), hash_from_u8(7)], - modified.new_del, + &modified.new_del, 10, ) .unwrap(); diff --git a/src/stump/mod.rs b/src/stump/mod.rs index f0695cc..c8abd80 100644 --- a/src/stump/mod.rs +++ b/src/stump/mod.rs @@ -41,18 +41,24 @@ use super::node_hash::BitcoinNodeHash; use super::proof::NodesAndRootsOldNew; use super::proof::Proof; use super::proof::ProofError; -use super::util; +use super::util::calc_next_pos; +use super::util::is_ancestor; +use super::util::left_sibling; +use super::util::parent; +use super::util::read_u64; +use super::util::roots_to_destroy; +use super::util::tree_rows; use crate::prelude::*; #[derive(Debug, Clone, Default)] pub struct UpdateData { - /// to_destroy is the positions of the empty roots removed after the add. + /// `to_destroy` is the positions of the empty roots removed after the add. pub(crate) to_destroy: Vec, - /// pre_num_leaves is the numLeaves of the stump before the add. + /// `pre_num_leaves` is the numLeaves of the stump before the add. pub(crate) prev_num_leaves: u64, - /// new_add are the new hashes for the newly created roots after the addition. + /// `new_add` are the new hashes for the newly created roots after the addition. pub(crate) new_add: Vec<(u64, Hash)>, - /// new_del are the new hashes after the deletion. + /// `new_del` are the new hashes after the deletion. pub(crate) new_del: Vec<(u64, Hash)>, } @@ -145,7 +151,7 @@ impl Stump { writer.write_all(&self.leaves.to_le_bytes())?; writer.write_all(&(self.roots.len() as u64).to_le_bytes())?; - for root in self.roots.iter() { + for root in &self.roots { len += 32; root.write(&mut writer)?; } @@ -179,7 +185,7 @@ impl Stump { impl Stump { /// Verifies the proof against the Stump. The proof is a list of hashes that are used to - /// recompute the root of the accumulator. The del_hashes are the hashes that are being + /// recompute the root of the accumulator. The `del_hashes` are the hashes that are being /// deleted from the accumulator. /// // TODO: Add example pub fn verify(&self, proof: &Proof, del_hashes: &[Hash]) -> Result { @@ -190,8 +196,8 @@ impl Stump { /// Creates a new Stump with a custom hash type /// - /// If you need to use a hash type that's not the [BitcoinNodeHash], you can use this - /// function to create a new Stump with the desired hash type. Use [BitcoinNodeHash::new] + /// If you need to use a hash type that's not the [`BitcoinNodeHash`], you can use this + /// function to create a new Stump with the desired hash type. Use [`BitcoinNodeHash::new`] /// to create a new Stump with the default hash type. pub fn new_with_hash() -> Self { Self { @@ -231,7 +237,7 @@ impl Stump { let (intermediate, mut computed_roots) = self.remove(del_hashes, proof)?; let mut new_roots = vec![]; - for root in self.roots.iter() { + for root in &self.roots { if let Some(pos) = computed_roots.iter().position(|(old, _new)| old == root) { let (_, new_root) = computed_roots.remove(pos); new_roots.push(new_root); @@ -290,8 +296,8 @@ impl Stump { /// ); /// ``` pub fn deserialize(mut data: Source) -> Result { - let leaves = util::read_u64(&mut data)?; - let roots_len = util::read_u64(&mut data)?; + let leaves = read_u64(&mut data)?; + let roots_len = read_u64(&mut data)?; let mut roots = vec![]; for _ in 0..roots_len { @@ -330,19 +336,19 @@ impl Stump { utxos: &[Hash], mut leaves: u64, ) -> (Vec, Vec<(u64, Hash)>, Vec) { - let after_rows = util::tree_rows(leaves + (utxos.len() as u64)); + let after_rows = tree_rows(leaves + (utxos.len() as u64)); let mut updated_subtree: BTreeSet<(u64, Hash)> = BTreeSet::new(); - let all_deleted = util::roots_to_destroy(utxos.len() as u64, leaves, &roots); + let all_deleted = roots_to_destroy(utxos.len() as u64, leaves, &roots); for (i, add) in utxos.iter().enumerate() { let mut pos = leaves; // deleted is the empty roots that are being added over. These force // the current root to move up. - let deleted = util::roots_to_destroy((utxos.len() - i) as u64, leaves, &roots); + let deleted = roots_to_destroy((utxos.len() - i) as u64, leaves, &roots); for del in deleted { - if util::is_ancestor(util::parent(del, after_rows), pos, after_rows).unwrap() { - pos = util::calc_next_pos(pos, del, after_rows).unwrap(); + if is_ancestor(parent(del, after_rows), pos, after_rows).unwrap() { + pos = calc_next_pos(pos, del, after_rows).unwrap(); } } let mut h = 0; @@ -359,9 +365,9 @@ impl Stump { if let Some(root) = root { if !root.is_empty() { - updated_subtree.insert((util::left_sibling(pos), root)); + updated_subtree.insert((left_sibling(pos), root)); updated_subtree.insert((pos, to_add)); - pos = util::parent(pos, after_rows); + pos = parent(pos, after_rows); to_add = AccumulatorHash::parent_hash(&root, &to_add); } @@ -555,7 +561,7 @@ mod test { assert_eq!(updated.prev_num_leaves, data.leaves); assert_eq!(updated.to_destroy, data.to_destroy); assert_eq!(updated.new_add, new_add); - for del in new_del.iter() { + for del in &new_del { assert!(updated.new_del.contains(del)); } } diff --git a/src/util/mod.rs b/src/util/mod.rs index d5070c5..c792253 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -2,13 +2,12 @@ use alloc::collections::BTreeSet; -// Rustreexo use super::node_hash::AccumulatorHash; use crate::prelude::*; // isRootPosition checks if the current position is a root given the number of // leaves and the entire rows of the forest. -pub fn is_root_position(position: u64, num_leaves: u64, forest_rows: u8) -> bool { +pub(crate) fn is_root_position(position: u64, num_leaves: u64, forest_rows: u8) -> bool { let row = detect_row(position, forest_rows); let root_present = num_leaves & (1 << row) != 0; @@ -19,7 +18,7 @@ pub fn is_root_position(position: u64, num_leaves: u64, forest_rows: u8) -> bool // removeBit removes the nth bit from the val passed in. For example, if the 2nd // bit is to be removed from 1011 (11 in dec), the returned value is 111 (7 in dec). -pub fn remove_bit(val: u64, bit: u64) -> u64 { +pub(crate) fn remove_bit(val: u64, bit: u64) -> u64 { let mask = ((2 << bit) - 1) as u64; let upper_mask = u64::MAX ^ mask; let upper = val & upper_mask; @@ -64,7 +63,7 @@ pub fn remove_bit(val: u64, bit: u64) -> u64 { /// /// This function simply computes how far away from the start of the row this leaf is, then uses /// that to offset the same amount in the new structure. -pub fn translate(pos: u64, from_rows: u8, to_rows: u8) -> u64 { +pub(crate) fn translate(pos: u64, from_rows: u8, to_rows: u8) -> u64 { let row = detect_row(pos, from_rows); if row == 0 { return pos; @@ -74,7 +73,7 @@ pub fn translate(pos: u64, from_rows: u8, to_rows: u8) -> u64 { offset + start_position_at_row(row, to_rows) } -pub fn calc_next_pos(position: u64, del_pos: u64, forest_rows: u8) -> Result { +pub(crate) fn calc_next_pos(position: u64, del_pos: u64, forest_rows: u8) -> Result { let del_row = detect_row(del_pos, forest_rows); let pos_row = detect_row(position, forest_rows); @@ -85,17 +84,16 @@ pub fn calc_next_pos(position: u64, del_pos: u64, forest_rows: u8) -> Result, forest_rows: u8) -> Vec { +pub(crate) fn detwin(nodes: Vec, forest_rows: u8) -> Vec { let mut computed: Vec = nodes; let mut detwinned = Vec::new(); @@ -106,12 +104,11 @@ pub fn detwin(nodes: Vec, forest_rows: u8) -> Vec { let node = computed.remove(0); let sibling = node ^ 1; - let next = match computed.first() { - Some(next) => *next, - None => { - detwinned.push(node); - continue; - } + let next = if let Some(next) = computed.first() { + *next + } else { + detwinned.push(node); + continue; }; if next == sibling { @@ -133,29 +130,29 @@ pub fn detwin(nodes: Vec, forest_rows: u8) -> Vec { // start_position_at_row returns the smallest position an accumulator can have for the // requested row for the given numLeaves. -pub fn start_position_at_row(row: u8, forest_rows: u8) -> u64 { +pub(crate) fn start_position_at_row(row: u8, forest_rows: u8) -> u64 { // 2 << forest_rows is 2 more than the max position // to get the correct offset for a given row, // subtract (2 << `row complement of forest_rows`) from (2 << forest_rows) ((2_u128 << forest_rows) - (2_u128 << (forest_rows - row))) as u64 } -pub fn is_left_niece(position: u64) -> bool { +pub(crate) fn is_left_niece(position: u64) -> bool { position & 1 == 0 } -pub fn left_sibling(position: u64) -> u64 { +pub(crate) fn left_sibling(position: u64) -> u64 { (position | 1) ^ 1 } // roots_to_destroy returns the empty roots that get written over after num_adds // amount of leaves have been added. -pub fn roots_to_destroy( +pub(crate) fn roots_to_destroy( num_adds: u64, mut num_leaves: u64, orig_roots: &[Hash], ) -> Vec { - if !orig_roots.iter().any(|root| root.is_empty()) { + if !orig_roots.iter().any(AccumulatorHash::is_empty) { return vec![]; } @@ -182,12 +179,12 @@ pub fn roots_to_destroy( deleted } -pub fn num_roots(leaves: u64) -> usize { +pub(crate) fn num_roots(leaves: u64) -> usize { leaves.count_ones() as usize } // detectRow finds the current row of a node, given the position // and the total forest rows. -pub fn detect_row(pos: u64, forest_rows: u8) -> u8 { +pub(crate) fn detect_row(pos: u64, forest_rows: u8) -> u8 { let mut marker: u64 = 1 << forest_rows; let mut h: u8 = 0; @@ -199,7 +196,7 @@ pub fn detect_row(pos: u64, forest_rows: u8) -> u8 { h } -pub fn detect_offset(pos: u64, num_leaves: u64) -> (u8, u8, u64) { +pub(crate) fn detect_offset(pos: u64, num_leaves: u64) -> (u8, u8, u64) { let mut tr = tree_rows(num_leaves); let nr = detect_row(pos, tr); @@ -244,38 +241,38 @@ pub fn detect_offset(pos: u64, num_leaves: u64) -> (u8, u8, u64) { (bigger_trees, tr - nr, !marker) } -pub fn children(pos: u64, forest_rows: u8) -> u64 { +pub(crate) fn children(pos: u64, forest_rows: u8) -> u64 { let mask = (2 << forest_rows) - 1; (pos << 1) & mask } -pub fn left_child(pos: u64, forest_rows: u8) -> u64 { +pub(crate) fn left_child(pos: u64, forest_rows: u8) -> u64 { children(pos, forest_rows) } -pub fn right_child(pos: u64, forest_rows: u8) -> u64 { +pub(crate) fn right_child(pos: u64, forest_rows: u8) -> u64 { children(pos, forest_rows) + 1 } -pub fn is_root_populated(row: u8, num_leaves: u64) -> bool { +pub(crate) fn is_root_populated(row: u8, num_leaves: u64) -> bool { (num_leaves >> row) & 1 == 1 } -/// max_position_at_row returns the biggest position an accumulator can have for the -/// requested row for the given num_leaves. -pub fn max_position_at_row(row: u8, total_rows: u8, num_leaves: u64) -> Result { +/// `max_position_at_row` returns the biggest position an accumulator can have for the +/// requested row for the given `num_leaves`. +pub(crate) fn max_position_at_row(row: u8, total_rows: u8, num_leaves: u64) -> Result { Ok(parent_many(num_leaves, row, total_rows)?.saturating_sub(1)) } // parent returns the parent position of the passed in child -pub fn parent(pos: u64, forest_rows: u8) -> u64 { +pub(crate) fn parent(pos: u64, forest_rows: u8) -> u64 { (pos >> 1) | (1 << forest_rows) } -pub fn read_u64(buf: &mut Source) -> Result { +pub(crate) fn read_u64(buf: &mut Source) -> Result { let mut bytes = [0u8; 8]; buf.read_exact(&mut bytes)?; Ok(u64::from_le_bytes(bytes)) } // tree_rows returns the number of rows given n leaves -pub fn tree_rows(n: u64) -> u8 { +pub(crate) fn tree_rows(n: u64) -> u8 { if n == 0 { return 0; } @@ -286,14 +283,15 @@ pub fn tree_rows(n: u64) -> u8 { // root_position returns the position of the root at a given row // TODO undefined behavior if the given row doesn't have a root -pub fn root_position(num_leaves: u64, row: u8, forest_rows: u8) -> u64 { +pub(crate) fn root_position(num_leaves: u64, row: u8, forest_rows: u8) -> u64 { let mask = (2 << forest_rows) - 1; let before = num_leaves & (mask << (row + 1)); let shifted = (before >> row) | (mask << (forest_rows + 1 - row)); shifted & mask } -pub fn parent_many(pos: u64, rise: u8, forest_rows: u8) -> Result { + +pub(crate) fn parent_many(pos: u64, rise: u8, forest_rows: u8) -> Result { if rise == 0 { return Ok(pos); } @@ -304,10 +302,14 @@ pub fn parent_many(pos: u64, rise: u8, forest_rows: u8) -> Result { } let mask = (2_u64 << forest_rows) - 1; - Ok((pos >> rise | (mask << (forest_rows - (rise - 1)) as u64)) & mask) + Ok((pos >> rise | (mask << u64::from(forest_rows - (rise - 1)))) & mask) } -pub fn is_ancestor(higher_pos: u64, lower_pos: u64, forest_rows: u8) -> Result { +pub(crate) fn is_ancestor( + higher_pos: u64, + lower_pos: u64, + forest_rows: u8, +) -> Result { if higher_pos == lower_pos { return Ok(false); } @@ -328,7 +330,7 @@ pub fn is_ancestor(higher_pos: u64, lower_pos: u64, forest_rows: u8) -> Result bool { +fn is_right_sibling(node: u64, next: u64) -> bool { node | 1 == next } @@ -340,7 +342,7 @@ fn is_sibling(a: u64, b: u64) -> bool { /// Returns which node should have its hashes on the proof, along with all nodes /// whose hashes will be calculated to reach a root -pub fn get_proof_positions(targets: &[u64], num_leaves: u64, forest_rows: u8) -> Vec { +pub(crate) fn get_proof_positions(targets: &[u64], num_leaves: u64, forest_rows: u8) -> Vec { let mut proof_positions = BTreeSet::new(); let mut map = new_hash_set(); @@ -384,11 +386,11 @@ pub fn get_proof_positions(targets: &[u64], num_leaves: u64, forest_rows: u8) -> } #[cfg(test)] -pub fn hash_from_u8(value: u8) -> super::node_hash::BitcoinNodeHash { +pub(crate) fn hash_from_u8(value: u8) -> super::node_hash::BitcoinNodeHash { use bitcoin_hashes::sha256; use bitcoin_hashes::HashEngine; - let mut engine = bitcoin_hashes::sha256::Hash::engine(); + let mut engine = sha256::Hash::engine(); engine.input(&[value]); @@ -401,11 +403,11 @@ mod tests { use alloc::vec::Vec; use core::str::FromStr; + use super::children; use super::roots_to_destroy; + use super::start_position_at_row; + use super::tree_rows; use crate::node_hash::BitcoinNodeHash; - use crate::util::children; - use crate::util::start_position_at_row; - use crate::util::tree_rows; #[test] fn test_proof_pos() { @@ -445,7 +447,7 @@ mod tests { let deleted = roots_to_destroy(1, 15, &roots); - assert_eq!(deleted, vec![22, 28]) + assert_eq!(deleted, vec![22, 28]); } #[test] @@ -483,10 +485,10 @@ mod tests { #[test] fn test_tree_rows() { - assert_eq!(super::tree_rows(8), 3); - assert_eq!(super::tree_rows(9), 4); - assert_eq!(super::tree_rows(12), 4); - assert_eq!(super::tree_rows(255), 8); + assert_eq!(tree_rows(8), 3); + assert_eq!(tree_rows(9), 4); + assert_eq!(tree_rows(12), 4); + assert_eq!(tree_rows(255), 8); } fn row_offset(row: u8, forest_rows: u8) -> u64 { @@ -519,8 +521,7 @@ mod tests { fn test_get_proof_positions() { let targets: Vec = vec![4, 5, 7, 8]; let num_leaves = 8; - let targets = - super::get_proof_positions(&targets, num_leaves, super::tree_rows(num_leaves)); + let targets = super::get_proof_positions(&targets, num_leaves, tree_rows(num_leaves)); assert_eq!(vec![6, 9], targets); } @@ -553,8 +554,8 @@ mod tests { assert_eq!(start_position_at_row(1, 12), 4096); // Check if we don't overflow with bigger forests - assert_eq!(start_position_at_row(63, 63), 18446744073709551614); - assert_eq!(start_position_at_row(44, 63), 18446744073708503040); + assert_eq!(start_position_at_row(63, 63), 18_446_744_073_709_551_614); + assert_eq!(start_position_at_row(44, 63), 18_446_744_073_708_503_040); assert_eq!(start_position_at_row(0, 63), 0); assert_eq!(start_position_at_row(0, 32), 0); From 425181b4bb0b20667b9e486e2fac1503709f7ae9 Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Fri, 17 Jul 2026 15:30:16 -0300 Subject: [PATCH 5/5] chore(ci): move `cargo rbmt tools` to vendored action --- .github/actions/setup-rbmt/action.yml | 4 ++++ .github/workflows/audit.yml | 4 ---- justfile | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/actions/setup-rbmt/action.yml b/.github/actions/setup-rbmt/action.yml index f2401b1..863f38c 100644 --- a/.github/actions/setup-rbmt/action.yml +++ b/.github/actions/setup-rbmt/action.yml @@ -14,3 +14,7 @@ runs: - name: Install Rust Toolchains shell: bash run: cargo rbmt toolchains + + - name: Intall cargo-rbmt Tools + shell: bash + run: cargo rbmt tools diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index f22e6a3..9a21a94 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -79,10 +79,6 @@ jobs: - name: Setup cargo-rbmt uses: ./.github/actions/setup-rbmt - - name: Install cargo-rbmt Tools - shell: bash - run: cargo rbmt tools - - name: Run Zizmor shell: bash run: zizmor . diff --git a/justfile b/justfile index d764d0b..c7bba06 100644 --- a/justfile +++ b/justfile @@ -47,8 +47,8 @@ test: cargo rbmt test --toolchain stable --lockfile recent cargo rbmt test --toolchain stable --lockfile minimal -[doc: "Install cargo-rbmt tools"] -tools: +[doc: "Setup cargo-rbmt tools"] +setup: RBMT_LOG_LEVEL=progress cargo rbmt tools [doc: "Run ShellCheck"]