Repository: rust-bitcoin/rust-bitcoin
Pull request #2740 (2024-05-03 23:32:59)

Add difficulty adjustment calculation
Hi, I hit a roadblock with the current `pow` API. As far as I can tell, the only workaround to calculate the next work required similar to `bitcoin/src/pow.cpp` is to use a general big integer library, convert the `Target` to bytes, do the math, and convert back to `Target` from bytes. I have also been working with [Floresta](https://github.com/Davidson-Souza/Floresta/tree/780ea8d0b024eb69ccb723ccce9b4e703b143bfd) and their [solution](https://github.com/Davidson-Souza/Floresta/blob/780ea8d0b024eb69ccb723ccce9b4e703b143bfd/crates/floresta-chain/src/pruned_utreexo/consensus.rs#L187) was to fork off and exposed the `U256` struct publicly on their branch. I think these home brewed difficulty adjustment solutions will continually pop up, so I created a `from_next_work_required` method to return a `Target`. My work veers significantly from #2180, as I only provided a single method to do so, without further guidance on when exactly this retarget occurs.

I am happy to add tests once I get further direction from maintainers if this as a likelihood of being accepted or not. Thanks.

coveralls: ## Pull Request Test Coverage Report for [Build 8946113001](https://coveralls.io/builds/67299683)


### Details

* **0** of **23**   **(0.0%)**  changed or added relevant lines in **1** file are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-0.08%**) to **83.08%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [bitcoin/src/pow.rs](https://coveralls.io/builds/67299683/source?filename=bitcoin%2Fsrc%2Fpow.rs#L235) | 0 | 23 | 0.0%
<!-- | **Total:** | **0** | **23** | **0.0%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/67299683/badge)](https://coveralls.io/builds/67299683) |
| :-- | --: |
| Change from base [Build 8939996688](https://coveralls.io/builds/67288490): |  -0.08% |
| Covered Lines: | 19194 |
| Relevant Lines: | 23103 |

---
##### 💛  - [Coveralls](https://coveralls.io)

apoelstra: cc https://github.com/rust-bitcoin/rust-bitcoin/pull/1820 which added some different-but-related missing functionality
apoelstra: In 206bc621d34f0ef25b5d91a512d2e67e635f77e4:

There is trailing whitespace on several lines that I'd like you to remove. You can find them with `git grep ' $'`.

And could you add a couple unit tests, maybe by taking headers from the actual blockchain, that demonstrate that the adjustment does the right thing under normal circumstances, when it hits the "minimum 1 difficulty" rule, and when it hits one of the factor-of-4 rails? (The latter has never happened on Bitcoin but it should be easy to gin up a test vector by editing block timestamps, since we're not validating the PoW here.)
apoelstra: But concept ACK -- this functionality is missing AFAICT. Thanks for your contribution!
rustaceanrob: I approach NACKed myself over the weekend. After thinking about it I decided that the Bitcoin Core approach is less susceptible to user misinterpretation. Also when considering the difficulty adjustment it is more appropriate to be dealing with `CompactTarget` since it is used in the headers and has lower precision. I updated the documentation with reference to the Bitcoin Core implementation and an example. Also added 5 test cases for each scenario. 
tcharding: I couldn't manage to get a link to a line number for some reason so I did this instead:
https://github.com/rust-bitcoin/rust-bitcoin/pull/2180/files#r1594975932

This is what I meant by my question about the `timespan` parameter pushing the knowledge of the off-by-one error onto users. 
apoelstra: @tcharding I don't understand exactly what you're suggesting. Whether the user passes a header or `header.timestamp` does not affect whether they will be burned by the off-by-one error.

It seems like our choices are either (a) take the whole blockchain, or at least the most recent few thousand blocks, and compute the block intervals ourselves, protecting the user, or (b) take just the two relevant blocks, trusting the user to choose the right ones.

This PR currently has both (though maybe they could be named more clearly to distinguish them), and I think we need both, since we don't want to require the user to have tons of blockheaders available when they're not even relevant.
tcharding: > @tcharding I don't understand exactly what you're suggesting. Whether the user passes a header or header.timestamp does not affect whether they will be burned by the off-by-one error.

Mine is just a question on API design.

In #2180 the user passes current header to `next_target`, that function then either returns the `header.bits` if the next block is not a retarget block or does the calculation, the caller does not need to be aware of the the off-by-one stuff. The function in this PR, which is basically identical to the `calculate_next_target` function from #2180 requires the caller to understand the off-by-one.

FTR #2180 suffers the same issue I'm describing because both `CompactTarget::from_next_work_required` (this PR) and `calculate_next_target` (#2180). @rustaceanrob has done better than me already by documenting it, I just cargo-cult-programmed the `-1`.
apoelstra: Ok, yes, you can encapsulate this by requiring the user provide an accessor for every single block in the chain. (Though you can cheat by knowing exactly which blocks it is going to access, of course.)

This PR does this, but *also* provides a method in which you only need to have the two relevant headers. So I think it's strictly better.
apoelstra: ACK ed5574f5b511ca803a9fb041d820631e7ae57158 except that we should restore the "just pass the whole chain" functionality that was present in the original PR.
rustaceanrob: > restore the "just pass the whole chain" functionality 

Okay, back to two `Headers`? I was struggling to find good names for them where the ordering is unambiguous. How is `current`, `previous`?
apoelstra: I think you want something like `current` and `last_epoch_boundary`.
apoelstra: > Okay, back to two `Header`?

Yeah, I think we should have both.
apoelstra: In 0aa279aa33791dcc462f90e1574bd8d443871fbb:

* If you are going to cite Core line numbers, please specify which commit of Core you are using
* Sorry, I meant that we should have both a "compute from two headers" method (and I don't care if it takes timestamps or whole headers or what) and also a "compute from all the headers" method (which chooses the right headers to use).

rustaceanrob: With the compute from all the headers approach, I've only come up with two solutions, both concerned with chain height. The only height-implicit approach I know of is to pass a `&[Header]` representing the current header chain. For low resource devices that might be a challenge, but then again the other methods of this PR would have them covered if they take 5 minutes to research how the adjustment works. Otherwise, the explicit approach is to pass a current chain height and some function to index into the chain at a specific height as in #2180. Implied in that approach would be pinning down the `Height` structure. I am compelled to just go with `&[Header]`, is that alright?
tcharding: I'm leaning towards thinking this is mergable as is and we do the additional functionality in a separate PR. This PR is already a big improvement.
apoelstra: > The only height-implicit approach I know of is to pass a `&[Header]` representing the current header chain.

You can take a closure which takes a usize and returns the header at that index. We have an existing PoW-related function that does this, I forget which one.

@tcharding 
> I'm leaning towards thinking this is mergable as is and we do the additional functionality in a separate PR. This PR is already a big improvement.

Sure, I'm happy to do this -- but we're a little ways from release and we're close to having this PR having the API we want. It's up to you @rustaceanrob. If you want me to just merge this I'll do it.
rustaceanrob: > If you are going to cite Core line numbers, please specify which commit of Core you are using

Updated to included the specific commit

> Sure, I'm happy to do this -- but we're a little ways from release and we're close to having this PR having the API we want. It's up to you @rustaceanrob. If you want me to just merge this I'll do it

I think this is an appropriate stopping point for now. Users are able to perform the adjustment and any additions would be additive. I am unsure how much time I will have to work on this in the next couple days. I am envisioning something similar to #2180 where there is a nice `FnMut` that takes a `Height`. In a follow up PR we can discuss this further.



Patch:
diff --git a/.github/workflows/README.md b/.github/workflows/README.md
deleted file mode 100644
index b8896a95..00000000
--- a/.github/workflows/README.md
+++ /dev/null
@@ -1,34 +0,0 @@
-# rust-bitcoin workflow notes
-
-We are attempting to run max 20 parallel jobs using GitHub actions (usage limit for free tier).
-
-ref: https://docs.github.com/en/actions/learn-github-actions/usage-limits-billing-and-administration
-
-The minimal/recent lock files are handled by CI (`rust.yml`).
-
-## Jobs
-
-Run from `rust.yml` unless stated otherwise. Total 21 jobs but
-`Prepare` is quick and must be run first anyway.
-
-0.  `Prepare`
-1.  `Stable - minimal`
-2.  `Stable - recent`
-3.  `Nightly - minimal`
-4.  `Nightly - recent`
-5.  `MSRV - minimal`
-6.  `MSRV - recent`
-7.  `Lint`
-8.  `Docs`
-9.  `Docsrs`
-10. `Bench`
-11. `ASAN`
-12. `WASM`
-13. `schemars`
-14. `Arch32bit`
-15. `Cross`
-16. `Embedded`
-17. `Kani`
-18. `Coveralls` - run by `coveralls.yml`
-19. `release` - run by `release.yml`
-20. `labeler` - run by `manage-pr.yml`
diff --git a/.github/workflows/cron-semi-weekly-update-nightly.yml b/.github/workflows/cron-daily-update-nightly.yml
similarity index 96%
rename from .github/workflows/cron-semi-weekly-update-nightly.yml
rename to .github/workflows/cron-daily-update-nightly.yml
index 51f7afa2..467abc07 100644
--- a/.github/workflows/cron-semi-weekly-update-nightly.yml
+++ b/.github/workflows/cron-daily-update-nightly.yml
@@ -1,7 +1,7 @@
 name: Update Nightly rustc
 on:
   schedule:
-    - cron: "0 0 * * 1,4" # runs every Monday and Thursday at 00:00
+    - cron: "0 0 * * *" # runs daily at 00:00
   workflow_dispatch: # allows manual triggering
 jobs:
   format:
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 7f778e8f..da20ed1d 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -1,5 +1,4 @@
----   # rust-bitcoin CI: If you edit this file please update README.md
-on:   # yamllint disable-line rule:truthy
+on:
   push:
     branches:
       - master
@@ -30,18 +29,12 @@ jobs:
     steps:
       - name: "Checkout repo"
         uses: actions/checkout@v4
-      - name: "Checkout maintainer tools"
-        uses: actions/checkout@v4
-        with:
-          repository: rust-bitcoin/rust-bitcoin-maintainer-tools
-          rev: b2ac115
-          path: maintainer-tools
       - name: "Select toolchain"
         uses: dtolnay/rust-toolchain@stable
       - name: "Set dependencies"
         run: cp Cargo-${{ matrix.dep }}.lock Cargo.lock
       - name: "Run test script"
-        run: ./maintainer-tools/ci/run_task.sh stable
+        run: ./contrib/run_task.sh stable
 
   Nightly:                      # 2 jobs, one per manifest.
     name: Test - nightly toolchain
@@ -54,12 +47,6 @@ jobs:
     steps:
       - name: "Checkout repo"
         uses: actions/checkout@v4
-      - name: "Checkout maintainer tools"
-        uses: actions/checkout@v4
-        with:
-          repository: rust-bitcoin/rust-bitcoin-maintainer-tools
-          rev: b2ac115
-          path: maintainer-tools
       - name: "Select toolchain"
         uses: dtolnay/rust-toolchain@v1
         with:
@@ -67,7 +54,7 @@ jobs:
       - name: "Set dependencies"
         run: cp Cargo-${{ matrix.dep }}.lock Cargo.lock
       - name: "Run test script"
-        run: ./maintainer-tools/ci/run_task.sh nightly
+        run: ./contrib/run_task.sh nightly
 
   MSRV:                         # 2 jobs, one per manifest.
     name: Test - 1.56.1 toolchain
@@ -79,20 +66,14 @@ jobs:
     steps:
       - name: "Checkout repo"
         uses: actions/checkout@v4
-      - name: "Checkout maintainer tools"
-        uses: actions/checkout@v4
-        with:
-          repository: rust-bitcoin/rust-bitcoin-maintainer-tools
-          rev: b2ac115
-          path: maintainer-tools
       - name: "Select toolchain"
         uses: dtolnay/rust-toolchain@stable
         with:
           toolchain: "1.56.1"
-      - name: "Set dependencies"
+      - name: "Copy lock file"
         run: cp Cargo-${{ matrix.dep }}.lock Cargo.lock
       - name: "Run test script"
-        run: ./maintainer-tools/ci/run_task.sh msrv
+        run: ./contrib/run_task.sh msrv
 
   Lint:
     name: Lint - nightly toolchain
@@ -105,12 +86,6 @@ jobs:
     steps:
       - name: "Checkout repo"
         uses: actions/checkout@v4
-      - name: "Checkout maintainer tools"
-        uses: actions/checkout@v4
-        with:
-          repository: rust-bitcoin/rust-bitcoin-maintainer-tools
-          rev: b2ac115
-          path: maintainer-tools
       - name: "Select toolchain"
         uses: dtolnay/rust-toolchain@v1
         with:
@@ -120,7 +95,7 @@ jobs:
       - name: "Set dependencies"
         run: cp Cargo-${{ matrix.dep }}.lock Cargo.lock
       - name: "Run test script"
-        run: ./maintainer-tools/ci/run_task.sh lint
+        run: ./contrib/run_task.sh lint
 
   Docs:
     name: Docs - stable toolchain
@@ -132,18 +107,12 @@ jobs:
     steps:
       - name: "Checkout repo"
         uses: actions/checkout@v4
-      - name: "Checkout maintainer tools"
-        uses: actions/checkout@v4
-        with:
-          repository: rust-bitcoin/rust-bitcoin-maintainer-tools
-          rev: b2ac115
-          path: maintainer-tools
       - name: "Select toolchain"
         uses: dtolnay/rust-toolchain@stable
-      - name: "Set dependencies"
+      - name: "Copy lock file"
         run: cp Cargo-${{ matrix.dep }}.lock Cargo.lock
       - name: "Run test script"
-        run: ./maintainer-tools/ci/run_task.sh docs
+        run: ./contrib/run_task.sh docs
 
   Docsrs:
     name: Docs - nightly toolchain
@@ -156,12 +125,6 @@ jobs:
     steps:
       - name: "Checkout repo"
         uses: actions/checkout@v4
-      - name: "Checkout maintainer tools"
-        uses: actions/checkout@v4
-        with:
-          repository: rust-bitcoin/rust-bitcoin-maintainer-tools
-          rev: b2ac115
-          path: maintainer-tools
       - name: "Select toolchain"
         uses: dtolnay/rust-toolchain@v1
         with:
@@ -169,7 +132,7 @@ jobs:
       - name: "Set dependencies"
         run: cp Cargo-${{ matrix.dep }}.lock Cargo.lock
       - name: "Run test script"
-        run: ./maintainer-tools/ci/run_task.sh docsrs
+        run: ./contrib/run_task.sh docsrs
 
   Bench:
     name: Bench - nightly toolchain
@@ -182,20 +145,14 @@ jobs:
     steps:
       - name: "Checkout repo"
         uses: actions/checkout@v4
-      - name: "Checkout maintainer tools"
-        uses: actions/checkout@v4
-        with:
-          repository: rust-bitcoin/rust-bitcoin-maintainer-tools
-          rev: b2ac115
-          path: maintainer-tools
       - name: "Select toolchain"
         uses: dtolnay/rust-toolchain@v1
         with:
           toolchain: ${{ needs.Prepare.outputs.nightly_version }}
-      - name: "Set dependencies"
+      - name: "Copy lock file"
         run: cp Cargo-${{ matrix.dep }}.lock Cargo.lock
       - name: "Run test script"
-        run: ./maintainer-tools/ci/run_task.sh bench
+        run: ./contrib/run_task.sh bench
 
   Arch32bit:
     name: Test 32-bit version
@@ -256,7 +213,7 @@ jobs:
       - name: "Run hashes/embedded with alloc"
         run: cd hashes/embedded && cargo run --target thumbv7m-none-eabi --features=alloc
 
-  ASAN:                         # hashes crate only.
+  ASAN: # hashes crate only.
     name: ASAN - nightly toolchain
     needs: Prepare
     runs-on: ubuntu-latest
@@ -273,12 +230,12 @@ jobs:
           toolchain: ${{ needs.Prepare.outputs.nightly_version }}
       - name: Install src
         run: rustup component add rust-src
-      - name: "Set dependencies"
+      - name: "Copy lock file"
         run: cp Cargo-${{ matrix.dep }}.lock Cargo.lock
-      - name: "Run sanitizer script"
-        run: cd ./hashes && ./contrib/sanitizer.sh
+      - name: "Run test script"
+        run: ./contrib/run_task.sh asan
 
-  WASM:                         # hashes crate only.
+  WASM: # hashes crate only.
     name: WASM - stable toolchain
     runs-on: ubuntu-latest
     strategy:
@@ -289,22 +246,25 @@ jobs:
         uses: actions/checkout@v4
       - name: "Select toolchain"
         uses: dtolnay/rust-toolchain@stable
-      - name: "Run wasm script"
-        run: cd hashes && ./contrib/wasm.sh
+      - name: "Run test script"
+        run: ./contrib/run_task.sh wasm
 
-  Schemars:                     # hashes crate only.
+  Schemars: # hashes crate only.
     name: Schemars - stable toolchain
     runs-on: ubuntu-latest
     strategy:
       fail-fast: false
-      # Note we do not use the recent lock file for schemars testing.
+      matrix:
+        dep: [recent]
     steps:
       - name: "Checkout repo"
         uses: actions/checkout@v4
       - name: "Select toolchain"
         uses: dtolnay/rust-toolchain@stable
-      - name: "Run schemars test"
-        run: cd hashes/extended_tests/schemars && cargo test
+      - name: "Copy lock file"
+        run: cp Cargo-${{ matrix.dep }}.lock Cargo.lock
+      - name: "Run test script"
+        run: ./contrib/run_task.sh schemars
 
   Kani:
     name: Kani codegen - stable toolchain
diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml
deleted file mode 100644
index 8577c050..00000000
--- a/.github/workflows/shellcheck.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-name: Shellcheck
-on:
-  pull_request:
-    branches:
-      - master
-jobs:
-  shellcheck:
-    name: Shellcheck
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v4
-    - name: Run ShellCheck
-      uses: ludeeus/action-shellcheck@2.0.0
-      env:
-        SHELLCHECK_OPTS: -x # allow outside sources
diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock
index cbc17e22..f30294e9 100644
--- a/Cargo-minimal.lock
+++ b/Cargo-minimal.lock
@@ -47,7 +47,7 @@ dependencies = [
 
 [[package]]
 name = "bitcoin"
-version = "0.33.0-unreleased"
+version = "0.32.0-rc1"
 dependencies = [
  "base58ck",
  "base64",
diff --git a/Cargo-recent.lock b/Cargo-recent.lock
index 2bd141b0..57490f67 100644
--- a/Cargo-recent.lock
+++ b/Cargo-recent.lock
@@ -46,7 +46,7 @@ dependencies = [
 
 [[package]]
 name = "bitcoin"
-version = "0.33.0-unreleased"
+version = "0.32.0-rc1"
 dependencies = [
  "base58ck",
  "base64",
diff --git a/base58/src/lib.rs b/base58/src/lib.rs
index 05544c43..9533365e 100644
--- a/base58/src/lib.rs
+++ b/base58/src/lib.rs
@@ -19,9 +19,6 @@
 #[macro_use]
 extern crate alloc;
 
-#[cfg(bench)]
-extern crate test;
-
 #[cfg(feature = "std")]
 extern crate std;
 
@@ -31,7 +28,7 @@
 
 #[cfg(not(feature = "std"))]
 pub use alloc::{string::String, vec::Vec};
-use core::{fmt, str};
+use core::{fmt, iter, slice, str};
 #[cfg(feature = "std")]
 pub use std::{string::String, vec::Vec};
 
@@ -151,7 +148,7 @@ fn format_iter<I, W>(writer: &mut W, data: I) -> Result<(), fmt::Error>
     I: Iterator<Item = u8> + Clone,
     W: fmt::Write,
 {
-    let mut ret = Vec::with_capacity(128);
+    let mut ret = SmallVec::new();
 
     let mut leading_zero_count = 0;
     let mut leading_zeroes = true;
@@ -176,7 +173,9 @@ fn format_iter<I, W>(writer: &mut W, data: I) -> Result<(), fmt::Error>
     }
 
     // ... then reverse it and convert to chars
-    ret.resize(ret.len() + leading_zero_count, 0);
+    for _ in 0..leading_zero_count {
+        ret.push(0);
+    }
 
     for ch in ret.iter().rev() {
         writer.write_char(BASE58_CHARS[*ch as usize] as char)?;
@@ -185,6 +184,37 @@ fn format_iter<I, W>(writer: &mut W, data: I) -> Result<(), fmt::Error>
     Ok(())
 }
 
+/// Vector-like object that holds the first 100 elements on the stack. If more space is needed it
+/// will be allocated on the heap.
+struct SmallVec<T> {
+    len: usize,
+    stack: [T; 100],
+    heap: Vec<T>,
+}
+
+impl<T: Default + Copy> SmallVec<T> {
+    fn new() -> SmallVec<T> { SmallVec { len: 0, stack: [T::default(); 100], heap: Vec::new() } }
+
+    fn push(&mut self, val: T) {
+        if self.len < 100 {
+            self.stack[self.len] = val;
+            self.len += 1;
+        } else {
+            self.heap.push(val);
+        }
+    }
+
+    fn iter(&self) -> iter::Chain<slice::Iter<T>, slice::Iter<T>> {
+        // If len<100 then we just append an empty vec
+        self.stack[0..self.len].iter().chain(self.heap.iter())
+    }
+
+    fn iter_mut(&mut self) -> iter::Chain<slice::IterMut<T>, slice::IterMut<T>> {
+        // If len<100 then we just append an empty vec
+        self.stack[0..self.len].iter_mut().chain(self.heap.iter_mut())
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use hex::test_hex_unwrap as hex;
@@ -254,28 +284,3 @@ fn test_base58_roundtrip() {
         assert_eq!(decode_check(&encode(&[1, 2, 3])), Err(TooShortError { length: 3 }.into()));
     }
 }
-
-#[cfg(bench)]
-mod benches {
-    use test::{black_box, Bencher};
-
-    #[bench]
-    pub fn bench_encode_check_50(bh: &mut Bencher) {
-        let data: alloc::vec::Vec<_> = (0u8..50).collect();
-
-        bh.iter(|| {
-            let r = super::encode_check(&data);
-            black_box(&r);
-        });
-    }
-
-    #[bench]
-    pub fn bench_encode_check_xpub(bh: &mut Bencher) {
-        let data: alloc::vec::Vec<_> = (0u8..78).collect(); // lenght of xpub
-
-        bh.iter(|| {
-            let r = super::encode_check(&data);
-            black_box(&r);
-        });
-    }
-}
diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml
index 1f02b287..6202839e 100644
--- a/bitcoin/Cargo.toml
+++ b/bitcoin/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "bitcoin"
-version = "0.33.0-unreleased"
+version = "0.32.0-rc1"
 authors = ["Andrew Poelstra <apoelstra@wpsoftware.net>"]
 license = "CC0-1.0"
 repository = "https://github.com/rust-bitcoin/rust-bitcoin/"
diff --git a/bitcoin/embedded/scripts/env.sh b/bitcoin/embedded/scripts/env.sh
index 798c4af8..125162b6 100644
--- a/bitcoin/embedded/scripts/env.sh
+++ b/bitcoin/embedded/scripts/env.sh
@@ -1,4 +1,2 @@
-# we don't want shebangs in env.sh, disable shellcheck warning
-# shellcheck disable=SC2148
 export RUSTFLAGS="-C link-arg=-Tlink.x"
 export CARGO_TARGET_THUMBV7M_NONE_EABI_RUNNER="qemu-system-arm -cpu cortex-m3 -machine mps2-an385 -nographic -semihosting-config enable=on,target=native -kernel"
diff --git a/bitcoin/examples/bip32.rs b/bitcoin/examples/bip32.rs
index 8b15ba52..901793ed 100644
--- a/bitcoin/examples/bip32.rs
+++ b/bitcoin/examples/bip32.rs
@@ -41,7 +41,7 @@ fn main() {
 
     // derive child xpub
     let path = DerivationPath::from_str("84h/0h/0h").unwrap();
-    let child = root.derive_priv(&secp, &path);
+    let child = root.derive_priv(&secp, &path).unwrap();
     println!("Child at {}: {}", path, child);
     let xpub = Xpub::from_priv(&secp, &child);
     println!("Public key at {}: {}", path, xpub);
diff --git a/bitcoin/examples/ecdsa-psbt.rs b/bitcoin/examples/ecdsa-psbt.rs
index 93631fcc..428461a9 100644
--- a/bitcoin/examples/ecdsa-psbt.rs
+++ b/bitcoin/examples/ecdsa-psbt.rs
@@ -116,11 +116,11 @@ fn new<C: Signing>(secp: &Secp256k1<C>, xpriv: &str) -> Result<ExportData> {
         // Hardened children require secret data to derive.
 
         let path = "84h/0h/0h".into_derivation_path()?;
-        let account_0_xpriv = master_xpriv.derive_priv(secp, &path);
+        let account_0_xpriv = master_xpriv.derive_priv(secp, &path)?;
         let account_0_xpub = Xpub::from_priv(secp, &account_0_xpriv);
 
         let path = INPUT_UTXO_DERIVATION_PATH.into_derivation_path()?;
-        let input_xpriv = master_xpriv.derive_priv(secp, &path);
+        let input_xpriv = master_xpriv.derive_priv(secp, &path)?;
         let input_xpub = Xpub::from_priv(secp, &input_xpriv);
 
         let wallet = ColdStorage { master_xpriv, master_xpub };
diff --git a/bitcoin/examples/taproot-psbt.rs b/bitcoin/examples/taproot-psbt.rs
index 1b6d59c1..b5db05ab 100644
--- a/bitcoin/examples/taproot-psbt.rs
+++ b/bitcoin/examples/taproot-psbt.rs
@@ -296,7 +296,7 @@ fn generate_bip86_key_spend_tx(
                 .get(&input.tap_internal_key.ok_or("Internal key missing in PSBT")?)
                 .ok_or("Missing taproot key origin")?;
 
-            let secret_key = master_xpriv.derive_priv(secp, &derivation_path).to_priv().inner;
+            let secret_key = master_xpriv.derive_priv(secp, &derivation_path)?.to_priv().inner;
             sign_psbt_taproot(
                 &secret_key,
                 input.tap_internal_key.unwrap(),
@@ -391,7 +391,7 @@ fn create_inheritance_funding_tx(
         // that we use an unhardened path so we can make use of xpubs.
         let derivation_path = DerivationPath::from_str(&format!("101/1/0/0/{}", self.next))?;
         let internal_keypair =
-            self.master_xpriv.derive_priv(&self.secp, &derivation_path).to_keypair(&self.secp);
+            self.master_xpriv.derive_priv(&self.secp, &derivation_path)?.to_keypair(&self.secp);
         let beneficiary_key =
             self.beneficiary_xpub.derive_pub(&self.secp, &derivation_path)?.to_x_only_pub();
 
@@ -481,7 +481,7 @@ fn refresh_tx(
                 DerivationPath::from_str(&format!("101/1/0/0/{}", self.next))?;
             let new_internal_keypair = self
                 .master_xpriv
-                .derive_priv(&self.secp, &new_derivation_path)
+                .derive_priv(&self.secp, &new_derivation_path)?
                 .to_keypair(&self.secp);
             let beneficiary_key =
                 self.beneficiary_xpub.derive_pub(&self.secp, &new_derivation_path)?.to_x_only_pub();
@@ -530,7 +530,7 @@ fn refresh_tx(
                     .get(&input.tap_internal_key.ok_or("Internal key missing in PSBT")?)
                     .ok_or("Missing taproot key origin")?;
                 let secret_key =
-                    self.master_xpriv.derive_priv(&self.secp, &derivation_path).to_priv().inner;
+                    self.master_xpriv.derive_priv(&self.secp, &derivation_path)?.to_priv().inner;
                 sign_psbt_taproot(
                     &secret_key,
                     spend_info.internal_key(),
@@ -651,7 +651,7 @@ fn spend_inheritance(
             &psbt.inputs[0].tap_key_origins.clone()
         {
             let secret_key =
-                self.master_xpriv.derive_priv(&self.secp, &derivation_path).to_priv().inner;
+                self.master_xpriv.derive_priv(&self.secp, &derivation_path)?.to_priv().inner;
             for lh in leaf_hashes {
                 let sighash_type = TapSighashType::All;
                 let hash = SighashCache::new(&unsigned_tx).taproot_script_spend_signature_hash(
diff --git a/bitcoin/src/bip32.rs b/bitcoin/src/bip32.rs
index dd685507..1014ffdc 100644
--- a/bitcoin/src/bip32.rs
+++ b/bitcoin/src/bip32.rs
@@ -45,7 +45,7 @@
 
 impl ChainCode {
     fn from_hmac(hmac: Hmac<sha512::Hash>) -> Self {
-        hmac.as_ref()[32..].try_into().expect("half of hmac is guaranteed to be 32 bytes")
+        hmac[32..].try_into().expect("half of hmac is guaranteed to be 32 bytes")
     }
 }
 
@@ -566,7 +566,7 @@ pub fn new_master(network: impl Into<NetworkKind>, seed: &[u8]) -> Result<Xpriv,
             depth: 0,
             parent_fingerprint: Default::default(),
             child_number: ChildNumber::from_normal_idx(0)?,
-            private_key: secp256k1::SecretKey::from_slice(&hmac_result.as_ref()[..32])?,
+            private_key: secp256k1::SecretKey::from_slice(&hmac_result[..32])?,
             chain_code: ChainCode::from_hmac(hmac_result),
         })
     }
@@ -583,23 +583,27 @@ pub fn to_keypair<C: secp256k1::Signing>(self, secp: &Secp256k1<C>) -> Keypair {
             .expect("BIP32 internal private key representation is broken")
     }
 
-    /// Derives an extended private key from a path.
+    /// Attempts to derive an extended private key from a path.
     ///
     /// The `path` argument can be both of type `DerivationPath` or `Vec<ChildNumber>`.
     pub fn derive_priv<C: secp256k1::Signing, P: AsRef<[ChildNumber]>>(
         &self,
         secp: &Secp256k1<C>,
         path: &P,
-    ) -> Xpriv {
+    ) -> Result<Xpriv, Error> {
         let mut sk: Xpriv = *self;
         for cnum in path.as_ref() {
-            sk = sk.ckd_priv(secp, *cnum)
+            sk = sk.ckd_priv(secp, *cnum)?;
         }
-        sk
+        Ok(sk)
     }
 
     /// Private->Private child key derivation
-    fn ckd_priv<C: secp256k1::Signing>(&self, secp: &Secp256k1<C>, i: ChildNumber) -> Xpriv {
+    fn ckd_priv<C: secp256k1::Signing>(
+        &self,
+        secp: &Secp256k1<C>,
+        i: ChildNumber,
+    ) -> Result<Xpriv, Error> {
         let mut hmac_engine: HmacEngine<sha512::Hash> = HmacEngine::new(&self.chain_code[..]);
         match i {
             ChildNumber::Normal { .. } => {
@@ -617,19 +621,19 @@ fn ckd_priv<C: secp256k1::Signing>(&self, secp: &Secp256k1<C>, i: ChildNumber) -
 
         hmac_engine.input(&u32::from(i).to_be_bytes());
         let hmac_result: Hmac<sha512::Hash> = Hmac::from_engine(hmac_engine);
-        let sk = secp256k1::SecretKey::from_slice(&hmac_result.as_ref()[..32])
+        let sk = secp256k1::SecretKey::from_slice(&hmac_result[..32])
             .expect("statistically impossible to hit");
         let tweaked =
             sk.add_tweak(&self.private_key.into()).expect("statistically impossible to hit");
 
-        Xpriv {
+        Ok(Xpriv {
             network: self.network,
             depth: self.depth + 1,
             parent_fingerprint: self.fingerprint(secp),
             child_number: i,
             private_key: tweaked,
             chain_code: ChainCode::from_hmac(hmac_result),
-        }
+        })
     }
 
     /// Decoding extended private key from binary data according to BIP 32
@@ -738,7 +742,7 @@ pub fn ckd_pub_tweak(
 
                 let hmac_result: Hmac<sha512::Hash> = Hmac::from_engine(hmac_engine);
 
-                let private_key = secp256k1::SecretKey::from_slice(&hmac_result.as_ref()[..32])?;
+                let private_key = secp256k1::SecretKey::from_slice(&hmac_result[..32])?;
                 let chain_code = ChainCode::from_hmac(hmac_result);
                 Ok((private_key, chain_code))
             }
@@ -997,7 +1001,7 @@ fn test_path<C: secp256k1::Signing + secp256k1::Verification>(
         let mut pk = Xpub::from_priv(secp, &sk);
 
         // Check derivation convenience method for Xpriv
-        assert_eq!(&sk.derive_priv(secp, &path).to_string()[..], expected_sk);
+        assert_eq!(&sk.derive_priv(secp, &path).unwrap().to_string()[..], expected_sk);
 
         // Check derivation convenience method for Xpub, should error
         // appropriately if any ChildNumber is hardened
@@ -1009,7 +1013,7 @@ fn test_path<C: secp256k1::Signing + secp256k1::Verification>(
 
         // Derive keys, checking hardened and non-hardened derivation one-by-one
         for &num in path.0.iter() {
-            sk = sk.ckd_priv(secp, num);
+            sk = sk.ckd_priv(secp, num).unwrap();
             match num {
                 Normal { .. } => {
                     let pk2 = pk.ckd_pub(secp, num).unwrap();
diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs
index 0ea93820..69d67f9b 100644
--- a/bitcoin/src/crypto/sighash.rs
+++ b/bitcoin/src/crypto/sighash.rs
@@ -61,6 +61,7 @@ fn from(hash: $ty) -> secp256k1::Message {
     /// Taproot-tagged hash with tag \"TapSighash\".
     ///
     /// This hash type is used for computing taproot signature hash."
+    #[hash_newtype(forward)]
     pub struct TapSighash(_);
 }
 
diff --git a/bitcoin/src/pow.rs b/bitcoin/src/pow.rs
index 8ef8a1a1..3f543b1a 100644
--- a/bitcoin/src/pow.rs
+++ b/bitcoin/src/pow.rs
@@ -15,6 +15,7 @@
 use mutagen::mutate;
 use units::parse;
 
+use crate::block::Header;
 use crate::blockdata::block::BlockHash;
 use crate::consensus::encode::{self, Decodable, Encodable};
 use crate::consensus::Params;
@@ -366,6 +367,82 @@ pub fn from_unprefixed_hex(s: &str) -> Result<Self, UnprefixedHexError> {
         Ok(Self::from_consensus(lock_time))
     }
 
+    /// Computes the [`CompactTarget`] from a difficulty adjustment.
+    ///
+    /// ref: <https://github.com/bitcoin/bitcoin/blob/0503cbea9aab47ec0a87d34611e5453158727169/src/pow.cpp>
+    ///
+    /// Given the previous Target, represented as a [`CompactTarget`], the difficulty is adjusted
+    /// by taking the timespan between them, and multipling the current [`CompactTarget`] by a factor
+    /// of the net timespan and expected timespan. The [`CompactTarget`] may not adjust by more than
+    /// a factor of 4, or adjust beyond the maximum threshold for the network.
+    ///
+    /// # Note
+    ///
+    /// Under the consensus rules, the difference in the number of blocks between the headers does
+    /// not equate to the `difficulty_adjustment_interval` of [`Params`]. This is due to an off-by-one
+    /// error, and, the expected number of blocks in between headers is `difficulty_adjustment_interval - 1`
+    /// when calculating the difficulty adjustment.
+    ///
+    /// Take the example of the first difficulty adjustment. Block 2016 introduces a new [`CompactTarget`],
+    /// which takes the net timespan between Block 2015 and Block 0, and recomputes the difficulty.
+    ///
+    /// # Returns
+    ///
+    /// The expected [`CompactTarget`] recalculation.
+    pub fn from_next_work_required(
+        last: CompactTarget,
+        timespan: u64,
+        params: impl AsRef<Params>,
+    ) -> CompactTarget {
+        let params = params.as_ref();
+        if params.no_pow_retargeting {
+            return last;
+        }
+        // Comments relate to the `pow.cpp` file from Core.
+        // ref: <https://github.com/bitcoin/bitcoin/blob/0503cbea9aab47ec0a87d34611e5453158727169/src/pow.cpp>
+        let min_timespan = params.pow_target_timespan >> 2; // Lines 56/57
+        let max_timespan = params.pow_target_timespan << 2; // Lines 58/59
+        let actual_timespan = timespan.clamp(min_timespan, max_timespan);
+        let prev_target: Target = last.into();
+        let maximum_retarget = prev_target.max_transition_threshold(params); // bnPowLimit
+        let retarget = prev_target.0; // bnNew
+        let retarget = retarget.mul(actual_timespan.into());
+        let retarget = retarget.div(params.pow_target_timespan.into());
+        let retarget = Target(retarget);
+        if retarget.ge(&maximum_retarget) {
+            return maximum_retarget.to_compact_lossy();
+        }
+        retarget.to_compact_lossy()
+    }
+
+    /// Computes the [`CompactTarget`] from a difficulty adjustment,
+    /// assuming these are the relevant block headers.
+    ///
+    /// Given two headers, representing the start and end of a difficulty adjustment epoch,
+    /// compute the [`CompactTarget`] based on the net time between them and the current
+    /// [`CompactTarget`].
+    ///
+    /// # Note
+    ///
+    /// See [`CompactTarget::from_next_work_required`]
+    ///
+    /// For example, to successfully compute the first difficulty adjustment on the Bitcoin network,
+    /// one would pass the header for Block 2015 as `current` and the header for Block 0 as
+    /// `last_epoch_boundary`.
+    ///
+    /// # Returns
+    ///
+    /// The expected [`CompactTarget`] recalculation.
+    pub fn from_header_difficulty_adjustment(
+        last_epoch_boundary: Header,
+        current: Header,
+        params: impl AsRef<Params>,
+    ) -> CompactTarget {
+        let timespan = current.time - last_epoch_boundary.time;
+        let bits = current.bits;
+        CompactTarget::from_next_work_required(bits, timespan.into(), params)
+    }
+
     /// Creates a [`CompactTarget`] from a consensus encoded `u32`.
     pub fn from_consensus(bits: u32) -> Self { Self(bits) }
 
@@ -1689,6 +1766,113 @@ fn compact_target_lower_hex_and_upper_hex() {
         assert_eq!(format!("{:08X}", CompactTarget(0x01d0f456)), "01D0F456");
     }
 
+    #[test]
+    fn compact_target_from_upwards_difficulty_adjustment() {
+        let params = Params::new(crate::Network::Signet);
+        let starting_bits = CompactTarget::from_consensus(503543726); // Genesis compact target on Signet
+        let start_time: u64 = 1598918400; // Genesis block unix time
+        let end_time: u64 = 1599332177; // Block 2015 unix time
+        let timespan = end_time - start_time; // Faster than expected
+        let adjustment = CompactTarget::from_next_work_required(starting_bits, timespan, &params);
+        let adjustment_bits = CompactTarget::from_consensus(503394215); // Block 2016 compact target
+        assert_eq!(adjustment, adjustment_bits);
+    }
+
+    #[test]
+    fn compact_target_from_downwards_difficulty_adjustment() {
+        let params = Params::new(crate::Network::Signet);
+        let starting_bits = CompactTarget::from_consensus(503394215); // Block 2016 compact target
+        let start_time: u64 = 1599332844; // Block 2016 unix time
+        let end_time: u64 = 1600591200; // Block 4031 unix time
+        let timespan = end_time - start_time; // Slower than expected
+        let adjustment = CompactTarget::from_next_work_required(starting_bits, timespan, &params);
+        let adjustment_bits = CompactTarget::from_consensus(503397348); // Block 4032 compact target
+        assert_eq!(adjustment, adjustment_bits);
+    }
+
+    #[test]
+    fn compact_target_from_upwards_difficulty_adjustment_using_headers() {
+        use crate::{block::Version, constants::genesis_block, TxMerkleNode};
+        use hashes::Hash;
+        let params = Params::new(crate::Network::Signet);
+        let epoch_start = genesis_block(&params).header;
+        // Block 2015, the only information used are `bits` and `time`
+        let current = Header {
+            version: Version::ONE,
+            prev_blockhash: BlockHash::all_zeros(),
+            merkle_root: TxMerkleNode::all_zeros(),
+            time: 1599332177,
+            bits: epoch_start.bits,
+            nonce: epoch_start.nonce
+        };
+        let adjustment = CompactTarget::from_header_difficulty_adjustment(epoch_start, current, params);
+        let adjustment_bits = CompactTarget::from_consensus(503394215); // Block 2016 compact target
+        assert_eq!(adjustment, adjustment_bits);
+    }
+
+    #[test]
+    fn compact_target_from_downwards_difficulty_adjustment_using_headers() {
+        use crate::{block::Version, TxMerkleNode};
+        use hashes::Hash;
+        let params = Params::new(crate::Network::Signet);
+        let starting_bits = CompactTarget::from_consensus(503394215); // Block 2016 compact target
+        // Block 2016, the only information used is `time`
+        let epoch_start = Header {
+            version: Version::ONE,
+            prev_blockhash: BlockHash::all_zeros(),
+            merkle_root: TxMerkleNode::all_zeros(),
+            time: 1599332844,
+            bits: starting_bits,
+            nonce: 0
+        };
+        // Block 4031, the only information used are `bits` and `time`
+        let current = Header {
+            version: Version::ONE,
+            prev_blockhash: BlockHash::all_zeros(),
+            merkle_root: TxMerkleNode::all_zeros(),
+            time: 1600591200,
+            bits: starting_bits,
+            nonce: 0
+        };
+        let adjustment = CompactTarget::from_header_difficulty_adjustment(epoch_start, current, params);
+        let adjustment_bits = CompactTarget::from_consensus(503397348); // Block 4032 compact target
+        assert_eq!(adjustment, adjustment_bits);
+    }
+
+    #[test]
+    fn compact_target_from_maximum_upward_difficulty_adjustment() {
+        let params = Params::new(crate::Network::Signet);
+        let starting_bits = CompactTarget::from_consensus(503403001);
+        let timespan = (0.2 * params.pow_target_timespan as f64) as u64;
+        let got = CompactTarget::from_next_work_required(starting_bits, timespan, params);
+        let want = Target::from_compact(starting_bits)
+            .min_transition_threshold()
+            .to_compact_lossy();
+        assert_eq!(got, want);
+    }
+
+    #[test]
+    fn compact_target_from_minimum_downward_difficulty_adjustment() {
+        let params = Params::new(crate::Network::Signet);
+        let starting_bits = CompactTarget::from_consensus(403403001); // High difficulty for Signet
+        let timespan =  5 * params.pow_target_timespan; // Really slow.
+        let got = CompactTarget::from_next_work_required(starting_bits, timespan, &params);
+        let want = Target::from_compact(starting_bits)
+            .max_transition_threshold(params)
+            .to_compact_lossy();
+        assert_eq!(got, want);
+    }
+
+    #[test]
+    fn compact_target_from_adjustment_is_max_target() {
+        let params = Params::new(crate::Network::Signet);
+        let starting_bits = CompactTarget::from_consensus(503543726); // Genesis compact target on Signet
+        let timespan =  5 * params.pow_target_timespan; // Really slow.
+        let got = CompactTarget::from_next_work_required(starting_bits, timespan, &params);
+        let want = params.max_attainable_target.to_compact_lossy();
+        assert_eq!(got, want);
+    }
+
     #[test]
     fn target_from_compact() {
         // (nBits, target)
diff --git a/bitcoin/src/psbt/map/input.rs b/bitcoin/src/psbt/map/input.rs
index cfe77811..6c722f2f 100644
--- a/bitcoin/src/psbt/map/input.rs
+++ b/bitcoin/src/psbt/map/input.rs
@@ -136,16 +136,6 @@ pub struct Input {
 /// type can be either [`EcdsaSighashType`] or [`TapSighashType`] but it is not possible to know
 /// directly which signature hash type the user is dealing with. Therefore, the user is responsible
 /// for converting to/from [`PsbtSighashType`] from/to the desired signature hash type they need.
-///
-/// # Examples
-///
-/// ```
-/// use bitcoin::{EcdsaSighashType, TapSighashType};
-/// use bitcoin::psbt::PsbtSighashType;
-///
-/// let ecdsa_sighash_all: PsbtSighashType = EcdsaSighashType::All.into();
-/// let tap_sighash_all: PsbtSighashType = TapSighashType::All.into();
-/// ```
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
@@ -524,7 +514,7 @@ fn psbt_insert_hash_pair<H>(
             if <H as hashes::Hash>::hash(&val) != key_val {
                 return Err(psbt::Error::InvalidPreimageHashPair {
                     preimage: val.into_boxed_slice(),
-                    hash: Box::from(key_val.as_ref()),
+                    hash: Box::from(key_val.borrow()),
                     hash_type,
                 });
             }
diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs
index 667719ec..3d428fda 100644
--- a/bitcoin/src/psbt/mod.rs
+++ b/bitcoin/src/psbt/mod.rs
@@ -762,7 +762,7 @@ fn get_key<C: Signing>(
             KeyRequest::Pubkey(_) => Err(GetKeyError::NotSupported),
             KeyRequest::Bip32((fingerprint, path)) => {
                 let key = if self.fingerprint(secp) == fingerprint {
-                    let k = self.derive_priv(secp, &path);
+                    let k = self.derive_priv(secp, &path)?;
                     Some(k.to_priv())
                 } else {
                     None
@@ -808,7 +808,7 @@ fn get_key<C: Signing>(
             KeyRequest::Bip32((fingerprint, path)) => {
                 for xpriv in self.iter() {
                     if xpriv.parent_fingerprint == fingerprint {
-                        let k = xpriv.derive_priv(secp, &path);
+                        let k = xpriv.derive_priv(secp, &path)?;
                         return Ok(Some(k.to_priv()));
                     }
                 }
@@ -1366,7 +1366,7 @@ fn serialize_then_deserialize_output() {
             ChildNumber::from_normal_idx(31337).unwrap(),
         ];
 
-        sk = sk.derive_priv(secp, &dpath);
+        sk = sk.derive_priv(secp, &dpath).unwrap();
 
         let pk = Xpub::from_priv(secp, &sk);
 
diff --git a/bitcoin/src/taproot/mod.rs b/bitcoin/src/taproot/mod.rs
index 87318ada..60ce28ee 100644
--- a/bitcoin/src/taproot/mod.rs
+++ b/bitcoin/src/taproot/mod.rs
@@ -36,6 +36,7 @@
     /// Taproot-tagged hash with tag \"TapLeaf\".
     ///
     /// This is used for computing tapscript script spend hash.
+    #[hash_newtype(forward)]
     pub struct TapLeafHash(_);
 
     pub struct TapBranchTag = hash_str("TapBranch");
@@ -43,6 +44,7 @@
     /// Tagged hash used in taproot trees.
     ///
     /// See BIP-340 for tagging rules.
+    #[hash_newtype(forward)]
     pub struct TapNodeHash(_);
 
     pub struct TapTweakTag = hash_str("TapTweak");
@@ -50,6 +52,7 @@
     /// Taproot-tagged hash with tag \"TapTweak\".
     ///
     /// This hash type is used while computing the tweaked public key.
+    #[hash_newtype(forward)]
     pub struct TapTweakHash(_);
 }
 
diff --git a/contrib/crates.sh b/contrib/crates.sh
deleted file mode 100755
index f74787e4..00000000
--- a/contrib/crates.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/usr/bin/env bash
-# disable verify unused vars, despite the fact that they are used when sourced
-# shellcheck disable=SC2034
-
-# Crates in this workspace to test (note "fuzz" is only built not tested).
-CRATES=("base58" "bitcoin" "fuzz" "hashes" "internals" "io" "units")
diff --git a/contrib/gen_label_config.sh b/contrib/gen_label_config.sh
index 11536bbf..089bd30d 100755
--- a/contrib/gen_label_config.sh
+++ b/contrib/gen_label_config.sh
@@ -20,7 +20,7 @@ fi
 
 excluded_crates="fuzz|dep_test"
 
-CRATES="$(cd "$SCAN_DIR" && cargo metadata --no-deps --format-version 1 | jq -j -r '.packages | map(.manifest_path | rtrimstr("/Cargo.toml") | ltrimstr("'"$PWD"'/")) | join(" ")')"
+CRATES="`cd "$SCAN_DIR" && cargo metadata --no-deps --format-version 1 | jq -j -r '.packages | map(.manifest_path | rtrimstr("/Cargo.toml") | ltrimstr("'$PWD'/")) | join(" ")'`"
 
 for crate in $CRATES;
 do
@@ -29,9 +29,7 @@ do
 		continue
 	fi
 
-	{
-		echo "C-$crate:"
-		echo "  - changed-files:"
-		echo "    - any-glob-to-any-file: $crate/**"
-	} >> "$config"
+	echo "C-$crate:" >> "$config"
+	echo "  - changed-files:" >> "$config"
+	echo "    - any-glob-to-any-file: $crate/**" >> "$config"
 done
diff --git a/contrib/get_matrix.sh b/contrib/get_matrix.sh
new file mode 100755
index 00000000..d72a865b
--- /dev/null
+++ b/contrib/get_matrix.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+. contrib/test_vars.sh
+
+crates="`cargo metadata --no-deps --format-version 1 | jq -c '.packages | map(.manifest_path | rtrimstr("/Cargo.toml") | ltrimstr("'$PWD'/"))'`"
+deps="`echo -n $DEPS | jq -R -c 'split(" ")'`"
+# debug
+echo "$crates"
+echo "$deps"
+
+echo "crates=$crates" >> $GITHUB_OUTPUT
+echo "deps=$deps" >> $GITHUB_OUTPUT
diff --git a/contrib/release.sh b/contrib/release.sh
index b30f2fb8..558ba0fb 100755
--- a/contrib/release.sh
+++ b/contrib/release.sh
@@ -28,7 +28,7 @@ main () {
 # preparation for releasing the crate.
 release_changes() {
     local crate=$1
-    git log --patch --reverse master.. -- "$crate"/Cargo.toml | grep version
+    git log --patch --reverse master.. -- $crate/Cargo.toml | grep version
 }
 
 # Do a dry run publish to crates.io using the correct package name for crate ($1).
@@ -47,4 +47,4 @@ publish_dry_run() {
 #
 # Main script.
 #
-main "$@"
+main $@
diff --git a/contrib/run_task.sh b/contrib/run_task.sh
new file mode 100755
index 00000000..424dd902
--- /dev/null
+++ b/contrib/run_task.sh
@@ -0,0 +1,341 @@
+#!/usr/bin/env bash
+#
+# We are attempting to run max 20 parallel jobs using GitHub actions (usage limit for free tier).
+#
+# ref: https://docs.github.com/en/actions/learn-github-actions/usage-limits-billing-and-administration
+#
+# The minimal/recent manifests are handled by CI (rust.yaml).
+#
+# Jobs (shell functions) that get run twice, once for each manifest:
+#
+# 1+2  stable
+# 3+4  nightly
+# 5+6  msrv
+#
+# Jobs (shell functions) that get run once:
+#
+# 7   lint
+# 8   docs
+# 9   docsrs
+# 10  bench
+# 11  asan
+# 12  wasm
+# 13  schemars
+#
+# Jobs run directly by rust.yml workflow:
+#
+# 0 Prepare
+#
+# 14  Arch32bit
+# 15  Cross
+# 16  Embedded
+# 17  Kani
+#
+# Jobs run directly from other workflows:
+#
+# 18  Coveralls - run by coveralls.yml
+# 19  release - run by release.yml
+# 20  labeler - run by manage-pr.yml
+
+set -euox pipefail
+
+REPO_DIR=$(git rev-parse --show-toplevel)
+CRATES=("bitcoin" "hashes" "internals" "io" "units" "base58")
+
+# Make all cargo invocations verbose.
+export CARGO_TERM_VERBOSE=true
+
+main() {
+    local task="$1"
+
+    check_required_commands
+
+    cargo --version
+    rustc --version
+    /usr/bin/env bash --version
+    locale
+    env
+
+    case $task in
+        # 2 jobs each for these (one for each lock file).
+	stable)
+            # Test, run examples, do feature matrix.
+            # crate/contrib/test_vars.sh is sourced in this function.
+            build_and_test
+            ;;
+
+	nightly)
+            build_and_test
+            ;;
+
+	msrv)
+            build_and_test
+            ;;
+
+        # 1 job each for these.
+        lint)
+            do_lint
+            do_dup_deps
+            ;;
+
+	docs)
+            build_docs_with_stable_toolchain
+	    ;;
+
+	docsrs)
+            build_docs_with_nightly_toolchain
+	    ;;
+
+	bench)
+	    do_bench
+	    ;;
+
+	wasm)
+            # hashes crate only.
+	    do_wasm
+	    ;;
+
+	asan)
+            # hashes crate only - hashes/contrib/test_vars.sh is sourced in this function.
+	    do_asan
+	    ;;
+
+	schemars)
+            # hashes crate only.
+	    do_schemars
+	    ;;
+
+        *)
+            err "Error: unknown task $task"
+            ;;
+    esac
+}
+
+# Build and test for each crate, done with each toolchain.
+build_and_test() {
+    # Building the fuzz crate is more-or-less just a sanity check.
+    pushd "$REPO_DIR/fuzz" > /dev/null
+    cargo --locked build
+    popd > /dev/null
+
+    for crate in "${CRATES[@]}"; do
+        pushd "$REPO_DIR/$crate" > /dev/null
+
+        # Set crate specific variables.
+        . contrib/test_vars.sh || exit 1
+
+        do_test
+        do_feature_matrix
+
+        popd > /dev/null
+    done
+}
+
+do_test() {
+    # Use the current (recent/minimal) lock file.
+    local cargo="cargo --locked"
+
+    # Defaults / sanity checks
+    $cargo build
+    $cargo test
+
+    for example in $EXAMPLES; do # EXAMPLES is set in contrib/test_vars.sh
+        name="$(echo "$example" | cut -d ':' -f 1)"
+        features="$(echo "$example" | cut -d ':' -f 2)"
+        $cargo run --example "$name" --features="$features"
+    done
+
+    if [ -e ./contrib/extra_tests.sh ];
+    then
+        ./contrib/extra_tests.sh
+    fi
+}
+
+# Each crate defines its own feature matrix test so feature combinations
+# can be better controlled.
+do_feature_matrix() {
+    local cargo="cargo --locked"
+
+    $cargo build --no-default-features
+    $cargo test --no-default-features
+
+    # All crates have a "std" feature and FEATURES_WITH_STD is set in
+    # contrib/test_vars.sh
+    loop_features "std" "$FEATURES_WITH_STD"
+
+    # All but `bitcoin` crate have an "alloc" feature, this tests it
+    # along with any other features that should work with "std".
+    if [ -n "$FEATURES_WITHOUT_STD" ]
+       then
+           loop_features "" "$FEATURES_WITHOUT_STD"
+    fi
+}
+
+# Build with each feature as well as all combinations of two features.
+#
+# Usage: loop_features "std" "this-feature that-feature other"
+loop_features() {
+    local use="$1"
+    local features="$2"
+    local cargo="cargo --locked"
+
+    # All the provided features including $use
+    $cargo build --no-default-features --features="$use $features"
+    $cargo test --no-default-features --features="$use $features"
+
+    read -r -a array <<< "$features"
+    local len="${#array[@]}"
+
+    if (( len > 1 )); then
+        for ((i = 0 ; i < len ; i++ ));
+        do
+            $cargo build --features="$use ${array[i]}"
+            $cargo test --features="$use ${array[i]}"
+
+            if (( i < len - 1 )); then
+               for ((j = i + 1 ; j < len ; j++ ));
+               do
+                   $cargo build --features="$use ${array[i]} ${array[j]}"
+                   $cargo test --features="$use ${array[i]} ${array[j]}"
+               done
+            fi
+        done
+    fi
+}
+
+# Lint the workspace.
+do_lint() {
+    need_nightly
+    local cargo="cargo --locked"
+
+    # Lint various feature combinations to try and catch mistakes in feature gating.
+    $cargo clippy --workspace --all-targets --keep-going -- -D warnings
+    $cargo clippy --workspace --all-targets --all-features --keep-going -- -D warnings
+    $cargo clippy --workspace --all-targets --no-default-features --keep-going -- -D warnings
+}
+
+# We should not have any duplicate dependencies. This catches mistakes made upgrading dependencies
+# in one crate and not in another (e.g. upgrade bitcoin_hashes in bitcoin but not in secp).
+do_dup_deps() {
+    # We can't use pipefail because these grep statements fail by design when there is no duplicate,
+    # the shell therefore won't pick up mistakes in your pipe - you are on your own.
+    set +o pipefail
+
+    duplicate_dependencies=$(
+        # Only show the actual duplicated deps, not their reverse tree, then
+        # whitelist the 'syn' crate which is duplicated but it's not our fault.
+        #
+        # Temporarily allow 2 versions of `hashes`, `internals`, and `hex` while we upgrade.
+        cargo tree  --target=all --all-features --duplicates \
+            | grep '^[0-9A-Za-z]' \
+            | grep -v 'syn' \
+            | wc -l
+                          )
+    if [ "$duplicate_dependencies" -ne 0 ]; then
+        echo "Dependency tree is broken, contains duplicates"
+        cargo tree  --target=all --all-features --duplicates
+        exit 1
+    fi
+
+    set -o pipefail
+}
+
+# Build the docs with a nightly toolchain, in unison with the function
+# below this checks that we feature guarded docs imports correctly.
+build_docs_with_nightly_toolchain() {
+    need_nightly
+    local cargo="cargo --locked"
+    RUSTDOCFLAGS="--cfg docsrs -D warnings -D rustdoc::broken-intra-doc-links" $cargo doc --all-features
+}
+
+# Build the docs with a stable toolchain, in unison with the function
+# above this checks that we feature guarded docs imports correctly.
+build_docs_with_stable_toolchain() {
+    local cargo="cargo +stable --locked"
+    RUSTDOCFLAGS="-D warnings" $cargo doc --all-features
+}
+
+# Bench only works with a non-stable toolchain (nightly, beta).
+do_bench() {
+    for crate in bitcoin hashes; do
+        pushd "$REPO_DIR/$crate" > /dev/null
+        RUSTFLAGS='--cfg=bench' cargo bench
+        popd > /dev/null
+    done
+}
+
+# This is only relevant for hashes.
+do_schemars() {
+    pushd "$REPO_DIR/hashes/extended_tests/schemars" > /dev/null
+    cargo test
+    popd > /dev/null
+}
+
+# Note we do not use the recent lock file or `--locked` when running the wasm tests.
+do_wasm() {
+    pushd "$REPO_DIR/hashes" > /dev/null
+
+    clang --version &&
+	CARGO_TARGET_DIR=wasm cargo install --force wasm-pack &&
+	printf '\n[target.wasm32-unknown-unknown.dev-dependencies]\nwasm-bindgen-test = "0.3"\n' >> Cargo.toml &&
+	printf '\n[lib]\ncrate-type = ["cdylib", "rlib"]\n' >> Cargo.toml &&
+	CC=clang-9 wasm-pack build &&
+	CC=clang-9 wasm-pack test --node;
+
+    popd > /dev/null
+}
+
+do_asan() {
+    pushd "$REPO_DIR/hashes" > /dev/null
+
+    # Set ASAN_FEATURES
+    . contrib/test_vars.sh || exit 1
+
+    cargo clean
+    CC='clang -fsanitize=address -fno-omit-frame-pointer'                                        \
+      RUSTFLAGS='-Zsanitizer=address -Clinker=clang -Cforce-frame-pointers=yes'                    \
+      ASAN_OPTIONS='detect_leaks=1 detect_invalid_pointer_pairs=1 detect_stack_use_after_return=1' \
+      cargo test --lib --no-default-features --features="$ASAN_FEATURES" -Zbuild-std --target x86_64-unknown-linux-gnu
+    # There is currently a bug in the MemorySanitizer (MSAN) - disable the job for now.
+    #
+    # cargo clean
+    # CC='clang -fsanitize=memory -fno-omit-frame-pointer'                                         \
+    #   RUSTFLAGS='-Zsanitizer=memory -Zsanitizer-memory-track-origins -Cforce-frame-pointers=yes'   \
+    #   cargo test --lib --no-default-features --features="$ASAN_FEATURES" -Zbuild-std --target x86_64-unknown-linux-gnu
+
+    popd > /dev/null
+}
+
+# Check all the commands we use are present in the current environment.
+check_required_commands() {
+    need_cmd cargo
+    need_cmd rustc
+    need_cmd jq
+    need_cmd cut
+    need_cmd grep
+    need_cmd wc
+}
+
+need_cmd() {
+    if ! command -v "$1" > /dev/null 2>&1
+    then err "need '$1' (command not found)"
+    fi
+}
+
+need_nightly() {
+    cargo_ver=$(cargo --version)
+    if echo "$cargo_ver" | grep -q -v nightly; then
+        err "Need a nightly compiler; have $(cargo --version)"
+    fi
+}
+
+err() {
+    echo "$1" >&2
+    exit 1
+}
+
+#
+# Main script
+#
+main "$@"
+exit 0
diff --git a/fuzz/cycle.sh b/fuzz/cycle.sh
index c642157d..b72c44da 100755
--- a/fuzz/cycle.sh
+++ b/fuzz/cycle.sh
@@ -8,8 +8,7 @@
 set -euo pipefail
 
 REPO_DIR=$(git rev-parse --show-toplevel)
-# can't find the file because of the ENV var
-# shellcheck source=/dev/null
+# shellcheck source=./fuzz-util.sh
 source "$REPO_DIR/fuzz/fuzz-util.sh"
 
 while :
diff --git a/fuzz/fuzz.sh b/fuzz/fuzz.sh
index 2632e6bc..c85b8e4e 100755
--- a/fuzz/fuzz.sh
+++ b/fuzz/fuzz.sh
@@ -3,8 +3,7 @@ set -euox pipefail
 
 REPO_DIR=$(git rev-parse --show-toplevel)
 
-# can't find the file because of the ENV var
-# shellcheck source=/dev/null
+# shellcheck source=./fuzz-util.sh
 source "$REPO_DIR/fuzz/fuzz-util.sh"
 
 # Check that input files are correct Windows file names
diff --git a/fuzz/generate-files.sh b/fuzz/generate-files.sh
index 39e64697..b3b32fb9 100755
--- a/fuzz/generate-files.sh
+++ b/fuzz/generate-files.sh
@@ -4,8 +4,7 @@ set -euo pipefail
 
 REPO_DIR=$(git rev-parse --show-toplevel)
 
-# can't find the file because of the ENV var
-# shellcheck source=/dev/null
+# shellcheck source=./fuzz-util.sh
 source "$REPO_DIR/fuzz/fuzz-util.sh"
 
 # 1. Generate fuzz/Cargo.toml
diff --git a/githooks/pre-commit b/githooks/pre-commit
index d0e92ee9..5d0b9fe6 100755
--- a/githooks/pre-commit
+++ b/githooks/pre-commit
@@ -21,13 +21,11 @@ exec 1>&2
 # Cross platform projects tend to avoid non-ASCII filenames; prevent
 # them from being added to the repository. We exploit the fact that the
 # printable range starts at the space character and ends with tilde.
-# disable the check to quoting the $(...) command
-# shellcheck disable=SC2046
 if [ "$allownonascii" != "true" ] &&
 	# Note that the use of brackets around a tr range is ok here, (it's
 	# even required, for portability to Solaris 10's /usr/bin/tr), since
 	# the square bracket bytes happen to fall in the designated range.
-	test $(git diff --cached --name-only --diff-filter=A -z "$against" |
+	test $(git diff --cached --name-only --diff-filter=A -z $against |
 	  LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
 then
 	cat <<\EOF
@@ -45,9 +43,7 @@ EOF
 fi
 
 # If there are whitespace errors, print the offending file names and fail.
-git diff-index --check --cached "$against" -- || exit 1
+git diff-index --check --cached $against -- || exit 1
 
 # Check that code lints cleanly.
-# disable the check to quoting the $(...) command
-# shellcheck disable=SC2046
 cargo +$(cat ./nightly-version) clippy --workspace --all-targets --all-features -- --deny warnings || exit 1
diff --git a/hashes/CHANGELOG.md b/hashes/CHANGELOG.md
index 887776a5..ca96b020 100644
--- a/hashes/CHANGELOG.md
+++ b/hashes/CHANGELOG.md
@@ -1,20 +1,3 @@
-# unreleased
-
-* Change the default display direction of for tagged hashes to forwards. [#2707](https://github.com/rust-bitcoin/rust-bitcoin/pull/2707)
-
-  Note please this usage if you need to display backward:
-
-```rust
-  sha256t_hash_newtype! {
-    /// Test detailed explanation.
-    struct NewTypeTag = hash_str("tag");
-
-    /// A test hash.
-    #[hash_newtype(backward)]
-    struct NewTypeHash(_);
-  }
-```
-
 # 0.14.0 - 2024-03-21
 
 * Bump MSRV to Rust version 1.56.1 [#2188](https://github.com/rust-bitcoin/rust-bitcoin/pull/2188)
diff --git a/hashes/contrib/sanitizer.sh b/hashes/contrib/sanitizer.sh
deleted file mode 100755
index 5a1e56c2..00000000
--- a/hashes/contrib/sanitizer.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env bash
-#
-# Run the Address/Memory Sanitizer tests.
-
-set -euox pipefail
-
-# Run the sanitizer with these features.
-FEATURES="std io serde"
-
-cargo clean
-CC='clang -fsanitize=address -fno-omit-frame-pointer'                                        \
-  RUSTFLAGS='-Zsanitizer=address -Clinker=clang -Cforce-frame-pointers=yes'                    \
-  ASAN_OPTIONS='detect_leaks=1 detect_invalid_pointer_pairs=1 detect_stack_use_after_return=1' \
-  cargo test --lib --no-default-features --features="$FEATURES" -Zbuild-std --target x86_64-unknown-linux-gnu
-
-# There is currently a bug in the MemorySanitizer (MSAN) - disable the job for now.
-#
-# cargo clean
-# CC='clang -fsanitize=memory -fno-omit-frame-pointer'                                         \
-    #   RUSTFLAGS='-Zsanitizer=memory -Zsanitizer-memory-track-origins -Cforce-frame-pointers=yes'   \
-    #   cargo test --lib --no-default-features --features="$FEATURES" -Zbuild-std --target x86_64-unknown-linux-gnu
diff --git a/hashes/contrib/wasm.sh b/hashes/contrib/wasm.sh
deleted file mode 100755
index 6139e307..00000000
--- a/hashes/contrib/wasm.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/env bash
-#
-# Run the WASM tests.
-
-set -euox pipefail
-
-clang --version &&
-    CARGO_TARGET_DIR=wasm cargo install --force wasm-pack &&
-    printf '\n[target.wasm32-unknown-unknown.dev-dependencies]\nwasm-bindgen-test = "0.3"\n' >> Cargo.toml &&
-    printf '\n[lib]\ncrate-type = ["cdylib", "rlib"]\n' >> Cargo.toml &&
-    CC=clang-9 wasm-pack build &&
-    CC=clang-9 wasm-pack test --node;
diff --git a/hashes/embedded/scripts/env.sh b/hashes/embedded/scripts/env.sh
index 798c4af8..7ea1e31c 100644
--- a/hashes/embedded/scripts/env.sh
+++ b/hashes/embedded/scripts/env.sh
@@ -1,4 +1,4 @@
-# we don't want shebangs in env.sh, disable shellcheck warning
-# shellcheck disable=SC2148
+#!/usr/bin/env bash
+
 export RUSTFLAGS="-C link-arg=-Tlink.x"
 export CARGO_TARGET_THUMBV7M_NONE_EABI_RUNNER="qemu-system-arm -cpu cortex-m3 -machine mps2-an385 -nographic -semihosting-config enable=on,target=native -kernel"
diff --git a/hashes/src/hash160.rs b/hashes/src/hash160.rs
index 21697f79..41098a8a 100644
--- a/hashes/src/hash160.rs
+++ b/hashes/src/hash160.rs
@@ -10,6 +10,7 @@
 
 use core::ops::Index;
 use core::slice::SliceIndex;
+use core::str;
 
 use crate::{ripemd160, sha256, FromSliceError};
 
diff --git a/hashes/src/hmac.rs b/hashes/src/hmac.rs
index 53be1959..32b967a5 100644
--- a/hashes/src/hmac.rs
+++ b/hashes/src/hmac.rs
@@ -8,7 +8,7 @@
 //! Hash-based Message Authentication Code (HMAC).
 //!
 
-use core::{convert, fmt, str};
+use core::{borrow, fmt, ops, str};
 
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Deserializer, Serialize, Serializer};
@@ -72,10 +72,10 @@ pub fn new(key: &[u8]) -> HmacEngine<T> {
 
         if key.len() > T::Engine::BLOCK_SIZE {
             let hash = <T as Hash>::hash(key);
-            for (b_i, b_h) in ipad.iter_mut().zip(hash.as_ref()) {
+            for (b_i, b_h) in ipad.iter_mut().zip(&hash[..]) {
                 *b_i ^= *b_h;
             }
-            for (b_o, b_h) in opad.iter_mut().zip(hash.as_ref()) {
+            for (b_o, b_h) in opad.iter_mut().zip(&hash[..]) {
                 *b_o ^= *b_h;
             }
         } else {
@@ -124,8 +124,33 @@ impl<T: Hash> fmt::LowerHex for Hmac<T> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(&self.0, f) }
 }
 
-impl<T: Hash> convert::AsRef<[u8]> for Hmac<T> {
-    fn as_ref(&self) -> &[u8] { self.0.as_ref() }
+impl<T: Hash> ops::Index<usize> for Hmac<T> {
+    type Output = u8;
+    fn index(&self, index: usize) -> &u8 { &self.0[index] }
+}
+
+impl<T: Hash> ops::Index<ops::Range<usize>> for Hmac<T> {
+    type Output = [u8];
+    fn index(&self, index: ops::Range<usize>) -> &[u8] { &self.0[index] }
+}
+
+impl<T: Hash> ops::Index<ops::RangeFrom<usize>> for Hmac<T> {
+    type Output = [u8];
+    fn index(&self, index: ops::RangeFrom<usize>) -> &[u8] { &self.0[index] }
+}
+
+impl<T: Hash> ops::Index<ops::RangeTo<usize>> for Hmac<T> {
+    type Output = [u8];
+    fn index(&self, index: ops::RangeTo<usize>) -> &[u8] { &self.0[index] }
+}
+
+impl<T: Hash> ops::Index<ops::RangeFull> for Hmac<T> {
+    type Output = [u8];
+    fn index(&self, index: ops::RangeFull) -> &[u8] { &self.0[index] }
+}
+
+impl<T: Hash> borrow::Borrow<[u8]> for Hmac<T> {
+    fn borrow(&self) -> &[u8] { &self[..] }
 }
 
 impl<T: Hash> Hash for Hmac<T> {
@@ -134,7 +159,7 @@ impl<T: Hash> Hash for Hmac<T> {
 
     fn from_engine(mut e: HmacEngine<T>) -> Hmac<T> {
         let ihash = T::from_engine(e.iengine);
-        e.oengine.input(ihash.as_ref());
+        e.oengine.input(&ihash[..]);
         let ohash = T::from_engine(e.oengine);
         Hmac(ohash)
     }
@@ -293,7 +318,7 @@ struct Test {
             let mut engine = HmacEngine::<sha256::Hash>::new(&test.key);
             engine.input(&test.input);
             let hash = Hmac::<sha256::Hash>::from_engine(engine);
-            assert_eq!(hash.as_ref(), &test.output[..]);
+            assert_eq!(&hash[..], &test.output[..]);
             assert_eq!(hash.as_byte_array(), test.output.as_slice());
         }
     }
diff --git a/hashes/src/internal_macros.rs b/hashes/src/internal_macros.rs
index 5635020e..9b3f9dde 100644
--- a/hashes/src/internal_macros.rs
+++ b/hashes/src/internal_macros.rs
@@ -86,7 +86,7 @@ impl<$($gen: $gent),*> Hash<$($gen),*> {
             }
         }
 
-        impl<$($gen: $gent),*> $crate::_export::_core::str::FromStr for Hash<$($gen),*> {
+        impl<$($gen: $gent),*> str::FromStr for Hash<$($gen),*> {
             type Err = $crate::hex::HexToArrayError;
             fn from_str(s: &str) -> $crate::_export::_core::result::Result<Self, Self::Err> {
                 use $crate::{Hash, hex::{FromHex}};
diff --git a/hashes/src/lib.rs b/hashes/src/lib.rs
index 1aa7a6b9..3bfdbf62 100644
--- a/hashes/src/lib.rs
+++ b/hashes/src/lib.rs
@@ -123,7 +123,7 @@ pub mod _core {
 pub mod sha512_256;
 pub mod siphash24;
 
-use core::{convert, fmt, hash};
+use core::{borrow, fmt, hash, ops};
 
 pub use hmac::{Hmac, HmacEngine};
 
@@ -158,7 +158,12 @@ pub trait Hash:
     + fmt::Debug
     + fmt::Display
     + fmt::LowerHex
-    + convert::AsRef<[u8]>
+    + ops::Index<ops::RangeFull, Output = [u8]>
+    + ops::Index<ops::RangeFrom<usize>, Output = [u8]>
+    + ops::Index<ops::RangeTo<usize>, Output = [u8]>
+    + ops::Index<ops::Range<usize>, Output = [u8]>
+    + ops::Index<usize, Output = u8>
+    + borrow::Borrow<[u8]>
 {
     /// A hashing engine which bytes can be serialized into. It is expected
     /// to implement the `io::Write` trait, and to never return errors under
diff --git a/hashes/src/ripemd160.rs b/hashes/src/ripemd160.rs
index 7c06e429..1581dd24 100644
--- a/hashes/src/ripemd160.rs
+++ b/hashes/src/ripemd160.rs
@@ -3,9 +3,9 @@
 //! RIPEMD160 implementation.
 //!
 
-use core::cmp;
 use core::ops::Index;
 use core::slice::SliceIndex;
+use core::{cmp, str};
 
 use crate::{FromSliceError, HashEngine as _};
 
diff --git a/hashes/src/sha1.rs b/hashes/src/sha1.rs
index 8f3a8cc1..00d8446d 100644
--- a/hashes/src/sha1.rs
+++ b/hashes/src/sha1.rs
@@ -3,9 +3,9 @@
 //! SHA1 implementation.
 //!
 
-use core::cmp;
 use core::ops::Index;
 use core::slice::SliceIndex;
+use core::{cmp, str};
 
 use crate::{FromSliceError, HashEngine as _};
 
diff --git a/hashes/src/sha256.rs b/hashes/src/sha256.rs
index 93bbd01e..8affa07b 100644
--- a/hashes/src/sha256.rs
+++ b/hashes/src/sha256.rs
@@ -7,9 +7,9 @@
 use core::arch::x86::*;
 #[cfg(all(feature = "std", target_arch = "x86_64"))]
 use core::arch::x86_64::*;
-use core::cmp;
 use core::ops::Index;
 use core::slice::SliceIndex;
+use core::{cmp, str};
 
 use crate::{sha256d, FromSliceError, HashEngine as _};
 
@@ -126,7 +126,7 @@ impl<I: SliceIndex<[u8]>> Index<I> for Midstate {
     fn index(&self, index: I) -> &Self::Output { &self.0[index] }
 }
 
-impl core::str::FromStr for Midstate {
+impl str::FromStr for Midstate {
     type Err = hex::HexToArrayError;
     fn from_str(s: &str) -> Result<Self, Self::Err> { hex::FromHex::from_hex(s) }
 }
diff --git a/hashes/src/sha256d.rs b/hashes/src/sha256d.rs
index 3628a878..fb3b0c5e 100644
--- a/hashes/src/sha256d.rs
+++ b/hashes/src/sha256d.rs
@@ -5,6 +5,7 @@
 
 use core::ops::Index;
 use core::slice::SliceIndex;
+use core::str;
 
 use crate::{sha256, FromSliceError};
 
diff --git a/hashes/src/sha256t.rs b/hashes/src/sha256t.rs
index 593b9fd6..1a613cd3 100644
--- a/hashes/src/sha256t.rs
+++ b/hashes/src/sha256t.rs
@@ -3,10 +3,10 @@
 //! SHA256t implementation (tagged SHA256).
 //!
 
-use core::cmp;
 use core::marker::PhantomData;
 use core::ops::Index;
 use core::slice::SliceIndex;
+use core::{cmp, str};
 
 use crate::{sha256, FromSliceError};
 
@@ -66,7 +66,7 @@ impl<T: Tag> core::hash::Hash for Hash<T> {
     fn hash<H: core::hash::Hasher>(&self, h: &mut H) { self.0.hash(h) }
 }
 
-crate::internal_macros::hash_trait_impls!(256, false, T: Tag);
+crate::internal_macros::hash_trait_impls!(256, true, T: Tag);
 
 fn from_engine<T: Tag>(e: sha256::HashEngine) -> Hash<T> {
     use crate::Hash as _;
@@ -91,8 +91,8 @@ fn from_engine<T: Tag>(e: sha256::HashEngine) -> Hash<T> {
 ///     pub struct FooTag = hash_str("foo");
 ///
 ///     /// A foo hash.
-///     // Direction works just like the hash_newtype! macro.
-///     #[hash_newtype(backward)]
+///     // Direction works just like in case of hash_newtype! macro.
+///     #[hash_newtype(forward)]
 ///     pub struct FooHash(_);
 /// }
 /// ```
@@ -174,15 +174,6 @@ mod tests {
         108, 71, 99, 110, 96, 125, 179, 62, 234, 221, 198, 240, 201,
     ];
 
-    // The digest created by sha256 hashing `&[0]` starting with `TEST_MIDSTATE`.
-    #[cfg(feature = "alloc")]
-    const HASH_ZERO_BACKWARD: &str =
-        "29589d5122ec666ab5b4695070b6debc63881a4f85d88d93ddc90078038213ed";
-    // And the same thing, forward.
-    #[cfg(feature = "alloc")]
-    const HASH_ZERO_FORWARD: &str =
-        "ed1382037800c9dd938dd8854f1a8863bcdeb6705069b4b56a66ec22519d5829";
-
     #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
     pub struct TestHashTag;
 
@@ -194,45 +185,44 @@ fn engine() -> sha256::HashEngine {
         }
     }
 
-    // We support manually implementing `Tag` and creating a tagged hash from it.
-    #[cfg(feature = "alloc")]
-    pub type TestHash = sha256t::Hash<TestHashTag>;
-
-    #[test]
-    #[cfg(feature = "alloc")]
-    fn manually_created_sha256t_hash_type() {
-        assert_eq!(TestHash::hash(&[0]).to_string(), HASH_ZERO_FORWARD);
-    }
-
-    // We also provide a macro to create the tag and the hash type.
-    sha256t_hash_newtype! {
-        /// Test detailed explanation.
-        struct NewTypeTagBackward = raw(TEST_MIDSTATE, 64);
-
-        /// A test hash.
-        #[hash_newtype(backward)]
-        struct NewTypeHashBackward(_);
+    #[cfg(feature = "schemars")]
+    impl schemars::JsonSchema for TestHashTag {
+        fn schema_name() -> String { "Hash".to_owned() }
+
+        fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
+            let mut schema: schemars::schema::SchemaObject = <String>::json_schema(gen).into();
+            schema.string = Some(Box::new(schemars::schema::StringValidation {
+                max_length: Some(64 * 2),
+                min_length: Some(64 * 2),
+                pattern: Some("[0-9a-fA-F]+".to_owned()),
+            }));
+            schema.into()
+        }
     }
 
-    #[test]
+    /// A hash tagged with `$name`.
     #[cfg(feature = "alloc")]
-    fn macro_created_sha256t_hash_type_backward() {
-        assert_eq!(NewTypeHashBackward::hash(&[0]).to_string(), HASH_ZERO_BACKWARD);
-    }
+    pub type TestHash = sha256t::Hash<TestHashTag>;
 
-    // We also provide a macro to create the tag and the hash type.
     sha256t_hash_newtype! {
         /// Test detailed explanation.
-        struct NewTypeTagForward = raw(TEST_MIDSTATE, 64);
+        struct NewTypeTag = raw(TEST_MIDSTATE, 64);
 
         /// A test hash.
-        #[hash_newtype(forward)]
-        struct NewTypeHashForward(_);
+        #[hash_newtype(backward)]
+        struct NewTypeHash(_);
     }
 
     #[test]
     #[cfg(feature = "alloc")]
-    fn macro_created_sha256t_hash_type_prints_forward() {
-        assert_eq!(NewTypeHashForward::hash(&[0]).to_string(), HASH_ZERO_FORWARD);
+    fn test_sha256t() {
+        assert_eq!(
+            TestHash::hash(&[0]).to_string(),
+            "29589d5122ec666ab5b4695070b6debc63881a4f85d88d93ddc90078038213ed"
+        );
+        assert_eq!(
+            NewTypeHash::hash(&[0]).to_string(),
+            "29589d5122ec666ab5b4695070b6debc63881a4f85d88d93ddc90078038213ed"
+        );
     }
 }
diff --git a/hashes/src/sha384.rs b/hashes/src/sha384.rs
index ce23c311..a9438753 100644
--- a/hashes/src/sha384.rs
+++ b/hashes/src/sha384.rs
@@ -4,6 +4,7 @@
 
 use core::ops::Index;
 use core::slice::SliceIndex;
+use core::str;
 
 use crate::{sha512, FromSliceError};
 
diff --git a/hashes/src/sha512.rs b/hashes/src/sha512.rs
index 3d1f0967..ae6155f9 100644
--- a/hashes/src/sha512.rs
+++ b/hashes/src/sha512.rs
@@ -3,9 +3,9 @@
 //! SHA512 implementation.
 //!
 
-use core::cmp;
 use core::ops::Index;
 use core::slice::SliceIndex;
+use core::{cmp, str};
 
 use crate::{FromSliceError, HashEngine as _};
 
diff --git a/hashes/src/sha512_256.rs b/hashes/src/sha512_256.rs
index bcaee921..e5c36624 100644
--- a/hashes/src/sha512_256.rs
+++ b/hashes/src/sha512_256.rs
@@ -9,6 +9,7 @@
 
 use core::ops::Index;
 use core::slice::SliceIndex;
+use core::str;
 
 use crate::{sha512, FromSliceError};
 
diff --git a/hashes/src/siphash24.rs b/hashes/src/siphash24.rs
index 81434aef..a20c1d33 100644
--- a/hashes/src/siphash24.rs
+++ b/hashes/src/siphash24.rs
@@ -5,7 +5,7 @@
 
 use core::ops::Index;
 use core::slice::SliceIndex;
-use core::{cmp, mem, ptr};
+use core::{cmp, mem, ptr, str};
 
 use crate::{FromSliceError, Hash as _, HashEngine as _};
 
diff --git a/nightly-version b/nightly-version
index e70649f1..3548c285 100644
--- a/nightly-version
+++ b/nightly-version
@@ -1,1 +1,1 @@
-nightly-2024-05-04
+nightly-2024-05-02


Test patch:
diff --git a/base58/contrib/test_vars.sh b/base58/contrib/test_vars.sh
index c456fe0d..0c25edc7 100644
--- a/base58/contrib/test_vars.sh
+++ b/base58/contrib/test_vars.sh
@@ -1,6 +1,4 @@
 #!/usr/bin/env bash
-# disable verify unused vars, despite the fact that they are used when sourced
-# shellcheck disable=SC2034
 
 # Test all these features with "std" enabled.
 FEATURES_WITH_STD=""
diff --git a/bitcoin/contrib/test_vars.sh b/bitcoin/contrib/test_vars.sh
index cab77a50..e1d02314 100644
--- a/bitcoin/contrib/test_vars.sh
+++ b/bitcoin/contrib/test_vars.sh
@@ -1,6 +1,4 @@
 #!/usr/bin/env bash
-# disable verify unused vars, despite the fact that they are used when sourced
-# shellcheck disable=SC2034
 
 # Test all these features with "std" enabled.
 FEATURES_WITH_STD="rand-std serde secp-recovery bitcoinconsensus-std base64 ordered"
diff --git a/bitcoin/tests/bip_174.rs b/bitcoin/tests/bip_174.rs
index 522360c6..fa32a0ce 100644
--- a/bitcoin/tests/bip_174.rs
+++ b/bitcoin/tests/bip_174.rs
@@ -317,7 +317,8 @@ fn parse_and_verify_keys(
 
         let path =
             derivation_path.into_derivation_path().expect("failed to convert derivation path");
-        let derived_priv = ext_priv.derive_priv(secp, &path).to_priv();
+        let derived_priv =
+            ext_priv.derive_priv(secp, &path).expect("failed to derive ext priv key").to_priv();
         assert_eq!(wif_priv, derived_priv);
         let derived_pub = derived_priv.public_key(secp);
         key_map.insert(derived_pub, derived_priv);
diff --git a/contrib/test_vars.sh b/contrib/test_vars.sh
index c490c367..3f96f89e 100644
--- a/contrib/test_vars.sh
+++ b/contrib/test_vars.sh
@@ -1,11 +1,4 @@
 #!/usr/bin/env bash
-#
-# Used by labeler.yaml
-#
-# Not to be confused with the per crate `test_vars.sh` used by
-# `rust-bitcoin-maintainer-tools-run_task.sh`.
-# disable verify unused vars, despite the fact that they are used when sourced
-# shellcheck disable=SC2034
 
-CRATES="$(cargo metadata --no-deps --format-version 1 | jq -j -r '.packages | map(.manifest_path | rtrimstr("/Cargo.toml") | ltrimstr("'"$PWD"'/")) | join(" ")')"
+CRATES="`cargo metadata --no-deps --format-version 1 | jq -j -r '.packages | map(.manifest_path | rtrimstr("/Cargo.toml") | ltrimstr("'$PWD'/")) | join(" ")'`"
 DEPS="recent minimal"
diff --git a/hashes/contrib/test_vars.sh b/hashes/contrib/test_vars.sh
index 4020401c..5e89c21e 100644
--- a/hashes/contrib/test_vars.sh
+++ b/hashes/contrib/test_vars.sh
@@ -1,6 +1,4 @@
 #!/usr/bin/env bash
-# disable verify unused vars, despite the fact that they are used when sourced
-# shellcheck disable=SC2034
 
 # Test all these features with "std" enabled.
 FEATURES_WITH_STD="io serde small-hash schemars"
@@ -8,5 +6,8 @@ FEATURES_WITH_STD="io serde small-hash schemars"
 # Test all these features without "std" enabled.
 FEATURES_WITHOUT_STD="alloc serde small-hash"
 
+# Run address sanitizer with these features.
+ASAN_FEATURES="std io serde"
+
 # Run these examples.
 EXAMPLES=""
diff --git a/internals/contrib/test_vars.sh b/internals/contrib/test_vars.sh
index c1731775..759f5afb 100644
--- a/internals/contrib/test_vars.sh
+++ b/internals/contrib/test_vars.sh
@@ -1,6 +1,4 @@
 #!/usr/bin/env bash
-# disable verify unused vars, despite the fact that they are used when sourced
-# shellcheck disable=SC2034
 
 # Test all these features with "std" enabled.
 FEATURES_WITH_STD="serde"
diff --git a/io/contrib/test_vars.sh b/io/contrib/test_vars.sh
index df392afa..0317fc3b 100644
--- a/io/contrib/test_vars.sh
+++ b/io/contrib/test_vars.sh
@@ -1,6 +1,4 @@
 #!/usr/bin/env bash
-# disable verify unused vars, despite the fact that they are used when sourced
-# shellcheck disable=SC2034
 
 # Test all these features with "std" enabled.
 FEATURES_WITH_STD=""
diff --git a/units/contrib/test_vars.sh b/units/contrib/test_vars.sh
index c1731775..759f5afb 100644
--- a/units/contrib/test_vars.sh
+++ b/units/contrib/test_vars.sh
@@ -1,6 +1,4 @@
 #!/usr/bin/env bash
-# disable verify unused vars, despite the fact that they are used when sourced
-# shellcheck disable=SC2034
 
 # Test all these features with "std" enabled.
 FEATURES_WITH_STD="serde"