diff --git a/.actrc b/.actrc deleted file mode 100644 index a078f6406e..0000000000 --- a/.actrc +++ /dev/null @@ -1 +0,0 @@ --e contrib/act/event.json diff --git a/.cargo/mutants.toml b/.cargo/mutants.toml index 96f3735504..70a1b8fd3e 100644 --- a/.cargo/mutants.toml +++ b/.cargo/mutants.toml @@ -4,12 +4,13 @@ exclude_globs = [ "units/src/amount/verification.rs" # kani tests ] exclude_re = [ - "impl Debug", "impl Arbitrary", - "impl Display", + "impl Debug", + "impl fmt::Debug", ".*Error", "deserialize", # Skip serde mutation tests "Iterator", # Mutating operations in an iterator can result in an infinite loop + "match arm", "match guard", # New addition in cargo-mutants 25.0.1 deletes match arms and replaces match guards even in excluded functions # ----------------------------------Crate-specific exclusions---------------------------------- # Units @@ -21,6 +22,9 @@ exclude_re = [ "Time::to_consensus_u32", # Mutant from replacing | with ^, this returns the same value since the XOR is taken against the u16 with an all-zero bitmask "FeeRate::fee_vb", # Deprecated "FeeRate::fee_wu", # Deprecated + "SignedAmount::checked_abs", # Deprecated + "NumberOfBlocks::value", # Deprecated + "NumberOf512Seconds::to_consensus_u32", # Deprecated # primitives "Sequence::from_512_second_intervals", # Mutant from replacing | with ^, this returns the same value since the XOR is taken against the u16 with an all-zero bitmask diff --git a/.github/workflows/cargo-semver-checks-version b/.github/workflows/cargo-semver-checks-version index 9b0025a785..72a8a6313b 100644 --- a/.github/workflows/cargo-semver-checks-version +++ b/.github/workflows/cargo-semver-checks-version @@ -1 +1 @@ -0.40.0 +0.41.0 diff --git a/.github/workflows/cron-daily-fuzz.yml b/.github/workflows/cron-daily-fuzz.yml index 253be2af24..d3ab431e15 100644 --- a/.github/workflows/cron-daily-fuzz.yml +++ b/.github/workflows/cron-daily-fuzz.yml @@ -27,6 +27,7 @@ jobs: bitcoin_deserialize_witness, bitcoin_deser_net_msg, bitcoin_outpoint_string, + bitcoin_p2p_address_roundtrip, bitcoin_script_bytes_to_asm_fmt, hashes_json, hashes_ripemd160, diff --git a/.github/workflows/cron-weekly-rustfmt.yml b/.github/workflows/cron-weekly-rustfmt.yml index d35073c4a6..ba11521049 100644 --- a/.github/workflows/cron-weekly-rustfmt.yml +++ b/.github/workflows/cron-weekly-rustfmt.yml @@ -13,7 +13,8 @@ jobs: with: components: rustfmt - name: Run Nightly rustfmt - run: cargo +nightly fmt + # Run the formatter and manually remove trailing whitespace. + run: cargo +nightly fmt && find . -type f -name '*.rs' -exec sed -i 's/ $//' {} \; - name: Get the current date run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_ENV - name: Create Pull Request diff --git a/.github/workflows/stable-version b/.github/workflows/stable-version index 8510ffad03..f634271672 100644 --- a/.github/workflows/stable-version +++ b/.github/workflows/stable-version @@ -1 +1 @@ -1.85.1 +1.87.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 98107dc615..bf35defc27 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,6 +16,7 @@ changes to this document in a pull request. - [Contribution workflow](#contribution-workflow) * [Preparing PRs](#preparing-prs) * [Peer review](#peer-review) + * [CI and Merging](#merging) * [Repository maintainers](#repository-maintainers) - [Coding conventions](#coding-conventions) * [Naming conventions](#naming-conventions) @@ -145,23 +146,20 @@ test out the patch set and opine on the technical merits of the patch. Please, first review PR on the conceptual level before focusing on code style or grammar fixes. -### API changes - -The API of the following crates is almost stable. Changing it is supposed to be non-trivial. To -assist in this effort ll PRs that change the public API of any these crates must include a patch to -the `api/` text files. This should be a separate final patch to the PR that is the diff created by -running `just check-api`. +### CI and Merging -- `hashes` -- `io` -- `primitives` -- `units` +We use GitHub for CI as well to test the final state of each PR. -Check the [API text files](api/README.md) for more information -on how to install the dependencies and create the text files. +Also we use a local CI box which runs a large matrix of feature combinations as +well as testing each patch in a PR. This box is often very backlogged, sometimes +by multiple days. Please be patient, we will get to merging your PRs when the +backlog clears. ### Repository maintainers +Like all open source projects our maintainers are busy. Please take it easy on +them and only bump if you get no response for a week or two. + Pull request merge requirements: - all CI test should pass, - at least one "accepts"/ACKs from the repository maintainers @@ -421,7 +419,7 @@ hacking on this codebase. Consider the following format, not all sections will b /// /// ### Relevant BIPs /// -/// * [BIP X - FooBar in Bitcoin](https://github.com/bitcoin/bips/blob/master/bip-0000.mediawiki) +/// * [BIP X - FooBar in Bitcoin](https://github.com/bitcoin/bips/blob/master/bip-0001.mediawiki) pub struct FooBar { /// The version in use. pub version: Version diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index f1b5097fa9..1ff3ceaa33 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -106,11 +106,16 @@ dependencies = [ "bitcoin_hashes 0.16.0", ] +[[package]] +name = "bitcoin-p2p-messages" +version = "0.1.0" + [[package]] name = "bitcoin-primitives" version = "0.101.0" dependencies = [ "arbitrary", + "arrayvec", "bincode", "bitcoin-internals", "bitcoin-units", @@ -149,7 +154,6 @@ dependencies = [ "bitcoin-internals", "hex-conservative 0.3.0", "serde", - "serde_json", "serde_test", ] @@ -182,7 +186,7 @@ checksum = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" [[package]] name = "chacha20-poly1305" -version = "0.1.1" +version = "0.1.2" dependencies = [ "hex-conservative 0.3.0", ] @@ -214,6 +218,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4afe881d0527571892c4034822e59bb10c6c991cce6abe8199b6f5cf10766f55" dependencies = [ "arrayvec", + "serde", ] [[package]] diff --git a/Cargo-recent.lock b/Cargo-recent.lock index 558ade0a89..edfae1793f 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -105,11 +105,16 @@ dependencies = [ "bitcoin_hashes 0.16.0", ] +[[package]] +name = "bitcoin-p2p-messages" +version = "0.1.0" + [[package]] name = "bitcoin-primitives" version = "0.101.0" dependencies = [ "arbitrary", + "arrayvec", "bincode", "bitcoin-internals", "bitcoin-units", @@ -148,7 +153,6 @@ dependencies = [ "bitcoin-internals", "hex-conservative 0.3.0", "serde", - "serde_json", "serde_test", ] @@ -184,7 +188,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chacha20-poly1305" -version = "0.1.1" +version = "0.1.2" dependencies = [ "hex-conservative 0.3.0", ] @@ -216,6 +220,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4afe881d0527571892c4034822e59bb10c6c991cce6abe8199b6f5cf10766f55" dependencies = [ "arrayvec", + "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8f8a3c163c..d467712956 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,27 +1,9 @@ [workspace] -members = ["addresses", "base58", "bitcoin", "chacha20_poly1305", "fuzz", "hashes", "internals", "io", "primitives", "units"] +members = ["addresses", "base58", "bitcoin", "chacha20_poly1305", "fuzz", "hashes", "internals", "io", "p2p", "primitives", "units"] resolver = "2" -[patch.crates-io.bitcoin-addresses] -path = "addresses" - -[patch.crates-io.base58ck] -path = "base58" - -[patch.crates-io.bitcoin] -path = "bitcoin" - +# Keep this patch for hashes because secp256k1 depends on bitcoin-hashes via crates.io +# This allows testing changes to hashes with secp256k1 +# See https://github.com/rust-bitcoin/rust-bitcoin/pull/4284#pullrequestreview-2714442229 [patch.crates-io.bitcoin_hashes] path = "hashes" - -[patch.crates-io.bitcoin-internals] -path = "internals" - -[patch.crates-io.bitcoin-io] -path = "io" - -[patch.crates-io.bitcoin-primitives] -path = "primitives" - -[patch.crates-io.bitcoin-units] -path = "units" diff --git a/README.md b/README.md index 5cfe2c98a4..1a287bdac3 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ trustworthiness of each of your dependencies, including this one. This library **must not** be used for consensus code (i.e. fully validating blockchain data). It technically supports doing this, but doing so is very ill-advised because there are many deviations, known and unknown, between this library and the Bitcoin Core reference implementation. In a -consensus based cryptocurrency such as Bitcoin it is critical that all parties are using the same +consensus based cryptocurrency, such as Bitcoin, it is critical that all parties are using the same rules to validate data, and this library is simply unable to implement the same rules as Core. Given the complexity of both C++ and Rust, it is unlikely that this will ever be fixed, and there @@ -68,11 +68,11 @@ and to expand on existing docs would be extremely appreciated. Contributions are generally welcome. If you intend to make larger changes please discuss them in an issue before PRing them to avoid duplicate work and architectural mismatches. If you have any -questions or ideas you want to discuss please join us in +questions or ideas you want to discuss, please join us in [#bitcoin-rust](https://web.libera.chat/?channel=#bitcoin-rust) on [libera.chat](https://libera.chat). -For more information please see [`CONTRIBUTING.md`](./CONTRIBUTING.md). +For more information, please see [`CONTRIBUTING.md`](./CONTRIBUTING.md). ## Minimum Supported Rust Version (MSRV) @@ -83,7 +83,7 @@ Use `Cargo-minimal.lock` to build the MSRV by copying to `Cargo.lock` and buildi ## External dependencies We integrate with a few external libraries, most notably `serde`. These -are available via feature flags. To ensure compatibility and MSRV stability we +are available via feature flags. To ensure compatibility and MSRV stability, we provide two lock files as a means of inspecting compatible versions: `Cargo-minimal.lock` containing minimal versions of dependencies and `Cargo-recent.lock` containing recent versions of dependencies tested in our CI. @@ -152,8 +152,8 @@ developers, especially new contributors looking for something to work on, we do: - Mutation testing with [`cargo-mutants`](https://github.com/sourcefrog/cargo-mutants) - Code verification with [`Kani`](https://github.com/model-checking/kani) -There are always more tests to write and more bugs to find, contributions to our testing efforts -extremely welcomed. Please consider testing code a first class citizen, we definitely do take PRs +There are always more tests to write and more bugs to find. PRs are extremely welcomed. +Please consider testing code as a first-class citizen. We definitely do take PRs improving and cleaning up test code. ### Unit/Integration tests @@ -178,7 +178,7 @@ We have started using [kani](https://github.com/model-checking/kani), install wi ## Pull Requests -Every PR needs at least two reviews to get merged. During the review phase maintainers and +Every PR needs at least two reviews to get merged. During the review phase, maintainers and contributors are likely to leave comments and request changes. Please try to address them, otherwise your PR might get closed without merging after a longer time of inactivity. If your PR isn't ready for review yet please mark it by prefixing the title with `WIP: `. @@ -201,7 +201,7 @@ to your githooks folder or run `just githooks-install` to copy them all. Since the altcoin landscape includes projects which [frequently appear and disappear, and are poorly designed anyway](https://download.wpsoftware.net/bitcoin/alts.pdf) we do not support any altcoins. -Supporting Bitcoin properly is already difficult enough and we do not want to increase the +Supporting Bitcoin properly is already difficult enough, and we do not want to increase the maintenance burden and decrease API stability by adding support for other coins. Our code is public domain so by all means fork it and go wild :) diff --git a/addresses/src/lib.rs b/addresses/src/lib.rs index 15b64efdc3..06c99f4e99 100644 --- a/addresses/src/lib.rs +++ b/addresses/src/lib.rs @@ -21,6 +21,7 @@ // Exclude lints we don't think are valuable. #![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134 #![allow(clippy::manual_range_contains)] // More readable than clippy's format. +#![allow(clippy::uninlined_format_args)] // Allow `format!("{}", x)`instead of enforcing `format!("{x}")` extern crate alloc; diff --git a/base58/Cargo.toml b/base58/Cargo.toml index 06000bcbf9..7a64cf5abb 100644 --- a/base58/Cargo.toml +++ b/base58/Cargo.toml @@ -18,8 +18,8 @@ std = ["alloc", "hashes/std", "internals/std"] alloc = ["hashes/alloc", "internals/alloc"] [dependencies] -hashes = { package = "bitcoin_hashes", version = "0.16.0", default-features = false } -internals = { package = "bitcoin-internals", version = "0.4.0" } +hashes = { package = "bitcoin_hashes", path = "../hashes", default-features = false } +internals = { package = "bitcoin-internals", path = "../internals" } [dev-dependencies] hex = { package = "hex-conservative", version = "0.3.0", default-features = false, features = ["alloc"] } diff --git a/base58/src/error.rs b/base58/src/error.rs index 678c22d930..6b7b21f6c3 100644 --- a/base58/src/error.rs +++ b/base58/src/error.rs @@ -141,7 +141,7 @@ impl fmt::Display for TooShortError { #[cfg(feature = "std")] impl std::error::Error for TooShortError {} -/// Found a invalid ASCII byte while decoding base58 string. +/// Found an invalid ASCII byte while decoding base58 string. #[derive(Debug, Clone, PartialEq, Eq)] pub struct InvalidCharacterError(pub(super) InvalidCharacterErrorInner); diff --git a/base58/src/lib.rs b/base58/src/lib.rs index 13db9fd1f1..be1026fbd5 100644 --- a/base58/src/lib.rs +++ b/base58/src/lib.rs @@ -21,6 +21,7 @@ #![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134 #![allow(clippy::manual_range_contains)] // More readable than clippy's format. #![allow(clippy::incompatible_msrv)] // Has FPs and we're testing it which is more reliable anyway. +#![allow(clippy::uninlined_format_args)] // Allow `format!("{}", x)`instead of enforcing `format!("{x}")` extern crate alloc; @@ -113,7 +114,8 @@ pub fn decode(data: &str) -> Result, InvalidCharacterError> { /// Decodes a base58check-encoded string into a byte vector verifying the checksum. pub fn decode_check(data: &str) -> Result, Error> { let mut ret: Vec = decode(data)?; - let (remaining, &data_check) = ret.split_last_chunk::<4>().ok_or(TooShortError { length: ret.len() })?; + let (remaining, &data_check) = + ret.split_last_chunk::<4>().ok_or(TooShortError { length: ret.len() })?; let hash_check = *sha256d::Hash::hash(remaining).as_byte_array().sub_array::<0, 4>(); @@ -251,7 +253,7 @@ where mod tests { use alloc::vec; - use hex::test_hex_unwrap as hex; + use hex::FromHex as _; use super::*; @@ -280,7 +282,7 @@ mod tests { assert_eq!(&res, exp); // Addresses - let addr = hex!("00f8917303bfa8ef24f292e8fa1419b20460ba064d"); + let addr = Vec::from_hex("00f8917303bfa8ef24f292e8fa1419b20460ba064d").unwrap(); assert_eq!(&encode_check(&addr[..]), "1PfJpZsjreyVrqeoAfabrRwwjQyoSQMmHH"); } @@ -299,7 +301,7 @@ mod tests { // Addresses assert_eq!( decode_check("1PfJpZsjreyVrqeoAfabrRwwjQyoSQMmHH").ok(), - Some(hex!("00f8917303bfa8ef24f292e8fa1419b20460ba064d")) + Some(Vec::from_hex("00f8917303bfa8ef24f292e8fa1419b20460ba064d").unwrap()) ); // Non Base58 char. assert_eq!(decode("¢").unwrap_err(), InvalidCharacterError::new(194)); diff --git a/bitcoin/CHANGELOG.md b/bitcoin/CHANGELOG.md index 0972ca23b7..6b593c7bfc 100644 --- a/bitcoin/CHANGELOG.md +++ b/bitcoin/CHANGELOG.md @@ -4,6 +4,10 @@ - Use MAX_MONEY in serde regression test [#3950](https://github.com/rust-bitcoin/rust-bitcoin/pull/3950) +## Breaking changes + +- Change Psbt serde implementation to contextually use the PSBT binary or base64 encoded formats described in BIP-174. + # 0.33.0-alpha.0 - 2024-11-18 This series of alpha releases is meant for two things: @@ -28,8 +32,8 @@ For changes to our dependencies included in this release see: - `bitcoin_hashes 0.15`: [changelog](https://github.com/rust-bitcoin/rust-bitcoin/blob/master/hashes/CHANGELOG.md) - `hex-conservative 0.3`: [changelog](https://github.com/rust-bitcoin/hex-conservative/blob/master/CHANGELOG.md) - `bitcoin-io 0.2`: [changelog](https://github.com/rust-bitcoin/rust-bitcoin/blob/master/io/CHANGELOG.md) -- `bitcoin-primitives: 0.101`: [changelog]((https://github.com/rust-bitcoin/rust-bitcoin/blob/master/primitives/CHANGELOG.md)) -- `bitcoin-units 0.2`: [changelog]((https://github.com/rust-bitcoin/rust-bitcoin/blob/master/units/CHANGELOG.md)) +- `bitcoin-primitives: 0.101`: [changelog](https://github.com/rust-bitcoin/rust-bitcoin/blob/master/primitives/CHANGELOG.md) +- `bitcoin-units 0.2`: [changelog](https://github.com/rust-bitcoin/rust-bitcoin/blob/master/units/CHANGELOG.md) - `bitcoinconsensus: 0.106.0+26`: [changelog](https://github.com/rust-bitcoin/rust-bitcoinconsensus/blob/master/CHANGELOG.md) ## Changes @@ -41,7 +45,7 @@ For changes to our dependencies included in this release see: - Add `Address::into_unchecked` [#3640](https://github.com/rust-bitcoin/rust-bitcoin/pull/3640) - Mark `checked_` functions as const [#3636](https://github.com/rust-bitcoin/rust-bitcoin/pull/3636) - Mark functions const in `fee_rate` [#3627](https://github.com/rust-bitcoin/rust-bitcoin/pull/3627) -- Mark funtions const [#3608](https://github.com/rust-bitcoin/rust-bitcoin/pull/3608) +- Mark functions const [#3608](https://github.com/rust-bitcoin/rust-bitcoin/pull/3608) - Add constructor to `FeeRate` [#3604](https://github.com/rust-bitcoin/rust-bitcoin/pull/3604) - Fix bug in witness stack getters [#3601](https://github.com/rust-bitcoin/rust-bitcoin/pull/3601) - Split `checked_div_by_weight` into floor and ceiling version [#3587](https://github.com/rust-bitcoin/rust-bitcoin/pull/3587) @@ -68,7 +72,7 @@ For changes to our dependencies included in this release see: - transaction: Remove `Default` implementations [#3386](https://github.com/rust-bitcoin/rust-bitcoin/pull/3386) - Add `FeeRate` addition and subtraction traits [#3381](https://github.com/rust-bitcoin/rust-bitcoin/pull/3381) - Add `Xpriv::to_xpub` and improve related method names [#3358](https://github.com/rust-bitcoin/rust-bitcoin/pull/3358) -- Support `impl AsRef<[#u8]>` in `signed_msg_hash` [3357](https://github.com/rust-bitcoin/rust-bitcoin/pull/u8) +- Support `impl AsRef<[u8]>` in `signed_msg_hash` [#3357](https://github.com/rust-bitcoin/rust-bitcoin/pull/3357) - Fix `GetKey` for sets (plus some related changes) [#3356](https://github.com/rust-bitcoin/rust-bitcoin/pull/3356) - Add a condition for parsing zero from string when not denominated [#3346](https://github.com/rust-bitcoin/rust-bitcoin/pull/3346) - Add basic `miri` checks [#3328](https://github.com/rust-bitcoin/rust-bitcoin/pull/3328) @@ -84,7 +88,7 @@ For changes to our dependencies included in this release see: - `OP_RETURN` standardness check [#2949](https://github.com/rust-bitcoin/rust-bitcoin/pull/2949) - Support Testnet4 Network [#2945](https://github.com/rust-bitcoin/rust-bitcoin/pull/2945) - Remove `VarInt` and use `ReadExt` and `WriteExt` trait methods instead [#2931](https://github.com/rust-bitcoin/rust-bitcoin/pull/2931) -- bip32: Add `From<&'a [#u32]>` for `DerivationPath` [2909](https://github.com/rust-bitcoin/rust-bitcoin/pull/u32) +- bip32: Add `From<&'a [#u32]>` for `DerivationPath` [#2909](https://github.com/rust-bitcoin/rust-bitcoin/pull/2909) - psbt: Encode keytype as a compact size unsigned integer [#2906](https://github.com/rust-bitcoin/rust-bitcoin/pull/2906) - Pass sigs and associated types by value [#2899](https://github.com/rust-bitcoin/rust-bitcoin/pull/2899) - Re-export `UnprefixedHexError` in the bitcoin crate root [#2895](https://github.com/rust-bitcoin/rust-bitcoin/pull/2895) @@ -311,7 +315,7 @@ In particular consider having some type that implements `AsRef`, we have - Rename `ExtendedPubKey` to `Xpub` [#2019](https://github.com/rust-bitcoin/rust-bitcoin/pull/2019) - Rename `ExtendedPrivKey` to `Xpriv` [#2019](https://github.com/rust-bitcoin/rust-bitcoin/pull/2019) - Remove `_v0` from various function names (eg, `new_v0_p2wpkh`) [#1994](https://github.com/rust-bitcoin/rust-bitcoin/pull/1994) - - Remove `SighashCache::segwit_signature_hash` (add `p2wpkh_signiture_hash` and `p2wsh_signature_hash`) [#1995](https://github.com/rust-bitcoin/rust-bitcoin/pull/1995) + - Remove `SighashCache::segwit_signature_hash` (add `p2wpkh_signature_hash` and `p2wsh_signature_hash`) [#1995](https://github.com/rust-bitcoin/rust-bitcoin/pull/1995) - Reexport all the hash types from the crate root [#1998](https://github.com/rust-bitcoin/rust-bitcoin/pull/1998) - Rename `opcodes::All` to `Opcode` [#1995](https://github.com/rust-bitcoin/rust-bitcoin/pull/1995) - Removed `TxOut::default()`, the same logic now exists as `TxOut::NULL` [#1811](https://github.com/rust-bitcoin/rust-bitcoin/pull/1811) and [#1838](https://github.com/rust-bitcoin/rust-bitcoin/pull/1838) @@ -435,7 +439,7 @@ to support human-readable formats. [#1033](https://github.com/rust-bitcoin/rust-bitcoin/pull/1033) [#996](https://github.com/rust-bitcoin/rust-bitcoin/pull/996) [#1053](https://github.com/rust-bitcoin/rust-bitcoin/pull/1053) [#1023](https://github.com/rust-bitcoin/rust-bitcoin/pull/1023) - PSBT improvements - [#853](https://github.com/rust-bitcoin/rust-bitcoin/pull/853) [#951] (https://github.com/rust-bitcoin/rust-bitcoin/pull/951) + [#853](https://github.com/rust-bitcoin/rust-bitcoin/pull/853) [#951](https://github.com/rust-bitcoin/rust-bitcoin/pull/951) [#940](https://github.com/rust-bitcoin/rust-bitcoin/pull/940) - Script improvements [#1021](https://github.com/rust-bitcoin/rust-bitcoin/pull/1021) [#954](https://github.com/rust-bitcoin/rust-bitcoin/pull/954) diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml index 617af77715..16c43c44ba 100644 --- a/bitcoin/Cargo.toml +++ b/bitcoin/Cargo.toml @@ -16,33 +16,33 @@ exclude = ["tests", "contrib"] # If you change features or optional dependencies in any way please update the "# Cargo features" section in lib.rs as well. [features] default = [ "std", "secp-recovery" ] -std = ["base58/std", "bech32/std", "hashes/std", "hex/std", "internals/std", "io/std", "primitives/std", "secp256k1/std", "units/std", "bitcoinconsensus?/std"] +std = ["base58/std", "bech32/std", "hashes/std", "hex/std", "internals/std", "io/std", "primitives/std", "secp256k1/std", "units/std", "base64?/std", "bitcoinconsensus?/std"] rand-std = ["secp256k1/rand", "std"] rand = ["secp256k1/rand"] -serde = ["dep:serde", "hashes/serde", "internals/serde", "primitives/serde", "secp256k1/serde", "units/serde"] +serde = ["base64", "dep:serde", "hashes/serde", "internals/serde", "primitives/serde", "secp256k1/serde", "units/serde"] secp-lowmemory = ["secp256k1/lowmemory"] secp-recovery = ["secp256k1/recovery"] arbitrary = ["dep:arbitrary", "units/arbitrary", "primitives/arbitrary"] [dependencies] -base58 = { package = "base58ck", version = "0.2.0", default-features = false, features = ["alloc"] } +base58 = { package = "base58ck", path = "../base58", default-features = false, features = ["alloc"] } bech32 = { version = "0.11.0", default-features = false, features = ["alloc"] } -hashes = { package = "bitcoin_hashes", version = "0.16.0", default-features = false, features = ["alloc", "hex"] } +hashes = { package = "bitcoin_hashes", path = "../hashes", default-features = false, features = ["alloc", "hex"] } hex = { package = "hex-conservative", version = "0.3.0", default-features = false, features = ["alloc"] } -internals = { package = "bitcoin-internals", version = "0.4.0", features = ["alloc", "hex"] } -io = { package = "bitcoin-io", version = "0.2.0", default-features = false, features = ["alloc", "hashes"] } -primitives = { package = "bitcoin-primitives", version = "0.101.0", default-features = false, features = ["alloc"] } +internals = { package = "bitcoin-internals", path = "../internals", features = ["alloc", "hex"] } +io = { package = "bitcoin-io", path = "../io", default-features = false, features = ["alloc", "hashes"] } +primitives = { package = "bitcoin-primitives", path = "../primitives", default-features = false, features = ["alloc", "hex"] } secp256k1 = { version = "0.30.0", default-features = false, features = ["hashes", "alloc", "rand"] } -units = { package = "bitcoin-units", version = "0.2.0", default-features = false, features = ["alloc"] } +units = { package = "bitcoin-units", path = "../units", default-features = false, features = ["alloc"] } arbitrary = { version = "1.4", optional = true } -base64 = { version = "0.22.0", optional = true } +base64 = { version = "0.22.0", optional = true, default-features = false, features = ["alloc"] } # `bitcoinconsensus` version includes metadata which indicates the version of Core. Use `cargo tree` to see it. bitcoinconsensus = { version = "0.106.0", default-features = false, optional = true } serde = { version = "1.0.103", default-features = false, features = [ "derive", "alloc" ], optional = true } [dev-dependencies] -internals = { package = "bitcoin-internals", version = "0.4.0", features = ["test-serde"] } +internals = { package = "bitcoin-internals", path = "../internals", features = ["test-serde"] } serde_json = "1.0.0" serde_test = "1.0.19" bincode = "1.3.1" @@ -94,5 +94,9 @@ name = "sighash" name = "io" required-features = ["std"] +[[example]] +name = "script" +required-features = ["std"] + [lints.rust] unexpected_cfgs = { level = "deny", check-cfg = ['cfg(bench)', 'cfg(fuzzing)', 'cfg(kani)'] } diff --git a/bitcoin/embedded/Cargo.toml b/bitcoin/embedded/Cargo.toml index e754e76ff3..8a6f7ecb96 100644 --- a/bitcoin/embedded/Cargo.toml +++ b/bitcoin/embedded/Cargo.toml @@ -10,10 +10,8 @@ version = "0.1.0" members = ["."] [dependencies] -cortex-m = "0.6.0" cortex-m-rt = "0.6.10" cortex-m-semihosting = "0.3.3" -panic-halt = "0.2.0" alloc-cortex-m = "0.4.1" bitcoin = { path="../", default-features = false, features = ["secp-lowmemory"] } @@ -27,20 +25,6 @@ codegen-units = 1 # better optimizations debug = true # symbols are nice and they don't increase the size on Flash lto = true # better optimizations -[patch.crates-io.base58ck] -path = "../../base58" [patch.crates-io.bitcoin_hashes] path = "../../hashes" - -[patch.crates-io.bitcoin-internals] -path = "../../internals" - -[patch.crates-io.bitcoin-io] -path = "../../io" - -[patch.crates-io.bitcoin-primitives] -path = "../../primitives" - -[patch.crates-io.bitcoin-units] -path = "../../units" diff --git a/bitcoin/examples/bip32.rs b/bitcoin/examples/bip32.rs index 9df5ef6732..41f3791ea6 100644 --- a/bitcoin/examples/bip32.rs +++ b/bitcoin/examples/bip32.rs @@ -22,7 +22,7 @@ fn main() { } let seed_hex = &args[1]; - println!("Seed: {}", seed_hex); + println!("Seed: {seed_hex}"); println!("Using mainnet network"); let seed = Vec::from_hex(seed_hex).unwrap(); @@ -33,20 +33,20 @@ fn main() { let secp = Secp256k1::preallocated_new(buf.as_mut_slice()).unwrap(); // calculate root key from seed - let root = Xpriv::new_master(NetworkKind::Main, &seed).unwrap(); - println!("Root key: {}", root); + let root = Xpriv::new_master(NetworkKind::Main, &seed); + println!("Root key: {root}"); // derive child xpub let path = "84h/0h/0h".parse::().unwrap(); - let child = root.derive_xpriv(&secp, &path); - println!("Child at {}: {}", path, child); + let child = root.derive_xpriv(&secp, &path).expect("only deriving three steps"); + println!("Child at {path}: {child}"); let xpub = Xpub::from_xpriv(&secp, &child); - println!("Public key at {}: {}", path, xpub); + println!("Public key at {path}: {xpub}"); // generate first receiving address at m/0/0 // manually creating indexes this time let zero = ChildNumber::ZERO_NORMAL; let public_key = xpub.derive_xpub(&secp, &[zero, zero]).unwrap().public_key; let address = Address::p2wpkh(CompressedPublicKey(public_key), KnownHrp::Mainnet); - println!("First receiving address: {}", address); + println!("First receiving address: {address}"); } diff --git a/bitcoin/examples/create-p2wpkh-address.rs b/bitcoin/examples/create-p2wpkh-address.rs index b8ec758f82..e92ffe5312 100644 --- a/bitcoin/examples/create-p2wpkh-address.rs +++ b/bitcoin/examples/create-p2wpkh-address.rs @@ -19,6 +19,6 @@ fn main() { // Create a Bitcoin P2WPKH address. let address = Address::p2wpkh(public_key, Network::Bitcoin); - println!("Private Key: {}", private_key); - println!("Address: {}", address); + println!("Private Key: {private_key}"); + println!("Address: {address}"); } diff --git a/bitcoin/examples/ecdsa-psbt-simple.rs b/bitcoin/examples/ecdsa-psbt-simple.rs index ac5d84b43e..73f209bb2e 100644 --- a/bitcoin/examples/ecdsa-psbt-simple.rs +++ b/bitcoin/examples/ecdsa-psbt-simple.rs @@ -62,11 +62,12 @@ fn get_external_address_xpriv( ) -> Xpriv { let derivation_path = BIP84_DERIVATION_PATH.into_derivation_path().expect("valid derivation path"); - let child_xpriv = master_xpriv.derive_xpriv(secp, &derivation_path); + let child_xpriv = + master_xpriv.derive_xpriv(secp, &derivation_path).expect("only deriving three steps"); let external_index = ChildNumber::ZERO_NORMAL; let idx = ChildNumber::from_normal_idx(index).expect("valid index number"); - child_xpriv.derive_xpriv(secp, &[external_index, idx]) + child_xpriv.derive_xpriv(secp, &[external_index, idx]).expect("only deriving two more steps") } // Derive the internal address xpriv. @@ -77,11 +78,12 @@ fn get_internal_address_xpriv( ) -> Xpriv { let derivation_path = BIP84_DERIVATION_PATH.into_derivation_path().expect("valid derivation path"); - let child_xpriv = master_xpriv.derive_xpriv(secp, &derivation_path); + let child_xpriv = + master_xpriv.derive_xpriv(secp, &derivation_path).expect("only deriving three steps"); let internal_index = ChildNumber::ONE_NORMAL; let idx = ChildNumber::from_normal_idx(index).expect("valid index number"); - child_xpriv.derive_xpriv(secp, &[internal_index, idx]) + child_xpriv.derive_xpriv(secp, &[internal_index, idx]).expect("only deriving two more steps") } // The address to send to. @@ -242,8 +244,8 @@ fn main() { // BOOM! Transaction signed and ready to broadcast. let signed_tx = psbt.extract_tx().expect("valid transaction"); let serialized_signed_tx = consensus::encode::serialize_hex(&signed_tx); - println!("Transaction Details: {:#?}", signed_tx); + println!("Transaction Details: {signed_tx:#?}"); // check with: // bitcoin-cli decoderawtransaction true - println!("Raw Transaction: {}", serialized_signed_tx); + println!("Raw Transaction: {serialized_signed_tx}"); } diff --git a/bitcoin/examples/ecdsa-psbt.rs b/bitcoin/examples/ecdsa-psbt.rs index e6bbaf1e66..0ac297654c 100644 --- a/bitcoin/examples/ecdsa-psbt.rs +++ b/bitcoin/examples/ecdsa-psbt.rs @@ -5,7 +5,7 @@ //! //! You can verify the workflow using `bitcoind` and `bitcoin-cli`. //! -//! ## Example Setup +//! # Example Setup //! //! 1. Start Bitcoin Core in Regtest mode, for example: //! @@ -87,7 +87,7 @@ fn main() -> Result<()> { tx.verify(|_| Some(previous_output())).expect("failed to verify transaction"); let hex = encode::serialize_hex(&tx); - println!("You should now be able to broadcast the following transaction: \n\n{}", hex); + println!("You should now be able to broadcast the following transaction: \n\n{hex}"); Ok(()) } @@ -118,11 +118,12 @@ impl ColdStorage { // Hardened children require secret data to derive. let path = "84h/0h/0h".into_derivation_path()?; - let account_0_xpriv = master_xpriv.derive_xpriv(secp, &path); + let account_0_xpriv = + master_xpriv.derive_xpriv(secp, &path).expect("derivation path is short"); let account_0_xpub = Xpub::from_xpriv(secp, &account_0_xpriv); let path = INPUT_UTXO_DERIVATION_PATH.into_derivation_path()?; - let input_xpriv = master_xpriv.derive_xpriv(secp, &path); + let input_xpriv = master_xpriv.derive_xpriv(secp, &path).expect("derivation path is short"); let input_xpub = Xpub::from_xpriv(secp, &input_xpriv); let wallet = ColdStorage { master_xpriv, master_xpub }; @@ -275,7 +276,7 @@ fn input_derivation_path() -> Result { } fn previous_output() -> TxOut { - let script_pubkey = ScriptBuf::from_hex(INPUT_UTXO_SCRIPT_PUBKEY) + let script_pubkey = ScriptBuf::from_hex_no_length_prefix(INPUT_UTXO_SCRIPT_PUBKEY) .expect("failed to parse input utxo scriptPubkey"); let amount = INPUT_UTXO_VALUE.parse::().expect("failed to parse input utxo value"); diff --git a/bitcoin/examples/handshake.rs b/bitcoin/examples/handshake.rs index db5a4fb171..4382de02a7 100644 --- a/bitcoin/examples/handshake.rs +++ b/bitcoin/examples/handshake.rs @@ -19,7 +19,7 @@ fn main() { let str_address = &args[1]; let address: SocketAddr = str_address.parse().unwrap_or_else(|error| { - eprintln!("error parsing address: {:?}", error); + eprintln!("error parsing address: {error:?}"); process::exit(1); }); diff --git a/bitcoin/examples/script.rs b/bitcoin/examples/script.rs new file mode 100644 index 0000000000..930b4899e0 --- /dev/null +++ b/bitcoin/examples/script.rs @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Demonstrates the API for parsing and formatting Bitcoin scripts. +//! +//! Bitcoin script is conceptually a vector of bytes. As such it is consensus encoded with a compact +//! size encoded length prefix. See [CompactSize]. +//! +//! [`CompactSize`]: + +use bitcoin::consensus::encode; +use bitcoin::key::WPubkeyHash; +use bitcoin::script::{self, ScriptBufExt, ScriptExt}; +use bitcoin::ScriptBuf; + +fn main() { + let pk = "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb".parse::().unwrap(); + + // TL;DR Use `to_hex_string_prefixed` and `from_hex_prefixed`. + let script_code = script::p2wpkh_script_code(pk); + let hex = script_code.to_hex_string_prefixed(); + let decoded = ScriptBuf::from_hex_prefixed(&hex).unwrap(); + assert_eq!(decoded, script_code); + + // Or if you prefer: `to_hex_string_no_length_prefix` and `from_hex_no_length_prefix`. + let script_code = script::p2wpkh_script_code(pk); + let hex = script_code.to_hex_string_no_length_prefix(); + let decoded = ScriptBuf::from_hex_no_length_prefix(&hex).unwrap(); + assert_eq!(decoded, script_code); + + // Writes the script as human-readable eg, OP_DUP OP_HASH160 OP_PUSHBYTES_20 ... + println!("human-readable script: {script_code}"); + + // We do not implement parsing scripts from human-readable format. + // let decoded = s.parse::().unwrap(); + + // This is not equivalent to consensus encoding i.e., does not include the length prefix. + let hex_lower_hex_trait = format!("{script_code:x}"); + println!("hex created using `LowerHex`: {hex_lower_hex_trait}"); + + // The `deserialize_hex` function requires the length prefix. + assert!(encode::deserialize_hex::(&hex_lower_hex_trait).is_err()); + // And so does `from_hex_prefixed`. + assert!(ScriptBuf::from_hex_prefixed(&hex_lower_hex_trait).is_err()); + // But we provide an explicit constructor that does not. + assert_eq!(ScriptBuf::from_hex_no_length_prefix(&hex_lower_hex_trait).unwrap(), script_code); + + // This is consensus encoding i.e., includes the length prefix. + let hex_inherent = script_code.to_hex_string_prefixed(); // Defined in `ScriptExt`. + println!("hex created using inherent `to_hex_string_prefixed`: {hex_inherent}"); + + // The inverse of `to_hex_string_prefixed` is `from_hex_string_prefixed`. + let decoded = ScriptBuf::from_hex_prefixed(&hex_inherent).unwrap(); // Defined in `ScriptBufExt`. + assert_eq!(decoded, script_code); + // We can also parse the output of `to_hex_string_prefixed` using `deserialize_hex`. + let decoded = encode::deserialize_hex::(&hex_inherent).unwrap(); + assert_eq!(decoded, script_code); + + // We also support encode/decode using `consensus::encode` functions. + let encoded = encode::serialize_hex(&script_code); + println!("hex created using consensus::encode::serialize_hex: {encoded}"); + + let decoded: ScriptBuf = encode::deserialize_hex(&encoded).unwrap(); + assert_eq!(decoded, script_code); + + // And we can mix these to calls because both include the length prefix. + let encoded = encode::serialize_hex(&script_code); + let decoded = ScriptBuf::from_hex_prefixed(&encoded).unwrap(); + assert_eq!(decoded, script_code); + + // Encode/decode using a byte vector. + let encoded = encode::serialize(&script_code); + assert_eq!(&encoded[1..], script_code.as_bytes()); // Shows that prefix is the first byte. + let decoded: ScriptBuf = encode::deserialize(&encoded).unwrap(); + assert_eq!(decoded, script_code); + + // to/from bytes excludes the prefix, these are not encoding/decoding functions so this is sane. + let bytes = script_code.to_bytes(); + let got = ScriptBuf::from_bytes(bytes); + assert_eq!(got, script_code); +} diff --git a/bitcoin/examples/sighash.rs b/bitcoin/examples/sighash.rs index 1055a52f7e..bfa82216eb 100644 --- a/bitcoin/examples/sighash.rs +++ b/bitcoin/examples/sighash.rs @@ -24,7 +24,7 @@ fn compute_sighash_p2wpkh(raw_tx: &[u8], inp_idx: usize, amount: Amount) { let tx: Transaction = consensus::deserialize(raw_tx).unwrap(); let inp = &tx.input[inp_idx]; let witness = &inp.witness; - println!("Witness: {:?}", witness); + println!("Witness: {witness:?}"); // BIP-141: The witness must consist of exactly 2 items (≤ 520 bytes each). The first one a // signature, and the second one a public key. @@ -38,16 +38,16 @@ fn compute_sighash_p2wpkh(raw_tx: &[u8], inp_idx: usize, amount: Amount) { //this is nothing but a standard P2PKH script OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG: let pk = CompressedPublicKey::from_slice(pk_bytes).expect("failed to parse pubkey"); let wpkh = pk.wpubkey_hash(); - println!("Script pubkey hash: {:x}", wpkh); + println!("Script pubkey hash: {wpkh:x}"); let spk = ScriptBuf::new_p2wpkh(wpkh); let mut cache = sighash::SighashCache::new(&tx); let sighash = cache .p2wpkh_signature_hash(inp_idx, &spk, amount, sig.sighash_type) .expect("failed to compute sighash"); - println!("SegWit p2wpkh sighash: {:x}", sighash); + println!("SegWit p2wpkh sighash: {sighash:x}"); let msg = secp256k1::Message::from(sighash); - println!("Message is {:x}", msg); + println!("Message is {msg:x}"); let secp = secp256k1::Secp256k1::verification_only(); pk.verify(&secp, msg, sig).unwrap() } @@ -63,7 +63,7 @@ fn compute_sighash_legacy(raw_tx: &[u8], inp_idx: usize, script_pubkey_bytes_opt let tx: Transaction = consensus::deserialize(raw_tx).unwrap(); let inp = &tx.input[inp_idx]; let script_sig = &inp.script_sig; - println!("scriptSig is: {}", script_sig); + println!("scriptSig is: {script_sig}"); let cache = sighash::SighashCache::new(&tx); //In the P2SH case we get scriptPubKey from scriptSig of the spending input. //The scriptSig that corresponds to an M of N multisig should be: PUSHBYTES_0 PUSHBYTES_K0 ... PUSHBYTES_Km PUSHBYTES_X @@ -83,8 +83,7 @@ fn compute_sighash_legacy(raw_tx: &[u8], inp_idx: usize, script_pubkey_bytes_opt let pushbytes_0 = instructions.remove(0).unwrap(); assert!( pushbytes_0.push_bytes().unwrap().as_bytes().is_empty(), - "first in ScriptSig must be PUSHBYTES_0 got {:?}", - pushbytes_0 + "first in ScriptSig must be PUSHBYTES_0 got {pushbytes_0:?}" ); //All other scriptSig instructions must be signatures @@ -109,7 +108,7 @@ fn compute_sighash_p2wsh(raw_tx: &[u8], inp_idx: usize, amount: Amount) { let tx: Transaction = consensus::deserialize(raw_tx).unwrap(); let inp = &tx.input[inp_idx]; let witness = &inp.witness; - println!("witness {:?}", witness); + println!("witness {witness:?}"); //last element is called witnessScript according to BIP141. It supersedes scriptPubKey. let witness_script_bytes: &[u8] = witness.last().expect("out of bounds"); @@ -122,7 +121,7 @@ fn compute_sighash_p2wsh(raw_tx: &[u8], inp_idx: usize, amount: Amount) { let sig = ecdsa::Signature::from_slice(sig_bytes).expect("failed to parse sig"); let sig_len = sig_bytes.len() - 1; //last byte is EcdsaSighashType sighash flag //ECDSA signature in DER format lengths are between 70 and 72 bytes - assert!((70..=72).contains(&sig_len), "signature length {} out of bounds", sig_len); + assert!((70..=72).contains(&sig_len), "signature length {sig_len} out of bounds"); //here we assume that all sighash_flags are the same. Can they be different? let sighash = cache .p2wsh_signature_hash(inp_idx, witness_script, amount, sig.sighash_type) diff --git a/bitcoin/examples/sign-tx-segwit-v0.rs b/bitcoin/examples/sign-tx-segwit-v0.rs index b163a24b08..e9a49385d5 100644 --- a/bitcoin/examples/sign-tx-segwit-v0.rs +++ b/bitcoin/examples/sign-tx-segwit-v0.rs @@ -82,7 +82,7 @@ fn main() { let tx = sighasher.into_transaction(); // BOOM! Transaction signed and ready to broadcast. - println!("{:#?}", tx); + println!("{tx:#?}"); } /// An example of keys controlled by the transaction sender. diff --git a/bitcoin/examples/sign-tx-taproot.rs b/bitcoin/examples/sign-tx-taproot.rs index 06a3fab162..a68c968b4b 100644 --- a/bitcoin/examples/sign-tx-taproot.rs +++ b/bitcoin/examples/sign-tx-taproot.rs @@ -71,7 +71,7 @@ fn main() { // Sign the sighash using the secp256k1 library (exported by rust-bitcoin). let tweaked: TweakedKeypair = keypair.tap_tweak(&secp, None); let msg = Message::from(sighash); - let signature = secp.sign_schnorr(msg.as_ref(), &tweaked.to_inner()); + let signature = secp.sign_schnorr(msg.as_ref(), tweaked.as_keypair()); // Update the witness stack. let signature = bitcoin::taproot::Signature { signature, sighash_type }; @@ -81,7 +81,7 @@ fn main() { let tx = sighasher.into_transaction(); // BOOM! Transaction signed and ready to broadcast. - println!("{:#?}", tx); + println!("{tx:#?}"); } /// An example of keys controlled by the transaction sender. @@ -113,10 +113,11 @@ fn receivers_address() -> Address { /// /// This output is locked to keys that we control, in a real application this would be a valid /// output taken from a transaction that appears in the chain. -fn dummy_unspent_transaction_output( +fn dummy_unspent_transaction_output>( secp: &Secp256k1, - internal_key: UntweakedPublicKey, + internal_key: K, ) -> (OutPoint, TxOut) { + let internal_key = internal_key.into(); let script_pubkey = ScriptBuf::new_p2tr(secp, internal_key, None); let out_point = OutPoint { diff --git a/bitcoin/examples/taproot-psbt-simple.rs b/bitcoin/examples/taproot-psbt-simple.rs index 8181573a27..c35dbe4635 100644 --- a/bitcoin/examples/taproot-psbt-simple.rs +++ b/bitcoin/examples/taproot-psbt-simple.rs @@ -60,11 +60,12 @@ fn get_external_address_xpriv( ) -> Xpriv { let derivation_path = BIP86_DERIVATION_PATH.into_derivation_path().expect("valid derivation path"); - let child_xpriv = master_xpriv.derive_xpriv(secp, &derivation_path); + let child_xpriv = + master_xpriv.derive_xpriv(secp, &derivation_path).expect("only deriving three steps"); let external_index = ChildNumber::ZERO_NORMAL; let idx = ChildNumber::from_normal_idx(index).expect("valid index number"); - child_xpriv.derive_xpriv(secp, &[external_index, idx]) + child_xpriv.derive_xpriv(secp, &[external_index, idx]).expect("only deriving two more steps") } // Derive the internal address xpriv. @@ -75,19 +76,21 @@ fn get_internal_address_xpriv( ) -> Xpriv { let derivation_path = BIP86_DERIVATION_PATH.into_derivation_path().expect("valid derivation path"); - let child_xpriv = master_xpriv.derive_xpriv(secp, &derivation_path); + let child_xpriv = + master_xpriv.derive_xpriv(secp, &derivation_path).expect("only deriving three steps"); let internal_index = ChildNumber::ONE_NORMAL; let idx = ChildNumber::from_normal_idx(index).expect("valid index number"); - child_xpriv.derive_xpriv(secp, &[internal_index, idx]) + child_xpriv.derive_xpriv(secp, &[internal_index, idx]).expect("only deriving two more steps") } // Get the Taproot Key Origin. -fn get_tap_key_origin( - x_only_key: UntweakedPublicKey, +fn get_tap_key_origin + std::cmp::Ord>( + x_only_key: K, master_fingerprint: Fingerprint, path: DerivationPath, ) -> BTreeMap, (Fingerprint, DerivationPath))> { + let x_only_key = x_only_key.into(); let mut map = BTreeMap::new(); map.insert(x_only_key, (vec![], (master_fingerprint, path))); map @@ -210,14 +213,14 @@ fn main() { Input { witness_utxo: Some(utxos[0].clone()), tap_key_origins: origins[0].clone(), - tap_internal_key: Some(pk_input_1), + tap_internal_key: Some(pk_input_1.into()), sighash_type: Some(ty), ..Default::default() }, Input { witness_utxo: Some(utxos[1].clone()), tap_key_origins: origins[1].clone(), - tap_internal_key: Some(pk_input_2), + tap_internal_key: Some(pk_input_2.into()), sighash_type: Some(ty), ..Default::default() }, @@ -242,8 +245,8 @@ fn main() { // BOOM! Transaction signed and ready to broadcast. let signed_tx = psbt.extract_tx().expect("valid transaction"); let serialized_signed_tx = consensus::encode::serialize_hex(&signed_tx); - println!("Transaction Details: {:#?}", signed_tx); + println!("Transaction Details: {signed_tx:#?}"); // check with: // bitcoin-cli decoderawtransaction true - println!("Raw Transaction: {}", serialized_signed_tx); + println!("Raw Transaction: {serialized_signed_tx}"); } diff --git a/bitcoin/examples/taproot-psbt.rs b/bitcoin/examples/taproot-psbt.rs index 92af851561..39b70f9f4e 100644 --- a/bitcoin/examples/taproot-psbt.rs +++ b/bitcoin/examples/taproot-psbt.rs @@ -125,8 +125,7 @@ fn main() -> Result<(), Box> { ], )?); println!( - "\nYou should now be able to broadcast the following transaction: \n\n{}", - tx_hex_string + "\nYou should now be able to broadcast the following transaction: \n\n{tx_hex_string}" ); println!("\nEND EXAMPLE 1\n"); @@ -147,7 +146,7 @@ fn main() -> Result<(), Box> { )?; let tx_hex = encode::serialize_hex(&tx); - println!("Inheritance funding tx hex:\n\n{}", tx_hex); + println!("Inheritance funding tx hex:\n\n{tx_hex}"); // You can now broadcast the transaction hex: // bt sendrawtransaction ... // @@ -160,7 +159,7 @@ fn main() -> Result<(), Box> { to_address, )?; let spending_tx_hex = encode::serialize_hex(&spending_tx); - println!("\nInheritance spending tx hex:\n\n{}", spending_tx_hex); + println!("\nInheritance spending tx hex:\n\n{spending_tx_hex}"); // If you try to broadcast now, the transaction will be rejected as it is timelocked. // First mine 900 blocks so we're sure we are over the 1000 block locktime: // bt generatetoaddress 900 $(bt-benefactor getnewaddress '' 'bech32m') @@ -185,7 +184,7 @@ fn main() -> Result<(), Box> { )?; let tx_hex = encode::serialize_hex(&tx); - println!("Inheritance funding tx hex:\n\n{}", tx_hex); + println!("Inheritance funding tx hex:\n\n{tx_hex}"); // You can now broadcast the transaction hex: // bt sendrawtransaction ... // @@ -200,7 +199,7 @@ fn main() -> Result<(), Box> { let (tx, _) = benefactor.refresh_tx(1000)?; let tx_hex = encode::serialize_hex(&tx); - println!("\nRefreshed inheritance tx hex:\n\n{}\n", tx_hex); + println!("\nRefreshed inheritance tx hex:\n\n{tx_hex}\n"); println!("\nEND EXAMPLE 3\n"); println!("----------------\n"); @@ -257,7 +256,7 @@ fn generate_bip86_key_spend_tx( let mut input = Input { witness_utxo: { - let script_pubkey = ScriptBuf::from_hex(input_utxo.script_pubkey) + let script_pubkey = ScriptBuf::from_hex_no_length_prefix(input_utxo.script_pubkey) .expect("failed to parse input utxo scriptPubkey"); Some(TxOut { value: from_amount, script_pubkey }) }, @@ -275,7 +274,7 @@ fn generate_bip86_key_spend_tx( for input in [&input_utxo].iter() { input_txouts.push(TxOut { value: input.amount, - script_pubkey: ScriptBuf::from_hex(input.script_pubkey)?, + script_pubkey: ScriptBuf::from_hex_no_length_prefix(input.script_pubkey)?, }); } @@ -299,7 +298,7 @@ fn generate_bip86_key_spend_tx( .ok_or("missing Taproot key origin")?; let secret_key = - master_xpriv.derive_xpriv(secp, &derivation_path).to_private_key().inner; + master_xpriv.derive_xpriv(secp, &derivation_path)?.to_private_key().inner; sign_psbt_taproot( secret_key, input.tap_internal_key.unwrap(), @@ -333,7 +332,7 @@ fn generate_bip86_key_spend_tx( tx.verify(|_| { Some(TxOut { value: from_amount, - script_pubkey: ScriptBuf::from_hex(input_utxo.script_pubkey).unwrap(), + script_pubkey: ScriptBuf::from_hex_no_length_prefix(input_utxo.script_pubkey).unwrap(), }) }) .expect("failed to verify transaction"); @@ -393,8 +392,11 @@ impl BenefactorWallet { // We use some other derivation path in this example for our inheritance protocol. The important thing is to ensure // that we use an unhardened path so we can make use of xpubs. let derivation_path = format!("101/1/0/0/{}", self.next).parse::()?; - let internal_keypair = - self.master_xpriv.derive_xpriv(&self.secp, &derivation_path).to_keypair(&self.secp); + let internal_keypair = self + .master_xpriv + .derive_xpriv(&self.secp, &derivation_path) + .expect("derivation path is short") + .to_keypair(&self.secp); let beneficiary_key = self.beneficiary_xpub.derive_xpub(&self.secp, &derivation_path)?.to_x_only_public_key(); @@ -442,7 +444,7 @@ impl BenefactorWallet { (vec![leaf_hash], (self.beneficiary_xpub.fingerprint(), derivation_path.clone())), ); origins.insert( - internal_keypair.x_only_public_key().0, + internal_keypair.x_only_public_key().0.into(), (vec![], (self.master_xpriv.fingerprint(&self.secp), derivation_path)), ); let ty = "SIGHASH_ALL".parse::()?; @@ -457,7 +459,7 @@ impl BenefactorWallet { tap_key_origins: origins, tap_merkle_root: taproot_spend_info.merkle_root(), sighash_type: Some(ty), - tap_internal_key: Some(internal_keypair.x_only_public_key().0), + tap_internal_key: Some(internal_keypair.x_only_public_key().0.into()), tap_scripts, ..Default::default() }; @@ -486,6 +488,7 @@ impl BenefactorWallet { let new_internal_keypair = self .master_xpriv .derive_xpriv(&self.secp, &new_derivation_path) + .expect("derivation path is short") .to_keypair(&self.secp); let beneficiary_key = self .beneficiary_xpub @@ -538,6 +541,7 @@ impl BenefactorWallet { let secret_key = self .master_xpriv .derive_xpriv(&self.secp, &derivation_path) + .expect("derivation path is short") .to_private_key() .inner; sign_psbt_taproot( @@ -608,7 +612,7 @@ impl BenefactorWallet { tap_key_origins: origins, tap_merkle_root: taproot_spend_info.merkle_root(), sighash_type: Some(ty), - tap_internal_key: Some(new_internal_keypair.x_only_public_key().0), + tap_internal_key: Some(new_internal_keypair.x_only_public_key().0.into()), tap_scripts, ..Default::default() }; @@ -660,8 +664,11 @@ impl BeneficiaryWallet { for (x_only_pubkey, (leaf_hashes, (_, derivation_path))) in &psbt.inputs[0].tap_key_origins.clone() { - let secret_key = - self.master_xpriv.derive_xpriv(&self.secp, &derivation_path).to_private_key().inner; + let secret_key = self + .master_xpriv + .derive_xpriv(&self.secp, &derivation_path)? + .to_private_key() + .inner; for lh in leaf_hashes { let sighash_type = TapSighashType::All; let hash = SighashCache::new(&unsigned_tx).taproot_script_spend_signature_hash( @@ -744,7 +751,7 @@ fn sign_psbt_taproot( ) { let keypair = secp256k1::Keypair::from_seckey_slice(secp, secret_key.as_ref()).unwrap(); let keypair = match leaf_hash { - None => keypair.tap_tweak(secp, psbt_input.tap_merkle_root).to_inner(), + None => keypair.tap_tweak(secp, psbt_input.tap_merkle_root).to_keypair(), Some(_) => keypair, // no tweak for script spend }; diff --git a/bitcoin/src/address/error.rs b/bitcoin/src/address/error.rs index c174710994..f1479425c1 100644 --- a/bitcoin/src/address/error.rs +++ b/bitcoin/src/address/error.rs @@ -158,7 +158,7 @@ pub struct NetworkValidationError { impl fmt::Display for NetworkValidationError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "address ")?; - fmt::Display::fmt(&self.address.0, f)?; + fmt::Display::fmt(&self.address.inner(), f)?; write!(f, " is not valid on {}", self.required) } } diff --git a/bitcoin/src/address/mod.rs b/bitcoin/src/address/mod.rs index 91bf828e19..5594a53a9b 100644 --- a/bitcoin/src/address/mod.rs +++ b/bitcoin/src/address/mod.rs @@ -50,7 +50,7 @@ use bech32::primitives::gf32::Fe32; use bech32::primitives::hrp::Hrp; use hashes::{hash160, HashEngine}; use internals::array::ArrayExt; -use secp256k1::{Secp256k1, Verification, XOnlyPublicKey}; +use secp256k1::{Secp256k1, Verification}; use crate::address::script_pubkey::ScriptBufExt as _; use crate::constants::{ @@ -59,6 +59,7 @@ use crate::constants::{ }; use crate::crypto::key::{ CompressedPublicKey, PubkeyHash, PublicKey, TweakedPublicKey, UntweakedPublicKey, + XOnlyPublicKey, }; use crate::network::{Network, NetworkKind, Params}; use crate::prelude::{String, ToOwned}; @@ -90,7 +91,7 @@ pub enum AddressType { P2wpkh, /// Pay to witness script hash. P2wsh, - /// Pay to taproot. + /// Pay to Taproot. P2tr, /// Pay to anchor. P2a, @@ -135,7 +136,9 @@ mod sealed { /// Marker of status of address's network validation. See section [*Parsing addresses*](Address#parsing-addresses) /// on [`Address`] for details. -pub trait NetworkValidation: sealed::NetworkValidation + Sync + Send + Sized + Unpin { +pub trait NetworkValidation: + sealed::NetworkValidation + Sync + Send + Sized + Unpin + Copy +{ /// Indicates whether this `NetworkValidation` is `NetworkChecked` or not. const IS_CHECKED: bool; } @@ -151,13 +154,13 @@ pub trait NetworkValidationUnchecked: /// Marker that address's network has been successfully validated. See section [*Parsing addresses*](Address#parsing-addresses) /// on [`Address`] for details. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum NetworkChecked {} /// Marker that address's network has not yet been validated. See section [*Parsing addresses*](Address#parsing-addresses) /// on [`Address`] for details. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum NetworkUnchecked {} @@ -174,7 +177,7 @@ impl NetworkValidationUnchecked for NetworkUnchecked {} /// /// This struct represents the inner representation of an address without the network validation /// tag, which is used to ensure that addresses are used only on the appropriate network. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] enum AddressInner { P2pkh { hash: PubkeyHash, network: NetworkKind }, P2sh { hash: ScriptHash, network: NetworkKind }, @@ -308,103 +311,110 @@ pub enum AddressData { }, } -/// A Bitcoin address. -/// -/// ### Parsing addresses -/// -/// When parsing string as an address, one has to pay attention to the network, on which the parsed -/// address is supposed to be valid. For the purpose of this validation, `Address` has -/// [`is_valid_for_network`](Address::is_valid_for_network) method. In order to provide more safety, -/// enforced by compiler, `Address` also contains a special marker type, which indicates whether network of the parsed -/// address has been checked. This marker type will prevent from calling certain functions unless the network -/// verification has been successfully completed. -/// -/// The result of parsing an address is `Address` suggesting that network of the parsed address -/// has not yet been verified. To perform this verification, method [`require_network`](Address::require_network) -/// can be called, providing network on which the address is supposed to be valid. If the verification succeeds, -/// `Address` is returned. -/// -/// The types `Address` and `Address` are synonymous, i. e. they can be used interchangeably. -/// -/// ```rust -/// use std::str::FromStr; -/// use bitcoin::{Address, Network}; -/// use bitcoin::address::{NetworkUnchecked, NetworkChecked}; -/// -/// // variant 1 -/// let address: Address = "32iVBEu4dxkUQk9dJbZUiBiQdmypcEyJRf".parse().unwrap(); -/// let _address: Address = address.require_network(Network::Bitcoin).unwrap(); -/// -/// // variant 2 -/// let _address: Address = Address::from_str("32iVBEu4dxkUQk9dJbZUiBiQdmypcEyJRf").unwrap() -/// .require_network(Network::Bitcoin).unwrap(); -/// -/// // variant 3 -/// let _address: Address = "32iVBEu4dxkUQk9dJbZUiBiQdmypcEyJRf".parse::>() -/// .unwrap().require_network(Network::Bitcoin).unwrap(); -/// ``` -/// -/// ### Formatting addresses -/// -/// To format address into its textual representation, both `Debug` (for usage in programmer-facing, -/// debugging context) and `Display` (for user-facing output) can be used, with the following caveats: -/// -/// 1. `Display` is implemented only for `Address`: -/// -/// ``` -/// # use bitcoin::address::{Address, NetworkChecked}; -/// let address: Address = "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM".parse::>() -/// .unwrap().assume_checked(); -/// assert_eq!(address.to_string(), "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM"); -/// ``` -/// -/// ```ignore -/// # use bitcoin::address::{Address, NetworkChecked}; -/// let address: Address = "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM".parse::>() -/// .unwrap(); -/// let s = address.to_string(); // does not compile -/// ``` -/// -/// 2. `Debug` on `Address` does not produce clean address but address wrapped by -/// an indicator that its network has not been checked. This is to encourage programmer to properly -/// check the network and use `Display` in user-facing context. -/// -/// ``` -/// # use bitcoin::address::{Address, NetworkUnchecked}; -/// let address: Address = "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM".parse::>() -/// .unwrap(); -/// assert_eq!(format!("{:?}", address), "Address(132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM)"); -/// ``` -/// -/// ``` -/// # use bitcoin::address::{Address, NetworkChecked}; -/// let address: Address = "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM".parse::>() -/// .unwrap().assume_checked(); -/// assert_eq!(format!("{:?}", address), "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM"); -/// ``` -/// -/// ### Relevant BIPs -/// -/// * [BIP13 - Address Format for pay-to-script-hash](https://github.com/bitcoin/bips/blob/master/bip-0013.mediawiki) -/// * [BIP16 - Pay to Script Hash](https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki) -/// * [BIP141 - Segregated Witness (Consensus layer)](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki) -/// * [BIP142 - Address Format for Segregated Witness](https://github.com/bitcoin/bips/blob/master/bip-0142.mediawiki) -/// * [BIP341 - Taproot: SegWit version 1 spending rules](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki) -/// * [BIP350 - Bech32m format for v1+ witness addresses](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki) -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -// The `#[repr(transparent)]` attribute is used to guarantee the layout of the `Address` struct. It -// is an implementation detail and users should not rely on it in their code. -#[repr(transparent)] -pub struct Address(AddressInner, PhantomData) -where - V: NetworkValidation; +internals::transparent_newtype! { + /// A Bitcoin address. + /// + /// # Parsing addresses + /// + /// When parsing string as an address, one has to pay attention to the network, on which the parsed + /// address is supposed to be valid. For the purpose of this validation, `Address` has + /// [`is_valid_for_network`](Address::is_valid_for_network) method. In order to provide more safety, + /// enforced by compiler, `Address` also contains a special marker type, which indicates whether network of the parsed + /// address has been checked. This marker type will prevent from calling certain functions unless the network + /// verification has been successfully completed. + /// + /// The result of parsing an address is `Address` suggesting that network of the parsed address + /// has not yet been verified. To perform this verification, method [`require_network`](Address::require_network) + /// can be called, providing network on which the address is supposed to be valid. If the verification succeeds, + /// `Address` is returned. + /// + /// The types `Address` and `Address` are synonymous, i. e. they can be used interchangeably. + /// + /// ```rust + /// use std::str::FromStr; + /// use bitcoin::{Address, Network}; + /// use bitcoin::address::{NetworkUnchecked, NetworkChecked}; + /// + /// // variant 1 + /// let address: Address = "32iVBEu4dxkUQk9dJbZUiBiQdmypcEyJRf".parse().unwrap(); + /// let _address: Address = address.require_network(Network::Bitcoin).unwrap(); + /// + /// // variant 2 + /// let _address: Address = Address::from_str("32iVBEu4dxkUQk9dJbZUiBiQdmypcEyJRf").unwrap() + /// .require_network(Network::Bitcoin).unwrap(); + /// + /// // variant 3 + /// let _address: Address = "32iVBEu4dxkUQk9dJbZUiBiQdmypcEyJRf".parse::>() + /// .unwrap().require_network(Network::Bitcoin).unwrap(); + /// ``` + /// + /// ### Formatting addresses + /// + /// To format address into its textual representation, both `Debug` (for usage in programmer-facing, + /// debugging context) and `Display` (for user-facing output) can be used, with the following caveats: + /// + /// 1. `Display` is implemented only for `Address`: + /// + /// ``` + /// # use bitcoin::address::{Address, NetworkChecked}; + /// let address: Address = "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM".parse::>() + /// .unwrap().assume_checked(); + /// assert_eq!(address.to_string(), "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM"); + /// ``` + /// + /// ```ignore + /// # use bitcoin::address::{Address, NetworkChecked}; + /// let address: Address = "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM".parse::>() + /// .unwrap(); + /// let s = address.to_string(); // does not compile + /// ``` + /// + /// 2. `Debug` on `Address` does not produce clean address but address wrapped by + /// an indicator that its network has not been checked. This is to encourage programmer to properly + /// check the network and use `Display` in user-facing context. + /// + /// ``` + /// # use bitcoin::address::{Address, NetworkUnchecked}; + /// let address: Address = "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM".parse::>() + /// .unwrap(); + /// assert_eq!(format!("{:?}", address), "Address(132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM)"); + /// ``` + /// + /// ``` + /// # use bitcoin::address::{Address, NetworkChecked}; + /// let address: Address = "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM".parse::>() + /// .unwrap().assume_checked(); + /// assert_eq!(format!("{:?}", address), "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM"); + /// ``` + /// + /// # Relevant BIPs + /// + /// * [BIP13 - Address Format for pay-to-script-hash](https://github.com/bitcoin/bips/blob/master/bip-0013.mediawiki) + /// * [BIP16 - Pay to Script Hash](https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki) + /// * [BIP141 - Segregated Witness (Consensus layer)](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki) + /// * [BIP142 - Address Format for Segregated Witness](https://github.com/bitcoin/bips/blob/master/bip-0142.mediawiki) + /// * [BIP341 - Taproot: SegWit version 1 spending rules](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki) + /// * [BIP350 - Bech32m format for v1+ witness addresses](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki) + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + // The `#[repr(transparent)]` attribute is used to guarantee the layout of the `Address` struct. It + // is an implementation detail and users should not rely on it in their code. + pub struct Address(PhantomData, AddressInner) + where + V: NetworkValidation; + + impl Address { + fn from_inner_ref(inner: &_) -> &Self; + } +} #[cfg(feature = "serde")] struct DisplayUnchecked<'a, N: NetworkValidation>(&'a Address); #[cfg(feature = "serde")] impl fmt::Display for DisplayUnchecked<'_, N> { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0 .0, fmt) } + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0.inner(), fmt) + } } #[cfg(feature = "serde")] @@ -433,7 +443,7 @@ impl<'de, U: NetworkValidationUnchecked> serde::Deserialize<'de> for Address { // We know that `U` is only ever `NetworkUnchecked` but the compiler does not. let address = v.parse::>().map_err(E::custom)?; - Ok(Address(address.0, PhantomData::)) + Ok(Address::from_inner(address.into_inner())) } } @@ -454,18 +464,26 @@ impl serde::Serialize for Address { /// Methods on [`Address`] that can be called on both `Address` and /// `Address`. impl Address { + fn from_inner(inner: AddressInner) -> Self { Address(PhantomData, inner) } + + fn into_inner(self) -> AddressInner { self.1 } + + fn inner(&self) -> &AddressInner { &self.1 } + /// Returns a reference to the address as if it was unchecked. pub fn as_unchecked(&self) -> &Address { - unsafe { &*(self as *const Address as *const Address) } + Address::from_inner_ref(self.inner()) } /// Marks the network of this address as unchecked. - pub fn into_unchecked(self) -> Address { Address(self.0, PhantomData) } + pub fn into_unchecked(self) -> Address { + Address::from_inner(self.into_inner()) + } /// Returns the [`NetworkKind`] of this address. pub fn network_kind(&self) -> NetworkKind { use AddressInner::*; - match self.0 { + match *self.inner() { P2pkh { hash: _, ref network } => *network, P2sh { hash: _, ref network } => *network, Segwit { program: _, ref hrp } => NetworkKind::from(*hrp), @@ -481,7 +499,7 @@ impl Address { #[inline] pub fn p2pkh(pk: impl Into, network: impl Into) -> Address { let hash = pk.into(); - Self(AddressInner::P2pkh { hash, network: network.into() }, PhantomData) + Self::from_inner(AddressInner::P2pkh { hash, network: network.into() }) } /// Constructs a new pay-to-script-hash (P2SH) [`Address`] from a script. @@ -504,7 +522,7 @@ impl Address { /// The `hash` pre-image (redeem script) must not exceed 520 bytes in length /// otherwise outputs created from the returned address will be un-spendable. pub fn p2sh_from_hash(hash: ScriptHash, network: impl Into) -> Address { - Self(AddressInner::P2sh { hash, network: network.into() }, PhantomData) + Self::from_inner(AddressInner::P2sh { hash, network: network.into() }) } /// Constructs a new pay-to-witness-public-key-hash (P2WPKH) [`Address`] from a public key. @@ -555,12 +573,13 @@ impl Address { } /// Constructs a new pay-to-Taproot (P2TR) [`Address`] from an untweaked key. - pub fn p2tr( + pub fn p2tr>( secp: &Secp256k1, - internal_key: UntweakedPublicKey, + internal_key: K, merkle_root: Option, hrp: impl Into, ) -> Address { + let internal_key = internal_key.into(); let program = WitnessProgram::p2tr(secp, internal_key, merkle_root); Address::from_witness_program(program, hrp) } @@ -577,7 +596,7 @@ impl Address { /// then you likely do not need this constructor. pub fn from_witness_program(program: WitnessProgram, hrp: impl Into) -> Address { let inner = AddressInner::Segwit { program, hrp: hrp.into() }; - Address(inner, PhantomData) + Address::from_inner(inner) } /// Gets the address type of the [`Address`]. @@ -587,7 +606,7 @@ impl Address { /// None if unknown, non-standard or related to the future witness version. #[inline] pub fn address_type(&self) -> Option { - match self.0 { + match *self.inner() { AddressInner::P2pkh { .. } => Some(AddressType::P2pkh), AddressInner::P2sh { .. } => Some(AddressType::P2sh), AddressInner::Segwit { ref program, hrp: _ } => @@ -606,10 +625,10 @@ impl Address { } /// Gets the address data from this address. - pub fn to_address_data(&self) -> AddressData { + pub fn to_address_data(self) -> AddressData { use AddressData::*; - match self.0 { + match *self.inner() { AddressInner::P2pkh { hash, network: _ } => P2pkh { pubkey_hash: hash }, AddressInner::P2sh { hash, network: _ } => P2sh { script_hash: hash }, AddressInner::Segwit { program, hrp: _ } => Segwit { witness_program: program }, @@ -620,7 +639,7 @@ impl Address { pub fn pubkey_hash(&self) -> Option { use AddressInner::*; - match self.0 { + match *self.inner() { P2pkh { ref hash, network: _ } => Some(*hash), _ => None, } @@ -630,7 +649,7 @@ impl Address { pub fn script_hash(&self) -> Option { use AddressInner::*; - match self.0 { + match *self.inner() { P2sh { ref hash, network: _ } => Some(*hash), _ => None, } @@ -640,7 +659,7 @@ impl Address { pub fn witness_program(&self) -> Option { use AddressInner::*; - match self.0 { + match *self.inner() { Segwit { ref program, hrp: _ } => Some(*program), _ => None, } @@ -689,7 +708,7 @@ impl Address { /// Generates a script pubkey spending to this address. pub fn script_pubkey(&self) -> ScriptBuf { use AddressInner::*; - match self.0 { + match *self.inner() { P2pkh { hash, network: _ } => ScriptBuf::new_p2pkh(hash), P2sh { hash, network: _ } => ScriptBuf::new_p2sh(hash), Segwit { ref program, hrp: _ } => { @@ -727,7 +746,7 @@ impl Address { /// # })().unwrap(); /// # assert_eq!(writer, ADDRESS); /// ``` - pub fn to_qr_uri(&self) -> String { format!("bitcoin:{:#}", self) } + pub fn to_qr_uri(self) -> String { format!("bitcoin:{:#}", self) } /// Returns true if the given pubkey is directly related to the address payload. /// @@ -756,7 +775,7 @@ impl Address { /// This function doesn't make any allocations. pub fn matches_script_pubkey(&self, script: &Script) -> bool { use AddressInner::*; - match self.0 { + match *self.inner() { P2pkh { ref hash, network: _ } if script.is_p2pkh() => &script.as_bytes()[3..23] == >::as_ref(hash), P2sh { ref hash, network: _ } if script.is_p2sh() => @@ -777,7 +796,7 @@ impl Address { /// - For SegWit addresses, the payload is the witness program. fn payload_as_bytes(&self) -> &[u8] { use AddressInner::*; - match self.0 { + match *self.inner() { P2sh { ref hash, network: _ } => hash.as_ref(), P2pkh { ref hash, network: _ } => hash.as_ref(), Segwit { ref program, hrp: _ } => program.program().as_bytes(), @@ -790,9 +809,7 @@ impl Address { /// Returns a reference to the checked address. /// /// This function is dangerous in case the address is not a valid checked address. - pub fn assume_checked_ref(&self) -> &Address { - unsafe { &*(self as *const Address as *const Address) } - } + pub fn assume_checked_ref(&self) -> &Address { Address::from_inner_ref(self.inner()) } /// Parsed addresses do not always have *one* network. The problem is that legacy testnet, /// regtest and signet addresses use the same prefix instead of multiple different ones. When @@ -817,7 +834,7 @@ impl Address { /// ``` pub fn is_valid_for_network(&self, n: Network) -> bool { use AddressInner::*; - match self.0 { + match *self.inner() { P2pkh { hash: _, ref network } => *network == NetworkKind::from(n), P2sh { hash: _, ref network } => *network == NetworkKind::from(n), Segwit { program: _, ref hrp } => *hrp == KnownHrp::from_network(n), @@ -882,7 +899,7 @@ impl Address { /// For details about this mechanism, see section [*Parsing addresses*](Address#parsing-addresses) /// on [`Address`]. #[inline] - pub fn assume_checked(self) -> Address { Address(self.0, PhantomData) } + pub fn assume_checked(self) -> Address { Address::from_inner(self.into_inner()) } /// Parse a bech32 Address string pub fn from_bech32_str(s: &str) -> Result, Bech32Error> { @@ -894,7 +911,7 @@ impl Address { let hrp = KnownHrp::from_hrp(hrp)?; let inner = AddressInner::Segwit { program, hrp }; - Ok(Address(inner, PhantomData)) + Ok(Address::from_inner(inner)) } /// Parse a base58 Address string @@ -903,7 +920,8 @@ impl Address { return Err(LegacyAddressTooLongError { length: s.len() }.into()); } let data = base58::decode_check(s)?; - let data: &[u8; 21] = (&*data).try_into().map_err(|_| InvalidBase58PayloadLengthError { length: s.len() })?; + let data: &[u8; 21] = + (&*data).try_into().map_err(|_| InvalidBase58PayloadLengthError { length: s.len() })?; let (prefix, &data) = data.split_first(); @@ -927,7 +945,7 @@ impl Address { invalid => return Err(InvalidLegacyPrefixError { invalid }.into()), }; - Ok(Address(inner, PhantomData)) + Ok(Address::from_inner(inner)) } } @@ -938,16 +956,16 @@ impl From
for ScriptBuf { // Alternate formatting `{:#}` is used to return uppercase version of bech32 addresses which should // be used in QR codes, see [`Address::to_qr_uri`]. impl fmt::Display for Address { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, fmt) } + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.inner(), fmt) } } impl fmt::Debug for Address { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if V::IS_CHECKED { - fmt::Display::fmt(&self.0, f) + fmt::Display::fmt(&self.inner(), f) } else { write!(f, "Address(")?; - fmt::Display::fmt(&self.0, f)?; + fmt::Display::fmt(&self.inner(), f)?; write!(f, ")") } } @@ -975,10 +993,10 @@ impl FromStr for Address { if ["bc1", "bcrt1", "tb1"].iter().any(|&prefix| s.to_lowercase().starts_with(prefix)) { let address = Address::from_bech32_str(s)?; // We know that `U` is only ever `NetworkUnchecked` but the compiler does not. - Ok(Address(address.0, PhantomData::)) + Ok(Address::from_inner(address.into_inner())) } else if ["1", "2", "3", "m", "n"].iter().any(|&prefix| s.starts_with(prefix)) { let address = Address::from_base58_str(s)?; - Ok(Address(address.0, PhantomData::)) + Ok(Address::from_inner(address.into_inner())) } else { let hrp = match s.rfind('1') { Some(pos) => &s[..pos], @@ -1037,7 +1055,10 @@ mod tests { assert_eq!( addr.script_pubkey(), - ScriptBuf::from_hex("76a914162c5ea71c0b23f5b9022ef047c4a86470a5b07088ac").unwrap() + ScriptBuf::from_hex_no_length_prefix( + "76a914162c5ea71c0b23f5b9022ef047c4a86470a5b07088ac" + ) + .unwrap() ); assert_eq!(&addr.to_string(), "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM"); assert_eq!(addr.address_type(), Some(AddressType::P2pkh)); @@ -1066,7 +1087,8 @@ mod tests { assert_eq!( addr.script_pubkey(), - ScriptBuf::from_hex("a914162c5ea71c0b23f5b9022ef047c4a86470a5b07087").unwrap(), + ScriptBuf::from_hex_no_length_prefix("a914162c5ea71c0b23f5b9022ef047c4a86470a5b07087") + .unwrap(), ); assert_eq!(&addr.to_string(), "33iFwdLuRpW1uK1RTRqsoi8rR4NpDzk66k"); assert_eq!(addr.address_type(), Some(AddressType::P2sh)); @@ -1075,7 +1097,7 @@ mod tests { #[test] fn p2sh_parse() { - let script = ScriptBuf::from_hex("552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae").unwrap(); + let script = ScriptBuf::from_hex_no_length_prefix("552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae").unwrap(); let addr = Address::p2sh(&script, NetworkKind::Test).unwrap(); assert_eq!(&addr.to_string(), "2N3zXjbwdTcPsJiy8sUK9FhWJhqQCxA8Jjr"); assert_eq!(addr.address_type(), Some(AddressType::P2sh)); @@ -1084,7 +1106,7 @@ mod tests { #[test] fn p2sh_parse_for_large_script() { - let script = ScriptBuf::from_hex("552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123").unwrap(); + let script = ScriptBuf::from_hex_no_length_prefix("552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123552103a765fc35b3f210b95223846b36ef62a4e53e34e2925270c2c7906b92c9f718eb2103c327511374246759ec8d0b89fa6c6b23b33e11f92c5bc155409d86de0c79180121038cae7406af1f12f4786d820a1466eec7bc5785a1b5e4a387eca6d797753ef6db2103252bfb9dcaab0cd00353f2ac328954d791270203d66c2be8b430f115f451b8a12103e79412d42372c55dd336f2eb6eb639ef9d74a22041ba79382c74da2338fe58ad21035049459a4ebc00e876a9eef02e72a3e70202d3d1f591fc0dd542f93f642021f82102016f682920d9723c61b27f562eb530c926c00106004798b6471e8c52c60ee02057ae12123122313123123ac1231231231231313123131231231231313212313213123123").unwrap(); let res = Address::p2sh(&script, NetworkKind::Test); assert_eq!(res.unwrap_err().invalid_size(), script.len()) } @@ -1104,7 +1126,7 @@ mod tests { #[test] fn p2wsh() { // stolen from Bitcoin transaction 5df912fda4becb1c29e928bec8d64d93e9ba8efa9b5b405bd683c86fd2c65667 - let script = ScriptBuf::from_hex("52210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae").unwrap(); + let script = ScriptBuf::from_hex_no_length_prefix("52210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae").unwrap(); let addr = Address::p2wsh(&script, KnownHrp::Mainnet).expect("script is valid"); assert_eq!( &addr.to_string(), @@ -1129,7 +1151,7 @@ mod tests { #[test] fn p2shwsh() { // stolen from Bitcoin transaction f9ee2be4df05041d0e0a35d7caa3157495ca4f93b233234c9967b6901dacf7a9 - let script = ScriptBuf::from_hex("522103e5529d8eaa3d559903adb2e881eb06c86ac2574ffa503c45f4e942e2a693b33e2102e5f10fcdcdbab211e0af6a481f5532536ec61a5fdbf7183770cf8680fe729d8152ae").unwrap(); + let script = ScriptBuf::from_hex_no_length_prefix("522103e5529d8eaa3d559903adb2e881eb06c86ac2574ffa503c45f4e942e2a693b33e2102e5f10fcdcdbab211e0af6a481f5532536ec61a5fdbf7183770cf8680fe729d8152ae").unwrap(); let addr = Address::p2shwsh(&script, NetworkKind::Main).expect("script is valid"); assert_eq!(&addr.to_string(), "36EqgNnsWW94SreZgBWc1ANC6wpFZwirHr"); assert_eq!(addr.address_type(), Some(AddressType::P2sh)); @@ -1162,7 +1184,7 @@ mod tests { let unchecked = addr_str.parse::>().unwrap(); assert_eq!( - format!("{:?}", Test { address: unchecked.clone() }), + format!("{:?}", Test { address: unchecked }), format!("Test {{ address: Address({}) }}", addr_str) ); @@ -1209,7 +1231,7 @@ mod tests { let addr = "132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM".parse::>().unwrap().assume_checked(); - let json = serde_json::to_value(&addr).unwrap(); + let json = serde_json::to_value(addr).unwrap(); assert_eq!( json, serde_json::Value::String("132F25rTsvBdp9JzLLBHP5mvGY66i1xdiM".to_owned()) @@ -1218,12 +1240,15 @@ mod tests { assert_eq!(addr.to_string(), into.to_string()); assert_eq!( into.script_pubkey(), - ScriptBuf::from_hex("76a914162c5ea71c0b23f5b9022ef047c4a86470a5b07088ac").unwrap() + ScriptBuf::from_hex_no_length_prefix( + "76a914162c5ea71c0b23f5b9022ef047c4a86470a5b07088ac" + ) + .unwrap() ); let addr = "33iFwdLuRpW1uK1RTRqsoi8rR4NpDzk66k".parse::>().unwrap().assume_checked(); - let json = serde_json::to_value(&addr).unwrap(); + let json = serde_json::to_value(addr).unwrap(); assert_eq!( json, serde_json::Value::String("33iFwdLuRpW1uK1RTRqsoi8rR4NpDzk66k".to_owned()) @@ -1232,7 +1257,8 @@ mod tests { assert_eq!(addr.to_string(), into.to_string()); assert_eq!( into.script_pubkey(), - ScriptBuf::from_hex("a914162c5ea71c0b23f5b9022ef047c4a86470a5b07087").unwrap() + ScriptBuf::from_hex_no_length_prefix("a914162c5ea71c0b23f5b9022ef047c4a86470a5b07087") + .unwrap() ); let addr: Address = @@ -1251,7 +1277,7 @@ mod tests { .parse::>() .unwrap() .assume_checked(); - let json = serde_json::to_value(&addr).unwrap(); + let json = serde_json::to_value(addr).unwrap(); assert_eq!( json, serde_json::Value::String( @@ -1262,7 +1288,7 @@ mod tests { assert_eq!(addr.to_string(), into.to_string()); assert_eq!( into.script_pubkey(), - ScriptBuf::from_hex( + ScriptBuf::from_hex_no_length_prefix( "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262" ) .unwrap() @@ -1272,7 +1298,7 @@ mod tests { .parse::>() .unwrap() .assume_checked(); - let json = serde_json::to_value(&addr).unwrap(); + let json = serde_json::to_value(addr).unwrap(); assert_eq!( json, serde_json::Value::String("bcrt1q2nfxmhd4n3c8834pj72xagvyr9gl57n5r94fsl".to_owned()) @@ -1281,7 +1307,8 @@ mod tests { assert_eq!(addr.to_string(), into.to_string()); assert_eq!( into.script_pubkey(), - ScriptBuf::from_hex("001454d26dddb59c7073c6a197946ea1841951fa7a74").unwrap() + ScriptBuf::from_hex_no_length_prefix("001454d26dddb59c7073c6a197946ea1841951fa7a74") + .unwrap() ); } @@ -1460,13 +1487,15 @@ mod tests { fn fail_address_from_script() { use crate::witness_program; - let bad_p2wpkh = ScriptBuf::from_hex("0014dbc5b0a8f9d4353b4b54c3db48846bb15abfec").unwrap(); - let bad_p2wsh = ScriptBuf::from_hex( + let bad_p2wpkh = + ScriptBuf::from_hex_no_length_prefix("15000014dbc5b0a8f9d4353b4b54c3db48846bb15abfec") + .unwrap(); + let bad_p2wsh = ScriptBuf::from_hex_no_length_prefix( "00202d4fa2eb233d008cc83206fa2f4f2e60199000f5b857a835e3172323385623", ) .unwrap(); let invalid_segwitv0_script = - ScriptBuf::from_hex("001161458e330389cd0437ee9fe3641d70cc18").unwrap(); + ScriptBuf::from_hex_no_length_prefix("001161458e330389cd0437ee9fe3641d70cc18").unwrap(); let expected = Err(FromScriptError::UnrecognizedScript); assert_eq!(Address::from_script(&bad_p2wpkh, Network::Bitcoin), expected); @@ -1534,7 +1563,7 @@ mod tests { let unchecked = addr_str.parse::>().unwrap(); // Serialize with an unchecked address. - let foo_unchecked = Foo { address: unchecked.clone() }; + let foo_unchecked = Foo { address: unchecked }; let ser = serde_json::to_string(&foo_unchecked).expect("failed to serialize"); let rinsed: Foo = serde_json::from_str(&ser).expect("failed to deserialize"); @@ -1551,7 +1580,7 @@ mod tests { #[test] fn pay_to_anchor_address_regtest() { - // Verify that p2a uses the expected address for regtest. + // Verify that P2A uses the expected address for regtest. // This test-vector is borrowed from the bitcoin source code. let address_str = "bcrt1pfeesnyr2tx"; @@ -1562,7 +1591,7 @@ mod tests { assert_eq!(address.to_string(), address_str); // Verify that the address is considered standard - // and that the output type is P2a + // and that the output type is P2A. assert!(address.is_spend_standard()); assert_eq!(address.address_type(), Some(AddressType::P2a)); } diff --git a/bitcoin/src/address/script_pubkey.rs b/bitcoin/src/address/script_pubkey.rs index b89008ca33..bc3637de3b 100644 --- a/bitcoin/src/address/script_pubkey.rs +++ b/bitcoin/src/address/script_pubkey.rs @@ -49,11 +49,12 @@ define_extension_trait! { /// Computes P2TR output with a given internal key and a single script spending path equal to /// the current script, assuming that the script is a Tapscript. - fn to_p2tr( + fn to_p2tr>( &self, secp: &Secp256k1, - internal_key: UntweakedPublicKey, + internal_key: K, ) -> ScriptBuf { + let internal_key = internal_key.into(); let leaf_hash = self.tapscript_leaf_hash(); let merkle_root = TapNodeHash::from(leaf_hash); ScriptBuf::new_p2tr(secp, internal_key, Some(merkle_root)) @@ -157,11 +158,12 @@ define_extension_trait! { /// Generates P2TR for script spending path using an internal public key and some optional /// script tree Merkle root. - fn new_p2tr( + fn new_p2tr>( secp: &Secp256k1, - internal_key: UntweakedPublicKey, + internal_key: K, merkle_root: Option, ) -> Self { + let internal_key = internal_key.into(); let (output_key, _) = internal_key.tap_tweak(secp, merkle_root); // output key is 32 bytes long, so it's safe to use `new_witness_program_unchecked` (Segwitv1) new_witness_program_unchecked(WitnessVersion::V1, output_key.serialize()) @@ -198,7 +200,7 @@ pub(super) fn new_witness_program_unchecked>( ) -> ScriptBuf { let program = program.as_ref(); debug_assert!(program.len() >= 2 && program.len() <= 40); - // In SegWit v0, the program must be either 20 (P2WPKH) bytes or 32 (P2WSH) bytes long + // In SegWit v0, the program must be either 20 bytes (P2WPKH) or 32 bytes (P2WSH) long. debug_assert!(version != WitnessVersion::V0 || program.len() == 20 || program.len() == 32); Builder::new().push_opcode(version.into()).push_slice(program).into_script() } diff --git a/bitcoin/src/bip152.rs b/bitcoin/src/bip152.rs index bb25066d18..5aed10c940 100644 --- a/bitcoin/src/bip152.rs +++ b/bitcoin/src/bip152.rs @@ -10,7 +10,8 @@ use core::{convert, fmt, mem}; use std::error; use hashes::{sha256, siphash24}; -use internals::{ToU64 as _, array::ArrayExt as _}; +use internals::array::ArrayExt as _; +use internals::ToU64 as _; use io::{BufRead, Write}; use crate::consensus::encode::{self, Decodable, Encodable, ReadExt, WriteExt}; @@ -483,7 +484,7 @@ mod test { #[test] fn getblocktx_differential_encoding_de_and_serialization() { let testcases = vec![ - // differentially encoded VarInts, indicies + // differentially encoded VarInts, indices (vec![4, 0, 5, 1, 10], vec![0, 6, 8, 19]), (vec![1, 0], vec![0]), (vec![5, 0, 0, 0, 0, 0], vec![0, 1, 2, 3, 4]), diff --git a/bitcoin/src/bip158.rs b/bitcoin/src/bip158.rs index abdde2a9c2..b62617e5c7 100644 --- a/bitcoin/src/bip158.rs +++ b/bitcoin/src/bip158.rs @@ -11,7 +11,7 @@ //! is an alternative to Bloom filters, as used in BIP 37, that minimizes filter //! size by using Golomb-Rice coding for compression. //! -//! ### Relevant BIPS +//! # Relevant BIPS //! //! * [BIP 157 - Client Side Block Filtering](https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki) //! * [BIP 158 - Compact Block Filters for Light Clients](https://github.com/bitcoin/bips/blob/master/bip-0158.mediawiki) @@ -42,7 +42,8 @@ use core::convert::Infallible; use core::fmt; use hashes::{sha256d, siphash24, HashEngine as _}; -use internals::{write_err, ToU64 as _, array::ArrayExt as _}; +use internals::array::ArrayExt as _; +use internals::{write_err, ToU64 as _}; use io::{BufRead, Write}; use crate::block::{Block, BlockHash, Checked}; @@ -577,7 +578,7 @@ impl<'a, W: Write> BitStreamWriter<'a, W> { mod test { use std::collections::HashMap; - use hex::test_hex_unwrap as hex; + use hex_lit::hex; use serde_json::Value; use super::*; @@ -586,19 +587,21 @@ mod test { #[test] fn blockfilters() { + let hex = |b| as hex::FromHex>::from_hex(b).unwrap(); + // test vectors from: https://github.com/jimpo/bitcoin/blob/c7efb652f3543b001b4dd22186a354605b14f47e/src/test/data/blockfilters.json let data = include_str!("../tests/data/blockfilters.json"); let testdata = serde_json::from_str::(data).unwrap().as_array().unwrap().clone(); for t in testdata.iter().skip(1) { let block_hash = t.get(1).unwrap().as_str().unwrap().parse::().unwrap(); - let block: Block = deserialize(&hex!(t.get(2).unwrap().as_str().unwrap())).unwrap(); + let block: Block = deserialize(&hex(t.get(2).unwrap().as_str().unwrap())).unwrap(); let block = block.assume_checked(None); assert_eq!(block.block_hash(), block_hash); let scripts = t.get(3).unwrap().as_array().unwrap(); let previous_filter_header = t.get(4).unwrap().as_str().unwrap().parse::().unwrap(); - let filter_content = hex!(t.get(5).unwrap().as_str().unwrap()); + let filter_content = hex(t.get(5).unwrap().as_str().unwrap()); let filter_header = t.get(6).unwrap().as_str().unwrap().parse::().unwrap(); @@ -608,7 +611,7 @@ mod test { for input in tx.input.iter() { txmap.insert( input.previous_output, - ScriptBuf::from(hex!(si.next().unwrap().as_str().unwrap())), + ScriptBuf::from(hex(si.next().unwrap().as_str().unwrap())), ); } } @@ -701,7 +704,7 @@ mod test { let reader = GcsFilterReader::new(0, 0, M, P); let mut query = Vec::new(); for p in &patterns { - query.push(p.clone()); + query.push(p); } assert!(reader .match_all(&mut bytes.as_slice(), &mut query.iter().map(|v| v.as_slice())) @@ -711,9 +714,9 @@ mod test { let reader = GcsFilterReader::new(0, 0, M, P); let mut query = Vec::new(); for p in &patterns { - query.push(p.clone()); + query.push(p); } - query.push(hex!("abcdef")); + query.push(&hex!("abcdef")); assert!(!reader .match_all(&mut bytes.as_slice(), &mut query.iter().map(|v| v.as_slice())) .unwrap()); diff --git a/bitcoin/src/bip32.rs b/bitcoin/src/bip32.rs index 73d74ffc82..22ed79d7a0 100644 --- a/bitcoin/src/bip32.rs +++ b/bitcoin/src/bip32.rs @@ -13,9 +13,9 @@ use core::{fmt, slice}; use hashes::{hash160, hash_newtype, sha512, Hash, HashEngine, Hmac, HmacEngine}; use internals::array::ArrayExt; use internals::write_err; -use secp256k1::{Secp256k1, XOnlyPublicKey}; +use secp256k1::Secp256k1; -use crate::crypto::key::{CompressedPublicKey, Keypair, PrivateKey}; +use crate::crypto::key::{CompressedPublicKey, Keypair, PrivateKey, XOnlyPublicKey}; use crate::internal_macros::{impl_array_newtype, impl_array_newtype_stringify}; use crate::network::NetworkKind; use crate::prelude::{String, Vec}; @@ -148,11 +148,11 @@ impl ChildNumber { /// [0, 2^31 - 1]. /// /// [`Normal`]: #variant.Normal - pub fn from_normal_idx(index: u32) -> Result { + pub fn from_normal_idx(index: u32) -> Result { if index & (1 << 31) == 0 { Ok(ChildNumber::Normal { index }) } else { - Err(Error::InvalidChildNumber(index)) + Err(IndexOutOfRangeError { index }) } } @@ -160,11 +160,11 @@ impl ChildNumber { /// [0, 2^31 - 1]. /// /// [`Hardened`]: #variant.Hardened - pub fn from_hardened_idx(index: u32) -> Result { + pub fn from_hardened_idx(index: u32) -> Result { if index & (1 << 31) == 0 { Ok(ChildNumber::Hardened { index }) } else { - Err(Error::InvalidChildNumber(index)) + Err(IndexOutOfRangeError { index }) } } @@ -184,7 +184,10 @@ impl ChildNumber { } /// Returns the child number that is a single increment from this one. - pub fn increment(self) -> Result { + pub fn increment(self) -> Result { + // Bare addition in this function is okay, because we have an invariant that + // `index` is always within [0, 2^31 - 1]. FIXME this is not actually an + // invariant because the fields are public. match self { ChildNumber::Normal { index: idx } => ChildNumber::from_normal_idx(idx + 1), ChildNumber::Hardened { index: idx } => ChildNumber::from_hardened_idx(idx + 1), @@ -225,16 +228,18 @@ impl fmt::Display for ChildNumber { } impl FromStr for ChildNumber { - type Err = Error; + type Err = ParseChildNumberError; - fn from_str(inp: &str) -> Result { + fn from_str(inp: &str) -> Result { let is_hardened = inp.chars().last().map_or(false, |l| l == '\'' || l == 'h'); Ok(if is_hardened { ChildNumber::from_hardened_idx( - inp[0..inp.len() - 1].parse().map_err(|_| Error::InvalidChildNumberFormat)?, - )? + inp[0..inp.len() - 1].parse().map_err(ParseChildNumberError::ParseInt)?, + ) + .map_err(ParseChildNumberError::IndexOutOfRange)? } else { - ChildNumber::from_normal_idx(inp.parse().map_err(|_| Error::InvalidChildNumberFormat)?)? + ChildNumber::from_normal_idx(inp.parse().map_err(ParseChildNumberError::ParseInt)?) + .map_err(ParseChildNumberError::IndexOutOfRange)? }) } } @@ -267,7 +272,7 @@ impl serde::Serialize for ChildNumber { /// derivation path pub trait IntoDerivationPath { /// Converts a given type into a [`DerivationPath`] with possible error - fn into_derivation_path(self) -> Result; + fn into_derivation_path(self) -> Result; } /// A BIP-32 derivation path. @@ -295,15 +300,17 @@ impl IntoDerivationPath for T where T: Into, { - fn into_derivation_path(self) -> Result { Ok(self.into()) } + fn into_derivation_path(self) -> Result { + Ok(self.into()) + } } impl IntoDerivationPath for String { - fn into_derivation_path(self) -> Result { self.parse() } + fn into_derivation_path(self) -> Result { self.parse() } } impl IntoDerivationPath for &'_ str { - fn into_derivation_path(self) -> Result { self.parse() } + fn into_derivation_path(self) -> Result { self.parse() } } impl From> for DerivationPath { @@ -338,9 +345,9 @@ impl AsRef<[ChildNumber]> for DerivationPath { } impl FromStr for DerivationPath { - type Err = Error; + type Err = ParseChildNumberError; - fn from_str(path: &str) -> Result { + fn from_str(path: &str) -> Result { if path.is_empty() || path == "m" || path == "m/" { return Ok(vec![].into()); } @@ -348,7 +355,7 @@ impl FromStr for DerivationPath { let path = path.strip_prefix("m/").unwrap_or(path); let parts = path.split('/'); - let ret: Result, Error> = parts.map(str::parse).collect(); + let ret: Result, _> = parts.map(str::parse).collect(); Ok(DerivationPath(ret?)) } } @@ -496,27 +503,15 @@ pub type KeySource = (Fingerprint, DerivationPath); /// A BIP32 error #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] -pub enum Error { - /// A pk->pk derivation was attempted on a hardened key - CannotDeriveFromHardenedKey, +pub enum ParseError { /// A secp256k1 error occurred Secp256k1(secp256k1::Error), - /// A child number was provided that was out of range - InvalidChildNumber(u32), - /// Invalid childnumber format. - InvalidChildNumberFormat, - /// Invalid derivation path format. - InvalidDerivationPathFormat, /// Unknown version magic bytes UnknownVersion([u8; 4]), /// Encoded extended key data has wrong length WrongExtendedKeyLength(usize), /// Base58 encoding error Base58(base58::Error), - /// Hexadecimal decoding error - Hex(hex::HexToArrayError), - /// `PublicKey` hex should be 66 or 130 digits long. - InvalidPublicKeyHexLength(usize), /// Base58 decoded data was an invalid length. InvalidBase58PayloadLength(InvalidBase58PayloadLengthError), /// Invalid private key prefix (byte 45 must be 0) @@ -527,29 +522,20 @@ pub enum Error { NonZeroChildNumberForMasterKey, } -impl From for Error { +impl From for ParseError { fn from(never: Infallible) -> Self { match never {} } } -impl fmt::Display for Error { +impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use Error::*; + use ParseError::*; match *self { - CannotDeriveFromHardenedKey => - f.write_str("cannot derive hardened key from public key"), Secp256k1(ref e) => write_err!(f, "secp256k1 error"; e), - InvalidChildNumber(ref n) => - write!(f, "child number {} is invalid (not within [0, 2^31 - 1])", n), - InvalidChildNumberFormat => f.write_str("invalid child number format"), - InvalidDerivationPathFormat => f.write_str("invalid derivation path format"), UnknownVersion(ref bytes) => write!(f, "unknown version magic bytes: {:?}", bytes), WrongExtendedKeyLength(ref len) => write!(f, "encoded extended key data has wrong length {}", len), Base58(ref e) => write_err!(f, "base58 encoding error"; e), - Hex(ref e) => write_err!(f, "Hexadecimal decoding error"; e), - InvalidPublicKeyHexLength(got) => - write!(f, "PublicKey hex should be 66 or 130 digits long, got: {}", got), InvalidBase58PayloadLength(ref e) => write_err!(f, "base58 payload"; e), InvalidPrivateKeyPrefix => f.write_str("invalid private key prefix, byte 45 must be 0 as required by BIP-32"), @@ -561,22 +547,15 @@ impl fmt::Display for Error { } #[cfg(feature = "std")] -impl std::error::Error for Error { +impl std::error::Error for ParseError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use Error::*; + use ParseError::*; match *self { Secp256k1(ref e) => Some(e), Base58(ref e) => Some(e), - Hex(ref e) => Some(e), InvalidBase58PayloadLength(ref e) => Some(e), - CannotDeriveFromHardenedKey - | InvalidChildNumber(_) - | InvalidChildNumberFormat - | InvalidDerivationPathFormat - | UnknownVersion(_) - | WrongExtendedKeyLength(_) - | InvalidPublicKeyHexLength(_) => None, + UnknownVersion(_) | WrongExtendedKeyLength(_) => None, InvalidPrivateKeyPrefix => None, NonZeroParentFingerprintForMasterKey => None, NonZeroChildNumberForMasterKey => None, @@ -584,35 +563,121 @@ impl std::error::Error for Error { } } -impl From for Error { - fn from(e: secp256k1::Error) -> Error { Error::Secp256k1(e) } +impl From for ParseError { + fn from(e: secp256k1::Error) -> ParseError { ParseError::Secp256k1(e) } +} + +impl From for ParseError { + fn from(err: base58::Error) -> Self { ParseError::Base58(err) } +} + +impl From for ParseError { + fn from(e: InvalidBase58PayloadLengthError) -> ParseError { + Self::InvalidBase58PayloadLength(e) + } +} + +/// A BIP32 error +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum DerivationError { + /// Attempted to derive a hardened child from an xpub. + /// + /// You can only derive hardened children from xprivs. + CannotDeriveHardenedChild, + /// Attempted to derive a child of depth 256 or higher. + /// + /// There is no way to encode such xkeys. + MaximumDepthExceeded, +} + +#[cfg(feature = "std")] +impl std::error::Error for DerivationError {} + +impl fmt::Display for DerivationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::CannotDeriveHardenedChild => + f.write_str("cannot derive hardened child of public key"), + Self::MaximumDepthExceeded => f.write_str("cannot derive child of depth 256 or higher"), + } + } +} + +/// Out-of-range index when constructing a child number. +/// +/// *Indices* are always in the range [0, 2^31 - 1]. Normal child numbers have the +/// same range, while hardened child numbers lie in the range [2^31, 2^32 - 1]. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct IndexOutOfRangeError { + /// The index that was out of range for a child number. + pub index: u32, } -impl From for Error { - fn from(err: base58::Error) -> Self { Error::Base58(err) } +#[cfg(feature = "std")] +impl std::error::Error for IndexOutOfRangeError {} + +impl From for IndexOutOfRangeError { + fn from(never: Infallible) -> Self { match never {} } } -impl From for Error { - fn from(e: InvalidBase58PayloadLengthError) -> Error { Self::InvalidBase58PayloadLength(e) } +impl fmt::Display for IndexOutOfRangeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "index {} out of range [0, 2^31 - 1] (do you have an hardened child number, rather than an index?)", self.index) + } +} + +/// Error parsing a child number. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ParseChildNumberError { + /// Parsed the child number as an integer, but the integer was out of range. + IndexOutOfRange(IndexOutOfRangeError), + /// Failed to parse the child number as an integer. + ParseInt(core::num::ParseIntError), +} + +impl From for ParseChildNumberError { + fn from(never: Infallible) -> Self { match never {} } +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseChildNumberError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + Self::IndexOutOfRange(ref e) => Some(e), + Self::ParseInt(ref e) => Some(e), + } + } +} + +impl fmt::Display for ParseChildNumberError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::IndexOutOfRange(ref e) => e.fmt(f), + Self::ParseInt(ref e) => e.fmt(f), + } + } } impl Xpriv { /// Constructs a new master key from a seed value - pub fn new_master(network: impl Into, seed: &[u8]) -> Result { + pub fn new_master(network: impl Into, seed: &[u8]) -> Xpriv { let mut engine = HmacEngine::::new(b"Bitcoin seed"); engine.input(seed); let hmac = engine.finalize(); - Ok(Xpriv { + Xpriv { network: network.into(), depth: 0, parent_fingerprint: Default::default(), child_number: ChildNumber::ZERO_NORMAL, private_key: secp256k1::SecretKey::from_byte_array( hmac.as_byte_array().split_array::<32, 32>().0, - )?, + ) + .expect("cryptographically unreachable"), chain_code: ChainCode::from_hmac(hmac), - }) + } } /// Constructs a new ECDSA compressed private key matching internal secret key representation. @@ -644,7 +709,7 @@ impl Xpriv { &self, secp: &Secp256k1, path: &P, - ) -> Xpriv { + ) -> Result { self.derive_xpriv(secp, path) } @@ -655,16 +720,20 @@ impl Xpriv { &self, secp: &Secp256k1, path: &P, - ) -> Xpriv { + ) -> Result { 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(&self, secp: &Secp256k1, i: ChildNumber) -> Xpriv { + fn ckd_priv( + &self, + secp: &Secp256k1, + i: ChildNumber, + ) -> Result { let mut engine = HmacEngine::::new(&self.chain_code[..]); match i { ChildNumber::Normal { .. } => { @@ -682,43 +751,36 @@ impl Xpriv { engine.input(&u32::from(i).to_be_bytes()); let hmac: Hmac = engine.finalize(); - let sk = secp256k1::SecretKey::from_byte_array( - hmac.as_byte_array().split_array::<32, 32>().0, - ) - .expect("statistically impossible to hit"); + let sk = + secp256k1::SecretKey::from_byte_array(hmac.as_byte_array().split_array::<32, 32>().0) + .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, + depth: self.depth.checked_add(1).ok_or(DerivationError::MaximumDepthExceeded)?, parent_fingerprint: self.fingerprint(secp), child_number: i, private_key: tweaked, chain_code: ChainCode::from_hmac(hmac), - } + }) } /// Decoding extended private key from binary data according to BIP 32 - pub fn decode(data: &[u8]) -> Result { - let Common { - network, - depth, - parent_fingerprint, - child_number, - chain_code, - key, - } = Common::decode(data)?; + pub fn decode(data: &[u8]) -> Result { + let Common { network, depth, parent_fingerprint, child_number, chain_code, key } = + Common::decode(data)?; let network = match network { VERSION_BYTES_MAINNET_PRIVATE => NetworkKind::Main, VERSION_BYTES_TESTNETS_PRIVATE => NetworkKind::Test, - unknown => return Err(Error::UnknownVersion(unknown)), + unknown => return Err(ParseError::UnknownVersion(unknown)), }; let (&zero, private_key) = key.split_first(); if zero != 0 { - return Err(Error::InvalidPrivateKeyPrefix); + return Err(ParseError::InvalidPrivateKeyPrefix); } Ok(Xpriv { @@ -801,7 +863,7 @@ impl Xpub { &self, secp: &Secp256k1, path: &P, - ) -> Result { + ) -> Result { self.derive_xpub(secp, path) } @@ -812,7 +874,7 @@ impl Xpub { &self, secp: &Secp256k1, path: &P, - ) -> Result { + ) -> Result { let mut pk: Xpub = *self; for cnum in path.as_ref() { pk = pk.ckd_pub(secp, *cnum)? @@ -824,9 +886,9 @@ impl Xpub { pub fn ckd_pub_tweak( &self, i: ChildNumber, - ) -> Result<(secp256k1::SecretKey, ChainCode), Error> { + ) -> Result<(secp256k1::SecretKey, ChainCode), DerivationError> { match i { - ChildNumber::Hardened { .. } => Err(Error::CannotDeriveFromHardenedKey), + ChildNumber::Hardened { .. } => Err(DerivationError::CannotDeriveHardenedChild), ChildNumber::Normal { index: n } => { let mut engine = HmacEngine::::new(&self.chain_code[..]); engine.input(&self.public_key.serialize()[..]); @@ -834,8 +896,9 @@ impl Xpub { let hmac = engine.finalize(); let private_key = secp256k1::SecretKey::from_byte_array( - hmac.as_byte_array().split_array::<32, 32>().0 - )?; + hmac.as_byte_array().split_array::<32, 32>().0, + ) + .expect("cryptographically unreachable"); let chain_code = ChainCode::from_hmac(hmac); Ok((private_key, chain_code)) } @@ -847,13 +910,14 @@ impl Xpub { &self, secp: &Secp256k1, i: ChildNumber, - ) -> Result { + ) -> Result { let (sk, chain_code) = self.ckd_pub_tweak(i)?; - let tweaked = self.public_key.add_exp_tweak(secp, &sk.into())?; + let tweaked = + self.public_key.add_exp_tweak(secp, &sk.into()).expect("cryptographically unreachable"); Ok(Xpub { network: self.network, - depth: self.depth + 1, + depth: self.depth.checked_add(1).ok_or(DerivationError::MaximumDepthExceeded)?, parent_fingerprint: self.fingerprint(), child_number: i, public_key: tweaked, @@ -862,20 +926,14 @@ impl Xpub { } /// Decoding extended public key from binary data according to BIP 32 - pub fn decode(data: &[u8]) -> Result { - let Common { - network, - depth, - parent_fingerprint, - child_number, - chain_code, - key, - } = Common::decode(data)?; + pub fn decode(data: &[u8]) -> Result { + let Common { network, depth, parent_fingerprint, child_number, chain_code, key } = + Common::decode(data)?; let network = match network { VERSION_BYTES_MAINNET_PUBLIC => NetworkKind::Main, VERSION_BYTES_TESTNETS_PUBLIC => NetworkKind::Test, - unknown => return Err(Error::UnknownVersion(unknown)), + unknown => return Err(ParseError::UnknownVersion(unknown)), }; Ok(Xpub { @@ -921,9 +979,9 @@ impl fmt::Display for Xpriv { } impl FromStr for Xpriv { - type Err = Error; + type Err = ParseError; - fn from_str(inp: &str) -> Result { + fn from_str(inp: &str) -> Result { let data = base58::decode_check(inp)?; if data.len() != 78 { @@ -941,9 +999,9 @@ impl fmt::Display for Xpub { } impl FromStr for Xpub { - type Err = Error; + type Err = ParseError; - fn from_str(inp: &str) -> Result { + fn from_str(inp: &str) -> Result { let data = base58::decode_check(inp)?; if data.len() != 78 { @@ -994,13 +1052,14 @@ struct Common { parent_fingerprint: Fingerprint, child_number: ChildNumber, chain_code: ChainCode, - // public key (compressed) or 0 byte followed by a private key + // public key (compressed) or 0 byte followed by a private key key: [u8; 33], } impl Common { - fn decode(data: &[u8]) -> Result { - let data: &[u8; 78] = data.try_into().map_err(|_| Error::WrongExtendedKeyLength(data.len()))?; + fn decode(data: &[u8]) -> Result { + let data: &[u8; 78] = + data.try_into().map_err(|_| ParseError::WrongExtendedKeyLength(data.len()))?; let (&network, data) = data.split_array::<4, 74>(); let (&depth, data) = data.split_first::<73>(); @@ -1010,11 +1069,11 @@ impl Common { if depth == 0 { if parent_fingerprint != [0u8; 4] { - return Err(Error::NonZeroParentFingerprintForMasterKey); + return Err(ParseError::NonZeroParentFingerprintForMasterKey); } if child_number != [0u8; 4] { - return Err(Error::NonZeroChildNumberForMasterKey); + return Err(ParseError::NonZeroChildNumberForMasterKey); } } @@ -1031,7 +1090,7 @@ impl Common { #[cfg(test)] mod tests { - use hex::test_hex_unwrap as hex; + use hex_lit::hex; #[cfg(feature = "serde")] use internals::serde_round_trip; @@ -1040,13 +1099,25 @@ mod tests { #[test] fn parse_derivation_path() { - assert_eq!("n/0'/0".parse::(), Err(Error::InvalidChildNumberFormat)); - assert_eq!("4/m/5".parse::(), Err(Error::InvalidChildNumberFormat)); - assert_eq!("//3/0'".parse::(), Err(Error::InvalidChildNumberFormat)); - assert_eq!("0h/0x".parse::(), Err(Error::InvalidChildNumberFormat)); + assert!(matches!( + "n/0'/0".parse::(), + Err(ParseChildNumberError::ParseInt(..)), + )); + assert!(matches!( + "4/m/5".parse::(), + Err(ParseChildNumberError::ParseInt(..)), + )); + assert!(matches!( + "//3/0'".parse::(), + Err(ParseChildNumberError::ParseInt(..)), + )); + assert!(matches!( + "0h/0x".parse::(), + Err(ParseChildNumberError::ParseInt(..)), + )); assert_eq!( "2147483648".parse::(), - Err(Error::InvalidChildNumber(2147483648)) + Err(ParseChildNumberError::IndexOutOfRange(IndexOutOfRangeError { index: 2147483648 })), ); assert_eq!(DerivationPath::master(), "".parse::().unwrap()); @@ -1120,23 +1191,26 @@ mod tests { expected_sk: &str, expected_pk: &str, ) { - let mut sk = Xpriv::new_master(network, seed).unwrap(); + let mut sk = Xpriv::new_master(network, seed); let mut pk = Xpub::from_xpriv(secp, &sk); // Check derivation convenience method for Xpriv - assert_eq!(&sk.derive_xpriv(secp, &path).to_string()[..], expected_sk); + assert_eq!(&sk.derive_xpriv(secp, &path).unwrap().to_string()[..], expected_sk); // Check derivation convenience method for Xpub, should error // appropriately if any ChildNumber is hardened if path.0.iter().any(|cnum| cnum.is_hardened()) { - assert_eq!(pk.derive_xpub(secp, &path), Err(Error::CannotDeriveFromHardenedKey)); + assert_eq!( + pk.derive_xpub(secp, &path), + Err(DerivationError::CannotDeriveHardenedChild) + ); } else { assert_eq!(&pk.derive_xpub(secp, &path).unwrap().to_string()[..], expected_pk); } // 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(); @@ -1144,7 +1218,10 @@ mod tests { assert_eq!(pk, pk2); } Hardened { .. } => { - assert_eq!(pk.ckd_pub(secp, num), Err(Error::CannotDeriveFromHardenedKey)); + assert_eq!( + pk.ckd_pub(secp, num), + Err(DerivationError::CannotDeriveHardenedChild) + ); pk = Xpub::from_xpriv(secp, &sk); } } @@ -1170,9 +1247,9 @@ mod tests { let max = (1 << 31) - 1; let cn = ChildNumber::from_normal_idx(max).unwrap(); - assert_eq!(cn.increment().err(), Some(Error::InvalidChildNumber(1 << 31))); + assert_eq!(cn.increment(), Err(IndexOutOfRangeError { index: 1 << 31 }),); let cn = ChildNumber::from_hardened_idx(max).unwrap(); - assert_eq!(cn.increment().err(), Some(Error::InvalidChildNumber(1 << 31))); + assert_eq!(cn.increment(), Err(IndexOutOfRangeError { index: 1 << 31 }),); let cn = ChildNumber::from_normal_idx(350).unwrap(); let path = "42'".parse::().unwrap(); @@ -1302,7 +1379,7 @@ mod tests { assert!(result.is_err()); match result { - Err(Error::InvalidPrivateKeyPrefix) => {} + Err(ParseError::InvalidPrivateKeyPrefix) => {} _ => panic!("Expected InvalidPrivateKeyPrefix error, got {:?}", result), } } @@ -1313,7 +1390,7 @@ mod tests { assert!(result.is_err()); match result { - Err(Error::NonZeroChildNumberForMasterKey) => {} + Err(ParseError::NonZeroChildNumberForMasterKey) => {} _ => panic!("Expected NonZeroChildNumberForMasterKey error, got {:?}", result), } } @@ -1324,7 +1401,7 @@ mod tests { assert!(result.is_err()); match result { - Err(Error::NonZeroParentFingerprintForMasterKey) => {} + Err(ParseError::NonZeroParentFingerprintForMasterKey) => {} _ => panic!("Expected NonZeroParentFingerprintForMasterKey error, got {:?}", result), } } diff --git a/bitcoin/src/blockdata/block.rs b/bitcoin/src/blockdata/block.rs index a8dae5e4ba..7154254d0b 100644 --- a/bitcoin/src/blockdata/block.rs +++ b/bitcoin/src/blockdata/block.rs @@ -30,7 +30,11 @@ use crate::transaction::{Transaction, TransactionExt as _, Wtxid}; #[doc(inline)] pub use primitives::block::{Block, Checked, Unchecked, Validation, Version, BlockHash, Header, WitnessCommitment}; #[doc(inline)] -pub use units::block::{BlockHeight, BlockInterval, TooBigForRelativeBlockHeightError}; +pub use units::block::{BlockHeight, BlockHeightInterval, TooBigForRelativeHeightError}; + +#[deprecated(since = "TBD", note = "use `BlockHeightInterval` instead")] +#[doc(hidden)] +pub type BlockInterval = BlockHeightInterval; impl_hashencode!(BlockHash); @@ -503,7 +507,7 @@ impl std::error::Error for ValidationError { #[cfg(test)] mod tests { - use hex::test_hex_unwrap as hex; + use hex_lit::hex; use internals::ToU64 as _; use super::*; diff --git a/bitcoin/src/blockdata/constants.rs b/bitcoin/src/blockdata/constants.rs index d0c3ce3f08..7115cadb09 100644 --- a/bitcoin/src/blockdata/constants.rs +++ b/bitcoin/src/blockdata/constants.rs @@ -31,9 +31,9 @@ pub const MAX_BLOCK_SIGOPS_COST: i64 = 80_000; pub const PUBKEY_ADDRESS_PREFIX_MAIN: u8 = 0; // 0x00 /// Mainnet (bitcoin) script address prefix. pub const SCRIPT_ADDRESS_PREFIX_MAIN: u8 = 5; // 0x05 -/// Test (tesnet, signet, regtest) pubkey address prefix. +/// Test (testnet, signet, regtest) pubkey address prefix. pub const PUBKEY_ADDRESS_PREFIX_TEST: u8 = 111; // 0x6f -/// Test (tesnet, signet, regtest) script address prefix. +/// Test (testnet, signet, regtest) script address prefix. pub const SCRIPT_ADDRESS_PREFIX_TEST: u8 = 196; // 0xc4 /// The maximum allowed redeem script size for a P2SH output. pub const MAX_REDEEM_SCRIPT_SIZE: usize = primitives::script::MAX_REDEEM_SCRIPT_SIZE; // 520 @@ -41,7 +41,7 @@ pub const MAX_REDEEM_SCRIPT_SIZE: usize = primitives::script::MAX_REDEEM_SCRIPT_ pub const MAX_WITNESS_SCRIPT_SIZE: usize = primitives::script::MAX_WITNESS_SCRIPT_SIZE; // 10_000 /// The maximum allowed size of any single witness stack element. pub const MAX_STACK_ELEMENT_SIZE: usize = 520; -/// How may blocks between halvings. +/// How many blocks between halvings. pub const SUBSIDY_HALVING_INTERVAL: u32 = 210_000; /// Maximum allowed value for an integer in Script. /// This constant has ambiguous semantics. Please carefully check your intended use-case and define @@ -282,7 +282,7 @@ impl ChainHash { #[cfg(test)] mod test { - use hex::test_hex_unwrap as hex; + use hex_lit::hex; use super::*; use crate::consensus::encode::serialize; diff --git a/bitcoin/src/blockdata/mod.rs b/bitcoin/src/blockdata/mod.rs index a6f39ba9a8..7974ba8075 100644 --- a/bitcoin/src/blockdata/mod.rs +++ b/bitcoin/src/blockdata/mod.rs @@ -19,7 +19,7 @@ pub use self::{ weight::Weight }; -/// Implements `FeeRate` and assoctiated features. +/// Implements `FeeRate` and associated features. pub mod fee_rate { #[cfg(feature = "serde")] pub use units::fee_rate::serde; @@ -32,7 +32,7 @@ pub mod locktime { pub mod absolute { //! Provides type [`LockTime`] that implements the logic around nLockTime/OP_CHECKLOCKTIMEVERIFY. //! - //! There are two types of lock time: lock-by-blockheight and lock-by-blocktime, distinguished by + //! There are two types of lock time: lock-by-height and lock-by-time, distinguished by //! whether `LockTime < LOCKTIME_THRESHOLD`. use io::{BufRead, Write}; @@ -41,7 +41,11 @@ pub mod locktime { /// Re-export everything from the `primitives::locktime::absolute` module. #[rustfmt::skip] // Keep public re-exports separate. - pub use primitives::locktime::absolute::{ConversionError, Height, LockTime, ParseHeightError, ParseTimeError, Time}; + pub use primitives::locktime::absolute::{ConversionError, Height, LockTime, ParseHeightError, ParseTimeError, MedianTimePast}; + + #[deprecated(since = "TBD", note = "use `MedianTimePast` instead")] + #[doc(hidden)] + pub type Time = MedianTimePast; impl Encodable for LockTime { #[inline] @@ -62,14 +66,22 @@ pub mod locktime { pub mod relative { //! Provides type [`LockTime`] that implements the logic around nSequence/OP_CHECKSEQUENCEVERIFY. //! - //! There are two types of lock time: lock-by-blockheight and lock-by-blocktime, distinguished by + //! There are two types of lock time: lock-by-height and lock-by-time, distinguished by //! whether bit 22 of the `u32` consensus value is set. /// Re-export everything from the `primitives::locktime::relative` module. pub use primitives::locktime::relative::{ - DisabledLockTimeError, Height, IncompatibleHeightError, IncompatibleTimeError, - LockTime, Time, TimeOverflowError, + DisabledLockTimeError, InvalidHeightError, InvalidTimeError, LockTime, + NumberOf512Seconds, NumberOfBlocks, TimeOverflowError, }; + + #[deprecated(since = "TBD", note = "use `NumberOfBlocks` instead")] + #[doc(hidden)] + pub type Height = NumberOfBlocks; + + #[deprecated(since = "TBD", note = "use `NumberOf512Seconds` instead")] + #[doc(hidden)] + pub type Time = NumberOf512Seconds; } } diff --git a/bitcoin/src/blockdata/script/borrowed.rs b/bitcoin/src/blockdata/script/borrowed.rs index c048ba3670..7cc5fca7d5 100644 --- a/bitcoin/src/blockdata/script/borrowed.rs +++ b/bitcoin/src/blockdata/script/borrowed.rs @@ -2,6 +2,7 @@ use core::fmt; +use hex::DisplayHex as _; use internals::ToU64 as _; use super::witness_version::WitnessVersion; @@ -9,11 +10,11 @@ use super::{ Builder, Instruction, InstructionIndices, Instructions, PushBytes, RedeemScriptSizeError, ScriptHash, WScriptHash, WitnessScriptSizeError, }; -use crate::consensus::Encodable; +use crate::consensus::{self, Encodable}; use crate::opcodes::all::*; use crate::opcodes::{self, Opcode}; use crate::policy::{DUST_RELAY_TX_FEE, MAX_OP_RETURN_RELAY}; -use crate::prelude::{sink, DisplayHex, String, ToString}; +use crate::prelude::{sink, String, ToString}; use crate::taproot::{LeafVersion, TapLeafHash}; use crate::{Amount, FeeRate}; @@ -261,7 +262,7 @@ crate::internal_macros::define_extension_trait! { /// Returns the minimum value an output with this script should have in order to be /// broadcastable on today’s Bitcoin network. #[deprecated(since = "0.32.0", note = "use `minimal_non_dust` etc. instead")] - fn dust_value(&self) -> Option { self.minimal_non_dust() } + fn dust_value(&self) -> Amount { self.minimal_non_dust() } /// Returns the minimum value an output with this script should have in order to be /// broadcastable on today's Bitcoin network. @@ -272,8 +273,9 @@ crate::internal_macros::define_extension_trait! { /// To use a custom value, use [`minimal_non_dust_custom`]. /// /// [`minimal_non_dust_custom`]: Script::minimal_non_dust_custom - fn minimal_non_dust(&self) -> Option { + fn minimal_non_dust(&self) -> Amount { self.minimal_non_dust_internal(DUST_RELAY_TX_FEE.into()) + .expect("dust_relay_fee or script length should not be absurdly large") } /// Returns the minimum value an output with this script should have in order to be @@ -373,12 +375,23 @@ crate::internal_macros::define_extension_trait! { #[deprecated(since = "TBD", note = "use `to_string()` instead")] fn to_asm_string(&self) -> String { self.to_string() } - /// Formats the script as lower-case hex. + /// Consensus encodes the script as lower-case hex. + #[deprecated(since = "TBD", note = "use `to_hex_string_no_length_prefix` instead")] + fn to_hex_string(&self) -> String { self.to_hex_string_no_length_prefix() } + + /// Consensus encodes the script as lower-case hex. + /// + /// Consensus encoding includes a length prefix. To hex encode without the length prefix use + /// `to_hex_string_no_length_prefix`. + fn to_hex_string_prefixed(&self) -> String { consensus::encode::serialize_hex(self) } + + /// Encodes the script as lower-case hex. /// - /// This is a more convenient and performant way to write `format!("{:x}", script)`. - /// For better performance you should generally prefer displaying the script but if `String` is - /// required (this is common in tests) this method can be used. - fn to_hex_string(&self) -> String { self.as_bytes().to_lower_hex_string() } + /// This is **not** consensus encoding. The returned hex string will not include the length + /// prefix. See `to_hex_string_prefixed`. + fn to_hex_string_no_length_prefix(&self) -> String { + self.as_bytes().to_lower_hex_string() + } /// Returns the first opcode of the script (if there is any). fn first_opcode(&self) -> Option { diff --git a/bitcoin/src/blockdata/script/builder.rs b/bitcoin/src/blockdata/script/builder.rs index b8a52ea91a..55d4c325a5 100644 --- a/bitcoin/src/blockdata/script/builder.rs +++ b/bitcoin/src/blockdata/script/builder.rs @@ -2,6 +2,8 @@ use core::fmt; +use primitives::relative; + use super::{opcode_to_verify, write_scriptint, Error, PushBytes, Script, ScriptBuf}; use crate::locktime::absolute; use crate::opcodes::all::*; @@ -19,6 +21,12 @@ impl Builder { #[inline] pub const fn new() -> Self { Builder(ScriptBuf::new(), None) } + /// Constructs a new empty script builder with at least the specified capacity. + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + Builder(ScriptBuf::with_capacity(capacity), None) + } + /// Returns the length in bytes of the script. pub fn len(&self) -> usize { self.0.len() } @@ -72,12 +80,31 @@ impl Builder { pub(in crate::blockdata) fn push_int_non_minimal(self, data: i64) -> Builder { let mut buf = [0u8; 8]; let len = write_scriptint(&mut buf, data); - self.push_slice(&<&PushBytes>::from(&buf)[..len]) + self.push_slice_non_minimal(&<&PushBytes>::from(&buf)[..len]) } /// Adds instructions to push some arbitrary data onto the stack. - pub fn push_slice>(mut self, data: T) -> Builder { - self.0.push_slice(data); + pub fn push_slice>(self, data: T) -> Builder { + let bytes = data.as_ref().as_bytes(); + if bytes.len() == 1 && (bytes[0] == 0x81 || bytes[0] <= 16) { + match bytes[0] { + 0x81 => self.push_opcode(OP_PUSHNUM_NEG1), + 0 => self.push_opcode(OP_PUSHBYTES_0), + 1..=16 => self.push_opcode(Opcode::from(bytes[0] + (OP_PUSHNUM_1.to_u8() - 1))), + _ => self, // unreachable arm + } + } else { + self.push_slice_non_minimal(data.as_ref()) + } + } + + /// Adds instructions to push some arbitrary data onto the stack without minimality. + /// + /// Standardness rules require push minimality according to [CheckMinimalPush] of core. + /// + /// [CheckMinimalPush]: + pub fn push_slice_non_minimal>(mut self, data: T) -> Builder { + self.0.push_slice_non_minimal(data); self.1 = None; self } @@ -115,7 +142,26 @@ impl Builder { self.push_int_unchecked(lock_time.to_consensus_u32().into()) } + /// Adds instructions to push a relative lock time onto the stack. + /// + /// This is used when creating scripts that use CHECKSEQUENCEVERIFY (CSV) to enforce + /// relative time locks. + pub fn push_relative_lock_time(self, lock_time: relative::LockTime) -> Builder { + self.push_int_unchecked(lock_time.to_consensus_u32().into()) + } + /// Adds instructions to push a sequence number onto the stack. + /// + /// # Deprecated + /// This method is deprecated in favor of `push_relative_lock_time`. + /// + /// In Bitcoin script semantics, when using CHECKSEQUENCEVERIFY, you typically + /// want to push a relative locktime value to be compared against the input's + /// sequence number, not the sequence number itself. + #[deprecated( + since = "TBD", + note = "Use push_relative_lock_time instead for working with timelocks in scripts" + )] pub fn push_sequence(self, sequence: Sequence) -> Builder { self.push_int_unchecked(sequence.to_consensus_u32().into()) } diff --git a/bitcoin/src/blockdata/script/mod.rs b/bitcoin/src/blockdata/script/mod.rs index 1bd2bddf35..dccfe67272 100644 --- a/bitcoin/src/blockdata/script/mod.rs +++ b/bitcoin/src/blockdata/script/mod.rs @@ -31,7 +31,7 @@ //! and forcing them to be larger would waste memory and, in case of Bitcoin script, even some //! performance (forcing allocations). //! -//! ## `Script` vs `ScriptBuf` vs `Builder` +//! # `Script` vs `ScriptBuf` vs `Builder` //! //! These are the most important types in this module and they are quite similar, so it may seem //! confusing what the differences are. `Script` is an unsized type much like `str` or `Path` are diff --git a/bitcoin/src/blockdata/script/owned.rs b/bitcoin/src/blockdata/script/owned.rs index 8b8b73e385..da3db6e117 100644 --- a/bitcoin/src/blockdata/script/owned.rs +++ b/bitcoin/src/blockdata/script/owned.rs @@ -3,10 +3,11 @@ #[cfg(doc)] use core::ops::Deref; -use hex::FromHex; +use hex::FromHex as _; use internals::ToU64 as _; use super::{opcode_to_verify, Builder, Instruction, PushBytes, ScriptExtPriv as _}; +use crate::consensus; use crate::opcodes::all::*; use crate::opcodes::{self, Opcode}; use crate::prelude::Vec; @@ -27,7 +28,23 @@ crate::internal_macros::define_extension_trait! { } /// Constructs a new [`ScriptBuf`] from a hex string. + /// + /// The input string is expected to be consensus encoded i.e., includes the length prefix. + fn from_hex_prefixed(s: &str) -> Result { + consensus::encode::deserialize_hex(s) + } + + /// Constructs a new [`ScriptBuf`] from a hex string. + #[deprecated(since = "TBD", note = "use `from_hex_string_no_length_prefix()` instead")] fn from_hex(s: &str) -> Result { + Self::from_hex_no_length_prefix(s) + } + + /// Constructs a new [`ScriptBuf`] from a hex string. + /// + /// This is **not** consensus encoding. If your hex string is a consensus encoded script + /// then use `ScriptBuf::from_hex_prefixed`. + fn from_hex_no_length_prefix(s: &str) -> Result { let v = Vec::from_hex(s)?; Ok(ScriptBuf::from_bytes(v)) } @@ -37,6 +54,25 @@ crate::internal_macros::define_extension_trait! { /// Adds instructions to push some arbitrary data onto the stack. fn push_slice>(&mut self, data: T) { + let bytes = data.as_ref().as_bytes(); + if bytes.len() == 1 && (bytes[0] == 0x81 || bytes[0] <= 16) { + match bytes[0] { + 0x81 => { self.push_opcode(OP_PUSHNUM_NEG1); }, + 0 => { self.push_opcode(OP_PUSHBYTES_0); }, + 1..=16 => { self.push_opcode(Opcode::from(bytes[0] + (OP_PUSHNUM_1.to_u8() - 1))); }, + _ => {}, // unreachable arm + } + } else { + self.push_slice_non_minimal(data); + } + } + + /// Adds instructions to push some arbitrary data onto the stack without minimality. + /// + /// Standardness rules require push minimality according to [CheckMinimalPush] of core. + /// + /// [CheckMinimalPush]: + fn push_slice_non_minimal>(&mut self, data: T) { let data = data.as_ref(); self.reserve(ScriptBuf::reserved_len_for_slice(data.len())); self.push_slice_no_opt(data); diff --git a/bitcoin/src/blockdata/script/push_bytes.rs b/bitcoin/src/blockdata/script/push_bytes.rs index bae7902b3b..7fb46742af 100644 --- a/bitcoin/src/blockdata/script/push_bytes.rs +++ b/bitcoin/src/blockdata/script/push_bytes.rs @@ -35,34 +35,29 @@ mod primitive { } } - /// Byte slices that can be in Bitcoin script. - /// - /// The encoding of Bitcoin script restricts data pushes to be less than 2^32 bytes long. - /// This type represents slices that are guaranteed to be within the limit so they can be put in - /// the script safely. - #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] - #[repr(transparent)] - pub struct PushBytes([u8]); - - impl PushBytes { - /// Constructs a new `&PushBytes` without checking the length. - /// - /// The caller is responsible for checking that the length is less than the 2^32. - fn from_slice_unchecked(bytes: &[u8]) -> &Self { - // SAFETY: The conversion is sound because &[u8] and &PushBytes - // have the same layout (because of #[repr(transparent)] on PushBytes). - unsafe { &*(bytes as *const [u8] as *const PushBytes) } - } - - /// Constructs a new `&mut PushBytes` without checking the length. + internals::transparent_newtype! { + /// Byte slices that can be in Bitcoin script. /// - /// The caller is responsible for checking that the length is less than the 2^32. - fn from_mut_slice_unchecked(bytes: &mut [u8]) -> &mut Self { - // SAFETY: The conversion is sound because &mut [u8] and &mut PushBytes - // have the same layout (because of #[repr(transparent)] on PushBytes). - unsafe { &mut *(bytes as *mut [u8] as *mut PushBytes) } + /// The encoding of Bitcoin script restricts data pushes to be less than 2^32 bytes long. + /// This type represents slices that are guaranteed to be within the limit so they can be put in + /// the script safely. + #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] + pub struct PushBytes([u8]); + + impl PushBytes { + /// Constructs a new `&PushBytes` without checking the length. + /// + /// The caller is responsible for checking that the length is less than the 2^32. + fn from_slice_unchecked(bytes: &_) -> &Self; + + /// Constructs a new `&mut PushBytes` without checking the length. + /// + /// The caller is responsible for checking that the length is less than the 2^32. + fn from_mut_slice_unchecked(bytes: &mut _) -> &mut Self; } + } + impl PushBytes { /// Constructs an empty `&PushBytes`. pub fn empty() -> &'static Self { Self::from_slice_unchecked(&[]) } diff --git a/bitcoin/src/blockdata/script/tests.rs b/bitcoin/src/blockdata/script/tests.rs index 27dcd32ea3..590d233d36 100644 --- a/bitcoin/src/blockdata/script/tests.rs +++ b/bitcoin/src/blockdata/script/tests.rs @@ -33,6 +33,24 @@ fn script() { // data script = script.push_slice(b"NRA4VR"); comp.extend([6u8, 78, 82, 65, 52, 86, 82].iter().cloned()); assert_eq!(script.as_bytes(), &comp[..]); + // data & number push minimality + // OP_0 + script = script.push_slice([0u8]); comp.extend([0u8].iter().cloned()); assert_eq!(script.as_bytes(), &comp[..]); + script = script.push_slice_non_minimal([0u8]); comp.extend([1, 0u8].iter().cloned()); assert_eq!(script.as_bytes(), &comp[..]); + script = script.push_int(0).unwrap(); comp.extend([0u8].iter().cloned()); assert_eq!(script.as_bytes(), &comp[..]); + script = script.push_int_non_minimal(0); comp.extend([0u8].iter().cloned()); assert_eq!(script.as_bytes(), &comp[..]); + // OP_1..16 + for n in 1..=16 { + script = script.push_slice([n]); comp.extend([0x50 + n].iter().cloned()); assert_eq!(script.as_bytes(), &comp[..]); + script = script.push_slice_non_minimal([n]); comp.extend([1, n].iter().cloned()); assert_eq!(script.as_bytes(), &comp[..]); + script = script.push_int(n.into()).unwrap(); comp.extend([0x50 + n].iter().cloned()); assert_eq!(script.as_bytes(), &comp[..]); + script = script.push_int_non_minimal(n.into()); comp.extend([1, n].iter().cloned()); assert_eq!(script.as_bytes(), &comp[..]); + } + // OP_1NEGATE + script = script.push_slice([0x81]); comp.extend([0x4f].iter().cloned()); assert_eq!(script.as_bytes(), &comp[..]); + script = script.push_slice_non_minimal([0x81]); comp.extend([1, 0x81].iter().cloned()); assert_eq!(script.as_bytes(), &comp[..]); + script = script.push_int(-1).unwrap(); comp.extend([0x4f].iter().cloned()); assert_eq!(script.as_bytes(), &comp[..]); + script = script.push_int_non_minimal(-1); comp.extend([1, 0x81].iter().cloned()); assert_eq!(script.as_bytes(), &comp[..]); // keys const KEYSTR1: &str = "21032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af"; @@ -65,7 +83,7 @@ fn p2pk_pubkey_bytes_no_checksig_returns_none() { } #[test] -fn p2pk_pubkey_bytes_emptry_script_returns_none() { +fn p2pk_pubkey_bytes_empty_script_returns_none() { let empty_script = Script::builder().into_script(); assert!(empty_script.p2pk_pubkey_bytes().is_none()); } @@ -194,7 +212,17 @@ fn script_builder() { .push_opcode(OP_EQUALVERIFY) .push_opcode(OP_CHECKSIG) .into_script(); - assert_eq!(script.to_hex_string(), "76a91416e1ae70ff0fa102905d4af297f6912bda6cce1988ac"); + assert_eq!( + script.to_hex_string_no_length_prefix(), + "76a91416e1ae70ff0fa102905d4af297f6912bda6cce1988ac" + ); +} + +#[test] +fn script_builder_with_capacity() { + let script = Builder::with_capacity(42); + + assert!(script.into_script().capacity() >= 42); } #[test] @@ -227,7 +255,7 @@ fn script_generators() { let op_return = ScriptBuf::new_op_return(data); assert!(op_return.is_op_return()); assert_eq!( - op_return.to_hex_string(), + op_return.to_hex_string_no_length_prefix(), "6a24aa21a9ed20280f53f2d21663cac89e6bd2ad19edbabb048cda08e73ed19e9268d0afea2a" ); } @@ -235,42 +263,42 @@ fn script_generators() { #[test] fn script_builder_verify() { let simple = Builder::new().push_verify().into_script(); - assert_eq!(simple.to_hex_string(), "69"); + assert_eq!(simple.to_hex_string_no_length_prefix(), "69"); let simple2 = Builder::from(vec![]).push_verify().into_script(); - assert_eq!(simple2.to_hex_string(), "69"); + assert_eq!(simple2.to_hex_string_no_length_prefix(), "69"); let nonverify = Builder::new().push_verify().push_verify().into_script(); - assert_eq!(nonverify.to_hex_string(), "6969"); + assert_eq!(nonverify.to_hex_string_no_length_prefix(), "6969"); let nonverify2 = Builder::from(vec![0x69]).push_verify().into_script(); - assert_eq!(nonverify2.to_hex_string(), "6969"); + assert_eq!(nonverify2.to_hex_string_no_length_prefix(), "6969"); let equal = Builder::new().push_opcode(OP_EQUAL).push_verify().into_script(); - assert_eq!(equal.to_hex_string(), "88"); + assert_eq!(equal.to_hex_string_no_length_prefix(), "88"); let equal2 = Builder::from(vec![0x87]).push_verify().into_script(); - assert_eq!(equal2.to_hex_string(), "88"); + assert_eq!(equal2.to_hex_string_no_length_prefix(), "88"); let numequal = Builder::new().push_opcode(OP_NUMEQUAL).push_verify().into_script(); - assert_eq!(numequal.to_hex_string(), "9d"); + assert_eq!(numequal.to_hex_string_no_length_prefix(), "9d"); let numequal2 = Builder::from(vec![0x9c]).push_verify().into_script(); - assert_eq!(numequal2.to_hex_string(), "9d"); + assert_eq!(numequal2.to_hex_string_no_length_prefix(), "9d"); let checksig = Builder::new().push_opcode(OP_CHECKSIG).push_verify().into_script(); - assert_eq!(checksig.to_hex_string(), "ad"); + assert_eq!(checksig.to_hex_string_no_length_prefix(), "ad"); let checksig2 = Builder::from(vec![0xac]).push_verify().into_script(); - assert_eq!(checksig2.to_hex_string(), "ad"); + assert_eq!(checksig2.to_hex_string_no_length_prefix(), "ad"); let checkmultisig = Builder::new().push_opcode(OP_CHECKMULTISIG).push_verify().into_script(); - assert_eq!(checkmultisig.to_hex_string(), "af"); + assert_eq!(checkmultisig.to_hex_string_no_length_prefix(), "af"); let checkmultisig2 = Builder::from(vec![0xae]).push_verify().into_script(); - assert_eq!(checkmultisig2.to_hex_string(), "af"); + assert_eq!(checkmultisig2.to_hex_string_no_length_prefix(), "af"); let trick_slice = Builder::new() .push_slice([0xae]) // OP_CHECKMULTISIG .push_verify() .into_script(); - assert_eq!(trick_slice.to_hex_string(), "01ae69"); + assert_eq!(trick_slice.to_hex_string_no_length_prefix(), "01ae69"); let trick_slice2 = Builder::from(vec![0x01, 0xae]).push_verify().into_script(); - assert_eq!(trick_slice2.to_hex_string(), "01ae69"); + assert_eq!(trick_slice2.to_hex_string_no_length_prefix(), "01ae69"); } #[test] @@ -364,7 +392,7 @@ fn non_minimal_scriptints() { #[test] fn script_hashes() { - let script = ScriptBuf::from_hex("410446ef0102d1ec5240f0d061a4246c1bdef63fc3dbab7733052fbbf0ecd8f41fc26bf049ebb4f9527f374280259e7cfa99c48b0e3f39c51347a19a5819651503a5ac").unwrap(); + let script = ScriptBuf::from_hex_no_length_prefix("410446ef0102d1ec5240f0d061a4246c1bdef63fc3dbab7733052fbbf0ecd8f41fc26bf049ebb4f9527f374280259e7cfa99c48b0e3f39c51347a19a5819651503a5ac").unwrap(); assert_eq!( script.script_hash().unwrap().to_string(), "8292bcfbef1884f73c813dfe9c82fd7e814291ea" @@ -374,10 +402,12 @@ fn script_hashes() { "3e1525eb183ad4f9b3c5fa3175bdca2a52e947b135bbb90383bf9f6408e2c324" ); assert_eq!( - ScriptBuf::from_hex("20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac") - .unwrap() - .tapscript_leaf_hash() - .to_string(), + ScriptBuf::from_hex_no_length_prefix( + "20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac" + ) + .unwrap() + .tapscript_leaf_hash() + .to_string(), "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21" ); } @@ -385,40 +415,50 @@ fn script_hashes() { #[test] fn provably_unspendable() { // p2pk - assert!(!ScriptBuf::from_hex("410446ef0102d1ec5240f0d061a4246c1bdef63fc3dbab7733052fbbf0ecd8f41fc26bf049ebb4f9527f374280259e7cfa99c48b0e3f39c51347a19a5819651503a5ac").unwrap().is_op_return()); - assert!(!ScriptBuf::from_hex("4104ea1feff861b51fe3f5f8a3b12d0f4712db80e919548a80839fc47c6a21e66d957e9c5d8cd108c7a2d2324bad71f9904ac0ae7336507d785b17a2c115e427a32fac").unwrap().is_op_return()); + assert!(!ScriptBuf::from_hex_no_length_prefix("410446ef0102d1ec5240f0d061a4246c1bdef63fc3dbab7733052fbbf0ecd8f41fc26bf049ebb4f9527f374280259e7cfa99c48b0e3f39c51347a19a5819651503a5ac").unwrap().is_op_return()); + assert!(!ScriptBuf::from_hex_no_length_prefix("4104ea1feff861b51fe3f5f8a3b12d0f4712db80e919548a80839fc47c6a21e66d957e9c5d8cd108c7a2d2324bad71f9904ac0ae7336507d785b17a2c115e427a32fac").unwrap().is_op_return()); // p2pkhash - assert!(!ScriptBuf::from_hex("76a914ee61d57ab51b9d212335b1dba62794ac20d2bcf988ac") - .unwrap() - .is_op_return()); - assert!(ScriptBuf::from_hex("6aa9149eb21980dc9d413d8eac27314938b9da920ee53e87") - .unwrap() - .is_op_return()); + assert!(!ScriptBuf::from_hex_no_length_prefix( + "76a914ee61d57ab51b9d212335b1dba62794ac20d2bcf988ac" + ) + .unwrap() + .is_op_return()); + assert!(ScriptBuf::from_hex_no_length_prefix( + "6aa9149eb21980dc9d413d8eac27314938b9da920ee53e87" + ) + .unwrap() + .is_op_return()); } #[test] fn op_return() { - assert!(ScriptBuf::from_hex("6aa9149eb21980dc9d413d8eac27314938b9da920ee53e87") - .unwrap() - .is_op_return()); - assert!(!ScriptBuf::from_hex("76a914ee61d57ab51b9d212335b1dba62794ac20d2bcf988ac") - .unwrap() - .is_op_return()); - assert!(!ScriptBuf::from_hex("").unwrap().is_op_return()); + assert!(ScriptBuf::from_hex_no_length_prefix( + "6aa9149eb21980dc9d413d8eac27314938b9da920ee53e87" + ) + .unwrap() + .is_op_return()); + assert!(!ScriptBuf::from_hex_no_length_prefix( + "76a914ee61d57ab51b9d212335b1dba62794ac20d2bcf988ac" + ) + .unwrap() + .is_op_return()); + assert!(!ScriptBuf::from_hex_no_length_prefix("").unwrap().is_op_return()); } #[test] fn standard_op_return() { - assert!(ScriptBuf::from_hex("6aa9149eb21980dc9d413d8eac27314938b9da920ee53e87") - .unwrap() - .is_standard_op_return()); - assert!(ScriptBuf::from_hex("6a48656c6c6f2c2074686973206973206d7920666972737420636f6e747269627574696f6e20746f207275737420626974636f696e2e20506c6561736520617070726f7665206d79205052206672656e") + assert!(ScriptBuf::from_hex_no_length_prefix( + "6aa9149eb21980dc9d413d8eac27314938b9da920ee53e87" + ) + .unwrap() + .is_standard_op_return()); + assert!(ScriptBuf::from_hex_no_length_prefix("6a48656c6c6f2c2074686973206973206d7920666972737420636f6e747269627574696f6e20746f207275737420626974636f696e2e20506c6561736520617070726f7665206d79205052206672656e") .unwrap() .is_standard_op_return()); - assert!(ScriptBuf::from_hex("6a48656c6c6f2c2074686973206973206d7920666972737420636f6e747269627574696f6e20746f207275737420626974636f696e2e20506c6561736520617070726f7665206d79205052206672656e21") + assert!(ScriptBuf::from_hex_no_length_prefix("6a48656c6c6f2c2074686973206973206d7920666972737420636f6e747269627574696f6e20746f207275737420626974636f696e2e20506c6561736520617070726f7665206d79205052206672656e21") .unwrap() .is_standard_op_return()); - assert!(!ScriptBuf::from_hex("6a48656c6c6f2c2074686973206973206d7920666972737420636f6e747269627574696f6e20746f207275737420626974636f696e2e20506c6561736520617070726f7665206d79205052206672656e21524f42") + assert!(!ScriptBuf::from_hex_no_length_prefix("6a48656c6c6f2c2074686973206973206d7920666972737420636f6e747269627574696f6e20746f207275737420626974636f696e2e20506c6561736520617070726f7665206d79205052206672656e21524f42") .unwrap() .is_standard_op_return()); } @@ -428,46 +468,48 @@ fn multisig() { // First multisig? 1-of-2 // In block 164467, txid 60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1 assert!( - ScriptBuf::from_hex("514104cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4410461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af52ae") + ScriptBuf::from_hex_no_length_prefix("514104cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4410461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af52ae") .unwrap() .is_multisig() ); // 2-of-2 assert!( - ScriptBuf::from_hex("5221021c4ac2ecebc398e390e07f045aac5cc421f82f0739c1ce724d3d53964dc6537d21023a2e9155e0b62f76737605504819a2b4e5ce20653f6c397d7a178ae42ba702f452ae") + ScriptBuf::from_hex_no_length_prefix("5221021c4ac2ecebc398e390e07f045aac5cc421f82f0739c1ce724d3d53964dc6537d21023a2e9155e0b62f76737605504819a2b4e5ce20653f6c397d7a178ae42ba702f452ae") .unwrap() .is_multisig() ); // Extra opcode after OP_CHECKMULTISIG assert!( - !ScriptBuf::from_hex("5221021c4ac2ecebc398e390e07f045aac5cc421f82f0739c1ce724d3d53964dc6537d21023a2e9155e0b62f76737605504819a2b4e5ce20653f6c397d7a178ae42ba702f452ae52") + !ScriptBuf::from_hex_no_length_prefix("5221021c4ac2ecebc398e390e07f045aac5cc421f82f0739c1ce724d3d53964dc6537d21023a2e9155e0b62f76737605504819a2b4e5ce20653f6c397d7a178ae42ba702f452ae52") .unwrap() .is_multisig() ); // Required sigs > num pubkeys assert!( - !ScriptBuf::from_hex("5321021c4ac2ecebc398e390e07f045aac5cc421f82f0739c1ce724d3d53964dc6537d21023a2e9155e0b62f76737605504819a2b4e5ce20653f6c397d7a178ae42ba702f452ae") + !ScriptBuf::from_hex_no_length_prefix("5321021c4ac2ecebc398e390e07f045aac5cc421f82f0739c1ce724d3d53964dc6537d21023a2e9155e0b62f76737605504819a2b4e5ce20653f6c397d7a178ae42ba702f452ae") .unwrap() .is_multisig() ); // Num pubkeys != pushnum assert!( - !ScriptBuf::from_hex("5221021c4ac2ecebc398e390e07f045aac5cc421f82f0739c1ce724d3d53964dc6537d21023a2e9155e0b62f76737605504819a2b4e5ce20653f6c397d7a178ae42ba702f453ae") + !ScriptBuf::from_hex_no_length_prefix("5221021c4ac2ecebc398e390e07f045aac5cc421f82f0739c1ce724d3d53964dc6537d21023a2e9155e0b62f76737605504819a2b4e5ce20653f6c397d7a178ae42ba702f453ae") .unwrap() .is_multisig() ); // Taproot hash from another test - assert!(!ScriptBuf::from_hex( + assert!(!ScriptBuf::from_hex_no_length_prefix( "20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac" ) .unwrap() .is_multisig()); // OP_RETURN from another test - assert!(!ScriptBuf::from_hex("6aa9149eb21980dc9d413d8eac27314938b9da920ee53e87") - .unwrap() - .is_multisig()); + assert!(!ScriptBuf::from_hex_no_length_prefix( + "6aa9149eb21980dc9d413d8eac27314938b9da920ee53e87" + ) + .unwrap() + .is_multisig()); } #[test] @@ -475,7 +517,7 @@ fn multisig() { fn script_json_serialize() { use serde_json; - let original = ScriptBuf::from_hex("827651a0698faaa9a8a7a687").unwrap(); + let original = ScriptBuf::from_hex_no_length_prefix("827651a0698faaa9a8a7a687").unwrap(); let json = serde_json::to_value(&original).unwrap(); assert_eq!(json, serde_json::Value::String("827651a0698faaa9a8a7a687".to_owned())); let des = serde_json::from_value::(json).unwrap(); @@ -485,27 +527,36 @@ fn script_json_serialize() { #[test] fn script_asm() { assert_eq!( - ScriptBuf::from_hex("6363636363686868686800").unwrap().to_string(), + ScriptBuf::from_hex_no_length_prefix("6363636363686868686800").unwrap().to_string(), "OP_IF OP_IF OP_IF OP_IF OP_IF OP_ENDIF OP_ENDIF OP_ENDIF OP_ENDIF OP_ENDIF OP_0" ); - assert_eq!(ScriptBuf::from_hex("2102715e91d37d239dea832f1460e91e368115d8ca6cc23a7da966795abad9e3b699ac").unwrap().to_string(), + assert_eq!(ScriptBuf::from_hex_no_length_prefix("2102715e91d37d239dea832f1460e91e368115d8ca6cc23a7da966795abad9e3b699ac").unwrap().to_string(), "OP_PUSHBYTES_33 02715e91d37d239dea832f1460e91e368115d8ca6cc23a7da966795abad9e3b699 OP_CHECKSIG"); // Elements Alpha peg-out transaction with some signatures removed for brevity. Mainly to test PUSHDATA1 - assert_eq!(ScriptBuf::from_hex("0047304402202457e78cc1b7f50d0543863c27de75d07982bde8359b9e3316adec0aec165f2f02200203fd331c4e4a4a02f48cf1c291e2c0d6b2f7078a784b5b3649fca41f8794d401004cf1552103244e602b46755f24327142a0517288cebd159eccb6ccf41ea6edf1f601e9af952103bbbacc302d19d29dbfa62d23f37944ae19853cf260c745c2bea739c95328fcb721039227e83246bd51140fe93538b2301c9048be82ef2fb3c7fc5d78426ed6f609ad210229bf310c379b90033e2ecb07f77ecf9b8d59acb623ab7be25a0caed539e2e6472103703e2ed676936f10b3ce9149fa2d4a32060fb86fa9a70a4efe3f21d7ab90611921031e9b7c6022400a6bb0424bbcde14cff6c016b91ee3803926f3440abf5c146d05210334667f975f55a8455d515a2ef1c94fdfa3315f12319a14515d2a13d82831f62f57ae").unwrap().to_string(), + assert_eq!(ScriptBuf::from_hex_no_length_prefix("0047304402202457e78cc1b7f50d0543863c27de75d07982bde8359b9e3316adec0aec165f2f02200203fd331c4e4a4a02f48cf1c291e2c0d6b2f7078a784b5b3649fca41f8794d401004cf1552103244e602b46755f24327142a0517288cebd159eccb6ccf41ea6edf1f601e9af952103bbbacc302d19d29dbfa62d23f37944ae19853cf260c745c2bea739c95328fcb721039227e83246bd51140fe93538b2301c9048be82ef2fb3c7fc5d78426ed6f609ad210229bf310c379b90033e2ecb07f77ecf9b8d59acb623ab7be25a0caed539e2e6472103703e2ed676936f10b3ce9149fa2d4a32060fb86fa9a70a4efe3f21d7ab90611921031e9b7c6022400a6bb0424bbcde14cff6c016b91ee3803926f3440abf5c146d05210334667f975f55a8455d515a2ef1c94fdfa3315f12319a14515d2a13d82831f62f57ae").unwrap().to_string(), "OP_0 OP_PUSHBYTES_71 304402202457e78cc1b7f50d0543863c27de75d07982bde8359b9e3316adec0aec165f2f02200203fd331c4e4a4a02f48cf1c291e2c0d6b2f7078a784b5b3649fca41f8794d401 OP_0 OP_PUSHDATA1 552103244e602b46755f24327142a0517288cebd159eccb6ccf41ea6edf1f601e9af952103bbbacc302d19d29dbfa62d23f37944ae19853cf260c745c2bea739c95328fcb721039227e83246bd51140fe93538b2301c9048be82ef2fb3c7fc5d78426ed6f609ad210229bf310c379b90033e2ecb07f77ecf9b8d59acb623ab7be25a0caed539e2e6472103703e2ed676936f10b3ce9149fa2d4a32060fb86fa9a70a4efe3f21d7ab90611921031e9b7c6022400a6bb0424bbcde14cff6c016b91ee3803926f3440abf5c146d05210334667f975f55a8455d515a2ef1c94fdfa3315f12319a14515d2a13d82831f62f57ae"); // Various weird scripts found in transaction 6d7ed9914625c73c0288694a6819196a27ef6c08f98e1270d975a8e65a3dc09a // which triggered overflow bugs on 32-bit machines in script formatting in the past. - assert_eq!(ScriptBuf::from_hex("01").unwrap().to_string(), "OP_PUSHBYTES_1 "); - assert_eq!(ScriptBuf::from_hex("0201").unwrap().to_string(), "OP_PUSHBYTES_2 "); - assert_eq!(ScriptBuf::from_hex("4c").unwrap().to_string(), ""); - assert_eq!(ScriptBuf::from_hex("4c0201").unwrap().to_string(), "OP_PUSHDATA1 "); - assert_eq!(ScriptBuf::from_hex("4d").unwrap().to_string(), ""); assert_eq!( - ScriptBuf::from_hex("4dffff01").unwrap().to_string(), + ScriptBuf::from_hex_no_length_prefix("01").unwrap().to_string(), + "OP_PUSHBYTES_1 " + ); + assert_eq!( + ScriptBuf::from_hex_no_length_prefix("0201").unwrap().to_string(), + "OP_PUSHBYTES_2 " + ); + assert_eq!(ScriptBuf::from_hex_no_length_prefix("4c").unwrap().to_string(), ""); + assert_eq!( + ScriptBuf::from_hex_no_length_prefix("4c0201").unwrap().to_string(), + "OP_PUSHDATA1 " + ); + assert_eq!(ScriptBuf::from_hex_no_length_prefix("4d").unwrap().to_string(), ""); + assert_eq!( + ScriptBuf::from_hex_no_length_prefix("4dffff01").unwrap().to_string(), "OP_PUSHDATA2 " ); assert_eq!( - ScriptBuf::from_hex("4effffffff01").unwrap().to_string(), + ScriptBuf::from_hex_no_length_prefix("4effffffff01").unwrap().to_string(), "OP_PUSHDATA4 " ); } @@ -513,71 +564,85 @@ fn script_asm() { #[test] fn script_buf_collect() { assert_eq!(&core::iter::empty::>().collect::(), Script::new()); - let script = ScriptBuf::from_hex("0047304402202457e78cc1b7f50d0543863c27de75d07982bde8359b9e3316adec0aec165f2f02200203fd331c4e4a4a02f48cf1c291e2c0d6b2f7078a784b5b3649fca41f8794d401004cf1552103244e602b46755f24327142a0517288cebd159eccb6ccf41ea6edf1f601e9af952103bbbacc302d19d29dbfa62d23f37944ae19853cf260c745c2bea739c95328fcb721039227e83246bd51140fe93538b2301c9048be82ef2fb3c7fc5d78426ed6f609ad210229bf310c379b90033e2ecb07f77ecf9b8d59acb623ab7be25a0caed539e2e6472103703e2ed676936f10b3ce9149fa2d4a32060fb86fa9a70a4efe3f21d7ab90611921031e9b7c6022400a6bb0424bbcde14cff6c016b91ee3803926f3440abf5c146d05210334667f975f55a8455d515a2ef1c94fdfa3315f12319a14515d2a13d82831f62f57ae").unwrap(); + let script = ScriptBuf::from_hex_no_length_prefix("0047304402202457e78cc1b7f50d0543863c27de75d07982bde8359b9e3316adec0aec165f2f02200203fd331c4e4a4a02f48cf1c291e2c0d6b2f7078a784b5b3649fca41f8794d401004cf1552103244e602b46755f24327142a0517288cebd159eccb6ccf41ea6edf1f601e9af952103bbbacc302d19d29dbfa62d23f37944ae19853cf260c745c2bea739c95328fcb721039227e83246bd51140fe93538b2301c9048be82ef2fb3c7fc5d78426ed6f609ad210229bf310c379b90033e2ecb07f77ecf9b8d59acb623ab7be25a0caed539e2e6472103703e2ed676936f10b3ce9149fa2d4a32060fb86fa9a70a4efe3f21d7ab90611921031e9b7c6022400a6bb0424bbcde14cff6c016b91ee3803926f3440abf5c146d05210334667f975f55a8455d515a2ef1c94fdfa3315f12319a14515d2a13d82831f62f57ae").unwrap(); assert_eq!(script.instructions().collect::>().unwrap(), script); } #[test] fn script_p2sh_p2p2k_template() { // random outputs I picked out of the mempool - assert!(ScriptBuf::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac") - .unwrap() - .is_p2pkh()); - assert!(!ScriptBuf::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac") - .unwrap() - .is_p2sh()); - assert!(!ScriptBuf::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ad") - .unwrap() - .is_p2pkh()); - assert!(!ScriptBuf::from_hex("").unwrap().is_p2pkh()); - assert!(ScriptBuf::from_hex("a914acc91e6fef5c7f24e5c8b3f11a664aa8f1352ffd87") - .unwrap() - .is_p2sh()); - assert!(!ScriptBuf::from_hex("a914acc91e6fef5c7f24e5c8b3f11a664aa8f1352ffd87") - .unwrap() - .is_p2pkh()); - assert!(!ScriptBuf::from_hex("a314acc91e6fef5c7f24e5c8b3f11a664aa8f1352ffd87") + assert!(ScriptBuf::from_hex_no_length_prefix( + "76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac" + ) + .unwrap() + .is_p2pkh()); + assert!(!ScriptBuf::from_hex_no_length_prefix( + "76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac" + ) + .unwrap() + .is_p2sh()); + assert!(!ScriptBuf::from_hex_no_length_prefix( + "76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ad" + ) + .unwrap() + .is_p2pkh()); + assert!(!ScriptBuf::from_hex_no_length_prefix("").unwrap().is_p2pkh()); + assert!(ScriptBuf::from_hex_no_length_prefix("a914acc91e6fef5c7f24e5c8b3f11a664aa8f1352ffd87") .unwrap() .is_p2sh()); + assert!(!ScriptBuf::from_hex_no_length_prefix( + "a914acc91e6fef5c7f24e5c8b3f11a664aa8f1352ffd87" + ) + .unwrap() + .is_p2pkh()); + assert!(!ScriptBuf::from_hex_no_length_prefix( + "a314acc91e6fef5c7f24e5c8b3f11a664aa8f1352ffd87" + ) + .unwrap() + .is_p2sh()); } #[test] fn script_p2pk() { - assert!(ScriptBuf::from_hex( + assert!(ScriptBuf::from_hex_no_length_prefix( "21021aeaf2f8638a129a3156fbe7e5ef635226b0bafd495ff03afe2c843d7e3a4b51ac" ) .unwrap() .is_p2pk()); - assert!(ScriptBuf::from_hex("410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac").unwrap().is_p2pk()); + assert!(ScriptBuf::from_hex_no_length_prefix("410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac").unwrap().is_p2pk()); } #[test] fn p2sh_p2wsh_conversion() { // Test vectors taken from Core tests/data/script_tests.json // bare p2wsh - let witness_script = ScriptBuf::from_hex("410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ac").unwrap(); - let expected_witout = - ScriptBuf::from_hex("0020b95237b48faaa69eb078e1170be3b5cbb3fddf16d0a991e14ad274f7b33a4f64") - .unwrap(); + let witness_script = ScriptBuf::from_hex_no_length_prefix("410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ac").unwrap(); + let expected_without = ScriptBuf::from_hex_no_length_prefix( + "0020b95237b48faaa69eb078e1170be3b5cbb3fddf16d0a991e14ad274f7b33a4f64", + ) + .unwrap(); assert!(witness_script.to_p2wsh().unwrap().is_p2wsh()); - assert_eq!(witness_script.to_p2wsh().unwrap(), expected_witout); + assert_eq!(witness_script.to_p2wsh().unwrap(), expected_without); // p2sh - let redeem_script = ScriptBuf::from_hex("0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8").unwrap(); + let redeem_script = ScriptBuf::from_hex_no_length_prefix("0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8").unwrap(); let expected_p2shout = - ScriptBuf::from_hex("a91491b24bf9f5288532960ac687abb035127b1d28a587").unwrap(); + ScriptBuf::from_hex_no_length_prefix("a91491b24bf9f5288532960ac687abb035127b1d28a587") + .unwrap(); assert!(redeem_script.to_p2sh().unwrap().is_p2sh()); assert_eq!(redeem_script.to_p2sh().unwrap(), expected_p2shout); // p2sh-p2wsh - let witness_script = ScriptBuf::from_hex("410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ac").unwrap(); - let expected_witout = - ScriptBuf::from_hex("0020b95237b48faaa69eb078e1170be3b5cbb3fddf16d0a991e14ad274f7b33a4f64") - .unwrap(); + let witness_script = ScriptBuf::from_hex_no_length_prefix("410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ac").unwrap(); + let expected_without = ScriptBuf::from_hex_no_length_prefix( + "0020b95237b48faaa69eb078e1170be3b5cbb3fddf16d0a991e14ad274f7b33a4f64", + ) + .unwrap(); let expected_out = - ScriptBuf::from_hex("a914f386c2ba255cc56d20cfa6ea8b062f8b5994551887").unwrap(); + ScriptBuf::from_hex_no_length_prefix("a914f386c2ba255cc56d20cfa6ea8b062f8b5994551887") + .unwrap(); assert!(witness_script.to_p2sh().unwrap().is_p2sh()); - assert_eq!(witness_script.to_p2wsh().unwrap(), expected_witout); + assert_eq!(witness_script.to_p2wsh().unwrap(), expected_without); assert_eq!(witness_script.to_p2wsh().unwrap().to_p2sh().unwrap(), expected_out); } @@ -591,12 +656,12 @@ macro_rules! unwrap_all { #[test] fn iterator() { - let zero = ScriptBuf::from_hex("00").unwrap(); - let zeropush = ScriptBuf::from_hex("0100").unwrap(); + let zero = ScriptBuf::from_hex_no_length_prefix("00").unwrap(); + let zeropush = ScriptBuf::from_hex_no_length_prefix("0100").unwrap(); - let nonminimal = ScriptBuf::from_hex("4c0169b2").unwrap(); // PUSHDATA1 for no reason - let minimal = ScriptBuf::from_hex("0169b2").unwrap(); // minimal - let nonminimal_alt = ScriptBuf::from_hex("026900b2").unwrap(); // non-minimal number but minimal push (should be OK) + let nonminimal = ScriptBuf::from_hex_no_length_prefix("4c0169b2").unwrap(); // PUSHDATA1 for no reason + let minimal = ScriptBuf::from_hex_no_length_prefix("0169b2").unwrap(); // minimal + let nonminimal_alt = ScriptBuf::from_hex_no_length_prefix("026900b2").unwrap(); // non-minimal number but minimal push (should be OK) let v_zero: Result, Error> = zero.instruction_indices_minimal().collect(); let v_zeropush: Result, Error> = zeropush.instruction_indices_minimal().collect(); @@ -684,9 +749,9 @@ fn default_dust_value() { // well-known scriptPubKey types. let script_p2wpkh = Builder::new().push_int_unchecked(0).push_slice([42; 20]).into_script(); assert!(script_p2wpkh.is_p2wpkh()); - assert_eq!(script_p2wpkh.minimal_non_dust(), Some(Amount::from_sat_u32(294))); + assert_eq!(script_p2wpkh.minimal_non_dust(), Amount::from_sat_u32(294)); assert_eq!( - script_p2wpkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb_unchecked(6)), + script_p2wpkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb_u32(6)), Some(Amount::from_sat_u32(588)) ); @@ -698,9 +763,9 @@ fn default_dust_value() { .push_opcode(OP_CHECKSIG) .into_script(); assert!(script_p2pkh.is_p2pkh()); - assert_eq!(script_p2pkh.minimal_non_dust(), Some(Amount::from_sat_u32(546))); + assert_eq!(script_p2pkh.minimal_non_dust(), Amount::from_sat_u32(546)); assert_eq!( - script_p2pkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb_unchecked(6)), + script_p2pkh.minimal_non_dust_custom(FeeRate::from_sat_per_vb_u32(6)), Some(Amount::from_sat_u32(1092)) ); } diff --git a/bitcoin/src/blockdata/script/witness_program.rs b/bitcoin/src/blockdata/script/witness_program.rs index e0fe7bc60e..7ef28b8466 100644 --- a/bitcoin/src/blockdata/script/witness_program.rs +++ b/bitcoin/src/blockdata/script/witness_program.rs @@ -70,7 +70,7 @@ impl WitnessProgram { WitnessProgram { version: WitnessVersion::V0, program: ArrayVec::from_slice(&program) } } - /// Constructs a new [`WitnessProgram`] from a 32 byte serialize Taproot xonly pubkey. + /// Constructs a new [`WitnessProgram`] from a 32 byte serialized Taproot x-only pubkey. fn new_p2tr(program: [u8; 32]) -> Self { WitnessProgram { version: WitnessVersion::V1, program: ArrayVec::from_slice(&program) } } @@ -91,24 +91,28 @@ impl WitnessProgram { WitnessProgram::new_p2wsh(hash.to_byte_array()) } - /// Constructs a new pay to Taproot address from an untweaked key. - pub fn p2tr( + /// Constructs a new [`WitnessProgram`] from an untweaked key for a P2TR output. + /// + /// This function applies BIP341 key-tweaking to the untweaked + /// key using the merkle root, if it's present. + pub fn p2tr>( secp: &Secp256k1, - internal_key: UntweakedPublicKey, + internal_key: K, merkle_root: Option, ) -> Self { + let internal_key = internal_key.into(); let (output_key, _parity) = internal_key.tap_tweak(secp, merkle_root); - let pubkey = output_key.to_inner().serialize(); + let pubkey = output_key.as_x_only_public_key().serialize(); WitnessProgram::new_p2tr(pubkey) } - /// Constructs a new pay to Taproot address from a pre-tweaked output key. + /// Constructs a new [`WitnessProgram`] from a tweaked key for a P2TR output. pub fn p2tr_tweaked(output_key: TweakedPublicKey) -> Self { - let pubkey = output_key.to_inner().serialize(); + let pubkey = output_key.as_x_only_public_key().serialize(); WitnessProgram::new_p2tr(pubkey) } - /// Constructs a new pay to anchor address + /// Constructs a new [`WitnessProgram`] for a P2A output. pub const fn p2a() -> Self { WitnessProgram { version: WitnessVersion::V1, program: ArrayVec::from_slice(&P2A_PROGRAM) } } @@ -137,7 +141,7 @@ impl WitnessProgram { /// Returns true if this witness program is for a P2TR output. pub fn is_p2tr(&self) -> bool { self.version == WitnessVersion::V1 && self.program.len() == 32 } - /// Returns true if this is a pay to anchor output. + /// Returns true if this witness program is for a P2A output. pub fn is_p2a(&self) -> bool { self.version == WitnessVersion::V1 && self.program == P2A_PROGRAM } @@ -222,5 +226,10 @@ mod tests { assert!(WitnessProgram::new(WitnessVersion::V1, &arbitrary_bytes) .expect("valid witness program") .is_p2tr()); + + let p2a_bytes = [78, 115]; + assert!(WitnessProgram::new(WitnessVersion::V1, &p2a_bytes) + .expect("valid witness program") + .is_p2a()); } } diff --git a/bitcoin/src/blockdata/script/witness_version.rs b/bitcoin/src/blockdata/script/witness_version.rs index 3c8f408dbc..0ede306411 100644 --- a/bitcoin/src/blockdata/script/witness_version.rs +++ b/bitcoin/src/blockdata/script/witness_version.rs @@ -28,7 +28,7 @@ use crate::script::Instruction; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[repr(u8)] pub enum WitnessVersion { - /// Initial version of witness program. Used for P2WPKH and P2WPK outputs + /// Initial version of witness program. Used for P2WPKH and P2WSH outputs V0 = 0, /// Version of witness program used for Taproot P2TR outputs. V1 = 1, diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index 0c60d3c654..0e64bdd3c2 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -12,15 +12,18 @@ use core::fmt; +#[cfg(feature = "arbitrary")] +use arbitrary::{Arbitrary, Unstructured}; use hashes::sha256d; use internals::{compact_size, write_err, ToU64}; use io::{BufRead, Write}; use primitives::Sequence; +use units::NumOpResult; use super::Weight; use crate::consensus::{self, encode, Decodable, Encodable}; use crate::internal_macros::{impl_consensus_encoding, impl_hashencode}; -use crate::locktime::absolute::{self, Height, Time}; +use crate::locktime::absolute::{self, Height, MedianTimePast}; use crate::prelude::{Borrow, Vec}; use crate::script::{Script, ScriptBuf, ScriptExt as _, ScriptExtPriv as _}; #[cfg(doc)] @@ -162,7 +165,7 @@ crate::internal_macros::define_extension_trait! { /// # Panics /// /// If output size * 4 overflows, this should never happen under normal conditions. Use - /// `Weght::from_vb_checked(self.size() as u64)` if you are concerned. + /// `Weight::from_vb_checked(self.size() as u64)` if you are concerned. fn weight(&self) -> Weight { // Size is equivalent to virtual size since all bytes of a TxOut are non-witness bytes. Weight::from_vb(self.size().to_u64()) @@ -183,8 +186,8 @@ crate::internal_macros::define_extension_trait! { /// To use a custom value, use [`minimal_non_dust_custom`]. /// /// [`minimal_non_dust_custom`]: TxOut::minimal_non_dust_custom - fn minimal_non_dust(script_pubkey: ScriptBuf) -> Option { - Some(TxOut { value: script_pubkey.minimal_non_dust()?, script_pubkey }) + fn minimal_non_dust(script_pubkey: ScriptBuf) -> TxOut { + TxOut { value: script_pubkey.minimal_non_dust(), script_pubkey } } /// Constructs a new `TxOut` with given script and the smallest possible `value` that is **not** dust @@ -295,7 +298,7 @@ pub trait TransactionExt: sealed::Sealed { /// By definition if the lock time is not enabled the transaction's absolute timelock is /// considered to be satisfied i.e., there are no timelock constraints restricting this /// transaction from being mined immediately. - fn is_absolute_timelock_satisfied(&self, height: Height, time: Time) -> bool; + fn is_absolute_timelock_satisfied(&self, height: Height, time: MedianTimePast) -> bool; /// Returns `true` if this transactions nLockTime is enabled ([BIP-65]). /// @@ -393,7 +396,7 @@ impl TransactionExt for Transaction { fn is_explicitly_rbf(&self) -> bool { self.input.iter().any(|input| input.sequence.is_rbf()) } - fn is_absolute_timelock_satisfied(&self, height: Height, time: Time) -> bool { + fn is_absolute_timelock_satisfied(&self, height: Height, time: MedianTimePast) -> bool { if !self.is_lock_time_enabled() { return true; } @@ -772,15 +775,20 @@ impl Decodable for Transaction { /// # Parameters /// /// * `fee_rate` - the fee rate of the transaction being created. -/// * `satisfaction_weight` - satisfied spending conditions weight. +/// * `input_weight_prediction` - the predicted input weight. +/// +/// # Returns +/// +/// This will return [`NumOpResult::Error`] if the fee calculation (fee_rate * weight) overflows. +/// Otherwise, [`NumOpResult::Valid`] will wrap the successful calculation. pub fn effective_value( fee_rate: FeeRate, - satisfaction_weight: Weight, + input_weight_prediction: InputWeightPrediction, value: Amount, -) -> Option { - let weight = satisfaction_weight.checked_add(TX_IN_BASE_WEIGHT)?; - let signed_input_fee = fee_rate.to_fee(weight)?.to_signed(); - value.to_signed().checked_sub(signed_input_fee) +) -> NumOpResult { + let weight = input_weight_prediction.total_weight(); + + fee_rate.to_fee(weight).map(Amount::to_signed).and_then(|fee| value.to_signed() - fee) // Cannot overflow. } /// Predicts the weight of a to-be-constructed transaction. @@ -799,7 +807,7 @@ pub fn effective_value( /// preceding compact size. The length of preceding compact size is computed and added inside the /// function for convenience. /// -/// If you have the transaction already constructed (except for signatures) with a dummy value for +/// If you have the transaction already constructed (except for signatures) with a dummy value for /// fee output you can use the return value of [`Transaction::script_pubkey_lens`] method directly /// as the second argument. /// @@ -922,7 +930,7 @@ pub const fn predict_weight_from_slices( /// This helper type collects information about an input to be used in [`predict_weight`] function. /// It can only be created using the [`new`](InputWeightPrediction::new) function or using other /// associated constants/methods. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct InputWeightPrediction { script_size: usize, witness_size: usize, @@ -1135,9 +1143,42 @@ mod sealed { impl Sealed for super::Version {} } +#[cfg(feature = "arbitrary")] +impl<'a> Arbitrary<'a> for InputWeightPrediction { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + // limit script size to 4Mwu block size. + let max_block = Weight::MAX_BLOCK.to_wu() as usize; + let input_script_len = u.int_in_range(0..=max_block)?; + let remaining = max_block - input_script_len; + + // create witness data if there is remaining space. + let mut witness_length = u.int_in_range(0..=remaining)?; + let mut witness_element_lengths = Vec::new(); + + // build vec of random witness element lengths. + while witness_length > 0 { + let elem = u.int_in_range(1..=witness_length)?; + witness_element_lengths.push(elem); + witness_length -= elem; + } + + match u.int_in_range(0..=7)? { + 0 => Ok(InputWeightPrediction::P2WPKH_MAX), + 1 => Ok(InputWeightPrediction::NESTED_P2WPKH_MAX), + 2 => Ok(InputWeightPrediction::P2PKH_COMPRESSED_MAX), + 3 => Ok(InputWeightPrediction::P2PKH_UNCOMPRESSED_MAX), + 4 => Ok(InputWeightPrediction::P2TR_KEY_DEFAULT_SIGHASH), + 5 => Ok(InputWeightPrediction::P2TR_KEY_NON_DEFAULT_SIGHASH), + 6 => Ok(InputWeightPrediction::new(input_script_len, witness_element_lengths)), + _ => Ok(InputWeightPrediction::from_slice(input_script_len, &witness_element_lengths)), + } + } +} + #[cfg(test)] mod tests { - use hex::{test_hex_unwrap as hex, FromHex}; + use hex::FromHex; + use hex_lit::hex; #[cfg(feature = "serde")] use internals::serde_round_trip; use units::parse; @@ -1376,7 +1417,7 @@ mod tests { assert_eq!(tx.output.len(), 1); let reser = serialize(&tx); - assert_eq!(tx_bytes, reser); + assert_eq!(tx_bytes, *reser); } #[test] @@ -1522,8 +1563,8 @@ mod tests { #[test] fn huge_witness() { - deserialize::(&hex!(include_str!("../../tests/data/huge_witness.hex").trim())) - .unwrap(); + let hex = Vec::from_hex(include_str!("../../tests/data/huge_witness.hex").trim()).unwrap(); + deserialize::(&hex).unwrap(); } #[test] @@ -1651,25 +1692,20 @@ mod tests { fn effective_value_happy_path() { let value = "1 cBTC".parse::().unwrap(); let fee_rate = FeeRate::from_sat_per_kwu(10); - let satisfaction_weight = Weight::from_wu(204); - let effective_value = effective_value(fee_rate, satisfaction_weight, value).unwrap(); + let effective_value = + effective_value(fee_rate, InputWeightPrediction::P2WPKH_MAX, value).unwrap(); - // 10 sat/kwu * (204wu + BASE_WEIGHT) = 4 sats - let expected_fee = "4 sats".parse::().unwrap(); + // 10 sat/kwu * 272 wu = 4 sats (rounding up) + let expected_fee = "3 sats".parse::().unwrap(); let expected_effective_value = (value.to_signed() - expected_fee).unwrap(); assert_eq!(effective_value, expected_effective_value); } #[test] fn effective_value_fee_rate_does_not_overflow() { - let eff_value = effective_value(FeeRate::MAX, Weight::ZERO, Amount::ZERO); - assert!(eff_value.is_none()); - } - - #[test] - fn effective_value_weight_does_not_overflow() { - let eff_value = effective_value(FeeRate::ZERO, Weight::MAX, Amount::ZERO); - assert!(eff_value.is_none()); + let eff_value = + effective_value(FeeRate::MAX, InputWeightPrediction::P2WPKH_MAX, Amount::ZERO); + assert!(eff_value.is_error()); } #[test] @@ -1858,7 +1894,7 @@ mod tests { fn return_none(_outpoint: &OutPoint) -> Option { None } for (hx, expected, spent_fn, expected_none) in tx_hexes.iter() { - let tx_bytes = hex!(hx); + let tx_bytes = Vec::from_hex(hx).unwrap(); let tx: Transaction = deserialize(&tx_bytes).unwrap(); assert_eq!(tx.total_sigop_cost(spent_fn), *expected); assert_eq!(tx.total_sigop_cost(return_none), *expected_none); @@ -1934,6 +1970,62 @@ mod tests { assert_eq!(weight, Weight::from_wu(2493)); } + #[test] + // needless_borrows_for_generic_args incorrecctly identifies &[] as a needless borrow + #[allow(clippy::needless_borrows_for_generic_args)] + fn weight_prediction_new() { + let p2wpkh_max = InputWeightPrediction::new(0, [72, 33]); + assert_eq!(p2wpkh_max.script_size, 1); + assert_eq!(p2wpkh_max.witness_size, 108); + assert_eq!(p2wpkh_max.total_weight(), Weight::from_wu(272)); + assert_eq!(p2wpkh_max.total_weight(), InputWeightPrediction::P2WPKH_MAX.total_weight()); + + let nested_p2wpkh_max = InputWeightPrediction::new(23, [72, 33]); + assert_eq!(nested_p2wpkh_max.script_size, 24); + assert_eq!(nested_p2wpkh_max.witness_size, 108); + assert_eq!(nested_p2wpkh_max.total_weight(), Weight::from_wu(364)); + assert_eq!( + nested_p2wpkh_max.total_weight(), + InputWeightPrediction::NESTED_P2WPKH_MAX.total_weight() + ); + + let p2pkh_compressed_max = InputWeightPrediction::new(107, &[]); + assert_eq!(p2pkh_compressed_max.script_size, 108); + assert_eq!(p2pkh_compressed_max.witness_size, 0); + assert_eq!(p2pkh_compressed_max.total_weight(), Weight::from_wu(592)); + assert_eq!( + p2pkh_compressed_max.total_weight(), + InputWeightPrediction::P2PKH_COMPRESSED_MAX.total_weight() + ); + + let p2pkh_uncompressed_max = InputWeightPrediction::new(139, &[]); + assert_eq!(p2pkh_uncompressed_max.script_size, 140); + assert_eq!(p2pkh_uncompressed_max.witness_size, 0); + assert_eq!(p2pkh_uncompressed_max.total_weight(), Weight::from_wu(720)); + assert_eq!( + p2pkh_uncompressed_max.total_weight(), + InputWeightPrediction::P2PKH_UNCOMPRESSED_MAX.total_weight() + ); + + let p2tr_key_default_sighash = InputWeightPrediction::new(0, [64]); + assert_eq!(p2tr_key_default_sighash.script_size, 1); + assert_eq!(p2tr_key_default_sighash.witness_size, 66); + assert_eq!(p2tr_key_default_sighash.total_weight(), Weight::from_wu(230)); + assert_eq!( + p2tr_key_default_sighash.total_weight(), + InputWeightPrediction::P2TR_KEY_DEFAULT_SIGHASH.total_weight() + ); + + let p2tr_key_non_default_sighash = InputWeightPrediction::new(0, [65]); + assert_eq!(p2tr_key_non_default_sighash.script_size, 1); + assert_eq!(p2tr_key_non_default_sighash.witness_size, 67); + assert_eq!(p2tr_key_non_default_sighash.total_weight(), Weight::from_wu(231)); + assert_eq!( + p2tr_key_non_default_sighash.total_weight(), + InputWeightPrediction::P2TR_KEY_NON_DEFAULT_SIGHASH.total_weight() + ); + } + #[test] fn sequence_debug_output() { let seq = Sequence::from_seconds_floor(1000); @@ -1966,20 +2058,17 @@ mod tests { #[cfg(bench)] mod benches { - use hex::test_hex_unwrap as hex; use io::sink; use test::{black_box, Bencher}; use super::*; - use crate::consensus::{deserialize, Encodable}; + use crate::consensus::{encode, Encodable}; const SOME_TX: &str = "0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000"; #[bench] pub fn bench_transaction_size(bh: &mut Bencher) { - let raw_tx = hex!(SOME_TX); - - let mut tx: Transaction = deserialize(&raw_tx).unwrap(); + let mut tx: Transaction = encode::deserialize_hex(SOME_TX).unwrap(); bh.iter(|| { black_box(black_box(&mut tx).total_size()); @@ -1988,10 +2077,8 @@ mod benches { #[bench] pub fn bench_transaction_serialize(bh: &mut Bencher) { - let raw_tx = hex!(SOME_TX); - let tx: Transaction = deserialize(&raw_tx).unwrap(); - - let mut data = Vec::with_capacity(raw_tx.len()); + let tx: Transaction = encode::deserialize_hex(SOME_TX).unwrap(); + let mut data = Vec::with_capacity(SOME_TX.len()); bh.iter(|| { let result = tx.consensus_encode(&mut data); @@ -2002,8 +2089,7 @@ mod benches { #[bench] pub fn bench_transaction_serialize_logic(bh: &mut Bencher) { - let raw_tx = hex!(SOME_TX); - let tx: Transaction = deserialize(&raw_tx).unwrap(); + let tx: Transaction = encode::deserialize_hex(SOME_TX).unwrap(); bh.iter(|| { let size = tx.consensus_encode(&mut sink()); @@ -2013,10 +2099,19 @@ mod benches { #[bench] pub fn bench_transaction_deserialize(bh: &mut Bencher) { - let raw_tx = hex!(SOME_TX); + // hex_lit does not work in bench code for some reason. Perhaps criterion fixes this. + let raw_tx = as hex::FromHex>::from_hex(SOME_TX).unwrap(); + + bh.iter(|| { + let tx: Transaction = encode::deserialize(&raw_tx).unwrap(); + black_box(&tx); + }); + } + #[bench] + pub fn bench_transaction_deserialize_hex(bh: &mut Bencher) { bh.iter(|| { - let tx: Transaction = deserialize(&raw_tx).unwrap(); + let tx: Transaction = encode::deserialize_hex(SOME_TX).unwrap(); black_box(&tx); }); } diff --git a/bitcoin/src/blockdata/witness.rs b/bitcoin/src/blockdata/witness.rs index f8e8abe5ba..a4859a02b0 100644 --- a/bitcoin/src/blockdata/witness.rs +++ b/bitcoin/src/blockdata/witness.rs @@ -10,15 +10,15 @@ use io::{BufRead, Write}; use crate::consensus::encode::{self, Error, ReadExt, WriteExt, MAX_VEC_SIZE}; use crate::consensus::{Decodable, Encodable}; use crate::crypto::ecdsa; +use crate::crypto::key::SerializedXOnlyPublicKey; use crate::prelude::Vec; #[cfg(doc)] use crate::script::ScriptExt as _; -use crate::taproot::{ - self, ControlBlock, LeafScript, LeafVersion, TAPROOT_ANNEX_PREFIX, TAPROOT_CONTROL_BASE_SIZE, - TAPROOT_LEAF_MASK, TaprootMerkleBranch, -}; +use crate::taproot::{self, ControlBlock, LeafScript, TaprootMerkleBranch, TAPROOT_ANNEX_PREFIX}; use crate::Script; +type BorrowedControlBlock<'a> = ControlBlock<&'a TaprootMerkleBranch, &'a SerializedXOnlyPublicKey>; + #[rustfmt::skip] // Keep public re-exports separate. #[doc(inline)] pub use primitives::witness::{Iter, Witness}; @@ -176,29 +176,28 @@ crate::internal_macros::define_extension_trait! { /// version. fn taproot_leaf_script(&self) -> Option> { match P2TrSpend::from_witness(self) { - Some(P2TrSpend::Script { leaf_script, control_block, .. }) if control_block.len() >= TAPROOT_CONTROL_BASE_SIZE => { - let version = LeafVersion::from_consensus(control_block[0] & TAPROOT_LEAF_MASK).ok()?; - Some(LeafScript { version, script: leaf_script, }) + Some(P2TrSpend::Script { leaf_script, control_block, .. }) => { + Some(LeafScript { version: control_block.leaf_version, script: leaf_script, }) }, _ => None, } } - /// Get the taproot control block following BIP341 rules. + /// Get the Taproot control block following BIP341 rules. /// /// This does not guarantee that this represents a P2TR [`Witness`]. It /// merely gets the last or second to last element depending on the first /// byte of the last element being equal to 0x50. /// /// See [`Script::is_p2tr`] to check whether this is actually a Taproot witness. - fn taproot_control_block(&self) -> Option<&[u8]> { + fn taproot_control_block(&self) -> Option> { match P2TrSpend::from_witness(self) { Some(P2TrSpend::Script { control_block, .. }) => Some(control_block), _ => None, } } - /// Get the taproot annex following BIP341 rules. + /// Get the Taproot annex following BIP341 rules. /// /// This does not guarantee that this represents a P2TR [`Witness`]. /// @@ -236,15 +235,15 @@ enum P2TrSpend<'a> { }, Script { leaf_script: &'a Script, - control_block: &'a [u8], + control_block: BorrowedControlBlock<'a>, annex: Option<&'a [u8]>, }, } impl<'a> P2TrSpend<'a> { - /// Parses `Witness` to determine what kind of taproot spend this is. + /// Parses `Witness` to determine what kind of Taproot spend this is. /// - /// Note: this assumes `witness` is a taproot spend. The function cannot figure it out for sure + /// Note: this assumes `witness` is a Taproot spend. The function cannot figure it out for sure /// (without knowing the output), so it doesn't attempt to check anything other than what is /// required for the program to not crash. /// @@ -275,17 +274,21 @@ impl<'a> P2TrSpend<'a> { // last one does NOT start with TAPROOT_ANNEX_PREFIX. This is handled in the catchall // arm. 3.. if witness.last().expect("len > 0").starts_with(&[TAPROOT_ANNEX_PREFIX]) => { + let control_block = witness.get_back(1).expect("len > 1"); + let control_block = BorrowedControlBlock::decode_borrowed(control_block).ok()?; let spend = P2TrSpend::Script { leaf_script: Script::from_bytes(witness.get_back(2).expect("len > 2")), - control_block: witness.get_back(1).expect("len > 1"), + control_block, annex: witness.last(), }; Some(spend) } _ => { + let control_block = witness.last().expect("len > 0"); + let control_block = BorrowedControlBlock::decode_borrowed(control_block).ok()?; let spend = P2TrSpend::Script { leaf_script: Script::from_bytes(witness.get_back(1).expect("len > 1")), - control_block: witness.last().expect("len > 0"), + control_block, annex: None, }; Some(spend) @@ -318,12 +321,13 @@ fn encode_cursor(bytes: &mut [u8], start_of_indices: usize, index: usize, value: #[cfg(test)] mod test { - use hex::test_hex_unwrap as hex; + use hex_lit::hex; use super::*; use crate::consensus::{deserialize, encode, serialize}; use crate::hex::DisplayHex; use crate::sighash::EcdsaSighashType; + use crate::taproot::LeafVersion; use crate::Transaction; #[test] @@ -357,14 +361,15 @@ mod test { #[test] fn consensus_serialize() { - let el_0 = hex!("03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105"); - let el_1 = hex!("000000"); + let el_0 = + hex!("03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105").to_vec(); + let el_1 = hex!("000000").to_vec(); let mut want_witness = Witness::default(); want_witness.push(&el_0); want_witness.push(&el_1); - let vec = vec![el_0.clone(), el_1.clone()]; + let vec = vec![el_0, el_1]; // Puts a CompactSize at the front as well as one at the front of each element. let want_ser: Vec = encode::serialize(&vec); @@ -383,12 +388,13 @@ mod test { #[test] fn get_tapscript() { let tapscript = hex!("deadbeef"); - let control_block = hex!("02"); + let control_block = + hex!("c0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // annex starting with 0x50 causes the branching logic. let annex = hex!("50"); - let witness = Witness::from([&*tapscript, &control_block]); - let witness_annex = Witness::from([&*tapscript, &control_block, &annex]); + let witness = Witness::from([tapscript.as_slice(), &control_block]); + let witness_annex = Witness::from([tapscript.as_slice(), &control_block, &annex]); // With or without annex, the tapscript should be returned. assert_eq!(witness.tapscript(), Some(Script::from_bytes(&tapscript[..]))); @@ -403,8 +409,8 @@ mod test { // annex starting with 0x50 causes the branching logic. let annex = hex!("50"); - let witness = Witness::from([&*tapscript, &control_block]); - let witness_annex = Witness::from([&*tapscript, &control_block, &annex]); + let witness = Witness::from([tapscript.as_slice(), &control_block]); + let witness_annex = Witness::from([tapscript.as_slice(), &control_block, &annex]); let expected_leaf_script = LeafScript { version: LeafVersion::TapScript, script: Script::from_bytes(&tapscript) }; @@ -420,8 +426,8 @@ mod test { // annex starting with 0x50 causes the branching logic. let annex = hex!("50"); - let witness = Witness::from([&*signature]); - let witness_annex = Witness::from([&*signature, &annex]); + let witness = Witness::from([signature]); + let witness_annex = Witness::from([signature.as_slice(), &annex]); // With or without annex, no tapscript should be returned. assert_eq!(witness.tapscript(), None); @@ -431,30 +437,33 @@ mod test { #[test] fn get_control_block() { let tapscript = hex!("deadbeef"); - let control_block = hex!("02"); + let control_block = + hex!("c0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + let expected_control_block = BorrowedControlBlock::decode_borrowed(&control_block).unwrap(); // annex starting with 0x50 causes the branching logic. let annex = hex!("50"); let signature = vec![0xff; 64]; - let witness = Witness::from([&*tapscript, &control_block]); - let witness_annex = Witness::from([&*tapscript, &control_block, &annex]); - let witness_key_spend_annex = Witness::from([&*signature, &annex]); + let witness = Witness::from([tapscript.as_slice(), &control_block]); + let witness_annex = Witness::from([tapscript.as_slice(), &control_block, &annex]); + let witness_key_spend_annex = Witness::from([signature.as_slice(), &annex]); // With or without annex, the tapscript should be returned. - assert_eq!(witness.taproot_control_block(), Some(&control_block[..])); - assert_eq!(witness_annex.taproot_control_block(), Some(&control_block[..])); + assert_eq!(witness.taproot_control_block().unwrap(), expected_control_block); + assert_eq!(witness_annex.taproot_control_block().unwrap(), expected_control_block); assert!(witness_key_spend_annex.taproot_control_block().is_none()) } #[test] fn get_annex() { let tapscript = hex!("deadbeef"); - let control_block = hex!("02"); + let control_block = + hex!("c0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // annex starting with 0x50 causes the branching logic. let annex = hex!("50"); - let witness = Witness::from([&*tapscript, &control_block]); - let witness_annex = Witness::from([&*tapscript, &control_block, &annex]); + let witness = Witness::from([tapscript.as_slice(), &control_block]); + let witness_annex = Witness::from([tapscript.as_slice(), &control_block, &annex]); // With or without annex, the tapscript should be returned. assert_eq!(witness.taproot_annex(), None); @@ -465,8 +474,8 @@ mod test { // annex starting with 0x50 causes the branching logic. let annex = hex!("50"); - let witness = Witness::from([&*signature]); - let witness_annex = Witness::from([&*signature, &annex]); + let witness = Witness::from([signature]); + let witness_annex = Witness::from([signature.as_slice(), &annex]); // With or without annex, the tapscript should be returned. assert_eq!(witness.taproot_annex(), None); diff --git a/bitcoin/src/consensus/encode.rs b/bitcoin/src/consensus/encode.rs index c2a751a0fe..4e6cd660f8 100644 --- a/bitcoin/src/consensus/encode.rs +++ b/bitcoin/src/consensus/encode.rs @@ -26,11 +26,6 @@ use crate::bip152::{PrefilledTransaction, ShortId}; use crate::bip158::{FilterHash, FilterHeader}; use crate::block::{self, BlockHash}; use crate::merkle_tree::TxMerkleNode; -#[cfg(feature = "std")] -use crate::p2p::{ - address::{AddrV2Message, Address}, - message_blockdata::Inventory, -}; use crate::prelude::{rc, sync, Box, Cow, String, Vec}; use crate::taproot::TapLeafHash; use crate::transaction::{Transaction, TxIn, TxOut}; @@ -284,7 +279,7 @@ pub trait Decodable: Sized { /// vector of a limited size, calling this function directly might be marginally faster (due to /// avoiding extra checks). /// - /// ### Rules for trait implementations + /// # Rules for trait implementations /// /// * Simple types that have a fixed size (own and member fields), don't have to overwrite /// this method, or be concern with it. @@ -552,13 +547,6 @@ impl_vec!(TapLeafHash); impl_vec!(ShortId); impl_vec!(PrefilledTransaction); -#[cfg(feature = "std")] -impl_vec!(Inventory); -#[cfg(feature = "std")] -impl_vec!((u32, Address)); -#[cfg(feature = "std")] -impl_vec!(AddrV2Message); - pub(crate) fn consensus_encode_with_size( data: &[u8], w: &mut W, @@ -765,8 +753,6 @@ mod tests { use core::mem::discriminant; use super::*; - #[cfg(feature = "std")] - use crate::p2p::{message_blockdata::Inventory, Address}; #[test] fn serialize_int() { @@ -1049,10 +1035,6 @@ mod tests { test_len_is_max_vec::(); test_len_is_max_vec::>(); test_len_is_max_vec::(); - #[cfg(feature = "std")] - test_len_is_max_vec::<(u32, Address)>(); - #[cfg(feature = "std")] - test_len_is_max_vec::(); } fn test_len_is_max_vec() diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs index 9db3079069..b20c76a09a 100644 --- a/bitcoin/src/crypto/key.rs +++ b/bitcoin/src/crypto/key.rs @@ -25,10 +25,119 @@ use crate::script::{self, ScriptBuf}; use crate::taproot::{TapNodeHash, TapTweakHash}; #[rustfmt::skip] // Keep public re-exports separate. -pub use secp256k1::{constants, Keypair, Parity, Secp256k1, Verification, XOnlyPublicKey}; - +pub use secp256k1::{constants, Keypair, Parity, Secp256k1, Verification}; #[cfg(feature = "rand-std")] pub use secp256k1::rand; +pub use serialized_x_only::SerializedXOnlyPublicKey; + +/// A Bitcoin Schnorr X-only public key used for BIP340 signatures. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct XOnlyPublicKey(secp256k1::XOnlyPublicKey); + +impl XOnlyPublicKey { + /// Constructs a new x-only public key from the provided generic Secp256k1 x-only public key. + pub fn new(key: impl Into) -> XOnlyPublicKey { + XOnlyPublicKey(key.into()) + } + + /// Creates an x-only public key from a keypair. + /// + /// Returns the x-only public key and the parity of the full public key. + #[inline] + pub fn from_keypair(keypair: &Keypair) -> (XOnlyPublicKey, Parity) { + let (xonly, parity) = secp256k1::XOnlyPublicKey::from_keypair(keypair); + (XOnlyPublicKey::new(xonly), parity) + } + + /// Creates an x-only public key from a 32-byte x-coordinate. + /// + /// Returns an error if the provided bytes don't represent a valid secp256k1 point x-coordinate. + #[inline] + pub fn from_byte_array( + data: &[u8; constants::SCHNORR_PUBLIC_KEY_SIZE], + ) -> Result { + secp256k1::XOnlyPublicKey::from_byte_array(data) + .map(XOnlyPublicKey::new) + .map_err(|_| ParseXOnlyPublicKeyError::InvalidXCoordinate) + } + + /// Serializes the x-only public key as a byte-encoded x coordinate value (32 bytes). + #[inline] + pub fn serialize(&self) -> [u8; constants::SCHNORR_PUBLIC_KEY_SIZE] { self.0.serialize() } + + /// Converts this x-only public key to a full public key given the parity. + #[inline] + pub fn public_key(&self, parity: Parity) -> PublicKey { self.0.public_key(parity).into() } + + /// Verifies that a tweak produced by [`XOnlyPublicKey::add_tweak`] was computed correctly. + /// + /// Should be called on the original untweaked key. Takes the tweaked key and output parity from + /// [`XOnlyPublicKey::add_tweak`] as input. + #[inline] + pub fn tweak_add_check( + &self, + secp: &Secp256k1, + tweaked_key: &Self, + tweaked_parity: Parity, + tweak: secp256k1::Scalar, + ) -> bool { + self.0.tweak_add_check(secp, &tweaked_key.0, tweaked_parity, tweak) + } + + /// Tweaks an [`XOnlyPublicKey`] by adding the generator multiplied with the given tweak to it. + /// + /// # Returns + /// + /// The newly tweaked key plus an opaque type representing the parity of the tweaked key, this + /// should be provided to `tweak_add_check` which can be used to verify a tweak more efficiently + /// than regenerating it and checking equality. + /// + /// # Errors + /// + /// If the resulting key would be invalid. + #[inline] + pub fn add_tweak( + &self, + secp: &Secp256k1, + tweak: &secp256k1::Scalar, + ) -> Result<(XOnlyPublicKey, Parity), TweakXOnlyPublicKeyError> { + match self.0.add_tweak(secp, tweak) { + Ok((xonly, parity)) => Ok((XOnlyPublicKey(xonly), parity)), + Err(secp256k1::Error::InvalidTweak) => Err(TweakXOnlyPublicKeyError::BadTweak), + Err(secp256k1::Error::InvalidParityValue(_)) => + Err(TweakXOnlyPublicKeyError::ParityError), + Err(_) => Err(TweakXOnlyPublicKeyError::ResultKeyInvalid), + } + } +} + +impl FromStr for XOnlyPublicKey { + type Err = ParseXOnlyPublicKeyError; + fn from_str(s: &str) -> Result { + secp256k1::XOnlyPublicKey::from_str(s) + .map(XOnlyPublicKey::from) + .map_err(|_| ParseXOnlyPublicKeyError::InvalidXCoordinate) + } +} + +impl From for XOnlyPublicKey { + fn from(pk: secp256k1::XOnlyPublicKey) -> XOnlyPublicKey { XOnlyPublicKey::new(pk) } +} + +impl From for XOnlyPublicKey { + fn from(pk: secp256k1::PublicKey) -> XOnlyPublicKey { XOnlyPublicKey::new(pk) } +} + +impl fmt::LowerHex for XOnlyPublicKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(&self.0, f) } +} +// Allocate for serialized size +impl_to_hex_from_lower_hex!(XOnlyPublicKey, |_| constants::SCHNORR_PUBLIC_KEY_SIZE * 2); + +impl fmt::Display for XOnlyPublicKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } +} /// A Bitcoin ECDSA public key. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -222,7 +331,7 @@ impl From for PublicKey { } impl From for XOnlyPublicKey { - fn from(pk: PublicKey) -> XOnlyPublicKey { pk.inner.into() } + fn from(pk: PublicKey) -> XOnlyPublicKey { XOnlyPublicKey::new(pk.inner) } } /// An opaque return type for PublicKey::to_sort_key. @@ -527,17 +636,13 @@ impl PrivateKey { } }; - Ok(PrivateKey { - compressed, - network, - inner: secp256k1::SecretKey::from_byte_array(key)?, - }) + Ok(PrivateKey { compressed, network, inner: secp256k1::SecretKey::from_byte_array(key)? }) } /// Returns a new private key with the negated secret value. /// /// The resulting key corresponds to the same x-only public key (identical x-coordinate) - /// but with the opposite y-coordinate parity. This is useful for ensuring compatibility + /// but with the opposite y-coordinate parity. This is useful for ensuring compatibility /// with specific public key formats and BIP-340 requirements. #[inline] pub fn negate(&self) -> Self { @@ -835,7 +940,7 @@ impl TapTweak for UntweakedPublicKey { secp: &Secp256k1, merkle_root: Option, ) -> (TweakedPublicKey, Parity) { - let tweak = TapTweakHash::from_key_and_tweak(self, merkle_root).to_scalar(); + let tweak = TapTweakHash::from_key_and_merkle_root(self, merkle_root).to_scalar(); let (output_key, parity) = self.add_tweak(secp, &tweak).expect("Tap tweak failed"); debug_assert!(self.tweak_add_check(secp, &output_key, parity, tweak)); @@ -865,7 +970,7 @@ impl TapTweak for UntweakedKeypair { merkle_root: Option, ) -> TweakedKeypair { let (pubkey, _parity) = XOnlyPublicKey::from_keypair(&self); - let tweak = TapTweakHash::from_key_and_tweak(pubkey, merkle_root).to_scalar(); + let tweak = TapTweakHash::from_key_and_merkle_root(pubkey, merkle_root).to_scalar(); let tweaked = self.add_xonly_tweak(secp, &tweak).expect("Tap tweak failed"); TweakedKeypair(tweaked) } @@ -878,7 +983,7 @@ impl TweakedPublicKey { #[inline] pub fn from_keypair(keypair: TweakedKeypair) -> Self { let (xonly, _parity) = keypair.0.x_only_public_key(); - TweakedPublicKey(xonly) + TweakedPublicKey(xonly.into()) } /// Constructs a new [`TweakedPublicKey`] from a [`XOnlyPublicKey`]. No tweak is applied, consider @@ -892,11 +997,20 @@ impl TweakedPublicKey { } /// Returns the underlying public key. + #[inline] + #[doc(hidden)] + #[deprecated(since = "0.32.6", note = "use to_x_only_public_key() instead")] pub fn to_inner(self) -> XOnlyPublicKey { self.0 } - /// Serializes the key as a byte-encoded pair of values. In compressed form - /// the y-coordinate is represented by only a single bit, as x determines - /// it up to one bit. + /// Returns the underlying x-only public key. + #[inline] + pub fn to_x_only_public_key(self) -> XOnlyPublicKey { self.0 } + + /// Returns a reference to the underlying x-only public key. + #[inline] + pub fn as_x_only_public_key(&self) -> &XOnlyPublicKey { &self.0 } + + /// Serializes the key as a byte-encoded x coordinate value (32 bytes). #[inline] pub fn serialize(&self) -> [u8; constants::SCHNORR_PUBLIC_KEY_SIZE] { self.0.serialize() } } @@ -912,13 +1026,23 @@ impl TweakedKeypair { /// Returns the underlying key pair. #[inline] + #[doc(hidden)] + #[deprecated(since = "0.32.6", note = "use to_keypair() instead")] pub fn to_inner(self) -> Keypair { self.0 } + /// Returns the underlying key pair. + #[inline] + pub fn to_keypair(self) -> Keypair { self.0 } + + /// Returns a reference to the underlying key pair. + #[inline] + pub fn as_keypair(&self) -> &Keypair { &self.0 } + /// Returns the [`TweakedPublicKey`] and its [`Parity`] for this [`TweakedKeypair`]. #[inline] pub fn public_parts(&self) -> (TweakedPublicKey, Parity) { let (xonly, parity) = self.0.x_only_public_key(); - (TweakedPublicKey(xonly), parity) + (TweakedPublicKey(xonly.into()), parity) } } @@ -1222,6 +1346,95 @@ impl fmt::Display for InvalidWifCompressionFlagError { #[cfg(feature = "std")] impl std::error::Error for InvalidWifCompressionFlagError {} +mod serialized_x_only { + internals::transparent_newtype! { + /// An array of bytes that's semantically an x-only public but was **not** validated. + /// + /// This can be useful when validation is not desired but semantics of the bytes should be + /// preserved. The validation can still happen using `to_validated()` method. + #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] + pub struct SerializedXOnlyPublicKey([u8; 32]); + + impl SerializedXOnlyPublicKey { + pub(crate) fn from_bytes_ref(bytes: &_) -> Self; + } + } + + impl SerializedXOnlyPublicKey { + /// Marks the supplied bytes as a serialized x-only public key. + pub const fn from_byte_array(bytes: [u8; 32]) -> Self { Self(bytes) } + + /// Returns the raw bytes. + pub const fn to_byte_array(self) -> [u8; 32] { self.0 } + + /// Returns a reference to the raw bytes. + pub const fn as_byte_array(&self) -> &[u8; 32] { &self.0 } + } +} + +impl SerializedXOnlyPublicKey { + /// Returns `XOnlyPublicKey` if the bytes are valid. + pub fn to_validated(self) -> Result { + XOnlyPublicKey::from_byte_array(self.as_byte_array()) + } +} + +impl AsRef<[u8; 32]> for SerializedXOnlyPublicKey { + fn as_ref(&self) -> &[u8; 32] { self.as_byte_array() } +} + +impl From<&SerializedXOnlyPublicKey> for SerializedXOnlyPublicKey { + fn from(borrowed: &SerializedXOnlyPublicKey) -> Self { *borrowed } +} + +impl fmt::Debug for SerializedXOnlyPublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.as_byte_array().as_hex(), f) + } +} + +/// Error that can occur when parsing an [`XOnlyPublicKey`] from bytes. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ParseXOnlyPublicKeyError { + /// The provided bytes do not represent a valid secp256k1 point x-coordinate. + InvalidXCoordinate, +} + +impl fmt::Display for ParseXOnlyPublicKeyError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::InvalidXCoordinate => write!(f, "Invalid X coordinate for secp256k1 point"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseXOnlyPublicKeyError {} + +/// Error that can occur when tweaking an [`XOnlyPublicKey`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TweakXOnlyPublicKeyError { + /// The tweak value was invalid. + BadTweak, + /// The resulting public key would be invalid. + ResultKeyInvalid, + /// Invalid parity value encountered during the operation. + ParityError, +} + +impl fmt::Display for TweakXOnlyPublicKeyError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::BadTweak => write!(f, "Invalid tweak value"), + Self::ResultKeyInvalid => write!(f, "Resulting public key would be invalid"), + Self::ParityError => write!(f, "Invalid parity value encountered"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TweakXOnlyPublicKeyError {} + #[cfg(test)] mod tests { use super::*; diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs index 8ef0047d4e..7cb92cd7e7 100644 --- a/bitcoin/src/crypto/sighash.rs +++ b/bitcoin/src/crypto/sighash.rs @@ -1532,7 +1532,8 @@ impl<'a> Arbitrary<'a> for TapSighashType { #[cfg(test)] mod tests { use hashes::HashEngine; - use hex::{test_hex_unwrap as hex, FromHex}; + use hex::FromHex; + use hex_lit::hex; use super::*; use crate::consensus::deserialize; @@ -1831,7 +1832,7 @@ mod tests { let leaf_hash = match (script_hex, script_leaf_hash) { (Some(script_hex), _) => { - let script_inner = ScriptBuf::from_hex(script_hex).unwrap(); + let script_inner = ScriptBuf::from_hex_no_length_prefix(script_hex).unwrap(); Some(ScriptPath::with_defaults(&script_inner).leaf_hash()) } (_, Some(script_leaf_hash)) => Some(script_leaf_hash.parse::().unwrap()), @@ -1877,9 +1878,10 @@ mod tests { }) } - use secp256k1::{SecretKey, XOnlyPublicKey}; + use secp256k1::SecretKey; use crate::consensus::serde as con_serde; + use crate::crypto::key::XOnlyPublicKey; use crate::taproot::{TapNodeHash, TapTweakHash}; #[derive(serde::Deserialize)] @@ -2007,7 +2009,7 @@ mod tests { // tests let keypair = secp256k1::Keypair::from_secret_key(secp, &internal_priv_key); let (internal_key, _parity) = XOnlyPublicKey::from_keypair(&keypair); - let tweak = TapTweakHash::from_key_and_tweak(internal_key, merkle_root); + let tweak = TapTweakHash::from_key_and_merkle_root(internal_key, merkle_root); let tweaked_keypair = keypair.add_xonly_tweak(secp, &tweak.to_scalar()).unwrap(); let mut sig_msg = Vec::new(); cache @@ -2088,7 +2090,9 @@ mod tests { ), ).unwrap(); - let spk = ScriptBuf::from_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1").unwrap(); + let spk = + ScriptBuf::from_hex_no_length_prefix("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1") + .unwrap(); let value = Amount::from_sat_u32(600_000_000); let mut cache = SighashCache::new(&tx); @@ -2129,7 +2133,8 @@ mod tests { ).unwrap(); let redeem_script = - ScriptBuf::from_hex("001479091972186c449eb1ded22b78e40d009bdf0089").unwrap(); + ScriptBuf::from_hex_no_length_prefix("001479091972186c449eb1ded22b78e40d009bdf0089") + .unwrap(); let value = Amount::from_sat_u32(1_000_000_000); let mut cache = SighashCache::new(&tx); @@ -2170,7 +2175,7 @@ mod tests { )) .unwrap(); - let witness_script = ScriptBuf::from_hex( + let witness_script = ScriptBuf::from_hex_no_length_prefix( "56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28\ bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b\ 9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58\ diff --git a/bitcoin/src/crypto/taproot.rs b/bitcoin/src/crypto/taproot.rs index fe66028b87..e8dfa2ee97 100644 --- a/bitcoin/src/crypto/taproot.rs +++ b/bitcoin/src/crypto/taproot.rs @@ -9,8 +9,8 @@ use core::fmt; #[cfg(feature = "arbitrary")] use arbitrary::{Arbitrary, Unstructured}; -use internals::write_err; use internals::array::ArrayExt; +use internals::write_err; use io::Write; use crate::prelude::Vec; diff --git a/bitcoin/src/hash_types.rs b/bitcoin/src/hash_types.rs index 4f5134ad72..296e1f1355 100644 --- a/bitcoin/src/hash_types.rs +++ b/bitcoin/src/hash_types.rs @@ -4,13 +4,10 @@ //! //! This module is deprecated. You can find hash types in their respective, hopefully obvious, modules. -#[deprecated(since = "TBD", note = "use `crate::T` instead")] -pub use crate::{ - BlockHash, TxMerkleNode, Txid, WitnessCommitment, WitnessMerkleNode, - Wtxid, -}; #[deprecated(since = "TBD", note = "use `crate::T` instead")] pub use crate::bip158::{FilterHash, FilterHeader}; +#[deprecated(since = "TBD", note = "use `crate::T` instead")] +pub use crate::{BlockHash, TxMerkleNode, Txid, WitnessCommitment, WitnessMerkleNode, Wtxid}; #[cfg(test)] mod tests { diff --git a/bitcoin/src/internal_macros.rs b/bitcoin/src/internal_macros.rs index 9b97bb9bbc..69cbfcc2ae 100644 --- a/bitcoin/src/internal_macros.rs +++ b/bitcoin/src/internal_macros.rs @@ -244,7 +244,7 @@ macro_rules! only_non_doc_attrs { } pub(crate) use only_non_doc_attrs; -/// Defines an trait `$trait_name` and implements it for `ty`, used to define extension traits. +/// Defines a trait `$trait_name` and implements it for `ty`, used to define extension traits. macro_rules! define_extension_trait { ($(#[$($trait_attrs:tt)*])* $trait_vis:vis trait $trait_name:ident impl for $ty:ident { $( diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs index 4265be8db3..60e1513554 100644 --- a/bitcoin/src/lib.rs +++ b/bitcoin/src/lib.rs @@ -39,6 +39,7 @@ #![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134 #![allow(clippy::manual_range_contains)] // More readable than clippy's format. #![allow(clippy::incompatible_msrv)] // Has FPs and we're testing it which is more reliable anyway. +#![allow(clippy::uninlined_format_args)] // Allow `format!("{}", x)`instead of enforcing `format!("{x}")` // We only support machines with index size of 4 bytes or more. // @@ -91,8 +92,6 @@ pub extern crate secp256k1; extern crate serde; mod internal_macros; -#[cfg(feature = "serde")] -mod serde_utils; #[macro_use] pub mod p2p; @@ -134,12 +133,16 @@ pub use primitives::{ #[doc(inline)] pub use units::{ amount::{Amount, Denomination, SignedAmount}, - block::{BlockHeight, BlockInterval}, + block::{BlockHeight, BlockHeightInterval, BlockMtp}, fee_rate::FeeRate, time::{self, BlockTime}, weight::Weight, }; +#[deprecated(since = "TBD", note = "use `BlockHeightInterval` instead")] +#[doc(hidden)] +pub type BlockInterval = BlockHeightInterval; + #[doc(inline)] pub use crate::{ address::{Address, AddressType, KnownHrp}, @@ -201,14 +204,15 @@ pub mod amount { #[rustfmt::skip] // Keep public re-exports separate. #[doc(inline)] + pub use units::CheckedSum; + #[cfg(feature = "serde")] + pub use units::amount::serde; pub use units::amount::{ - Amount, CheckedSum, Denomination, Display, InvalidCharacterError, MissingDenominationError, + Amount, Denomination, Display, InvalidCharacterError, MissingDenominationError, MissingDigitsError, OutOfRangeError, ParseAmountError, ParseDenominationError, ParseError, PossiblyConfusingDenominationError, SignedAmount, TooPreciseError, UnknownDenominationError, }; - #[cfg(feature = "serde")] - pub use units::amount::serde; impl Decodable for Amount { #[inline] @@ -242,7 +246,7 @@ mod encode_impls { //! Encodable/Decodable implementations. // While we are deprecating, re-exporting, and generally moving things around just put these here. - use units::{BlockHeight, BlockInterval}; + use units::{BlockHeight, BlockHeightInterval}; use crate::consensus::{encode, Decodable, Encodable}; use crate::io::{BufRead, Write}; @@ -274,5 +278,5 @@ mod encode_impls { } impl_encodable_for_u32_wrapper!(BlockHeight); - impl_encodable_for_u32_wrapper!(BlockInterval); + impl_encodable_for_u32_wrapper!(BlockHeightInterval); } diff --git a/bitcoin/src/merkle_tree/block.rs b/bitcoin/src/merkle_tree/block.rs index e39fab19af..fe6589bd36 100644 --- a/bitcoin/src/merkle_tree/block.rs +++ b/bitcoin/src/merkle_tree/block.rs @@ -512,6 +512,8 @@ impl std::error::Error for MerkleBlockError { #[cfg(test)] mod tests { + use hex::{DisplayHex, FromHex}; + use hex_lit::hex; #[cfg(feature = "rand-std")] use {core::cmp, secp256k1::rand::prelude::*}; @@ -519,7 +521,6 @@ mod tests { use crate::block::{BlockUncheckedExt as _, Unchecked}; use crate::consensus::encode; use crate::hash_types::Txid; - use crate::hex::{test_hex_unwrap as hex, DisplayHex, FromHex}; #[cfg(feature = "rand-std")] macro_rules! pmt_tests { @@ -649,7 +650,8 @@ mod tests { // `gettxoutproof '["220ebc64e21abece964927322cba69180ed853bb187fbc6923bac7d010b9d87a"]'` let mb_hex = include_str!("../../tests/data/merkle_block.hex"); - let mb: MerkleBlock = encode::deserialize(&hex!(mb_hex)).unwrap(); + let bytes = Vec::from_hex(mb_hex).unwrap(); + let mb: MerkleBlock = encode::deserialize(&bytes).unwrap(); assert_eq!(get_block_13b8a().block_hash(), mb.header.block_hash()); assert_eq!( mb.header.merkle_root, diff --git a/bitcoin/src/network/mod.rs b/bitcoin/src/network/mod.rs index f32b67e560..356bcb2cb9 100644 --- a/bitcoin/src/network/mod.rs +++ b/bitcoin/src/network/mod.rs @@ -372,7 +372,6 @@ impl TryFrom for Network { mod tests { use super::{Network, TestnetVersion}; use crate::consensus::encode::{deserialize, serialize}; - use crate::p2p::ServiceFlags; #[test] fn serialize_deserialize() { @@ -420,47 +419,6 @@ mod tests { assert!("fakenet".parse::().is_err()); } - #[test] - fn service_flags() { - let all = [ - ServiceFlags::NETWORK, - ServiceFlags::GETUTXO, - ServiceFlags::BLOOM, - ServiceFlags::WITNESS, - ServiceFlags::COMPACT_FILTERS, - ServiceFlags::NETWORK_LIMITED, - ServiceFlags::P2P_V2, - ]; - - let mut flags = ServiceFlags::NONE; - for f in all.iter() { - assert!(!flags.has(*f)); - } - - flags |= ServiceFlags::WITNESS; - assert_eq!(flags, ServiceFlags::WITNESS); - - let mut flags2 = flags | ServiceFlags::GETUTXO; - for f in all.iter() { - assert_eq!(flags2.has(*f), *f == ServiceFlags::WITNESS || *f == ServiceFlags::GETUTXO); - } - - flags2 ^= ServiceFlags::WITNESS; - assert_eq!(flags2, ServiceFlags::GETUTXO); - - flags2 |= ServiceFlags::COMPACT_FILTERS; - flags2 ^= ServiceFlags::GETUTXO; - assert_eq!(flags2, ServiceFlags::COMPACT_FILTERS); - - // Test formatting. - assert_eq!("ServiceFlags(NONE)", ServiceFlags::NONE.to_string()); - assert_eq!("ServiceFlags(WITNESS)", ServiceFlags::WITNESS.to_string()); - let flag = ServiceFlags::WITNESS | ServiceFlags::BLOOM | ServiceFlags::NETWORK; - assert_eq!("ServiceFlags(NETWORK|BLOOM|WITNESS)", flag.to_string()); - let flag = ServiceFlags::WITNESS | 0xf0.into(); - assert_eq!("ServiceFlags(WITNESS|COMPACT_FILTERS|0xb0)", flag.to_string()); - } - #[test] #[cfg(feature = "serde")] fn serde_roundtrip() { diff --git a/bitcoin/src/network/params.rs b/bitcoin/src/network/params.rs index 6124e83a5f..92333fee60 100644 --- a/bitcoin/src/network/params.rs +++ b/bitcoin/src/network/params.rs @@ -8,7 +8,7 @@ //! # Custom Signets Example //! //! In various places in this crate we take `AsRef` as a parameter, in order to create a -//! custom type that can be used is such places you might want to do the following: +//! custom type that can be used in such places you might want to do the following: //! //! ``` //! use bitcoin::network::Params; @@ -68,13 +68,13 @@ //! ``` use primitives::Block; -use units::{BlockHeight, BlockInterval}; +use units::{BlockHeight, BlockHeightInterval}; use crate::network::Network; #[cfg(doc)] use crate::pow::CompactTarget; use crate::pow::Target; -use crate::TestnetVersion; +use crate::{BlockInterval, TestnetVersion}; /// Parameters that influence chain consensus. #[non_exhaustive] @@ -93,9 +93,9 @@ pub struct Params { /// Minimum blocks including miner confirmation of the total of 2016 blocks in a retargeting period, /// (nPowTargetTimespan / nPowTargetSpacing) which is also used for BIP9 deployments. /// Examples: 1916 for 95%, 1512 for testchains. - pub rule_change_activation_threshold: BlockInterval, + pub rule_change_activation_threshold: BlockHeightInterval, /// Number of blocks with the same set of rules. - pub miner_confirmation_window: BlockInterval, + pub miner_confirmation_window: BlockHeightInterval, /// Proof of work limit value. It contains the lowest possible difficulty. #[deprecated(since = "0.32.0", note = "use `max_attainable_target` instead")] pub pow_limit: Target, @@ -155,8 +155,8 @@ impl Params { bip34_height: BlockHeight::from_u32(227931), // 000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8 bip65_height: BlockHeight::from_u32(388381), // 000000000000000004c2b624ed5d7756c508d90fd0da2c7c679febfa6c4735f0 bip66_height: BlockHeight::from_u32(363725), // 00000000000000000379eaa19dce8c9b722d46ae6a57c2f1a988119488b50931 - rule_change_activation_threshold: BlockInterval::from_u32(1916), // 95% - miner_confirmation_window: BlockInterval::from_u32(2016), + rule_change_activation_threshold: BlockHeightInterval::from_u32(1916), // 95% + miner_confirmation_window: BlockHeightInterval::from_u32(2016), pow_limit: Target::MAX_ATTAINABLE_MAINNET, max_attainable_target: Target::MAX_ATTAINABLE_MAINNET, pow_target_spacing: 10 * 60, // 10 minutes. @@ -173,8 +173,8 @@ impl Params { bip34_height: BlockHeight::from_u32(21111), // 0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8 bip65_height: BlockHeight::from_u32(581885), // 00000000007f6655f22f98e72ed80d8b06dc761d5da09df0fa1dc4be4f861eb6 bip66_height: BlockHeight::from_u32(330776), // 000000002104c8c45e99a8853285a3b592602a3ccde2b832481da85e9e4ba182 - rule_change_activation_threshold: BlockInterval::from_u32(1512), // 75% - miner_confirmation_window: BlockInterval::from_u32(2016), + rule_change_activation_threshold: BlockHeightInterval::from_u32(1512), // 75% + miner_confirmation_window: BlockHeightInterval::from_u32(2016), pow_limit: Target::MAX_ATTAINABLE_TESTNET, max_attainable_target: Target::MAX_ATTAINABLE_TESTNET, pow_target_spacing: 10 * 60, // 10 minutes. @@ -190,8 +190,8 @@ impl Params { bip34_height: BlockHeight::from_u32(21111), // 0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8 bip65_height: BlockHeight::from_u32(581885), // 00000000007f6655f22f98e72ed80d8b06dc761d5da09df0fa1dc4be4f861eb6 bip66_height: BlockHeight::from_u32(330776), // 000000002104c8c45e99a8853285a3b592602a3ccde2b832481da85e9e4ba182 - rule_change_activation_threshold: BlockInterval::from_u32(1512), // 75% - miner_confirmation_window: BlockInterval::from_u32(2016), + rule_change_activation_threshold: BlockHeightInterval::from_u32(1512), // 75% + miner_confirmation_window: BlockHeightInterval::from_u32(2016), pow_limit: Target::MAX_ATTAINABLE_TESTNET, max_attainable_target: Target::MAX_ATTAINABLE_TESTNET, pow_target_spacing: 10 * 60, // 10 minutes. @@ -207,8 +207,8 @@ impl Params { bip34_height: BlockHeight::from_u32(1), bip65_height: BlockHeight::from_u32(1), bip66_height: BlockHeight::from_u32(1), - rule_change_activation_threshold: BlockInterval::from_u32(1512), // 75% - miner_confirmation_window: BlockInterval::from_u32(2016), + rule_change_activation_threshold: BlockHeightInterval::from_u32(1512), // 75% + miner_confirmation_window: BlockHeightInterval::from_u32(2016), pow_limit: Target::MAX_ATTAINABLE_TESTNET, max_attainable_target: Target::MAX_ATTAINABLE_TESTNET, pow_target_spacing: 10 * 60, // 10 minutes. @@ -224,8 +224,8 @@ impl Params { bip34_height: BlockHeight::from_u32(1), bip65_height: BlockHeight::from_u32(1), bip66_height: BlockHeight::from_u32(1), - rule_change_activation_threshold: BlockInterval::from_u32(1916), // 95% - miner_confirmation_window: BlockInterval::from_u32(2016), + rule_change_activation_threshold: BlockHeightInterval::from_u32(1916), // 95% + miner_confirmation_window: BlockHeightInterval::from_u32(2016), pow_limit: Target::MAX_ATTAINABLE_SIGNET, max_attainable_target: Target::MAX_ATTAINABLE_SIGNET, pow_target_spacing: 10 * 60, // 10 minutes. @@ -241,8 +241,8 @@ impl Params { bip34_height: BlockHeight::from_u32(100000000), // not activated on regtest bip65_height: BlockHeight::from_u32(1351), bip66_height: BlockHeight::from_u32(1251), // used only in rpc tests - rule_change_activation_threshold: BlockInterval::from_u32(108), // 75% - miner_confirmation_window: BlockInterval::from_u32(144), + rule_change_activation_threshold: BlockHeightInterval::from_u32(108), // 75% + miner_confirmation_window: BlockHeightInterval::from_u32(144), pow_limit: Target::MAX_ATTAINABLE_REGTEST, max_attainable_target: Target::MAX_ATTAINABLE_REGTEST, pow_target_spacing: 10 * 60, // 10 minutes. diff --git a/bitcoin/src/p2p/address.rs b/bitcoin/src/p2p/address.rs index accb7cc345..34481675a5 100644 --- a/bitcoin/src/p2p/address.rs +++ b/bitcoin/src/p2p/address.rs @@ -6,7 +6,7 @@ //! network addresses in Bitcoin messages. use core::{fmt, iter}; -use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}; use io::{BufRead, Read, Write}; @@ -128,8 +128,6 @@ pub enum AddrV2 { Ipv4(Ipv4Addr), /// IPV6 Ipv6(Ipv6Addr), - /// TORV2 - TorV2([u8; 10]), /// TORV3 TorV3([u8; 32]), /// I2P @@ -140,6 +138,69 @@ pub enum AddrV2 { Unknown(u8, Vec), } + +impl TryFrom for IpAddr { + type Error = AddrV2ToIpAddrError; + + fn try_from(addr: AddrV2) -> Result { + match addr { + AddrV2::Ipv4(ip) => Ok(IpAddr::V4(ip)), + AddrV2::Ipv6(ip) => Ok(IpAddr::V6(ip)), + AddrV2::Cjdns(_) => Err(AddrV2ToIpAddrError::Cjdns), + AddrV2::TorV3(_) => Err(AddrV2ToIpAddrError::TorV3), + AddrV2::I2p(_) => Err(AddrV2ToIpAddrError::I2p), + AddrV2::Unknown(_, _) => Err(AddrV2ToIpAddrError::Unknown), + } + } +} + +impl TryFrom for Ipv4Addr { + type Error = AddrV2ToIpv4AddrError; + + fn try_from(addr: AddrV2) -> Result { + match addr { + AddrV2::Ipv4(ip) => Ok(ip), + AddrV2::Ipv6(_) => Err(AddrV2ToIpv4AddrError::Ipv6), + AddrV2::Cjdns(_) => Err(AddrV2ToIpv4AddrError::Cjdns), + AddrV2::TorV3(_) => Err(AddrV2ToIpv4AddrError::TorV3), + AddrV2::I2p(_) => Err(AddrV2ToIpv4AddrError::I2p), + AddrV2::Unknown(_, _) => Err(AddrV2ToIpv4AddrError::Unknown), + } + } +} + +impl TryFrom for Ipv6Addr { + type Error = AddrV2ToIpv6AddrError; + + fn try_from(addr: AddrV2) -> Result { + match addr { + AddrV2::Ipv6(ip) => Ok(ip), + AddrV2::Cjdns(_) => Err(AddrV2ToIpv6AddrError::Cjdns), + AddrV2::Ipv4(_) => Err(AddrV2ToIpv6AddrError::Ipv4), + AddrV2::TorV3(_) => Err(AddrV2ToIpv6AddrError::TorV3), + AddrV2::I2p(_) => Err(AddrV2ToIpv6AddrError::I2p), + AddrV2::Unknown(_, _) => Err(AddrV2ToIpv6AddrError::Unknown), + } + } +} + +impl From for AddrV2 { + fn from(addr: IpAddr) -> Self { + match addr { + IpAddr::V4(ip) => AddrV2::Ipv4(ip), + IpAddr::V6(ip) => AddrV2::Ipv6(ip), + } + } +} + +impl From for AddrV2 { + fn from(addr: Ipv4Addr) -> Self { AddrV2::Ipv4(addr) } +} + +impl From for AddrV2 { + fn from(addr: Ipv6Addr) -> Self { AddrV2::Ipv6(addr) } +} + impl Encodable for AddrV2 { fn consensus_encode(&self, w: &mut W) -> Result { fn encode_addr( @@ -152,7 +213,6 @@ impl Encodable for AddrV2 { Ok(match *self { AddrV2::Ipv4(ref addr) => encode_addr(w, 1, &addr.octets())?, AddrV2::Ipv6(ref addr) => encode_addr(w, 2, &addr.octets())?, - AddrV2::TorV2(ref bytes) => encode_addr(w, 3, bytes)?, AddrV2::TorV3(ref bytes) => encode_addr(w, 4, bytes)?, AddrV2::I2p(ref bytes) => encode_addr(w, 5, bytes)?, AddrV2::Cjdns(ref addr) => encode_addr(w, 6, &addr.octets())?, @@ -195,13 +255,7 @@ impl Decodable for AddrV2 { addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], )) } - 3 => { - if len != 10 { - return Err(consensus::parse_failed_error("invalid TorV2 address")); - } - let id = Decodable::consensus_decode(r)?; - AddrV2::TorV2(id) - } + 4 => { if len != 32 { return Err(consensus::parse_failed_error("invalid TorV3 address")); @@ -297,14 +351,99 @@ impl ToSocketAddrs for AddrV2Message { } } +/// Error types for [`AddrV2`] to [`IpAddr`] conversion. +#[derive(Debug, PartialEq, Eq)] +pub enum AddrV2ToIpAddrError { + /// A [`AddrV2::TorV3`] address cannot be converted to a [`IpAddr`]. + TorV3, + /// A [`AddrV2::I2p`] address cannot be converted to a [`IpAddr`]. + I2p, + /// A [`AddrV2::Cjdns`] address cannot be converted to a [`IpAddr`], + Cjdns, + /// A [`AddrV2::Unknown`] address cannot be converted to a [`IpAddr`]. + Unknown, +} + +impl fmt::Display for AddrV2ToIpAddrError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::TorV3 => write!(f, "TorV3 addresses cannot be converted to IpAddr"), + Self::I2p => write!(f, "I2P addresses cannot be converted to IpAddr"), + Self::Cjdns => write!(f, "Cjdns addresses cannot be converted to IpAddr"), + Self::Unknown => write!(f, "Unknown address type cannot be converted to IpAddr"), + } + } +} + +impl std::error::Error for AddrV2ToIpAddrError {} + +/// Error types for [`AddrV2`] to [`Ipv4Addr`] conversion. +#[derive(Debug, PartialEq, Eq)] +pub enum AddrV2ToIpv4AddrError { + /// A [`AddrV2::Ipv6`] address cannot be converted to a [`Ipv4Addr`]. + Ipv6, + /// A [`AddrV2::TorV3`] address cannot be converted to a [`Ipv4Addr`]. + TorV3, + /// A [`AddrV2::I2p`] address cannot be converted to a [`Ipv4Addr`]. + I2p, + /// A [`AddrV2::Cjdns`] address cannot be converted to a [`Ipv4Addr`], + Cjdns, + /// A [`AddrV2::Unknown`] address cannot be converted to a [`Ipv4Addr`]. + Unknown, +} + +impl fmt::Display for AddrV2ToIpv4AddrError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Ipv6 => write!(f, "Ipv6 addresses cannot be converted to Ipv4Addr"), + Self::TorV3 => write!(f, "TorV3 addresses cannot be converted to Ipv4Addr"), + Self::I2p => write!(f, "I2P addresses cannot be converted to Ipv4Addr"), + Self::Cjdns => write!(f, "Cjdns addresses cannot be converted to Ipv4Addr"), + Self::Unknown => write!(f, "Unknown address type cannot be converted to Ipv4Addr"), + } + } +} + +impl std::error::Error for AddrV2ToIpv4AddrError {} + +/// Error types for [`AddrV2`] to [`Ipv6Addr`] conversion. +#[derive(Debug, PartialEq, Eq)] +pub enum AddrV2ToIpv6AddrError { + /// A [`AddrV2::Ipv4`] address cannot be converted to a [`Ipv6Addr`]. + Ipv4, + /// A [`AddrV2::TorV3`] address cannot be converted to a [`Ipv6Addr`]. + TorV3, + /// A [`AddrV2::I2p`] address cannot be converted to a [`Ipv6Addr`]. + I2p, + /// A [`AddrV2::Cjdns`] address cannot be converted to a [`Ipv6Addr`], + Cjdns, + /// A [`AddrV2::Unknown`] address cannot be converted to a [`Ipv6Addr`]. + Unknown, +} + +impl fmt::Display for AddrV2ToIpv6AddrError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Ipv4 => write!(f, "Ipv addresses cannot be converted to Ipv6Addr"), + Self::TorV3 => write!(f, "TorV3 addresses cannot be converted to Ipv6Addr"), + Self::I2p => write!(f, "I2P addresses cannot be converted to Ipv6Addr"), + Self::Cjdns => write!(f, "Cjdns addresses cannot be converted to Ipv6Addr"), + Self::Unknown => write!(f, "Unknown address type cannot be converted to Ipv6Addr"), + } + } +} + +impl std::error::Error for AddrV2ToIpv6AddrError {} + #[cfg(test)] mod test { use std::net::IpAddr; - use hex::{test_hex_unwrap as hex, FromHex}; + use hex::FromHex; + use hex_lit::hex; use super::*; - use crate::consensus::encode::{deserialize, serialize}; + use crate::{consensus::encode::{deserialize, serialize}, p2p::message::AddrV2Payload}; #[test] fn serialize_address() { @@ -398,9 +537,6 @@ mod test { AddrV2::Ipv6("1a1b:2a2b:3a3b:4a4b:5a5b:6a6b:7a7b:8a8b".parse::().unwrap()); assert_eq!(serialize(&ip), hex!("02101a1b2a2b3a3b4a4b5a5b6a6b7a7b8a8b")); - let ip = AddrV2::TorV2(FromHex::from_hex("f1f2f3f4f5f6f7f8f9fa").unwrap()); - assert_eq!(serialize(&ip), hex!("030af1f2f3f4f5f6f7f8f9fa")); - let ip = AddrV2::TorV3( FromHex::from_hex("53cd5648488c4707914182655b7664034e09e66f7e8cbf1084e654eb56c5bd88") .unwrap(), @@ -422,7 +558,7 @@ mod test { let ip = AddrV2::Cjdns("fc01:1:2:3:4:5:6:7".parse::().unwrap()); assert_eq!(serialize(&ip), hex!("0610fc010001000200030004000500060007")); - let ip = AddrV2::Unknown(170, hex!("01020304")); + let ip = AddrV2::Unknown(170, hex!("01020304").to_vec()); assert_eq!(serialize(&ip), hex!("aa0401020304")); } @@ -459,13 +595,6 @@ mod test { // Invalid IPv6, contains embedded TORv2. assert!(deserialize::(&hex!("0210fd87d87eeb430102030405060708090a")).is_err()); - // Valid TORv2. - let ip: AddrV2 = deserialize(&hex!("030af1f2f3f4f5f6f7f8f9fa")).unwrap(); - assert_eq!(ip, AddrV2::TorV2(FromHex::from_hex("f1f2f3f4f5f6f7f8f9fa").unwrap())); - - // Invalid TORv2, with bogus length. - assert!(deserialize::(&hex!("030700")).is_err()); - // Valid TORv3. let ip: AddrV2 = deserialize(&hex!( "042079bcc625184b05194975c28b66b66b0469f7f6556fb1ac3189a79b40dda32f1f" @@ -517,7 +646,7 @@ mod test { // Unknown, with reasonable length. let ip: AddrV2 = deserialize(&hex!("aa0401020304")).unwrap(); - assert_eq!(ip, AddrV2::Unknown(170, hex!("01020304"))); + assert_eq!(ip, AddrV2::Unknown(170, hex!("01020304").to_vec())); // Unknown, with zero length. let ip: AddrV2 = deserialize(&hex!("aa00")).unwrap(); @@ -527,16 +656,16 @@ mod test { #[test] fn addrv2message() { let raw = hex!("0261bc6649019902abab208d79627683fd4804010409090909208d"); - let addresses: Vec = deserialize(&raw).unwrap(); + let addresses: AddrV2Payload = deserialize(&raw).unwrap(); assert_eq!( - addresses, + addresses.0, vec![ AddrV2Message { services: ServiceFlags::NETWORK, time: 0x4966bc61, port: 8333, - addr: AddrV2::Unknown(153, hex!("abab")) + addr: AddrV2::Unknown(153, hex!("abab").to_vec()) }, AddrV2Message { services: ServiceFlags::NETWORK_LIMITED @@ -551,4 +680,162 @@ mod test { assert_eq!(serialize(&addresses), raw); } + + #[test] + fn addrv2_to_ipaddr_ipv4() { + let addr = AddrV2::Ipv4(Ipv4Addr::new(192, 168, 1, 1)); + let ip_addr = IpAddr::try_from(addr).unwrap(); + + assert_eq!(ip_addr, IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))); + } + + #[test] + fn addrv2_to_ipaddr_ipv6() { + let addr = AddrV2::Ipv6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)); + let ip_addr = IpAddr::try_from(addr).unwrap(); + + assert_eq!(ip_addr, IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1))); + } + + #[test] + fn addrv2_to_ipaddr_cjdns() { + let addr = AddrV2::Cjdns(Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 1)); + let result = IpAddr::try_from(addr); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), AddrV2ToIpAddrError::Cjdns); + } + + #[test] + fn addrv2_to_ipaddr_torv3() { + let addr = AddrV2::TorV3([0; 32]); + let result = IpAddr::try_from(addr); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), AddrV2ToIpAddrError::TorV3); + } + + #[test] + fn addrv2_to_ipaddr_i2p() { + let addr = AddrV2::I2p([0; 32]); + let result = IpAddr::try_from(addr); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), AddrV2ToIpAddrError::I2p); + } + + #[test] + fn addrv2_to_ipaddr_unknown() { + let addr = AddrV2::Unknown(42, vec![1, 2, 3, 4]); + let result = IpAddr::try_from(addr); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), AddrV2ToIpAddrError::Unknown); + } + + #[test] + fn addrv2_to_ipv4addr_ipv4() { + let addr = AddrV2::Ipv4(Ipv4Addr::new(192, 168, 1, 1)); + let ip_addr = Ipv4Addr::try_from(addr).unwrap(); + + assert_eq!(ip_addr, Ipv4Addr::new(192, 168, 1, 1)); + } + + #[test] + fn addrv2_to_ipv4addr_ipv6() { + let addr = AddrV2::Ipv6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)); + let result = Ipv4Addr::try_from(addr); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), AddrV2ToIpv4AddrError::Ipv6); + } + + #[test] + fn addrv2_to_ipv4addr_cjdns() { + let addr = AddrV2::Cjdns(Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 1)); + let result = Ipv4Addr::try_from(addr); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), AddrV2ToIpv4AddrError::Cjdns); + } + + #[test] + fn addrv2_to_ipv4addr_torv3() { + let addr = AddrV2::TorV3([0; 32]); + let result = Ipv4Addr::try_from(addr); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), AddrV2ToIpv4AddrError::TorV3); + } + + #[test] + fn addrv2_to_ipv4addr_i2p() { + let addr = AddrV2::I2p([0; 32]); + let result = Ipv4Addr::try_from(addr); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), AddrV2ToIpv4AddrError::I2p); + } + + #[test] + fn addrv2_to_ipv4addr_unknown() { + let addr = AddrV2::Unknown(42, vec![1, 2, 3, 4]); + let result = Ipv4Addr::try_from(addr); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), AddrV2ToIpv4AddrError::Unknown); + } + + #[test] + fn addrv2_to_ipv6addr_ipv4() { + let addr = AddrV2::Ipv4(Ipv4Addr::new(192, 168, 1, 1)); + let result = Ipv6Addr::try_from(addr); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), AddrV2ToIpv6AddrError::Ipv4); + } + + #[test] + fn addrv2_to_ipv6addr_ipv6() { + let addr = AddrV2::Ipv6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)); + let ip_addr = Ipv6Addr::try_from(addr).unwrap(); + + assert_eq!(ip_addr, Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)); + } + + #[test] + fn addrv2_to_ipv6addr_cjdns() { + let addr = AddrV2::Cjdns(Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 1)); + let result = Ipv6Addr::try_from(addr); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), AddrV2ToIpv6AddrError::Cjdns); + } + + #[test] + fn addrv2_to_ipv6addr_torv3() { + let addr = AddrV2::TorV3([0; 32]); + let result = Ipv6Addr::try_from(addr); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), AddrV2ToIpv6AddrError::TorV3); + } + + #[test] + fn addrv2_to_ipv6addr_i2p() { + let addr = AddrV2::I2p([0; 32]); + let result = Ipv6Addr::try_from(addr); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), AddrV2ToIpv6AddrError::I2p); + } + + #[test] + fn addrv2_to_ipv6addr_unknown() { + let addr = AddrV2::Unknown(42, vec![1, 2, 3, 4]); + let result = Ipv6Addr::try_from(addr); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), AddrV2ToIpv6AddrError::Unknown); + } } diff --git a/bitcoin/src/p2p/deser.rs b/bitcoin/src/p2p/deser.rs new file mode 100644 index 0000000000..09a18f23b7 --- /dev/null +++ b/bitcoin/src/p2p/deser.rs @@ -0,0 +1,41 @@ +macro_rules! impl_vec_wrapper { + ($wrapper: ident, $type: ty) => { + impl crate::consensus::encode::Encodable for $wrapper { + #[inline] + fn consensus_encode( + &self, + w: &mut W, + ) -> core::result::Result { + let mut len = 0; + len += w.emit_compact_size(self.0.len())?; + for c in self.0.iter() { + len += c.consensus_encode(w)?; + } + Ok(len) + } + } + + impl crate::consensus::encode::Decodable for $wrapper { + #[inline] + fn consensus_decode_from_finite_reader( + r: &mut R, + ) -> core::result::Result<$wrapper, crate::consensus::encode::Error> { + let len = r.read_compact_size()?; + // Do not allocate upfront more items than if the sequence of type + // occupied roughly quarter a block. This should never be the case + // for normal data, but even if that's not true - `push` will just + // reallocate. + // Note: OOM protection relies on reader eventually running out of + // data to feed us. + let max_capacity = crate::consensus::encode::MAX_VEC_SIZE / 4 / core::mem::size_of::<$type>(); + let mut ret = Vec::with_capacity(core::cmp::min(len as usize, max_capacity)); + for _ in 0..len { + ret.push(Decodable::consensus_decode_from_finite_reader(r)?); + } + Ok($wrapper(ret)) + } + } + }; +} + +pub(crate) use impl_vec_wrapper; diff --git a/bitcoin/src/p2p/message.rs b/bitcoin/src/p2p/message.rs index e9522a7c51..3bfc8513bc 100644 --- a/bitcoin/src/p2p/message.rs +++ b/bitcoin/src/p2p/message.rs @@ -19,6 +19,7 @@ use crate::p2p::{ Magic, }; use crate::prelude::{Box, Cow, String, ToOwned, Vec}; +use crate::p2p::deser::impl_vec_wrapper; use crate::{block, consensus, transaction}; /// The maximum number of [super::message_blockdata::Inventory] items in an `inv` message. @@ -27,7 +28,7 @@ use crate::{block, consensus, transaction}; pub const MAX_INV_SIZE: usize = 50_000; /// Maximum size, in bytes, of an encoded message -/// This by necessity should be larger tham `MAX_VEC_SIZE` +/// This by necessity should be larger than `MAX_VEC_SIZE` pub const MAX_MSG_SIZE: usize = 5_000_000; /// Serializer for command string @@ -163,6 +164,22 @@ pub struct V2NetworkMessage { payload: NetworkMessage, } +/// A list of inventory items. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct InventoryPayload(pub Vec); + +/// A list of legacy p2p address messages. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct AddrPayload(pub Vec<(u32, Address)>); + +/// A list of v2 address messages. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct AddrV2Payload(pub Vec); + +impl_vec_wrapper!(InventoryPayload, message_blockdata::Inventory); +impl_vec_wrapper!(AddrPayload, (u32, Address)); +impl_vec_wrapper!(AddrV2Payload, AddrV2Message); + /// A Network message payload. Proper documentation is available on at /// [Bitcoin Wiki: Protocol Specification](https://en.bitcoin.it/wiki/Protocol_specification) #[derive(Clone, PartialEq, Eq, Debug)] @@ -172,13 +189,13 @@ pub enum NetworkMessage { /// `verack` Verack, /// `addr` - Addr(Vec<(u32, Address)>), + Addr(AddrPayload), /// `inv` - Inv(Vec), + Inv(InventoryPayload), /// `getdata` - GetData(Vec), + GetData(InventoryPayload), /// `notfound` - NotFound(Vec), + NotFound(InventoryPayload), /// `getblocks` GetBlocks(message_blockdata::GetBlocksMessage), /// `getheaders` @@ -236,7 +253,7 @@ pub enum NetworkMessage { /// `wtxidrelay` WtxidRelay, /// `addrv2` - AddrV2(Vec), + AddrV2(AddrV2Payload), /// `sendaddrv2` SendAddrV2, @@ -697,12 +714,12 @@ impl Decodable for V2NetworkMessage { mod test { use std::net::Ipv4Addr; - use hex::test_hex_unwrap as hex; + use hex_lit::hex; use units::BlockHeight; use super::*; use crate::bip152::BlockTransactionsRequest; - use crate::bip158::{FilterHeader, FilterHash}; + use crate::bip158::{FilterHash, FilterHeader}; use crate::block::{Block, BlockHash}; use crate::consensus::encode::{deserialize, deserialize_partial, serialize}; use crate::p2p::address::AddrV2; @@ -734,23 +751,29 @@ mod test { let msgs = [ NetworkMessage::Version(version_msg), NetworkMessage::Verack, - NetworkMessage::Addr(vec![( + NetworkMessage::Addr(AddrPayload(vec![( 45, Address::new(&([123, 255, 000, 100], 833).into(), ServiceFlags::NETWORK), - )]), - NetworkMessage::Inv(vec![Inventory::Block(BlockHash::from_byte_array(hash([8u8; 32]).to_byte_array()))]), - NetworkMessage::GetData(vec![Inventory::Transaction(Txid::from_byte_array(hash([45u8; 32]).to_byte_array()))]), - NetworkMessage::NotFound(vec![Inventory::Error([0u8; 32])]), + )])), + NetworkMessage::Inv(InventoryPayload(vec![Inventory::Block(BlockHash::from_byte_array( + hash([8u8; 32]).to_byte_array(), + ))])), + NetworkMessage::GetData(InventoryPayload(vec![Inventory::Transaction(Txid::from_byte_array( + hash([45u8; 32]).to_byte_array(), + ))])), + NetworkMessage::NotFound(InventoryPayload(vec![Inventory::Error([0u8; 32])])), NetworkMessage::GetBlocks(GetBlocksMessage::new( vec![ BlockHash::from_byte_array(hash([1u8; 32]).to_byte_array()), - BlockHash::from_byte_array(hash([4u8; 32]).to_byte_array())], + BlockHash::from_byte_array(hash([4u8; 32]).to_byte_array()), + ], BlockHash::from_byte_array(hash([5u8; 32]).to_byte_array()), )), NetworkMessage::GetHeaders(GetHeadersMessage::new( vec![ BlockHash::from_byte_array(hash([10u8; 32]).to_byte_array()), - BlockHash::from_byte_array(hash([40u8; 32]).to_byte_array())], + BlockHash::from_byte_array(hash([40u8; 32]).to_byte_array()), + ], BlockHash::from_byte_array(hash([50u8; 32]).to_byte_array()), )), NetworkMessage::MemPool, @@ -763,7 +786,7 @@ mod test { NetworkMessage::Pong(23), NetworkMessage::MerkleBlock(merkle_block), NetworkMessage::FilterLoad(FilterLoad { - filter: hex!("03614e9b050000000000000001"), + filter: hex!("03614e9b050000000000000001").to_vec(), hash_funcs: 1, tweak: 2, flags: BloomFlags::All, @@ -791,8 +814,13 @@ mod test { NetworkMessage::CFHeaders(CFHeaders { filter_type: 13, stop_hash: BlockHash::from_byte_array(hash([53u8; 32]).to_byte_array()), - previous_filter_header: FilterHeader::from_byte_array(hash([12u8; 32]).to_byte_array()), - filter_hashes: vec![FilterHash::from_byte_array(hash([4u8; 32]).to_byte_array()), FilterHash::from_byte_array(hash([12u8; 32]).to_byte_array())], + previous_filter_header: FilterHeader::from_byte_array( + hash([12u8; 32]).to_byte_array(), + ), + filter_hashes: vec![ + FilterHash::from_byte_array(hash([4u8; 32]).to_byte_array()), + FilterHash::from_byte_array(hash([12u8; 32]).to_byte_array()), + ], }), NetworkMessage::GetCFCheckpt(GetCFCheckpt { filter_type: 17, @@ -801,7 +829,10 @@ mod test { NetworkMessage::CFCheckpt(CFCheckpt { filter_type: 27, stop_hash: BlockHash::from_byte_array(hash([77u8; 32]).to_byte_array()), - filter_headers: vec![FilterHeader::from_byte_array(hash([3u8; 32]).to_byte_array()), FilterHeader::from_byte_array(hash([99u8; 32]).to_byte_array())], + filter_headers: vec![ + FilterHeader::from_byte_array(hash([3u8; 32]).to_byte_array()), + FilterHeader::from_byte_array(hash([99u8; 32]).to_byte_array()), + ], }), NetworkMessage::Alert(vec![45, 66, 3, 2, 6, 8, 9, 12, 3, 130]), NetworkMessage::Reject(Reject { @@ -812,12 +843,12 @@ mod test { }), NetworkMessage::FeeFilter(1000), NetworkMessage::WtxidRelay, - NetworkMessage::AddrV2(vec![AddrV2Message { + NetworkMessage::AddrV2(AddrV2Payload(vec![AddrV2Message { addr: AddrV2::Ipv4(Ipv4Addr::new(127, 0, 0, 1)), port: 0, services: ServiceFlags::NONE, time: 0, - }]), + }])), NetworkMessage::SendAddrV2, NetworkMessage::CmpctBlock(cmptblock), NetworkMessage::GetBlockTxn(GetBlockTxn { diff --git a/bitcoin/src/p2p/message_blockdata.rs b/bitcoin/src/p2p/message_blockdata.rs index 6ccff60d91..3d92312043 100644 --- a/bitcoin/src/p2p/message_blockdata.rs +++ b/bitcoin/src/p2p/message_blockdata.rs @@ -144,7 +144,7 @@ impl_consensus_encoding!(GetHeadersMessage, version, locator_hashes, stop_hash); #[cfg(test)] mod tests { - use hex::test_hex_unwrap as hex; + use hex_lit::hex; use super::*; use crate::consensus::encode::{deserialize, serialize}; diff --git a/bitcoin/src/p2p/message_compact_blocks.rs b/bitcoin/src/p2p/message_compact_blocks.rs index d4d8153b51..333a8c243f 100644 --- a/bitcoin/src/p2p/message_compact_blocks.rs +++ b/bitcoin/src/p2p/message_compact_blocks.rs @@ -9,7 +9,7 @@ use crate::internal_macros::impl_consensus_encoding; /// sendcmpct message #[derive(PartialEq, Eq, Clone, Debug, Copy, PartialOrd, Ord, Hash)] pub struct SendCmpct { - /// Request to be send compact blocks. + /// Request to be sent compact blocks. pub send_compact: bool, /// Compact Blocks protocol version number. pub version: u64, diff --git a/bitcoin/src/p2p/message_network.rs b/bitcoin/src/p2p/message_network.rs index 9425781480..d4a41b9e1c 100644 --- a/bitcoin/src/p2p/message_network.rs +++ b/bitcoin/src/p2p/message_network.rs @@ -138,7 +138,7 @@ pub struct Reject { pub message: Cow<'static, str>, /// reason of rejection as code pub ccode: RejectReason, - /// reason of rejectection + /// reason of rejection pub reason: Cow<'static, str>, /// reference to rejected item pub hash: sha256d::Hash, @@ -148,7 +148,7 @@ impl_consensus_encoding!(Reject, message, ccode, reason, hash); #[cfg(test)] mod tests { - use hex::test_hex_unwrap as hex; + use hex_lit::hex; use super::*; use crate::consensus::encode::{deserialize, serialize}; diff --git a/bitcoin/src/p2p/mod.rs b/bitcoin/src/p2p/mod.rs index f99ba8b591..a1b87df2a9 100644 --- a/bitcoin/src/p2p/mod.rs +++ b/bitcoin/src/p2p/mod.rs @@ -19,6 +19,8 @@ pub mod message_compact_blocks; pub mod message_filter; #[cfg(feature = "std")] pub mod message_network; +#[cfg(feature = "std")] +mod deser; use core::str::FromStr; use core::{fmt, ops}; diff --git a/bitcoin/src/pow.rs b/bitcoin/src/pow.rs index ae295a3b7d..81216d0eae 100644 --- a/bitcoin/src/pow.rs +++ b/bitcoin/src/pow.rs @@ -565,7 +565,7 @@ impl U256 { fn low_u128(&self) -> u128 { self.1 } /// Returns this `U256` as a `u128` saturating to `u128::MAX` if `self` is too big. - // Matagen gives false positive because >= and > both return u128::MAX + // Mutagen gives false positive because >= and > both return u128::MAX fn saturating_to_u128(&self) -> u128 { if *self > U256::from(u128::MAX) { u128::MAX diff --git a/bitcoin/src/psbt/error.rs b/bitcoin/src/psbt/error.rs index dcf244ad38..58338ab691 100644 --- a/bitcoin/src/psbt/error.rs +++ b/bitcoin/src/psbt/error.rs @@ -96,7 +96,7 @@ pub enum Error { InvalidLeafVersion, /// Parsing error indicating a Taproot error Taproot(&'static str), - /// Taproot tree deserilaization error + /// Taproot tree deserialization error TapTree(crate::taproot::IncompleteBuilderError), /// Error related to an xpub key XPubKey(&'static str), diff --git a/bitcoin/src/psbt/map/input.rs b/bitcoin/src/psbt/map/input.rs index 66802623b1..2422c57d26 100644 --- a/bitcoin/src/psbt/map/input.rs +++ b/bitcoin/src/psbt/map/input.rs @@ -4,10 +4,9 @@ use core::fmt; use core::str::FromStr; use hashes::{hash160, ripemd160, sha256, sha256d}; -use secp256k1::XOnlyPublicKey; use crate::bip32::KeySource; -use crate::crypto::key::PublicKey; +use crate::crypto::key::{PublicKey, XOnlyPublicKey}; use crate::crypto::{ecdsa, taproot}; use crate::prelude::{btree_map, BTreeMap, Borrow, Box, ToOwned, Vec}; use crate::psbt::map::Map; @@ -60,13 +59,14 @@ const PSBT_IN_TAP_BIP32_DERIVATION: u64 = 0x16; const PSBT_IN_TAP_INTERNAL_KEY: u64 = 0x17; /// Type: Taproot Merkle Root PSBT_IN_TAP_MERKLE_ROOT = 0x18 const PSBT_IN_TAP_MERKLE_ROOT: u64 = 0x18; +/// Type: MuSig2 Public Keys Participating in Aggregate Input PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS = 0x1a +const PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS: u64 = 0x1a; /// Type: Proprietary Use Type PSBT_IN_PROPRIETARY = 0xFC const PSBT_IN_PROPRIETARY: u64 = 0xFC; /// A key-value map for an input of the corresponding index in the unsigned /// transaction. #[derive(Clone, Default, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Input { /// The non-witness transaction this input spends from. Should only be /// `Option::Some` for inputs which spend non-SegWit outputs or @@ -88,7 +88,6 @@ pub struct Input { pub witness_script: Option, /// A map from public keys needed to sign this input to their corresponding /// master key fingerprints and derivation paths. - #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq"))] pub bip32_derivation: BTreeMap, /// The finalized, fully-constructed scriptSig with signatures and any other /// scripts necessary for this input to pass validation. @@ -97,37 +96,30 @@ pub struct Input { /// other scripts necessary for this input to pass validation. pub final_script_witness: Option, /// RIPEMD160 hash to preimage map. - #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_byte_values"))] pub ripemd160_preimages: BTreeMap>, /// SHA256 hash to preimage map. - #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_byte_values"))] pub sha256_preimages: BTreeMap>, - /// HSAH160 hash to preimage map. - #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_byte_values"))] + /// HASH160 hash to preimage map. pub hash160_preimages: BTreeMap>, - /// HAS256 hash to preimage map. - #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_byte_values"))] + /// HASH256 hash to preimage map. pub hash256_preimages: BTreeMap>, /// Serialized Taproot signature with sighash type for key spend. pub tap_key_sig: Option, /// Map of `|` with signature. - #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq"))] pub tap_script_sigs: BTreeMap<(XOnlyPublicKey, TapLeafHash), taproot::Signature>, /// Map of Control blocks to Script version pair. - #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq"))] pub tap_scripts: BTreeMap, /// Map of tap root x only keys to origin info and leaf hashes contained in it. - #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq"))] pub tap_key_origins: BTreeMap, KeySource)>, /// Taproot Internal key. pub tap_internal_key: Option, /// Taproot Merkle root. pub tap_merkle_root: Option, + /// Mapping from MuSig2 aggregate keys to the participant keys from which they were aggregated. + pub musig2_participant_pubkeys: BTreeMap>, /// Proprietary key-value pairs for this input. - #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq_byte_values"))] pub proprietary: BTreeMap>, /// Unknown key-value pairs for this input. - #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq_byte_values"))] pub unknown: BTreeMap>, } @@ -148,7 +140,6 @@ pub struct Input { /// let _tap_sighash_all: PsbtSighashType = TapSighashType::All.into(); /// ``` #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct PsbtSighashType { pub(in crate::psbt) inner: u32, } @@ -365,6 +356,11 @@ impl Input { self.tap_merkle_root <= |< raw_value: TapNodeHash> } } + PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS => { + impl_psbt_insert_pair! { + self.musig2_participant_pubkeys <= |< raw_value: Vec > + } + } PSBT_IN_PROPRIETARY => { let key = raw::ProprietaryKey::try_from(raw_key.clone())?; match self.proprietary.entry(key) { @@ -403,6 +399,7 @@ impl Input { self.tap_script_sigs.extend(other.tap_script_sigs); self.tap_scripts.extend(other.tap_scripts); self.tap_key_origins.extend(other.tap_key_origins); + self.musig2_participant_pubkeys.extend(other.musig2_participant_pubkeys); self.proprietary.extend(other.proprietary); self.unknown.extend(other.unknown); @@ -495,6 +492,11 @@ impl Map for Input { impl_psbt_get_pair! { rv.push(self.tap_merkle_root, PSBT_IN_TAP_MERKLE_ROOT) } + + impl_psbt_get_pair! { + rv.push_map(self.musig2_participant_pubkeys, PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS) + } + for (key, value) in self.proprietary.iter() { rv.push(raw::Pair { key: key.to_key(), value: value.clone() }); } diff --git a/bitcoin/src/psbt/map/output.rs b/bitcoin/src/psbt/map/output.rs index 4829e39f56..37e853f816 100644 --- a/bitcoin/src/psbt/map/output.rs +++ b/bitcoin/src/psbt/map/output.rs @@ -1,8 +1,7 @@ // SPDX-License-Identifier: CC0-1.0 -use secp256k1::XOnlyPublicKey; - use crate::bip32::KeySource; +use crate::crypto::key::XOnlyPublicKey; use crate::prelude::{btree_map, BTreeMap, Vec}; use crate::psbt::map::Map; use crate::psbt::{raw, Error}; @@ -21,13 +20,14 @@ const PSBT_OUT_TAP_INTERNAL_KEY: u64 = 0x05; const PSBT_OUT_TAP_TREE: u64 = 0x06; /// Type: Taproot Key BIP 32 Derivation Path PSBT_OUT_TAP_BIP32_DERIVATION = 0x07 const PSBT_OUT_TAP_BIP32_DERIVATION: u64 = 0x07; +/// Type: MuSig2 Public Keys Participating in Aggregate Output PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS = 0x08 +const PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS: u64 = 0x08; /// Type: Proprietary Use Type PSBT_IN_PROPRIETARY = 0xFC const PSBT_OUT_PROPRIETARY: u64 = 0xFC; /// A key-value map for an output of the corresponding index in the unsigned /// transaction. #[derive(Clone, Default, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Output { /// The redeem script for this output. pub redeem_script: Option, @@ -35,20 +35,18 @@ pub struct Output { pub witness_script: Option, /// A map from public keys needed to spend this output to their /// corresponding master key fingerprints and derivation paths. - #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq"))] pub bip32_derivation: BTreeMap, /// The internal pubkey. pub tap_internal_key: Option, /// Taproot Output tree. pub tap_tree: Option, /// Map of tap root x only keys to origin info and leaf hashes contained in it. - #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq"))] pub tap_key_origins: BTreeMap, KeySource)>, + /// Mapping from MuSig2 aggregate keys to the participant keys from which they were aggregated. + pub musig2_participant_pubkeys: BTreeMap>, /// Proprietary key-value pairs for this output. - #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq_byte_values"))] pub proprietary: BTreeMap>, /// Unknown key-value pairs for this output. - #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq_byte_values"))] pub unknown: BTreeMap>, } @@ -96,6 +94,11 @@ impl Output { self.tap_key_origins <= |< raw_value: (Vec, KeySource)> } } + PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS => { + impl_psbt_insert_pair! { + self.musig2_participant_pubkeys <= |< raw_value: Vec > + } + } _ => match self.unknown.entry(raw_key) { btree_map::Entry::Vacant(empty_key) => { empty_key.insert(raw_value); @@ -113,6 +116,7 @@ impl Output { self.proprietary.extend(other.proprietary); self.unknown.extend(other.unknown); self.tap_key_origins.extend(other.tap_key_origins); + self.musig2_participant_pubkeys.extend(other.musig2_participant_pubkeys); combine!(redeem_script, self, other); combine!(witness_script, self, other); @@ -149,6 +153,10 @@ impl Map for Output { rv.push_map(self.tap_key_origins, PSBT_OUT_TAP_BIP32_DERIVATION) } + impl_psbt_get_pair! { + rv.push_map(self.musig2_participant_pubkeys, PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS) + } + for (key, value) in self.proprietary.iter() { rv.push(raw::Pair { key: key.to_key(), value: value.clone() }); } diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs index 6f0ce3fc4b..3e5c9c2da0 100644 --- a/bitcoin/src/psbt/mod.rs +++ b/bitcoin/src/psbt/mod.rs @@ -20,6 +20,7 @@ use std::collections::{HashMap, HashSet}; use internals::write_err; use secp256k1::{Keypair, Message, Secp256k1, Signing, Verification}; +use units::NumOpResult; use crate::bip32::{self, DerivationPath, KeySource, Xpriv, Xpub}; use crate::crypto::key::{PrivateKey, PublicKey}; @@ -40,7 +41,6 @@ pub use self::{ /// A Partially Signed Transaction. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Psbt { /// The unsigned transaction, scriptSigs and witnesses for each input must be empty. pub unsigned_tx: Transaction, @@ -50,10 +50,8 @@ pub struct Psbt { /// derivation path as defined by BIP 32. pub xpub: BTreeMap, /// Global proprietary key-value pairs. - #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq_byte_values"))] pub proprietary: BTreeMap>, /// Unknown global key-value pairs. - #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_as_seq_byte_values"))] pub unknown: BTreeMap>, /// The corresponding key-value map for each input in the unsigned transaction. @@ -130,7 +128,7 @@ impl Psbt { /// 1000 sats/vByte. 25k sats/vByte is obviously a mistake at this point. /// /// [`extract_tx`]: Psbt::extract_tx - pub const DEFAULT_MAX_FEE_RATE: FeeRate = FeeRate::from_sat_per_vb_unchecked(25_000); + pub const DEFAULT_MAX_FEE_RATE: FeeRate = FeeRate::from_sat_per_vb_u32(25_000); /// An alias for [`extract_tx_fee_rate_limit`]. /// @@ -209,11 +207,14 @@ impl Psbt { let tx = self.internal_extract_tx(); // Now that the extracted Transaction is made, decide how to return it. - let fee_rate = - FeeRate::from_sat_per_kwu(fee.to_sat().saturating_mul(1000) / tx.weight().to_wu()); - // Prefer to return an AbsurdFeeRate error when both trigger. - if fee_rate > max_fee_rate { - return Err(ExtractTxError::AbsurdFeeRate { fee_rate, tx }); + match fee / tx.weight() { + NumOpResult::Valid(fee_rate) => { + // Prefer to return an AbsurdFeeRate error when both trigger. + if fee_rate > max_fee_rate { + return Err(ExtractTxError::AbsurdFeeRate { fee_rate, tx }); + } + } + NumOpResult::Error(_) => unreachable!("weight() is always non-zero"), } Ok(tx) @@ -448,7 +449,7 @@ impl Psbt { let (msg, sighash_type) = self.sighash_taproot(input_index, cache, None)?; let key_pair = Keypair::from_secret_key(secp, &sk.inner) .tap_tweak(secp, input.tap_merkle_root) - .to_inner(); + .to_keypair(); #[cfg(feature = "rand-std")] let signature = secp.sign_schnorr(msg.as_ref(), &key_pair); @@ -731,6 +732,51 @@ impl Psbt { } } +#[cfg(feature = "serde")] +impl serde::Serialize for Psbt { + fn serialize(&self, serializer: S) -> Result { + use crate::prelude::ToString; + + if serializer.is_human_readable() { + serializer.serialize_str(&self.to_string()) + } else { + serializer.serialize_bytes(&self.serialize()) + } + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for Psbt { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct Visitor; + + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = Psbt; + + fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "a psbt") + } + + fn visit_bytes(self, bytes: &[u8]) -> Result { + Psbt::deserialize(bytes).map_err(|e| serde::de::Error::custom(e)) + } + + fn visit_str(self, s: &str) -> Result { + s.parse().map_err(|e| serde::de::Error::custom(e)) + } + } + + if deserializer.is_human_readable() { + deserializer.deserialize_str(Visitor) + } else { + deserializer.deserialize_bytes(Visitor) + } + } +} + /// Data required to call [`GetKey`] to get the private key to sign an input. #[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] @@ -775,14 +821,14 @@ impl GetKey for Xpriv { KeyRequest::XOnlyPubkey(_) => Err(GetKeyError::NotSupported), KeyRequest::Bip32((fingerprint, path)) => { let key = if self.fingerprint(secp) == *fingerprint { - let k = self.derive_xpriv(secp, &path); + let k = self.derive_xpriv(secp, &path).map_err(GetKeyError::Bip32)?; Some(k.to_private_key()) } else if self.parent_fingerprint == *fingerprint && !path.is_empty() && path[0] == self.child_number { let path = DerivationPath::from_iter(path.into_iter().skip(1).copied()); - let k = self.derive_xpriv(secp, &path); + let k = self.derive_xpriv(secp, &path).map_err(GetKeyError::Bip32)?; Some(k.to_private_key()) } else { None @@ -850,14 +896,14 @@ impl GetKey for $map { match key_request { KeyRequest::Pubkey(pk) => Ok(self.get(&pk).cloned()), KeyRequest::XOnlyPubkey(xonly) => { - let pubkey_even = PublicKey::new(xonly.public_key(secp256k1::Parity::Even)); + let pubkey_even = xonly.public_key(secp256k1::Parity::Even); let key = self.get(&pubkey_even).cloned(); - + if key.is_some() { return Ok(key); } - - let pubkey_odd = PublicKey::new(xonly.public_key(secp256k1::Parity::Odd)); + + let pubkey_odd = xonly.public_key(secp256k1::Parity::Odd); if let Some(priv_key) = self.get(&pubkey_odd).copied() { let negated_priv_key = priv_key.negate(); return Ok(Some(negated_priv_key)); @@ -889,18 +935,18 @@ impl GetKey for $map { KeyRequest::XOnlyPubkey(xonly) => Ok(self.get(xonly).cloned()), KeyRequest::Pubkey(pk) => { let (xonly, parity) = pk.inner.x_only_public_key(); - + if let Some(mut priv_key) = self.get(&XOnlyPublicKey::from(xonly)).cloned() { let computed_pk = priv_key.public_key(&secp); let (_, computed_parity) = computed_pk.inner.x_only_public_key(); - + if computed_parity != parity { priv_key = priv_key.negate(); } - + return Ok(Some(priv_key)); } - + Ok(None) }, KeyRequest::Bip32(_) => Err(GetKeyError::NotSupported), @@ -915,8 +961,8 @@ impl_get_key_for_xonly_map!(HashMap); #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] pub enum GetKeyError { - /// A bip32 error. - Bip32(bip32::Error), + /// A bip32 derivation error. + Bip32(bip32::DerivationError), /// The GetKey operation is not supported for this key request. NotSupported, } @@ -930,7 +976,7 @@ impl fmt::Display for GetKeyError { use GetKeyError::*; match *self { - Bip32(ref e) => write_err!(f, "a bip23 error"; e), + Bip32(ref e) => write_err!(f, "bip32 derivation"; e), NotSupported => f.write_str("the GetKey operation is not supported for this key request"), } @@ -949,10 +995,6 @@ impl std::error::Error for GetKeyError { } } -impl From for GetKeyError { - fn from(e: bip32::Error) -> Self { GetKeyError::Bip32(e) } -} - /// The various output types supported by the Bitcoin network. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[non_exhaustive] @@ -1137,7 +1179,7 @@ impl fmt::Display for ExtractTxError { match *self { AbsurdFeeRate { fee_rate, .. } => - write!(f, "an absurdly high fee rate of {}", fee_rate), + write!(f, "an absurdly high fee rate of {} sat/kwu", fee_rate.to_sat_per_kwu()), MissingInputValue { .. } => write!( f, "one of the inputs lacked value information (witness_utxo or non_witness_utxo)" @@ -1285,7 +1327,8 @@ pub use self::display_from_str::PsbtParseError; #[cfg(test)] mod tests { use hashes::{hash160, ripemd160, sha256}; - use hex::{test_hex_unwrap as hex, FromHex}; + use hex::FromHex; + use hex_lit::hex; #[cfg(feature = "rand-std")] use { crate::address::script_pubkey::ScriptBufExt as _, @@ -1296,6 +1339,8 @@ mod tests { secp256k1::{All, SecretKey}, }; + use std::str::FromStr; + use super::*; use crate::address::script_pubkey::ScriptExt as _; use crate::bip32::ChildNumber; @@ -1307,6 +1352,11 @@ mod tests { use crate::witness::Witness; use crate::Sequence; + /// Fee rate in sat/kwu for a high-fee PSBT with an input=5_000_000_000_000, output=1000 + const ABSURD_FEE_RATE: FeeRate = FeeRate::from_sat_per_kwu(15_060_240_960_843); + /// Fee rate which is just below absurd threshold (1 sat/kwu less) + const JUST_BELOW_ABSURD_FEE_RATE: FeeRate = FeeRate::from_sat_per_kwu(15_060_240_960_842); + #[track_caller] pub fn hex_psbt(s: &str) -> Result { let r = Vec::from_hex(s); @@ -1335,7 +1385,7 @@ mod tests { }], output: vec![TxOut { value: Amount::from_sat(output).unwrap(), - script_pubkey: ScriptBuf::from_hex( + script_pubkey: ScriptBuf::from_hex_no_length_prefix( "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787", ) .unwrap(), @@ -1349,7 +1399,7 @@ mod tests { inputs: vec![Input { witness_utxo: Some(TxOut { value: Amount::from_sat(input).unwrap(), - script_pubkey: ScriptBuf::from_hex( + script_pubkey: ScriptBuf::from_hex_no_length_prefix( "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587", ) .unwrap(), @@ -1396,27 +1446,25 @@ mod tests { ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate, _ => panic!(""), }), - Err(FeeRate::from_sat_per_kwu(15060240960843)) + Err(ABSURD_FEE_RATE) ); assert_eq!( psbt.clone().extract_tx_fee_rate_limit().map_err(|e| match e { ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate, _ => panic!(""), }), - Err(FeeRate::from_sat_per_kwu(15060240960843)) + Err(ABSURD_FEE_RATE) ); assert_eq!( - psbt.clone() - .extract_tx_with_fee_rate_limit(FeeRate::from_sat_per_kwu(15060240960842)) - .map_err(|e| match e { + psbt.clone().extract_tx_with_fee_rate_limit(JUST_BELOW_ABSURD_FEE_RATE).map_err(|e| { + match e { ExtractTxError::AbsurdFeeRate { fee_rate, .. } => fee_rate, _ => panic!(""), - }), - Err(FeeRate::from_sat_per_kwu(15060240960843)) + } + }), + Err(ABSURD_FEE_RATE) ); - assert!(psbt - .extract_tx_with_fee_rate_limit(FeeRate::from_sat_per_kwu(15060240960843)) - .is_ok()); + assert!(psbt.extract_tx_with_fee_rate_limit(ABSURD_FEE_RATE).is_ok()); // Testing that extract_tx will error at 25k sat/vbyte (6250000 sat/kwu) assert_eq!( @@ -1439,7 +1487,7 @@ mod tests { let mut hd_keypaths: BTreeMap = Default::default(); - let mut sk: Xpriv = Xpriv::new_master(NetworkKind::Main, &seed).unwrap(); + let mut sk: Xpriv = Xpriv::new_master(NetworkKind::Main, &seed); let fprint = sk.fingerprint(secp); @@ -1454,7 +1502,7 @@ mod tests { ChildNumber::from_normal_idx(31337).unwrap(), ]; - sk = sk.derive_xpriv(secp, &dpath); + sk = sk.derive_xpriv(secp, &dpath).unwrap(); let pk = Xpub::from_xpriv(secp, &sk); @@ -1462,10 +1510,16 @@ mod tests { let expected: Output = Output { redeem_script: Some( - ScriptBuf::from_hex("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac").unwrap(), + ScriptBuf::from_hex_no_length_prefix( + "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac", + ) + .unwrap(), ), witness_script: Some( - ScriptBuf::from_hex("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(), + ScriptBuf::from_hex_no_length_prefix( + "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787", + ) + .unwrap(), ), bip32_derivation: hd_keypaths, ..Default::default() @@ -1496,14 +1550,14 @@ mod tests { output: vec![ TxOut { value: Amount::from_sat_u32(99_999_699), - script_pubkey: ScriptBuf::from_hex( + script_pubkey: ScriptBuf::from_hex_no_length_prefix( "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac", ) .unwrap(), }, TxOut { value: Amount::from_sat_u32(100_000_000), - script_pubkey: ScriptBuf::from_hex( + script_pubkey: ScriptBuf::from_hex_no_length_prefix( "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787", ) .unwrap(), @@ -1560,8 +1614,10 @@ mod tests { .unwrap(), vout: 1, }, - script_sig: ScriptBuf::from_hex("160014be18d152a9b012039daf3da7de4f53349eecb985") - .unwrap(), + script_sig: ScriptBuf::from_hex_no_length_prefix( + "160014be18d152a9b012039daf3da7de4f53349eecb985", + ) + .unwrap(), sequence: Sequence::MAX, witness: Witness::from_slice(&[hex!( "03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105" @@ -1569,14 +1625,14 @@ mod tests { }], output: vec![TxOut { value: Amount::from_sat(190_303_501_938).unwrap(), - script_pubkey: ScriptBuf::from_hex( + script_pubkey: ScriptBuf::from_hex_no_length_prefix( "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587", ) .unwrap(), }], }; let unknown: BTreeMap> = - vec![(raw::Key { type_value: 1, key_data: vec![0, 1] }, vec![3, 4, 5])] + vec![(raw::Key { type_value: 42, key_data: vec![0, 1] }, vec![3, 4, 5])] .into_iter() .collect(); let key_source = ("deadbeef".parse().unwrap(), "0'/1".parse().unwrap()); @@ -1620,7 +1676,7 @@ mod tests { non_witness_utxo: Some(tx), witness_utxo: Some(TxOut { value: Amount::from_sat(190_303_501_938).unwrap(), - script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(), + script_pubkey: ScriptBuf::from_hex_no_length_prefix("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(), }), sighash_type: Some("SIGHASH_SINGLE|SIGHASH_ANYONECANPAY".parse::().unwrap()), redeem_script: Some(vec![0x51].into()), @@ -1631,10 +1687,10 @@ mod tests { )].into_iter().collect(), bip32_derivation: keypaths.clone(), final_script_witness: Some(Witness::from_slice(&[vec![1, 3], vec![5]])), - ripemd160_preimages: vec![(ripemd160::Hash::hash(&[]), vec![1, 2])].into_iter().collect(), - sha256_preimages: vec![(sha256::Hash::hash(&[]), vec![1, 2])].into_iter().collect(), - hash160_preimages: vec![(hash160::Hash::hash(&[]), vec![1, 2])].into_iter().collect(), - hash256_preimages: vec![(sha256d::Hash::hash(&[]), vec![1, 2])].into_iter().collect(), + ripemd160_preimages: vec![(ripemd160::Hash::hash(&[1, 2]), vec![1, 2])].into_iter().collect(), + sha256_preimages: vec![(sha256::Hash::hash(&[1, 2]), vec![1, 2])].into_iter().collect(), + hash160_preimages: vec![(hash160::Hash::hash(&[1, 2]), vec![1, 2])].into_iter().collect(), + hash256_preimages: vec![(sha256d::Hash::hash(&[1, 2]), vec![1, 2])].into_iter().collect(), proprietary: proprietary.clone(), unknown: unknown.clone(), ..Default::default() @@ -1745,11 +1801,11 @@ mod tests { output: vec![ TxOut { value: Amount::from_sat_u32(99_999_699), - script_pubkey: ScriptBuf::from_hex("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac").unwrap(), + script_pubkey: ScriptBuf::from_hex_no_length_prefix("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac").unwrap(), }, TxOut { value: Amount::from_sat_u32(100_000_000), - script_pubkey: ScriptBuf::from_hex("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(), + script_pubkey: ScriptBuf::from_hex_no_length_prefix("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(), }, ], }, @@ -1769,11 +1825,11 @@ mod tests { txid: "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389".parse().unwrap(), vout: 1, }, - script_sig: ScriptBuf::from_hex("160014be18d152a9b012039daf3da7de4f53349eecb985").unwrap(), + script_sig: ScriptBuf::from_hex_no_length_prefix("160014be18d152a9b012039daf3da7de4f53349eecb985").unwrap(), sequence: Sequence::MAX, witness: Witness::from_slice(&[ - hex!("304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01"), - hex!("03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105"), + hex!("304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01").as_slice(), + hex!("03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105").as_slice(), ]), }, TxIn { @@ -1781,22 +1837,22 @@ mod tests { txid: "b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886".parse().unwrap(), vout: 1, }, - script_sig: ScriptBuf::from_hex("160014fe3e9ef1a745e974d902c4355943abcb34bd5353").unwrap(), + script_sig: ScriptBuf::from_hex_no_length_prefix("160014fe3e9ef1a745e974d902c4355943abcb34bd5353").unwrap(), sequence: Sequence::MAX, witness: Witness::from_slice(&[ - hex!("3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01"), - hex!("0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3"), + hex!("3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01").as_slice(), + hex!("0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3").as_slice(), ]), } ], output: vec![ TxOut { value: Amount::from_sat_u32(200_000_000), - script_pubkey: ScriptBuf::from_hex("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac").unwrap(), + script_pubkey: ScriptBuf::from_hex_no_length_prefix("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac").unwrap(), }, TxOut { value: Amount::from_sat(190_303_501_938).unwrap(), - script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(), + script_pubkey: ScriptBuf::from_hex_no_length_prefix("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(), }, ], }), @@ -1837,8 +1893,10 @@ mod tests { assert!(&psbt.inputs[0].final_script_sig.is_some()); let redeem_script = psbt.inputs[1].redeem_script.as_ref().unwrap(); - let expected_out = - ScriptBuf::from_hex("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(); + let expected_out = ScriptBuf::from_hex_no_length_prefix( + "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787", + ) + .unwrap(); assert!(redeem_script.is_p2wpkh()); assert_eq!( @@ -1883,8 +1941,10 @@ mod tests { assert!(&psbt.inputs[1].final_script_sig.is_none()); let redeem_script = psbt.inputs[1].redeem_script.as_ref().unwrap(); - let expected_out = - ScriptBuf::from_hex("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(); + let expected_out = ScriptBuf::from_hex_no_length_prefix( + "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787", + ) + .unwrap(); assert!(redeem_script.is_p2wpkh()); assert_eq!( @@ -1908,8 +1968,10 @@ mod tests { assert!(&psbt.inputs[0].final_script_sig.is_none()); let redeem_script = psbt.inputs[0].redeem_script.as_ref().unwrap(); - let expected_out = - ScriptBuf::from_hex("a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87").unwrap(); + let expected_out = ScriptBuf::from_hex_no_length_prefix( + "a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87", + ) + .unwrap(); assert!(redeem_script.is_p2wsh()); assert_eq!( @@ -1935,8 +1997,8 @@ mod tests { let mut unknown: BTreeMap> = BTreeMap::new(); let key: raw::Key = - raw::Key { type_value: 0x0fu64, key_data: hex!("010203040506070809") }; - let value: Vec = hex!("0102030405060708090a0b0c0d0e0f"); + raw::Key { type_value: 0x0fu64, key_data: hex!("010203040506070809").to_vec() }; + let value = hex!("0102030405060708090a0b0c0d0e0f").to_vec(); unknown.insert(key, value); @@ -2078,11 +2140,11 @@ mod tests { output: vec![ TxOut { value: Amount::from_sat_u32(99_999_699), - script_pubkey: ScriptBuf::from_hex("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac").unwrap(), + script_pubkey: ScriptBuf::from_hex_no_length_prefix("76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac").unwrap(), }, TxOut { value: Amount::from_sat_u32(100_000_000), - script_pubkey: ScriptBuf::from_hex("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(), + script_pubkey: ScriptBuf::from_hex_no_length_prefix("a9143545e6e33b832c47050f24d3eeb93c9c03948bc787").unwrap(), }, ], }, @@ -2102,11 +2164,11 @@ mod tests { txid: "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389".parse().unwrap(), vout: 1, }, - script_sig: ScriptBuf::from_hex("160014be18d152a9b012039daf3da7de4f53349eecb985").unwrap(), + script_sig: ScriptBuf::from_hex_no_length_prefix("160014be18d152a9b012039daf3da7de4f53349eecb985").unwrap(), sequence: Sequence::MAX, witness: Witness::from_slice(&[ - hex!("304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01"), - hex!("03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105"), + hex!("304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01").as_slice(), + hex!("03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105").as_slice(), ]), }, TxIn { @@ -2114,22 +2176,22 @@ mod tests { txid: "b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886".parse().unwrap(), vout: 1, }, - script_sig: ScriptBuf::from_hex("160014fe3e9ef1a745e974d902c4355943abcb34bd5353").unwrap(), + script_sig: ScriptBuf::from_hex_no_length_prefix("160014fe3e9ef1a745e974d902c4355943abcb34bd5353").unwrap(), sequence: Sequence::MAX, witness: Witness::from_slice(&[ - hex!("3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01"), - hex!("0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3"), + hex!("3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01").as_slice(), + hex!("0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3").as_slice(), ]), } ], output: vec![ TxOut { value: Amount::from_sat_u32(200_000_000), - script_pubkey: ScriptBuf::from_hex("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac").unwrap(), + script_pubkey: ScriptBuf::from_hex_no_length_prefix("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac").unwrap(), }, TxOut { value: Amount::from_sat(190_303_501_938).unwrap(), - script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(), + script_pubkey: ScriptBuf::from_hex_no_length_prefix("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(), }, ], }), @@ -2173,6 +2235,49 @@ mod tests { assert!(!rtt.proprietary.is_empty()); } + // Deserialize MuSig2 PSBT participant keys according to BIP-373 + #[test] + fn serialize_and_deserialize_musig2_participants() { + // XXX: Does not cover PSBT_IN_MUSIG2_PUB_NONCE, PSBT_IN_MUSIG2_PARTIAL_SIG (yet) + + let expected_in_agg_pk = secp256k1::PublicKey::from_str("021401301810a46a4e3f39e4603ec228ed301d9f2079767fda758dee7224b32e00").unwrap(); + let expected_in_pubkeys = vec![ + secp256k1::PublicKey::from_str("02bebd7a1cef20283444b96e9ce78137e951ce48705390933896311a9abc75736a").unwrap(), + secp256k1::PublicKey::from_str("0355212dff7b3d7e8126687a62fd0435a3fb4de56d9af9ae23a1c9ca05b349c8e2").unwrap(), + ]; + + let expected_out_agg_pk = secp256k1::PublicKey::from_str("0364934a64831bd917a2667b886671650846f021e1c025e4b2bb65e49ab3e7cba5").unwrap(); + + let expected_out_pubkeys = vec![ + secp256k1::PublicKey::from_str("02841d69a8b80ae23a8090e6f3765540ea5efd8c287b1307c983a6e2a3a171b525").unwrap(), + secp256k1::PublicKey::from_str("02bad833849a98cdfb0a0749609ddccab16ad54485ecc67f828df4bdc4f2b90d4c").unwrap(), + ]; + + const PSBT_HEX: &str = "70736274ff01005e02000000017b42be5ea467afe0d0571dc4a91bef97ff9605a590c0b8d5892323946414d1810000000000ffffffff01f0b9f50500000000225120bc7e18f55e2c7a28d78cadac1bc72c248372375d269bafe6b315bc40505d07e5000000000001012b00e1f50500000000225120de564ebf8ff7bd9bb41bd88264c04b1713ebb9dc8df36319091d2eabb16cda6221161401301810a46a4e3f39e4603ec228ed301d9f2079767fda758dee7224b32e000500eb4cbe62211655212dff7b3d7e8126687a62fd0435a3fb4de56d9af9ae23a1c9ca05b349c8e20500755abbf92116bebd7a1cef20283444b96e9ce78137e951ce48705390933896311a9abc75736a05002a33dfd90117201401301810a46a4e3f39e4603ec228ed301d9f2079767fda758dee7224b32e00221a021401301810a46a4e3f39e4603ec228ed301d9f2079767fda758dee7224b32e004202bebd7a1cef20283444b96e9ce78137e951ce48705390933896311a9abc75736a0355212dff7b3d7e8126687a62fd0435a3fb4de56d9af9ae23a1c9ca05b349c8e20001052064934a64831bd917a2667b886671650846f021e1c025e4b2bb65e49ab3e7cba5210764934a64831bd917a2667b886671650846f021e1c025e4b2bb65e49ab3e7cba50500fa4c6afa22080364934a64831bd917a2667b886671650846f021e1c025e4b2bb65e49ab3e7cba54202841d69a8b80ae23a8090e6f3765540ea5efd8c287b1307c983a6e2a3a171b52502bad833849a98cdfb0a0749609ddccab16ad54485ecc67f828df4bdc4f2b90d4c00"; + + let psbt = hex_psbt(PSBT_HEX).unwrap(); + + assert_eq!(psbt.inputs[0].musig2_participant_pubkeys.len(), 1); + assert_eq!( + psbt.inputs[0].musig2_participant_pubkeys.iter().next().unwrap(), + (&expected_in_agg_pk, &expected_in_pubkeys) + ); + + assert_eq!(psbt.outputs[0].musig2_participant_pubkeys.len(), 1); + assert_eq!( + psbt.outputs[0].musig2_participant_pubkeys.iter().next().unwrap(), + (&expected_out_agg_pk, &expected_out_pubkeys) + ); + + // Check round trip de/serialization + assert_eq!(psbt.serialize_hex(), PSBT_HEX); + + const PSBT_TRUNCATED_MUSIG_PARTICIPANTS_HEX: &str = "70736274ff01005e0200000001f034711ce319b1db76ce73440f2cb64a7e3a02e75c936b8d8a4958a024ea8d870000000000ffffffff01f0b9f50500000000225120bc7e18f55e2c7a28d78cadac1bc72c248372375d269bafe6b315bc40505d07e5000000000001012b00e1f50500000000225120de564ebf8ff7bd9bb41bd88264c04b1713ebb9dc8df36319091d2eabb16cda6221161401301810a46a4e3f39e4603ec228ed301d9f2079767fda758dee7224b32e000500eb4cbe62211655212dff7b3d7e8126687a62fd0435a3fb4de56d9af9ae23a1c9ca05b349c8e20500755abbf92116bebd7a1cef20283444b96e9ce78137e951ce48705390933896311a9abc75736a05002a33dfd90117201401301810a46a4e3f39e4603ec228ed301d9f2079767fda758dee7224b32e00221a021401301810a46a4e3f39e4603ec228ed301d9f2079767fda758dee7224b32e002a02bebd7a1cef20283444b96e9ce78137e951ce48705390933896311a9abc75736a0355212dff7b3d7e810001052064934a64831bd917a2667b886671650846f021e1c025e4b2bb65e49ab3e7cba5210764934a64831bd917a2667b886671650846f021e1c025e4b2bb65e49ab3e7cba50500fa4c6afa22080364934a64831bd917a2667b886671650846f021e1c025e4b2bb65e49ab3e7cba52a02841d69a8b80ae23a8090e6f3765540ea5efd8c287b1307c983a6e2a3a171b52502bad833849a98cdfb00"; + + hex_psbt(PSBT_TRUNCATED_MUSIG_PARTICIPANTS_HEX) + .expect_err("Deserializing PSBT with truncated musig participants should error"); + } + // PSBTs taken from BIP 174 test vectors. #[test] fn combine_psbts() { @@ -2256,7 +2361,7 @@ mod tests { pubkey_map.insert(pk, priv_key); - let req_result = pubkey_map.get_key(&KeyRequest::XOnlyPubkey(xonly), &secp).unwrap(); + let req_result = pubkey_map.get_key(&KeyRequest::XOnlyPubkey(xonly.into()), &secp).unwrap(); let retrieved_key = req_result.unwrap(); diff --git a/bitcoin/src/psbt/raw.rs b/bitcoin/src/psbt/raw.rs index 4502bde61f..205d076b57 100644 --- a/bitcoin/src/psbt/raw.rs +++ b/bitcoin/src/psbt/raw.rs @@ -21,25 +21,21 @@ use crate::psbt::Error; /// /// ` := ` #[derive(Debug, PartialEq, Hash, Eq, Clone, Ord, PartialOrd)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Key { /// The type of this PSBT key. pub type_value: u64, // Encoded as a compact size. /// The key data itself in raw byte form. - #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::hex_bytes"))] pub key_data: Vec, } /// A PSBT key-value pair in its raw byte form. /// ` := ` #[derive(Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Pair { /// The key of this key-value pair. pub key: Key, /// The value data of this key-value pair in raw byte form. /// ` := ` - #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::hex_bytes"))] pub value: Vec, } @@ -49,19 +45,16 @@ pub type ProprietaryType = u64; /// Proprietary keys (i.e. keys starting with 0xFC byte) with their internal /// structure according to BIP 174. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct ProprietaryKey where Subtype: Copy + From + Into, { /// Proprietary type prefix used for grouping together keys under some /// application and avoid namespace collision - #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::hex_bytes"))] pub prefix: Vec, /// Custom proprietary subtype pub subtype: Subtype, /// Additional key bytes (like serialized public key data etc) - #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::hex_bytes"))] pub key: Vec, } diff --git a/bitcoin/src/psbt/serialize.rs b/bitcoin/src/psbt/serialize.rs index 703bcb79d5..270f8a75e5 100644 --- a/bitcoin/src/psbt/serialize.rs +++ b/bitcoin/src/psbt/serialize.rs @@ -9,12 +9,11 @@ use hashes::{hash160, ripemd160, sha256, sha256d}; use internals::compact_size; #[allow(unused)] // MSRV polyfill use internals::slice::SliceExt; -use secp256k1::XOnlyPublicKey; use super::map::{Input, Map, Output, PsbtSighashType}; use crate::bip32::{ChildNumber, Fingerprint, KeySource}; use crate::consensus::encode::{self, deserialize_partial, serialize, Decodable, Encodable}; -use crate::crypto::key::PublicKey; +use crate::crypto::key::{PublicKey, XOnlyPublicKey}; use crate::crypto::{ecdsa, taproot}; use crate::io::Write; use crate::prelude::{DisplayHex, String, Vec}; @@ -173,6 +172,28 @@ impl Deserialize for secp256k1::PublicKey { } } +impl Serialize for Vec { + fn serialize(&self) -> Vec { + let mut result: Vec = Vec::with_capacity(secp256k1::constants::PUBLIC_KEY_SIZE * self.len()); + + for pubkey in self.iter() { + result.extend(Serialize::serialize(pubkey)); + } + + result + } +} + +impl Deserialize for Vec { + fn deserialize(bytes: &[u8]) -> Result { + bytes.chunks(secp256k1::constants::PUBLIC_KEY_SIZE) + .map(|pubkey_bytes| { + secp256k1::PublicKey::deserialize(pubkey_bytes) + }) + .collect() + } +} + impl Serialize for ecdsa::Signature { fn serialize(&self) -> Vec { self.to_vec() } } @@ -216,8 +237,8 @@ impl Serialize for KeySource { impl Deserialize for KeySource { fn deserialize(bytes: &[u8]) -> Result { - let (fingerprint, mut d) = bytes.split_first_chunk::<4>() - .ok_or(io::Error::from(io::ErrorKind::UnexpectedEof))?; + let (fingerprint, mut d) = + bytes.split_first_chunk::<4>().ok_or(io::Error::from(io::ErrorKind::UnexpectedEof))?; let fprint: Fingerprint = fingerprint.into(); let mut dpath: Vec = Default::default(); @@ -370,7 +391,7 @@ impl Serialize for TapTree { // safe to cast from usize to u8 buf.push(leaf_info.merkle_branch().len() as u8); buf.push(leaf_info.version().to_consensus()); - leaf_info.script().consensus_encode(&mut buf).expect("Vecs dont err"); + leaf_info.script().consensus_encode(&mut buf).expect("Vecs don't err"); } buf } @@ -414,7 +435,7 @@ mod tests { let mut val = opcode; let mut builder = TaprootBuilder::new(); for depth in depth_map { - let script = ScriptBuf::from_hex(&format!("{:02x}", val)).unwrap(); + let script = ScriptBuf::from_hex_no_length_prefix(&format!("{:02x}", val)).unwrap(); builder = builder.add_leaf(*depth, script).unwrap(); let (new_val, _) = val.overflowing_add(1); val = new_val; @@ -429,7 +450,7 @@ mod tests { builder = builder .add_leaf_with_ver( 3, - ScriptBuf::from_hex("b9").unwrap(), + ScriptBuf::from_hex_no_length_prefix("b9").unwrap(), LeafVersion::from_consensus(0xC2).unwrap(), ) .unwrap(); @@ -443,7 +464,7 @@ mod tests { builder = builder .add_leaf_with_ver( 3, - ScriptBuf::from_hex("b9").unwrap(), + ScriptBuf::from_hex_no_length_prefix("b9").unwrap(), LeafVersion::from_consensus(0xC2).unwrap(), ) .unwrap(); diff --git a/bitcoin/src/serde_utils.rs b/bitcoin/src/serde_utils.rs deleted file mode 100644 index df306d9df5..0000000000 --- a/bitcoin/src/serde_utils.rs +++ /dev/null @@ -1,298 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! Bitcoin serde utilities. -//! -//! This module is for special serde serializations. - -pub(crate) struct SerializeBytesAsHex<'a>(pub(crate) &'a [u8]); - -impl serde::Serialize for SerializeBytesAsHex<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - use hex::DisplayHex; - - serializer.collect_str(&format_args!("{:x}", self.0.as_hex())) - } -} - -pub mod btreemap_byte_values { - //! Module for serialization of BTreeMaps with hex byte values. - #![allow(missing_docs)] - - // NOTE: This module can be exactly copied to use with HashMap. - - use hex::FromHex; - - use crate::prelude::{BTreeMap, Vec}; - - pub fn serialize(v: &BTreeMap>, s: S) -> Result - where - S: serde::Serializer, - T: serde::Serialize + core::hash::Hash + Eq + Ord, - { - use serde::ser::SerializeMap; - - // Don't do anything special when not human readable. - if !s.is_human_readable() { - serde::Serialize::serialize(v, s) - } else { - let mut map = s.serialize_map(Some(v.len()))?; - for (key, value) in v.iter() { - map.serialize_entry(key, &super::SerializeBytesAsHex(value))?; - } - map.end() - } - } - - pub fn deserialize<'de, D, T>(d: D) -> Result>, D::Error> - where - D: serde::Deserializer<'de>, - T: serde::Deserialize<'de> + core::hash::Hash + Eq + Ord, - { - use core::marker::PhantomData; - - struct Visitor(PhantomData); - impl<'de, T> serde::de::Visitor<'de> for Visitor - where - T: serde::Deserialize<'de> + core::hash::Hash + Eq + Ord, - { - type Value = BTreeMap>; - - fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - write!(f, "a map with hexadecimal values") - } - - fn visit_map>( - self, - mut a: A, - ) -> Result { - let mut ret = BTreeMap::new(); - while let Some((key, value)) = a.next_entry()? { - ret.insert(key, FromHex::from_hex(value).map_err(serde::de::Error::custom)?); - } - Ok(ret) - } - } - - // Don't do anything special when not human readable. - if !d.is_human_readable() { - serde::Deserialize::deserialize(d) - } else { - d.deserialize_map(Visitor(PhantomData)) - } - } -} - -pub mod btreemap_as_seq { - //! Module for serialization of BTreeMaps as lists of sequences because - //! serde_json will not serialize hashmaps with non-string keys be default. - #![allow(missing_docs)] - - // NOTE: This module can be exactly copied to use with HashMap. - - use crate::prelude::BTreeMap; - - pub fn serialize(v: &BTreeMap, s: S) -> Result - where - S: serde::Serializer, - T: serde::Serialize + core::hash::Hash + Eq + Ord, - U: serde::Serialize, - { - use serde::ser::SerializeSeq; - - // Don't do anything special when not human readable. - if !s.is_human_readable() { - serde::Serialize::serialize(v, s) - } else { - let mut seq = s.serialize_seq(Some(v.len()))?; - for pair in v.iter() { - seq.serialize_element(&pair)?; - } - seq.end() - } - } - - pub fn deserialize<'de, D, T, U>(d: D) -> Result, D::Error> - where - D: serde::Deserializer<'de>, - T: serde::Deserialize<'de> + core::hash::Hash + Eq + Ord, - U: serde::Deserialize<'de>, - { - use core::marker::PhantomData; - - struct Visitor(PhantomData<(T, U)>); - impl<'de, T, U> serde::de::Visitor<'de> for Visitor - where - T: serde::Deserialize<'de> + core::hash::Hash + Eq + Ord, - U: serde::Deserialize<'de>, - { - type Value = BTreeMap; - - fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - write!(f, "a sequence of pairs") - } - - fn visit_seq>( - self, - mut a: A, - ) -> Result { - let mut ret = BTreeMap::new(); - while let Some((key, value)) = a.next_element()? { - ret.insert(key, value); - } - Ok(ret) - } - } - - // Don't do anything special when not human readable. - if !d.is_human_readable() { - serde::Deserialize::deserialize(d) - } else { - d.deserialize_seq(Visitor(PhantomData)) - } - } -} - -pub mod btreemap_as_seq_byte_values { - //! Module for serialization of BTreeMaps as lists of sequences because - //! serde_json will not serialize hashmaps with non-string keys be default. - #![allow(missing_docs)] - - // NOTE: This module can be exactly copied to use with HashMap. - - use crate::prelude::{BTreeMap, Vec}; - - /// A custom key-value pair type that serialized the bytes as hex. - #[derive(Debug, Deserialize)] - struct OwnedPair( - T, - #[serde(deserialize_with = "crate::serde_utils::hex_bytes::deserialize")] Vec, - ); - - /// A custom key-value pair type that serialized the bytes as hex. - #[derive(Debug, Serialize)] - struct BorrowedPair<'a, T: 'static>( - &'a T, - #[serde(serialize_with = "crate::serde_utils::hex_bytes::serialize")] &'a [u8], - ); - - pub fn serialize(v: &BTreeMap>, s: S) -> Result - where - S: serde::Serializer, - T: serde::Serialize + core::hash::Hash + Eq + Ord + 'static, - { - use serde::ser::SerializeSeq; - - // Don't do anything special when not human readable. - if !s.is_human_readable() { - serde::Serialize::serialize(v, s) - } else { - let mut seq = s.serialize_seq(Some(v.len()))?; - for (key, value) in v.iter() { - seq.serialize_element(&BorrowedPair(key, value))?; - } - seq.end() - } - } - - pub fn deserialize<'de, D, T>(d: D) -> Result>, D::Error> - where - D: serde::Deserializer<'de>, - T: serde::Deserialize<'de> + core::hash::Hash + Eq + Ord, - { - use core::marker::PhantomData; - - struct Visitor(PhantomData); - impl<'de, T> serde::de::Visitor<'de> for Visitor - where - T: serde::Deserialize<'de> + core::hash::Hash + Eq + Ord, - { - type Value = BTreeMap>; - - fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - write!(f, "a sequence of pairs") - } - - fn visit_seq>( - self, - mut a: A, - ) -> Result { - let mut ret = BTreeMap::new(); - while let Option::Some(OwnedPair(key, value)) = a.next_element()? { - ret.insert(key, value); - } - Ok(ret) - } - } - - // Don't do anything special when not human readable. - if !d.is_human_readable() { - serde::Deserialize::deserialize(d) - } else { - d.deserialize_seq(Visitor(PhantomData)) - } - } -} - -pub mod hex_bytes { - //! Module for serialization of byte arrays as hex strings. - #![allow(missing_docs)] - - use hex::FromHex; - - pub fn serialize(bytes: &T, s: S) -> Result - where - T: serde::Serialize + AsRef<[u8]>, - S: serde::Serializer, - { - // Don't do anything special when not human readable. - if !s.is_human_readable() { - serde::Serialize::serialize(bytes, s) - } else { - serde::Serialize::serialize(&super::SerializeBytesAsHex(bytes.as_ref()), s) - } - } - - pub fn deserialize<'de, D, B>(d: D) -> Result - where - D: serde::Deserializer<'de>, - B: serde::Deserialize<'de> + FromHex, - { - struct Visitor(core::marker::PhantomData); - - impl serde::de::Visitor<'_> for Visitor { - type Value = B; - - fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { - formatter.write_str("an ASCII hex string") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: serde::de::Error, - { - if let Ok(hex) = core::str::from_utf8(v) { - FromHex::from_hex(hex).map_err(E::custom) - } else { - Err(E::invalid_value(serde::de::Unexpected::Bytes(v), &self)) - } - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - FromHex::from_hex(v).map_err(E::custom) - } - } - - // Don't do anything special when not human readable. - if !d.is_human_readable() { - serde::Deserialize::deserialize(d) - } else { - d.deserialize_str(Visitor(core::marker::PhantomData)) - } - } -} diff --git a/bitcoin/src/taproot/merkle_branch/borrowed.rs b/bitcoin/src/taproot/merkle_branch/borrowed.rs index 58538f09d2..85a839db60 100644 --- a/bitcoin/src/taproot/merkle_branch/borrowed.rs +++ b/bitcoin/src/taproot/merkle_branch/borrowed.rs @@ -1,18 +1,27 @@ use core::borrow::{Borrow, BorrowMut}; -use internals::slice::SliceExt; - -use super::{DecodeError, InvalidMerkleBranchSizeError, InvalidMerkleTreeDepthError, TaprootMerkleBranchBuf, TapNodeHash, TAPROOT_CONTROL_MAX_NODE_COUNT, TAPROOT_CONTROL_NODE_SIZE}; +use internals::slice::SliceExt; pub use privacy_boundary::TaprootMerkleBranch; +use super::{ + DecodeError, InvalidMerkleBranchSizeError, InvalidMerkleTreeDepthError, TapNodeHash, + TaprootMerkleBranchBuf, TAPROOT_CONTROL_MAX_NODE_COUNT, TAPROOT_CONTROL_NODE_SIZE, +}; + /// Makes sure only the allowed conversions are accessible to external code. mod privacy_boundary { use super::*; - /// The Merkle proof for inclusion of a tree in a Taproot tree hash. - #[repr(transparent)] - #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub struct TaprootMerkleBranch([TapNodeHash]); + internals::transparent_newtype! { + /// The Merkle proof for inclusion of a tree in a Taproot tree hash. + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct TaprootMerkleBranch([TapNodeHash]); + + impl TaprootMerkleBranch { + pub(super) const fn from_hashes_unchecked(hashes: &_) -> &Self; + pub(super) fn from_mut_hashes_unchecked(hashes: &mut _) -> &mut Self; + } + } impl TaprootMerkleBranch { /// Returns a reference to the slice of hashes. @@ -22,26 +31,12 @@ mod privacy_boundary { /// Returns a reference to the mutable slice of hashes. #[inline] pub fn as_mut_slice(&mut self) -> &mut [TapNodeHash] { &mut self.0 } - - pub(super) const fn from_hashes_unchecked(hashes: &[TapNodeHash]) -> &Self { - unsafe { - &*(hashes as *const _ as *const Self) - } - } - - pub(super) fn from_mut_hashes_unchecked(hashes: &mut [TapNodeHash]) -> &mut Self { - unsafe { - &mut *(hashes as *mut _ as *mut Self) - } - } } } impl TaprootMerkleBranch { /// Returns an empty branch. - pub const fn new() -> &'static Self { - Self::from_hashes_unchecked(&[]) - } + pub const fn new() -> &'static Self { Self::from_hashes_unchecked(&[]) } /// Returns the number of nodes in this Merkle proof. #[inline] @@ -97,7 +92,9 @@ impl TaprootMerkleBranch { /// Decodes a byte slice that is statically known to be multiple of 32. /// /// This can be used as a building block for other ways of decoding. - fn decode_exact(nodes: &[[u8; TAPROOT_CONTROL_NODE_SIZE]]) -> Result<&Self, InvalidMerkleTreeDepthError> { + fn decode_exact( + nodes: &[[u8; TAPROOT_CONTROL_NODE_SIZE]], + ) -> Result<&Self, InvalidMerkleTreeDepthError> { // SAFETY: // The lifetime of the returned reference is the same as the lifetime of the input // reference, the size of `TapNodeHash` is equal to `TAPROOT_CONTROL_NODE_SIZE` and the @@ -105,7 +102,7 @@ impl TaprootMerkleBranch { Self::from_hashes(unsafe { &*(nodes as *const _ as *const [TapNodeHash]) }) } - fn from_hashes(nodes: &[TapNodeHash]) -> Result<&Self, InvalidMerkleTreeDepthError>{ + fn from_hashes(nodes: &[TapNodeHash]) -> Result<&Self, InvalidMerkleTreeDepthError> { if nodes.len() <= TAPROOT_CONTROL_MAX_NODE_COUNT { Ok(Self::from_hashes_unchecked(nodes)) } else { @@ -115,21 +112,15 @@ impl TaprootMerkleBranch { } impl Default for &'_ TaprootMerkleBranch { - fn default() -> Self { - TaprootMerkleBranch::new() - } + fn default() -> Self { TaprootMerkleBranch::new() } } impl AsRef for TaprootMerkleBranch { - fn as_ref(&self) -> &TaprootMerkleBranch { - self - } + fn as_ref(&self) -> &TaprootMerkleBranch { self } } impl AsMut for TaprootMerkleBranch { - fn as_mut(&mut self) -> &mut TaprootMerkleBranch { - self - } + fn as_mut(&mut self) -> &mut TaprootMerkleBranch { self } } impl AsRef for TaprootMerkleBranchBuf { @@ -254,18 +245,14 @@ impl alloc::borrow::ToOwned for TaprootMerkleBranch { // `Cow`. type Owned = TaprootMerkleBranchBuf; - fn to_owned(&self) -> Self::Owned { - self.into() - } + fn to_owned(&self) -> Self::Owned { self.into() } } impl<'a> IntoIterator for &'a TaprootMerkleBranch { type IntoIter = core::slice::Iter<'a, TapNodeHash>; type Item = &'a TapNodeHash; - fn into_iter(self) -> Self::IntoIter { - self.as_slice().iter() - } + fn into_iter(self) -> Self::IntoIter { self.as_slice().iter() } } impl<'a> IntoIterator for &'a mut TaprootMerkleBranch { @@ -280,7 +267,10 @@ impl<'a> IntoIterator for &'a mut TaprootMerkleBranch { mod tests { #[test] fn alignment() { - assert!(core::mem::align_of_val(super::TaprootMerkleBranch::new()) == core::mem::align_of::()); + assert!( + core::mem::align_of_val(super::TaprootMerkleBranch::new()) + == core::mem::align_of::() + ); } const _: () = { diff --git a/bitcoin/src/taproot/merkle_branch/buf.rs b/bitcoin/src/taproot/merkle_branch/buf.rs index a6e0e7526f..11b2850c45 100644 --- a/bitcoin/src/taproot/merkle_branch/buf.rs +++ b/bitcoin/src/taproot/merkle_branch/buf.rs @@ -86,7 +86,10 @@ impl TaprootMerkleBranchBuf { } /// Appends elements to proof. - pub(in super::super) fn push(&mut self, h: TapNodeHash) -> Result<(), InvalidMerkleTreeDepthError> { + pub(in super::super) fn push( + &mut self, + h: TapNodeHash, + ) -> Result<(), InvalidMerkleTreeDepthError> { if self.len() >= TAPROOT_CONTROL_MAX_NODE_COUNT { Err(InvalidMerkleTreeDepthError(self.0.len())) } else { @@ -213,9 +216,7 @@ impl BorrowMut<[TapNodeHash]> for TaprootMerkleBranchBuf { } impl<'a> From<&'a TaprootMerkleBranch> for TaprootMerkleBranchBuf { - fn from(value: &'a TaprootMerkleBranch) -> Self { - Self(value.as_slice().into()) - } + fn from(value: &'a TaprootMerkleBranch) -> Self { Self(value.as_slice().into()) } } /// Iterator over node hashes within Taproot Merkle branch. diff --git a/bitcoin/src/taproot/merkle_branch/mod.rs b/bitcoin/src/taproot/merkle_branch/mod.rs index 0fd32c2594..56a1423450 100644 --- a/bitcoin/src/taproot/merkle_branch/mod.rs +++ b/bitcoin/src/taproot/merkle_branch/mod.rs @@ -1,12 +1,13 @@ //! Contains `TaprootMerkleBranchBuf` and its associated types. -mod buf; mod borrowed; +mod buf; + +use core::fmt; -pub use buf::TaprootMerkleBranchBuf; pub use borrowed::TaprootMerkleBranch; +pub use buf::TaprootMerkleBranchBuf; -use core::fmt; use super::{ InvalidMerkleBranchSizeError, InvalidMerkleTreeDepthError, TapNodeHash, TaprootError, TAPROOT_CONTROL_MAX_NODE_COUNT, TAPROOT_CONTROL_NODE_SIZE, @@ -28,27 +29,30 @@ pub struct DecodeError { } impl From for DecodeError { - fn from(value: InvalidMerkleBranchSizeError) -> Self { - Self { - num_bytes: value.0, - } - } + fn from(value: InvalidMerkleBranchSizeError) -> Self { Self { num_bytes: value.0 } } } impl From for DecodeError { fn from(value: InvalidMerkleTreeDepthError) -> Self { - Self { - num_bytes: value.0 * TAPROOT_CONTROL_NODE_SIZE, - } + Self { num_bytes: value.0 * TAPROOT_CONTROL_NODE_SIZE } } } impl fmt::Display for DecodeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.num_bytes % TAPROOT_CONTROL_NODE_SIZE == 0 { - write!(f, "the Merkle branch has {} nodes which is more than the limit {}", self.num_bytes / TAPROOT_CONTROL_NODE_SIZE, TAPROOT_CONTROL_MAX_NODE_COUNT) + write!( + f, + "the Merkle branch has {} nodes which is more than the limit {}", + self.num_bytes / TAPROOT_CONTROL_NODE_SIZE, + TAPROOT_CONTROL_MAX_NODE_COUNT + ) } else { - write!(f, "the Merkle branch is {} bytes long which is not an integer multiple of {}", self.num_bytes, TAPROOT_CONTROL_NODE_SIZE) + write!( + f, + "the Merkle branch is {} bytes long which is not an integer multiple of {}", + self.num_bytes, TAPROOT_CONTROL_NODE_SIZE + ) } } } diff --git a/bitcoin/src/taproot/mod.rs b/bitcoin/src/taproot/mod.rs index b14264f249..723f61a87c 100644 --- a/bitcoin/src/taproot/mod.rs +++ b/bitcoin/src/taproot/mod.rs @@ -13,16 +13,19 @@ use core::fmt; use core::iter::FusedIterator; use hashes::{hash_newtype, sha256t, sha256t_tag, HashEngine}; +use hex::{FromHex, HexToBytesError}; use internals::array::ArrayExt; -use internals::{impl_to_hex_from_lower_hex, write_err}; #[allow(unused)] // MSRV polyfill use internals::slice::SliceExt; - +use internals::{impl_to_hex_from_lower_hex, write_err}; use io::Write; use secp256k1::{Scalar, Secp256k1}; use crate::consensus::Encodable; -use crate::crypto::key::{TapTweak, TweakedPublicKey, UntweakedPublicKey, XOnlyPublicKey}; +use crate::crypto::key::{ + SerializedXOnlyPublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey, +}; +use crate::key::ParseXOnlyPublicKeyError; use crate::prelude::{BTreeMap, BTreeSet, BinaryHeap, Vec}; use crate::{Script, ScriptBuf}; @@ -31,9 +34,12 @@ use crate::{Script, ScriptBuf}; #[doc(inline)] pub use crate::crypto::taproot::{SigFromSliceError, Signature}; #[doc(inline)] +pub use merkle_branch::TaprootMerkleBranch; +#[doc(inline)] pub use merkle_branch::TaprootMerkleBranchBuf; + #[doc(inline)] -pub use merkle_branch::TaprootMerkleBranch; +pub use crate::XOnlyPublicKey; type ControlBlockArrayVec = internals::array_vec::ArrayVec; @@ -89,12 +95,13 @@ impl From for TapNodeHash { } impl TapTweakHash { - /// Constructs a new BIP341 [`TapTweakHash`] from key and tweak. Produces `H_taptweak(P||R)` where + /// Constructs a new BIP341 [`TapTweakHash`] from key and Merkle root. Produces `H_taptweak(P||R)` where /// `P` is the internal key and `R` is the Merkle root. - pub fn from_key_and_tweak( - internal_key: UntweakedPublicKey, + pub fn from_key_and_merkle_root>( + internal_key: K, merkle_root: Option, ) -> TapTweakHash { + let internal_key = internal_key.into(); let mut eng = sha256t::Hash::::engine(); // always hash the key eng.input(&internal_key.serialize()); @@ -242,14 +249,15 @@ impl TaprootSpendInfo { /// weights of satisfaction for that script. /// /// See [`TaprootBuilder::with_huffman_tree`] for more detailed documentation. - pub fn with_huffman_tree( + pub fn with_huffman_tree( secp: &Secp256k1, - internal_key: UntweakedPublicKey, + internal_key: K, script_weights: I, ) -> Result where I: IntoIterator, C: secp256k1::Verification, + K: Into, { let builder = TaprootBuilder::with_huffman_tree(script_weights)?; Ok(builder.finalize(secp, internal_key).expect("Huffman tree is always complete")) @@ -266,11 +274,12 @@ impl TaprootSpendInfo { /// /// Refer to BIP 341 footnote ('Why should the output key always have a Taproot commitment, even /// if there is no script path?') for more details. - pub fn new_key_spend( + pub fn new_key_spend>( secp: &Secp256k1, - internal_key: UntweakedPublicKey, + internal_key: K, merkle_root: Option, ) -> Self { + let internal_key = internal_key.into(); let (output_key, parity) = internal_key.tap_tweak(secp, merkle_root); Self { internal_key, @@ -284,7 +293,7 @@ impl TaprootSpendInfo { /// Returns the `TapTweakHash` for this [`TaprootSpendInfo`] i.e., the tweak using `internal_key` /// and `merkle_root`. pub fn tap_tweak(&self) -> TapTweakHash { - TapTweakHash::from_key_and_tweak(self.internal_key, self.merkle_root) + TapTweakHash::from_key_and_merkle_root(self.internal_key, self.merkle_root) } /// Returns the internal key for this [`TaprootSpendInfo`]. @@ -306,9 +315,9 @@ impl TaprootSpendInfo { /// /// This is useful when you want to manually build a Taproot tree without using /// [`TaprootBuilder`]. - pub fn from_node_info( + pub fn from_node_info>( secp: &Secp256k1, - internal_key: UntweakedPublicKey, + internal_key: K, node: NodeInfo, ) -> TaprootSpendInfo { // Create as if it is a key spend path with the given Merkle root @@ -542,7 +551,22 @@ impl TaprootBuilder { /// Converts the builder into a [`TapTree`] if the builder is a full tree and /// does not contain any hidden nodes + #[deprecated(since = "TBD", note = "use `try_into_tap_tree()` instead")] + #[doc(hidden)] pub fn try_into_taptree(self) -> Result { + self.try_into_tap_tree() + } + + /// Converts the builder into a [`TapTree`] if the builder is a full tree and + /// does not contain any hidden nodes. + /// + /// This function finalizes the taproot construction process by validating that the builder + /// is complete, and there are no hidden nodes, which would make the tree incomplete or ambiguous. + /// + /// # Errors + /// + /// Returns [`IncompleteBuilderError::HiddenParts`] if the builder contains any hidden nodes. + pub fn try_into_tap_tree(self) -> Result { let node = self.try_into_node_info()?; if node.has_hidden_nodes { // Reconstruct the builder as it was if it has hidden nodes @@ -562,11 +586,12 @@ impl TaprootBuilder { /// /// Returns the unmodified builder as Err if the builder is not finalizable. /// See also [`TaprootBuilder::is_finalizable`] - pub fn finalize( + pub fn finalize>( mut self, secp: &Secp256k1, - internal_key: UntweakedPublicKey, + internal_key: K, ) -> Result { + let internal_key = internal_key.into(); match self.branch.len() { 0 => Ok(TaprootSpendInfo::new_key_spend(secp, internal_key, None)), 1 => @@ -769,7 +794,9 @@ impl TryFrom for TapTree { /// /// A [`TapTree`] iff the `builder` is complete, otherwise return [`IncompleteBuilderError`] /// error with the content of incomplete `builder` instance. - fn try_from(builder: TaprootBuilder) -> Result { builder.try_into_taptree() } + fn try_from(builder: TaprootBuilder) -> Result { + builder.try_into_tap_tree() + } } impl TryFrom for TapTree { @@ -1141,13 +1168,16 @@ impl<'leaf> ScriptLeaf<'leaf> { /// Control block data structure used in Tapscript satisfaction. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct ControlBlock where Branch: ?Sized { +pub struct ControlBlock +where + Branch: ?Sized, +{ /// The tapleaf version. pub leaf_version: LeafVersion, /// The parity of the output key (NOT THE INTERNAL KEY WHICH IS ALWAYS XONLY). pub output_key_parity: secp256k1::Parity, /// The internal key. - pub internal_key: UntweakedPublicKey, + pub internal_key: Key, /// The Merkle proof of a script associated with this leaf. pub merkle_branch: Branch, } @@ -1166,7 +1196,32 @@ impl ControlBlock { /// - [`TaprootError::InvalidInternalKey`] if internal key is invalid (first 32 bytes after the parity byte). /// - [`TaprootError::InvalidMerkleTreeDepth`] if Merkle tree is too deep (more than 128 levels). pub fn decode(sl: &[u8]) -> Result { - let (base, merkle_branch) = sl.split_first_chunk::() + use alloc::borrow::ToOwned; + + let ControlBlock { leaf_version, output_key_parity, internal_key, merkle_branch } = + ControlBlock::<&TaprootMerkleBranch, &SerializedXOnlyPublicKey>::decode_borrowed(sl)?; + + let internal_key = internal_key.to_validated().map_err(TaprootError::InvalidInternalKey)?; + let merkle_branch = merkle_branch.to_owned(); + + Ok(ControlBlock { leaf_version, output_key_parity, internal_key, merkle_branch }) + } + + /// Constructs a new [`ControlBlock`] from a hex string. + pub fn from_hex(hex: &str) -> Result { + let vec = Vec::from_hex(hex).map_err(TaprootError::InvalidControlBlockHex)?; + ControlBlock::decode(vec.as_slice()) + } +} + +impl ControlBlock { + pub(crate) fn decode_borrowed<'a>(sl: &'a [u8]) -> Result + where + B: From<&'a TaprootMerkleBranch>, + K: From<&'a SerializedXOnlyPublicKey>, + { + let (base, merkle_branch) = sl + .split_first_chunk::() .ok_or(InvalidControlBlockSizeError(sl.len()))?; let (&first, internal_key) = base.split_first(); @@ -1177,9 +1232,8 @@ impl ControlBlock { }; let leaf_version = LeafVersion::from_consensus(first & TAPROOT_LEAF_MASK)?; - let internal_key = UntweakedPublicKey::from_byte_array(internal_key) - .map_err(TaprootError::InvalidInternalKey)?; - let merkle_branch = TaprootMerkleBranchBuf::decode(merkle_branch)?; + let internal_key = SerializedXOnlyPublicKey::from_bytes_ref(internal_key).into(); + let merkle_branch = TaprootMerkleBranch::decode(merkle_branch)?.into(); Ok(ControlBlock { leaf_version, output_key_parity, internal_key, merkle_branch }) } } @@ -1206,7 +1260,8 @@ impl + ?Sized> ControlBlock { self.encode_inner(|bytes| -> Result<(), core::convert::Infallible> { result.extend_from_slice(bytes); Ok(()) - }).unwrap_or_else(|never| match never {}); + }) + .unwrap_or_else(|never| match never {}); result } @@ -1250,7 +1305,7 @@ impl + ?Sized> ControlBlock { } // compute the taptweak let tweak = - TapTweakHash::from_key_and_tweak(self.internal_key, Some(curr_hash)).to_scalar(); + TapTweakHash::from_key_and_merkle_root(self.internal_key, Some(curr_hash)).to_scalar(); self.internal_key.tweak_add_check(secp, &output_key, self.output_key_parity, tweak) } } @@ -1268,7 +1323,7 @@ impl FutureLeafVersion { ) -> Result { match version { TAPROOT_LEAF_TAPSCRIPT => unreachable!( - "FutureLeafVersion::from_consensus should be never called for 0xC0 value" + "FutureLeafVersion::from_consensus should never be called for 0xC0 value" ), TAPROOT_ANNEX_PREFIX => Err(InvalidTaprootLeafVersionError(TAPROOT_ANNEX_PREFIX)), odd if odd & 0xFE != odd => Err(InvalidTaprootLeafVersionError(odd)), @@ -1462,6 +1517,7 @@ impl From for TaprootBuilderError { /// Detailed error type for Taproot utilities. #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] +#[allow(clippy::enum_variant_names)] pub enum TaprootError { /// Proof size must be a multiple of 32. InvalidMerkleBranchSize(InvalidMerkleBranchSizeError), @@ -1472,9 +1528,9 @@ pub enum TaprootError { /// Invalid control block size. InvalidControlBlockSize(InvalidControlBlockSizeError), /// Invalid Taproot internal key. - InvalidInternalKey(secp256k1::Error), - /// Empty Taproot tree. - EmptyTree, + InvalidInternalKey(ParseXOnlyPublicKeyError), + /// Invalid control block hex + InvalidControlBlockHex(HexToBytesError), } impl From for TaprootError { @@ -1490,8 +1546,8 @@ impl fmt::Display for TaprootError { InvalidMerkleTreeDepth(ref e) => write_err!(f, "invalid Merkle tree depth"; e), InvalidTaprootLeafVersion(ref e) => write_err!(f, "invalid Taproot leaf version"; e), InvalidControlBlockSize(ref e) => write_err!(f, "invalid control block size"; e), + InvalidControlBlockHex(ref e) => write_err!(f, "invalid control block hex"; e), InvalidInternalKey(ref e) => write_err!(f, "invalid internal x-only key"; e), - EmptyTree => write!(f, "Taproot tree must contain at least one script"), } } } @@ -1505,7 +1561,8 @@ impl std::error::Error for TaprootError { InvalidInternalKey(e) => Some(e), InvalidTaprootLeafVersion(ref e) => Some(e), InvalidMerkleTreeDepth(ref e) => Some(e), - InvalidMerkleBranchSize(_) | InvalidControlBlockSize(_) | EmptyTree => None, + InvalidControlBlockHex(ref e) => Some(e), + InvalidMerkleBranchSize(_) | InvalidControlBlockSize(_) => None, } } } @@ -1629,7 +1686,7 @@ impl std::error::Error for InvalidControlBlockSizeError {} #[cfg(test)] mod test { use hashes::sha256; - use hex::{DisplayHex, FromHex}; + use hex::DisplayHex; use secp256k1::VerifyOnly; use super::*; @@ -1729,11 +1786,14 @@ mod test { ) { let out_pk = out_spk_hex[4..].parse::().unwrap(); let out_pk = TweakedPublicKey::dangerous_assume_tweaked(out_pk); - let script = ScriptBuf::from_hex(script_hex).unwrap(); - let control_block = - ControlBlock::decode(&Vec::::from_hex(control_block_hex).unwrap()).unwrap(); + let script = ScriptBuf::from_hex_no_length_prefix(script_hex).unwrap(); + let control_block = ControlBlock::from_hex(control_block_hex).unwrap(); assert_eq!(control_block_hex, control_block.serialize().to_lower_hex_string()); - assert!(control_block.verify_taproot_commitment(secp, out_pk.to_inner(), &script)); + assert!(control_block.verify_taproot_commitment( + secp, + out_pk.to_x_only_public_key(), + &script + )); } #[test] @@ -1796,11 +1856,11 @@ mod test { .unwrap(); let script_weights = [ - (10, ScriptBuf::from_hex("51").unwrap()), // semantics of script don't matter for this test - (20, ScriptBuf::from_hex("52").unwrap()), - (20, ScriptBuf::from_hex("53").unwrap()), - (30, ScriptBuf::from_hex("54").unwrap()), - (19, ScriptBuf::from_hex("55").unwrap()), + (10, ScriptBuf::from_hex_no_length_prefix("51").unwrap()), // semantics of script don't matter for this test + (20, ScriptBuf::from_hex_no_length_prefix("52").unwrap()), + (20, ScriptBuf::from_hex_no_length_prefix("53").unwrap()), + (30, ScriptBuf::from_hex_no_length_prefix("54").unwrap()), + (19, ScriptBuf::from_hex_no_length_prefix("55").unwrap()), ]; let tree_info = TaprootSpendInfo::with_huffman_tree(&secp, internal_key, script_weights.clone()) @@ -1821,7 +1881,10 @@ mod test { *length, tree_info .script_map - .get(&(ScriptBuf::from_hex(script).unwrap(), LeafVersion::TapScript)) + .get(&( + ScriptBuf::from_hex_no_length_prefix(script).unwrap(), + LeafVersion::TapScript + )) .expect("Present Key") .iter() .next() @@ -1839,7 +1902,7 @@ mod test { let ctrl_block = tree_info.control_block(&ver_script).unwrap(); assert!(ctrl_block.verify_taproot_commitment( &secp, - output_key.to_inner(), + output_key.to_x_only_public_key(), &ver_script.0 )) } @@ -1862,11 +1925,11 @@ mod test { // / \ / \ // A B C / \ // D E - let a = ScriptBuf::from_hex("51").unwrap(); - let b = ScriptBuf::from_hex("52").unwrap(); - let c = ScriptBuf::from_hex("53").unwrap(); - let d = ScriptBuf::from_hex("54").unwrap(); - let e = ScriptBuf::from_hex("55").unwrap(); + let a = ScriptBuf::from_hex_no_length_prefix("51").unwrap(); + let b = ScriptBuf::from_hex_no_length_prefix("52").unwrap(); + let c = ScriptBuf::from_hex_no_length_prefix("53").unwrap(); + let d = ScriptBuf::from_hex_no_length_prefix("54").unwrap(); + let e = ScriptBuf::from_hex_no_length_prefix("55").unwrap(); let builder = builder.add_leaf(2, a.clone()).unwrap(); let builder = builder.add_leaf(2, b.clone()).unwrap(); let builder = builder.add_leaf(2, c.clone()).unwrap(); @@ -1913,7 +1976,7 @@ mod test { let ctrl_block = tree_info.control_block(&ver_script).unwrap(); assert!(ctrl_block.verify_taproot_commitment( &secp, - output_key.to_inner(), + output_key.to_x_only_public_key(), &ver_script.0 )) } @@ -1974,7 +2037,8 @@ mod test { builder = process_script_trees(leaf, builder, leaves, depth + 1); } } else { - let script = ScriptBuf::from_hex(v["script"].as_str().unwrap()).unwrap(); + let script = + ScriptBuf::from_hex_no_length_prefix(v["script"].as_str().unwrap()).unwrap(); let ver = LeafVersion::from_consensus(v["leafVersion"].as_u64().unwrap() as u8).unwrap(); leaves.push((script.clone(), ver)); @@ -2012,10 +2076,8 @@ mod test { let spend_info = builder.finalize(secp, internal_key).unwrap(); for (i, script_ver) in leaves.iter().enumerate() { let expected_leaf_hash = leaf_hashes[i].as_str().unwrap(); - let expected_ctrl_blk = ControlBlock::decode( - &Vec::::from_hex(ctrl_blks[i].as_str().unwrap()).unwrap(), - ) - .unwrap(); + let expected_ctrl_blk = + ControlBlock::from_hex(ctrl_blks[i].as_str().unwrap()).unwrap(); let leaf_hash = TapLeafHash::from_script(&script_ver.0, script_ver.1); let ctrl_blk = spend_info.control_block(script_ver).unwrap(); @@ -2030,8 +2092,10 @@ mod test { .unwrap(); let expected_tweak = arr["intermediary"]["tweak"].as_str().unwrap().parse::().unwrap(); - let expected_spk = - ScriptBuf::from_hex(arr["expected"]["scriptPubKey"].as_str().unwrap()).unwrap(); + let expected_spk = ScriptBuf::from_hex_no_length_prefix( + arr["expected"]["scriptPubKey"].as_str().unwrap(), + ) + .unwrap(); let expected_addr = arr["expected"]["bip350Address"] .as_str() .unwrap() @@ -2039,12 +2103,12 @@ mod test { .unwrap() .assume_checked(); - let tweak = TapTweakHash::from_key_and_tweak(internal_key, merkle_root); + let tweak = TapTweakHash::from_key_and_merkle_root(internal_key, merkle_root); let (output_key, _parity) = internal_key.tap_tweak(secp, merkle_root); let addr = Address::p2tr(secp, internal_key, merkle_root, KnownHrp::Mainnet); let spk = addr.script_pubkey(); - assert_eq!(expected_output_key, output_key.to_inner()); + assert_eq!(expected_output_key, output_key.to_x_only_public_key()); assert_eq!(expected_tweak, tweak); assert_eq!(expected_addr, addr); assert_eq!(expected_spk, spk); diff --git a/bitcoin/src/taproot/serialized_signature.rs b/bitcoin/src/taproot/serialized_signature.rs index be388e2c3c..a63af0fe21 100644 --- a/bitcoin/src/taproot/serialized_signature.rs +++ b/bitcoin/src/taproot/serialized_signature.rs @@ -230,7 +230,7 @@ mod into_iter { self.pos = self.signature.len(); None } else { - // if n < signtature.len() - self.pos then n + self.pos < signature.len() which neither + // if n < signature.len() - self.pos then n + self.pos < signature.len() which neither // overflows nor breaks the invariant self.pos += n; self.next() diff --git a/bitcoin/tests/bip_174.rs b/bitcoin/tests/bip_174.rs index 396fc6f3a8..10fb81acf9 100644 --- a/bitcoin/tests/bip_174.rs +++ b/bitcoin/tests/bip_174.rs @@ -22,7 +22,9 @@ fn hex_psbt(s: &str) -> Psbt { } #[track_caller] -fn hex_script(s: &str) -> ScriptBuf { ScriptBuf::from_hex(s).expect("valid hex digits") } +fn hex_script(s: &str) -> ScriptBuf { + ScriptBuf::from_hex_no_length_prefix(s).expect("valid hex digits") +} #[test] fn bip174_psbt_workflow() { @@ -120,7 +122,7 @@ fn build_extended_private_key() -> Xpriv { let xpriv = extended_private_key.parse::().unwrap(); let sk = PrivateKey::from_wif(seed).unwrap(); - let seeded = Xpriv::new_master(NetworkKind::Test, &sk.inner.secret_bytes()).unwrap(); + let seeded = Xpriv::new_master(NetworkKind::Test, &sk.inner.secret_bytes()); assert_eq!(xpriv, seeded); xpriv @@ -181,13 +183,13 @@ fn create_transaction() -> Transaction { TxOut { value: Amount::from_str_in(output_0.amount, Denomination::Bitcoin) .expect("failed to parse amount"), - script_pubkey: ScriptBuf::from_hex(output_0.script_pubkey) + script_pubkey: ScriptBuf::from_hex_no_length_prefix(output_0.script_pubkey) .expect("failed to parse script"), }, TxOut { value: Amount::from_str_in(output_1.amount, Denomination::Bitcoin) .expect("failed to parse amount"), - script_pubkey: ScriptBuf::from_hex(output_1.script_pubkey) + script_pubkey: ScriptBuf::from_hex_no_length_prefix(output_1.script_pubkey) .expect("failed to parse script"), }, ], @@ -263,15 +265,15 @@ fn update_psbt(mut psbt: Psbt, fingerprint: Fingerprint) -> Psbt { psbt } -/// `pk_path` holds tuples of `(public_key, derivation_path)`. `indecies` is used to access the +/// `pk_path` holds tuples of `(public_key, derivation_path)`. `indices` is used to access the /// `pk_path` vector. `fingerprint` is from the parent extended public key. fn bip32_derivation( fingerprint: Fingerprint, pk_path: &[(&str, &str)], - indecies: Vec, + indices: Vec, ) -> BTreeMap { let mut tree = BTreeMap::new(); - for i in indecies { + for i in indices { let pk = pk_path[i].0; let path = pk_path[i].1; @@ -315,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_xpriv(secp, &path).to_private_key(); + let derived_priv = + ext_priv.derive_xpriv(secp, &path).expect("derivation path too long").to_private_key(); assert_eq!(wif_priv, derived_priv); let derived_pub = derived_priv.public_key(secp); key_map.insert(derived_pub, derived_priv); diff --git a/bitcoin/tests/data/serde/proprietary_key_bincode b/bitcoin/tests/data/serde/proprietary_key_bincode deleted file mode 100644 index 8c0100713b..0000000000 Binary files a/bitcoin/tests/data/serde/proprietary_key_bincode and /dev/null differ diff --git a/bitcoin/tests/data/serde/psbt_base64.json b/bitcoin/tests/data/serde/psbt_base64.json new file mode 100644 index 0000000000..6bb318f103 --- /dev/null +++ b/bitcoin/tests/data/serde/psbt_base64.json @@ -0,0 +1 @@ +"cHNidP8BAFMBAAAAAYmjxx6rTSDgNxu7pMxpj6KVyUY6+i45f4UzzLYvlWflAQAAAAD/////AXL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAAAAAE8BBIiyHgAAAAAAAAAAAIc9/4HAL1JWI/0f5RZ+rDpVoEnePTFLtC7iJ//tN9UIAzmjYBMwFZfa70H75ZOgLMUT0LVVJ+wt8QUOLo/0nIXCDN6tvu8AAACAAQAAABD8BXByZWZ4KnRlc3Rfa2V5AwUGBwMJAAEDAwQFAAEAjwEAAAAAAQGJo8ceq00g4Dcbu6TMaY+ilclGOvouOX+FM8y2L5Vn5QEAAAAXFgAUvhjRUqmwEgOdrz2n3k9TNJ7suYX/////AXL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUAAAAAAQEgcv74TiwAAAAXqRQzlyW6Ie/WKsdTqbzQZ9bHpqOdBYciAgM5iA3JI5S3NV49BDn6KDwx3nWQgS6gEcQkXAZ0poXog0cwRAIgT2fir7dhQtRPrliiSV0zo0GdqibNDbjQTzRStjKJrA8CIBB2Kp+2fpTMXK2QJvbcmf9/Bw9CeNMPvH0Mhp3TjH/nAQEDBIMAAAABBAFRIgYDOYgNySOUtzVePQQ5+ig8Md51kIEuoBHEJFwGdKaF6IMM3q2+7wAAAIABAAAAAQgGAgIBAwEFFQoYn3yLGjhv/o7tkbODDHp7zR53jAIBAiELoShx/uIQ+4YZKR6uoZRYHL0lMeSyN1nSJfaAaSP2MiICAQIVDBXMSeGRy8Ug2RlEYApct3r2qjKRAgECIQ12pWrO2RXSUT3NhMLDeLLoqlzWMrW3HKLyrFsOOmSb2wIBAhD8BXByZWZ4KnRlc3Rfa2V5AwUGBwMJAAEDAwQFACICAzmIDckjlLc1Xj0EOfooPDHedZCBLqARxCRcBnSmheiDDN6tvu8AAACAAQAAABD8BXByZWZ4KnRlc3Rfa2V5AwUGBwMJAAEDAwQFAA==" \ No newline at end of file diff --git a/bitcoin/tests/data/serde/psbt_bincode b/bitcoin/tests/data/serde/psbt_bincode index 2c352b99c3..35d580f4b0 100644 Binary files a/bitcoin/tests/data/serde/psbt_bincode and b/bitcoin/tests/data/serde/psbt_bincode differ diff --git a/bitcoin/tests/data/serde/raw_pair_bincode b/bitcoin/tests/data/serde/raw_pair_bincode deleted file mode 100644 index bf1d3218ac..0000000000 Binary files a/bitcoin/tests/data/serde/raw_pair_bincode and /dev/null differ diff --git a/bitcoin/tests/psbt-sign-taproot.rs b/bitcoin/tests/psbt-sign-taproot.rs index 0f8cf5933c..e692cb1b5b 100644 --- a/bitcoin/tests/psbt-sign-taproot.rs +++ b/bitcoin/tests/psbt-sign-taproot.rs @@ -11,9 +11,9 @@ use bitcoin::taproot::{LeafVersion, TaprootBuilder, TaprootSpendInfo}; use bitcoin::transaction::Version; use bitcoin::{ absolute, script, Address, Network, OutPoint, PrivateKey, Psbt, ScriptBuf, Sequence, - Transaction, TxIn, TxOut, Witness, + Transaction, TxIn, TxOut, Witness, XOnlyPublicKey, }; -use secp256k1::{Keypair, Secp256k1, Signing, XOnlyPublicKey}; +use secp256k1::{Keypair, Secp256k1, Signing}; use units::Amount; #[test] @@ -83,11 +83,8 @@ fn psbt_sign_taproot() { // // Step 1: create psbt for key path spend. // - let mut psbt_key_path_spend = create_psbt_for_taproot_key_path_spend( - address.clone(), - to_address.clone(), - tree.clone(), - ); + let mut psbt_key_path_spend = + create_psbt_for_taproot_key_path_spend(address, to_address, tree.clone()); // // Step 2: sign psbt. @@ -131,8 +128,8 @@ fn psbt_sign_taproot() { // Step 1: create psbt for script path spend. // let mut psbt_script_path_spend = create_psbt_for_taproot_script_path_spend( - address.clone(), - to_address.clone(), + address, + to_address, tree.clone(), x_only_pubkey, signing_key_path, @@ -149,7 +146,7 @@ fn psbt_sign_taproot() { sig, psbt_script_path_spend.inputs[0] .tap_script_sigs - .get(&(x_only_pubkey, script2.clone().tapscript_leaf_hash())) + .get(&(x_only_pubkey.into(), script2.clone().tapscript_leaf_hash())) .unwrap() .signature .to_string() @@ -179,13 +176,14 @@ fn create_basic_single_sig_script(secp: &Secp256k1, sk: &str) -> .into_script() } -fn create_taproot_tree( +fn create_taproot_tree>( secp: &Secp256k1, script1: ScriptBuf, script2: ScriptBuf, script3: ScriptBuf, - internal_key: XOnlyPublicKey, + internal_key: K, ) -> TaprootSpendInfo { + let internal_key = internal_key.into(); let builder = TaprootBuilder::new(); let builder = builder.add_leaf(2, script1).unwrap(); let builder = builder.add_leaf(2, script2).unwrap(); @@ -270,14 +268,15 @@ fn finalize_psbt_for_key_path_spend(mut psbt: Psbt) -> Psbt { psbt } -fn create_psbt_for_taproot_script_path_spend( +fn create_psbt_for_taproot_script_path_spend>( from_address: Address, to_address: Address, tree: TaprootSpendInfo, - x_only_pubkey_of_signing_key: XOnlyPublicKey, + x_only_pubkey_of_signing_key: K, signing_key_path: &str, use_script: ScriptBuf, ) -> Psbt { + let x_only_pubkey_of_signing_key = x_only_pubkey_of_signing_key.into(); let utxo_value = 6280; let send_value = 6000; let mfp = "73c5da0a"; diff --git a/bitcoin/tests/serde.rs b/bitcoin/tests/serde.rs index 25c89306c0..a0842e2b89 100644 --- a/bitcoin/tests/serde.rs +++ b/bitcoin/tests/serde.rs @@ -30,8 +30,7 @@ use bitcoin::consensus::encode::deserialize; use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d}; use bitcoin::hex::FromHex; use bitcoin::locktime::{absolute, relative}; -use bitcoin::psbt::raw::{self, Key, Pair, ProprietaryKey}; -use bitcoin::psbt::{Input, Output, Psbt, PsbtSighashType}; +use bitcoin::psbt::{raw, Input, Output, Psbt, PsbtSighashType}; use bitcoin::script::ScriptBufExt as _; use bitcoin::sighash::{EcdsaSighashType, TapSighashType}; use bitcoin::taproot::{self, ControlBlock, LeafVersion, TapTree, TaprootBuilder}; @@ -64,7 +63,7 @@ fn serde_regression_absolute_lock_time_height() { #[test] fn serde_regression_absolute_lock_time_time() { let seconds: u32 = 1653195600; // May 22nd, 5am UTC. - let t = absolute::LockTime::from_time(seconds).expect("valid time"); + let t = absolute::LockTime::from_mtp(seconds).expect("valid time"); let got = serialize(&t).unwrap(); let want = include_bytes!("data/serde/absolute_lock_time_seconds_bincode") as &[_]; @@ -226,8 +225,10 @@ fn serde_regression_psbt() { .unwrap(), vout: 1, }, - script_sig: ScriptBuf::from_hex("160014be18d152a9b012039daf3da7de4f53349eecb985") - .unwrap(), + script_sig: ScriptBuf::from_hex_no_length_prefix( + "160014be18d152a9b012039daf3da7de4f53349eecb985", + ) + .unwrap(), sequence: Sequence::from_consensus(4294967295), witness: Witness::from_slice(&[Vec::from_hex( "03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105", @@ -236,8 +237,10 @@ fn serde_regression_psbt() { }], output: vec![TxOut { value: Amount::from_sat(190_303_501_938).unwrap(), - script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587") - .unwrap(), + script_pubkey: ScriptBuf::from_hex_no_length_prefix( + "a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587", + ) + .unwrap(), }], }; let unknown: BTreeMap> = @@ -283,7 +286,7 @@ fn serde_regression_psbt() { non_witness_utxo: Some(tx), witness_utxo: Some(TxOut { value: Amount::from_sat(190_303_501_938).unwrap(), - script_pubkey: ScriptBuf::from_hex("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(), + script_pubkey: ScriptBuf::from_hex_no_length_prefix("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587").unwrap(), }), sighash_type: Some(PsbtSighashType::from("SIGHASH_SINGLE|SIGHASH_ANYONECANPAY".parse::().unwrap())), redeem_script: Some(vec![0x51].into()), @@ -316,30 +319,11 @@ fn serde_regression_psbt() { let got = serialize(&psbt).unwrap(); let want = include_bytes!("data/serde/psbt_bincode") as &[_]; - assert_eq!(got, want) -} - -#[test] -fn serde_regression_raw_pair() { - let pair = Pair { - key: Key { type_value: 1u64, key_data: vec![0u8, 1u8, 2u8, 3u8] }, - value: vec![0u8, 1u8, 2u8, 3u8], - }; - let got = serialize(&pair).unwrap(); - let want = include_bytes!("data/serde/raw_pair_bincode") as &[_]; - assert_eq!(got, want) -} + assert_eq!(got, want); -#[test] -fn serde_regression_proprietary_key() { - let key = ProprietaryKey { - prefix: vec![0u8, 1u8, 2u8, 3u8], - subtype: 1u64, - key: vec![0u8, 1u8, 2u8, 3u8], - }; - let got = serialize(&key).unwrap(); - let want = include_bytes!("data/serde/proprietary_key_bincode") as &[_]; - assert_eq!(got, want) + let got = serde_json::to_string(&psbt).unwrap(); + let want = include_str!("data/serde/psbt_base64.json"); + assert_eq!(got, want); } #[test] diff --git a/chacha20_poly1305/CHANGELOG.md b/chacha20_poly1305/CHANGELOG.md index 8757ffb7b1..7b7a6e9268 100644 --- a/chacha20_poly1305/CHANGELOG.md +++ b/chacha20_poly1305/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.1.2 - 2025-05-15 + +* Fixed a bug which was doubling the amount of work, performance should be improved [#4083](https://github.com/rust-bitcoin/rust-bitcoin/pull/4083). + # 0.1.1 - 2024-11-07 * The crate is now `no_std`. diff --git a/chacha20_poly1305/Cargo.toml b/chacha20_poly1305/Cargo.toml index da5242bf99..daf4391932 100644 --- a/chacha20_poly1305/Cargo.toml +++ b/chacha20_poly1305/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "chacha20-poly1305" -version = "0.1.1" +version = "0.1.2" +authors = ["Nick Johnson ", "Robert Netzke "] license = "CC0-1.0" repository = "https://github.com/rust-bitcoin/rust-bitcoin/" description = "The ChaCha20 stream cipher and Poly1305 MAC based AEAD." diff --git a/chacha20_poly1305/src/chacha20.rs b/chacha20_poly1305/src/chacha20.rs index 0f332fbaba..3902c7c0f6 100644 --- a/chacha20_poly1305/src/chacha20.rs +++ b/chacha20_poly1305/src/chacha20.rs @@ -31,6 +31,14 @@ impl Nonce { pub const fn new(nonce: [u8; 12]) -> Self { Nonce(nonce) } } +// Const validation trait for compile time check with max of 3. +trait UpTo3 {} + +impl UpTo3<0> for () {} +impl UpTo3<1> for () {} +impl UpTo3<2> for () {} +impl UpTo3<3> for () {} + /// A SIMD-friendly structure which holds 25% of the cipher state. /// /// The cipher's quarter round function is the bulk of its work @@ -52,12 +60,10 @@ impl Nonce { /// * For-each loops are easy for the compiler to recognize as vectorizable. /// * The type is a based on an array instead of tuple since the heterogeneous /// nature of tuples can confuse the compiler into thinking it is not vectorizable. -/// * Memory alignment lines up with SIMD size. /// /// In the future, a "blacklist" for the alignment option might be useful to /// disable it on architectures which definitely do not support SIMD in order to avoid /// needless memory inefficientcies. -#[repr(align(16))] #[derive(Clone, Copy, PartialEq)] struct U32x4([u32; 4]); @@ -81,21 +87,29 @@ impl U32x4 { } #[inline(always)] - fn rotate_elements_left(self) -> Self { - let mut result = [0u32; 4]; - (0..4).for_each(|i| { - result[i] = self.0[(i + N as usize) % 4]; - }); - U32x4(result) + fn rotate_elements_left(self) -> Self + where + (): UpTo3, + { + match N { + 1 => U32x4([self.0[1], self.0[2], self.0[3], self.0[0]]), + 2 => U32x4([self.0[2], self.0[3], self.0[0], self.0[1]]), + 3 => U32x4([self.0[3], self.0[0], self.0[1], self.0[2]]), + _ => self, // Rotate by 0 is a no-op. + } } #[inline(always)] - fn rotate_elements_right(self) -> Self { - let mut result = [0u32; 4]; - (0..4).for_each(|i| { - result[i] = self.0[(i + 4 - N as usize) % 4]; - }); - U32x4(result) + fn rotate_elements_right(self) -> Self + where + (): UpTo3, + { + match N { + 1 => U32x4([self.0[3], self.0[0], self.0[1], self.0[2]]), + 2 => U32x4([self.0[2], self.0[3], self.0[0], self.0[1]]), + 3 => U32x4([self.0[1], self.0[2], self.0[3], self.0[0]]), + _ => self, // Rotate by 0 is a no-op. + } } #[inline(always)] @@ -163,7 +177,7 @@ impl State { /// Four quarter rounds performed on the entire state of the cipher in a vectorized SIMD friendly fashion. #[inline(always)] - fn quarter_round(a: U32x4, b: U32x4, c: U32x4, d: U32x4) -> (U32x4, U32x4, U32x4, U32x4) { + fn quarter_round(a: U32x4, b: U32x4, c: U32x4, d: U32x4) -> [U32x4; 4] { let a = a.wrapping_add(b); let d = d.bitxor(a).rotate_left(16); @@ -176,7 +190,7 @@ impl State { let c = c.wrapping_add(d); let b = b.bitxor(c).rotate_left(7); - (a, b, c, d) + [a, b, c, d] } /// Perform a round on "columns" and then "diagonals" of the state. @@ -193,13 +207,13 @@ impl State { let [mut a, mut b, mut c, mut d] = state; // Column round. - (a, b, c, d) = Self::quarter_round(a, b, c, d); + [a, b, c, d] = Self::quarter_round(a, b, c, d); // Diagonal round (with rotations). b = b.rotate_elements_left::<1>(); c = c.rotate_elements_left::<2>(); d = d.rotate_elements_left::<3>(); - (a, b, c, d) = Self::quarter_round(a, b, c, d); + [a, b, c, d] = Self::quarter_round(a, b, c, d); // Rotate the words back into their normal positions. b = b.rotate_elements_right::<1>(); c = c.rotate_elements_right::<2>(); @@ -209,6 +223,7 @@ impl State { } /// Transform the state by performing the ChaCha block function. + #[inline(always)] fn chacha_block(&mut self) { let mut working_state = self.matrix; @@ -223,6 +238,7 @@ impl State { } /// Expose the 512-bit state as a byte stream. + #[inline(always)] fn keystream(&self) -> [u8; 64] { let mut keystream = [0u8; 64]; for i in 0..4 { @@ -260,39 +276,64 @@ impl ChaCha20 { ChaCha20 { key, nonce, block_count: block, seek_offset_bytes: 0 } } - /// Apply the keystream to a buffer. + /// Get the keystream for a specific block. + #[inline(always)] + fn keystream_at_block(&self, block: u32) -> [u8; 64] { + let mut state = State::new(self.key, self.nonce, block); + state.chacha_block(); + state.keystream() + } + + /// Apply the keystream to a buffer updating the cipher block state as necessary. pub fn apply_keystream(&mut self, buffer: &mut [u8]) { - let num_full_blocks = buffer.len() / CHACHA_BLOCKSIZE; - for block in 0..num_full_blocks { - let keystream = - keystream_at_slice(self.key, self.nonce, self.block_count, self.seek_offset_bytes); - for (buffer_byte, keystream_byte) in buffer - [block * CHACHA_BLOCKSIZE..(block + 1) * CHACHA_BLOCKSIZE] - .iter_mut() - .zip(keystream.iter()) + // If we have an initial offset, handle the first partial block to get back to alignment. + let remaining_buffer = if self.seek_offset_bytes != 0 { + let bytes_until_aligned = 64 - self.seek_offset_bytes; + let bytes_to_process = buffer.len().min(bytes_until_aligned); + + let keystream = self.keystream_at_block(self.block_count); + for (buffer_byte, keystream_byte) in + buffer[..bytes_to_process].iter_mut().zip(&keystream[self.seek_offset_bytes..]) { - *buffer_byte ^= *keystream_byte + *buffer_byte ^= *keystream_byte; + } + + if bytes_to_process < bytes_until_aligned { + self.seek_offset_bytes += bytes_to_process; + return; } + self.block_count += 1; - } - if buffer.len() % 64 > 0 { - let keystream = - keystream_at_slice(self.key, self.nonce, self.block_count, self.seek_offset_bytes); - for (buffer_byte, keystream_byte) in - buffer[num_full_blocks * CHACHA_BLOCKSIZE..].iter_mut().zip(keystream.iter()) - { - *buffer_byte ^= *keystream_byte + self.seek_offset_bytes = 0; + &mut buffer[bytes_to_process..] + } else { + buffer + }; + + // Process full blocks. + let mut chunks = remaining_buffer.chunks_exact_mut(CHACHA_BLOCKSIZE); + for chunk in &mut chunks { + let keystream = self.keystream_at_block(self.block_count); + for (buffer_byte, keystream_byte) in chunk.iter_mut().zip(keystream.iter()) { + *buffer_byte ^= *keystream_byte; } self.block_count += 1; } - } - /// Get the keystream block at a specified block. - pub fn get_keystream(&mut self, block: u32) -> [u8; 64] { - self.block(block); - keystream_at_slice(self.key, self.nonce, self.block_count, self.seek_offset_bytes) + // Handle any remaining bytes as partial block. + let remainder = chunks.into_remainder(); + if !remainder.is_empty() { + let keystream = self.keystream_at_block(self.block_count); + for (buffer_byte, keystream_byte) in remainder.iter_mut().zip(keystream.iter()) { + *buffer_byte ^= *keystream_byte; + } + self.seek_offset_bytes = remainder.len(); + } } + /// Get the keystream for specified block. + pub fn get_keystream(&self, block: u32) -> [u8; 64] { self.keystream_at_block(block) } + /// Update the index of the keystream to the given byte. pub fn seek(&mut self, seek: u32) { self.block_count = seek / 64; @@ -306,23 +347,6 @@ impl ChaCha20 { } } -fn keystream_at_slice(key: Key, nonce: Nonce, count: u32, seek: usize) -> [u8; 64] { - let mut keystream: [u8; 128] = [0; 128]; - let (first_half, second_half) = keystream.split_at_mut(64); - - let mut state = State::new(key, nonce, count); - state.chacha_block(); - first_half.copy_from_slice(&state.keystream()); - - let mut state = State::new(key, nonce, count + 1); - state.chacha_block(); - second_half.copy_from_slice(&state.keystream()); - - let seeked_keystream: [u8; 64] = - keystream[seek..seek + 64].try_into().expect("slicing produces 64-byte slice"); - seeked_keystream -} - #[cfg(test)] #[cfg(feature = "alloc")] mod tests { @@ -446,4 +470,35 @@ mod tests { let binding = *b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."; assert_eq!(binding, to); } + + #[test] + fn multiple_partial_applies() { + let key = + Key(Vec::from_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + .unwrap() + .try_into() + .unwrap()); + let nonce = Nonce(Vec::from_hex("000000000000004a00000000").unwrap().try_into().unwrap()); + + // Create two instances, one for a full single pass and one for chunked partial calls. + let mut chacha_full = ChaCha20::new(key, nonce, 0); + let mut chacha_chunked = ChaCha20::new(key, nonce, 0); + + // Test data that crosses block boundaries. + let mut full_buffer = [0u8; 100]; + let mut chunked_buffer = [0u8; 100]; + for (i, byte) in full_buffer.iter_mut().enumerate() { + *byte = i as u8; + } + chunked_buffer.copy_from_slice(&full_buffer); + + // Apply keystream to full buffer. + chacha_full.apply_keystream(&mut full_buffer); + // Apply keystream in multiple calls to chunked buffer. + chacha_chunked.apply_keystream(&mut chunked_buffer[..30]); // Partial block + chacha_chunked.apply_keystream(&mut chunked_buffer[30..82]); // Cross block boundary + chacha_chunked.apply_keystream(&mut chunked_buffer[82..]); // End with partial block + + assert_eq!(full_buffer, chunked_buffer); + } } diff --git a/chacha20_poly1305/src/lib.rs b/chacha20_poly1305/src/lib.rs index 0deea843df..045a5b374c 100644 --- a/chacha20_poly1305/src/lib.rs +++ b/chacha20_poly1305/src/lib.rs @@ -16,6 +16,7 @@ // Exclude lints we don't think are valuable. #![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134 #![allow(clippy::manual_range_contains)] // More readable than clippy's format. +#![allow(clippy::uninlined_format_args)] // Allow `format!("{}", x)`instead of enforcing `format!("{x}")` #[cfg(feature = "alloc")] extern crate alloc; @@ -75,7 +76,7 @@ impl ChaCha20Poly1305 { /// Encrypt content in place and return the Poly1305 16-byte authentication tag. /// - /// # Arguments + /// # Parameters /// /// - `content` - Plaintext to be encrypted in place. /// - `aad` - Optional metadata covered by the authentication tag. @@ -110,7 +111,7 @@ impl ChaCha20Poly1305 { /// Decrypt the ciphertext in place if authentication tag is correct. /// - /// # Arguments + /// # Parameters /// /// - `content` - Ciphertext to be decrypted in place. /// - `tag` - 16-byte authentication tag. @@ -121,7 +122,7 @@ impl ChaCha20Poly1305 { tag: [u8; 16], aad: Option<&[u8]>, ) -> Result<(), Error> { - let mut chacha = ChaCha20::new_from_block(self.key, self.nonce, 0); + let chacha = ChaCha20::new_from_block(self.key, self.nonce, 0); let keystream = chacha.get_keystream(0); let mut poly = Poly1305::new(keystream[..32].try_into().expect("slicing produces 32-byte slice")); diff --git a/chacha20_poly1305/src/poly1305.rs b/chacha20_poly1305/src/poly1305.rs index 4aef1343ca..9e47602e03 100644 --- a/chacha20_poly1305/src/poly1305.rs +++ b/chacha20_poly1305/src/poly1305.rs @@ -55,7 +55,7 @@ impl Poly1305 { // Process previous leftovers if the message is long enough to fill the leftovers buffer. If // the message is too short then it will just be added to the leftovers at the end. Now if there // are no leftovers, but the message can fill the buffer, it will process that buffer and - // and process the rest of the message later on. + // process the rest of the message later on. let fill = if self.leftovers_len + message.len() >= 16 { 16 - self.leftovers_len } else { 0 }; diff --git a/contrib/check-semver-feature.sh b/contrib/check-semver-feature.sh index 8527a8bc9f..46839d6775 100755 --- a/contrib/check-semver-feature.sh +++ b/contrib/check-semver-feature.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # # Checks semver compatibility between the `--no-features` and `all-features`. -# This is important since it tests for the presence non-additive cargo features. +# This is important since it tests for the presence of non-additive cargo features. # # Under the hood uses cargo semver-checks to check for breaking changes. # We cannot use it directly since it only supports checking against published @@ -40,7 +40,7 @@ main() { generate_json_files_all_features "bitcoin-io" generate_json_files_no_default_features "bitcoin-io" - # Check for API semver non-addivite cargo features on all the generated JSON files above. + # Check for API semver non-additive cargo features on all the generated JSON files above. run_cargo_semver_check "bitcoin" run_cargo_semver_check "base58ck" run_cargo_semver_check "bitcoin_hashes" diff --git a/contrib/gen_label_config.sh b/contrib/gen_label_config.sh index 11536bbf44..85ad9a93f0 100755 --- a/contrib/gen_label_config.sh +++ b/contrib/gen_label_config.sh @@ -10,7 +10,7 @@ config=.github/labeler.yml # Define a default value for SCAN_DIR if not set : "${SCAN_DIR:=.}" -if [ "${1:-default}" '!=' "--force" ] && ! git diff --exit-code "$config"; +if [ "${1:-default}" != "--force" ] && ! git diff --exit-code "$config"; then echo "Error: $config is not committed." echo "Refusing to overwrite it to prevent disaster." diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index f69af96f7a..38adb8b8c9 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -85,3 +85,7 @@ path = "fuzz_targets/hashes/sha512.rs" [[bin]] name = "units_deserialize_amount" path = "fuzz_targets/units/deserialize_amount.rs" + +[[bin]] +name = "bitcoin_p2p_address_roundtrip" +path = "fuzz_targets/bitcoin/p2p_address_roundtrip.rs" diff --git a/fuzz/fuzz_targets/bitcoin/p2p_address_roundtrip.rs b/fuzz/fuzz_targets/bitcoin/p2p_address_roundtrip.rs new file mode 100644 index 0000000000..4fa4ffb272 --- /dev/null +++ b/fuzz/fuzz_targets/bitcoin/p2p_address_roundtrip.rs @@ -0,0 +1,69 @@ +use std::convert::TryFrom; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + +use bitcoin::consensus::Decodable; +use bitcoin::p2p::address::AddrV2; +use honggfuzz::fuzz; + +fn do_test(data: &[u8]) { + if data.len() < 2 { + return; + } + + let mut cursor = std::io::Cursor::new(data); + let addr_v2 = if let Ok(addr) = AddrV2::consensus_decode(&mut cursor) { + addr + } else { + return; + }; + + if let Ok(ip_addr) = IpAddr::try_from(addr_v2.clone()) { + let round_trip: AddrV2 = AddrV2::from(ip_addr); + assert_eq!(addr_v2, round_trip, "AddrV2 -> IpAddr -> AddrV2 should round-trip correctly"); + } + + if let Ok(ip_addr) = Ipv4Addr::try_from(addr_v2.clone()) { + let round_trip: AddrV2 = AddrV2::from(ip_addr); + assert_eq!(addr_v2, round_trip, "AddrV2 -> Ipv4Addr -> AddrV2 should round-trip correctly"); + } + + if let Ok(ip_addr) = Ipv6Addr::try_from(addr_v2.clone()) { + let round_trip: AddrV2 = AddrV2::from(ip_addr); + assert_eq!(addr_v2, round_trip, "AddrV2 -> Ipv6Addr -> AddrV2 should round-trip correctly"); + } +} + +fn main() { + loop { + fuzz!(|data| { + do_test(data); + }); + } +} + +#[cfg(all(test, fuzzing))] +mod tests { + fn extend_vec_from_hex(hex: &str, out: &mut Vec) { + let mut b = 0; + for (idx, c) in hex.as_bytes().iter().enumerate() { + b <<= 4; + match *c { + b'A'..=b'F' => b |= c - b'A' + 10, + b'a'..=b'f' => b |= c - b'a' + 10, + b'0'..=b'9' => b |= c - b'0', + _ => panic!("Bad hex"), + } + if (idx & 1) == 1 { + out.push(b); + b = 0; + } + } + } + + #[test] + fn duplicate_crash() { + let mut a = Vec::new(); + extend_vec_from_hex("00", &mut a); + super::do_test(&a); + } +} diff --git a/githooks/post-merge b/githooks/post-merge index 0ca5182950..2d78934d11 100755 --- a/githooks/post-merge +++ b/githooks/post-merge @@ -22,7 +22,7 @@ do >&2 cat <<- EOF ================================================== Project githooks have changed. Please inspect the - changes and re-reun \`just githooks-install\` if + changes and re-run \`just githooks-install\` if they are legitimate. Remove $HOOKS_DIR/post-merge to skip this warning diff --git a/hashes/Cargo.toml b/hashes/Cargo.toml index 3e020d4b9a..99caab0b0b 100644 --- a/hashes/Cargo.toml +++ b/hashes/Cargo.toml @@ -22,13 +22,12 @@ serde = ["dep:serde", "hex"] small-hash = [] [dependencies] -internals = { package = "bitcoin-internals", version = "0.4.0" } +internals = { package = "bitcoin-internals", path = "../internals" } hex = { package = "hex-conservative", version = "0.3.0", default-features = false, optional = true } -serde = { version = "1.0", default-features = false, optional = true } +serde = { version = "1.0.103", default-features = false, optional = true } [dev-dependencies] serde_test = "1.0" -serde_json = "1.0" [package.metadata.docs.rs] all-features = true diff --git a/hashes/embedded/Cargo.toml b/hashes/embedded/Cargo.toml index dcf5a7ecbc..531c98b693 100644 --- a/hashes/embedded/Cargo.toml +++ b/hashes/embedded/Cargo.toml @@ -14,7 +14,6 @@ alloc = ["alloc-cortex-m", "bitcoin_hashes/alloc"] hex = ["bitcoin_hashes/hex"] [dependencies] -cortex-m = "0.6.0" cortex-m-rt = "0.6.10" cortex-m-semihosting = "0.3.3" panic-halt = "0.2.0" @@ -34,12 +33,3 @@ lto = true # better optimizations [patch.crates-io.bitcoin_hashes] path = "../../hashes" - -[patch.crates-io.bitcoin-internals] -path = "../../internals" - -[patch.crates-io.bitcoin-io] -path = "../../io" - -[patch.crates-io.bitcoin-units] -path = "../../units" diff --git a/hashes/src/cmp.rs b/hashes/src/cmp.rs index 3a72b389ff..99ccea994a 100644 --- a/hashes/src/cmp.rs +++ b/hashes/src/cmp.rs @@ -15,41 +15,50 @@ /// As of rust 1.31.0 disassembly looks completely within reason for this, see /// . pub fn fixed_time_eq(a: &[u8], b: &[u8]) -> bool { - assert!(a.len() == b.len()); - let count = a.len(); - let lhs = &a[..count]; - let rhs = &b[..count]; - - let mut r: u8 = 0; - for i in 0..count { - let mut rs = unsafe { core::ptr::read_volatile(&r) }; - rs |= lhs[i] ^ rhs[i]; - unsafe { - core::ptr::write_volatile(&mut r, rs); - } - } + #[cfg(hashes_fuzz)] { - let mut t = unsafe { core::ptr::read_volatile(&r) }; - t |= t >> 4; - unsafe { - core::ptr::write_volatile(&mut r, t); - } + // Fuzzers want to break memcmp calls into separate comparisons for coverage monitoring, + // so we avoid our fancy fixed-time comparison below for fuzzers. + a == b } + #[cfg(not(hashes_fuzz))] { - let mut t = unsafe { core::ptr::read_volatile(&r) }; - t |= t >> 2; - unsafe { - core::ptr::write_volatile(&mut r, t); + assert!(a.len() == b.len()); + let count = a.len(); + let lhs = &a[..count]; + let rhs = &b[..count]; + + let mut r: u8 = 0; + for i in 0..count { + let mut rs = unsafe { core::ptr::read_volatile(&r) }; + rs |= lhs[i] ^ rhs[i]; + unsafe { + core::ptr::write_volatile(&mut r, rs); + } } - } - { - let mut t = unsafe { core::ptr::read_volatile(&r) }; - t |= t >> 1; - unsafe { - core::ptr::write_volatile(&mut r, t); + { + let mut t = unsafe { core::ptr::read_volatile(&r) }; + t |= t >> 4; + unsafe { + core::ptr::write_volatile(&mut r, t); + } + } + { + let mut t = unsafe { core::ptr::read_volatile(&r) }; + t |= t >> 2; + unsafe { + core::ptr::write_volatile(&mut r, t); + } + } + { + let mut t = unsafe { core::ptr::read_volatile(&r) }; + t |= t >> 1; + unsafe { + core::ptr::write_volatile(&mut r, t); + } } + unsafe { (::core::ptr::read_volatile(&r) & 1) == 0 } } - unsafe { (::core::ptr::read_volatile(&r) & 1) == 0 } } #[cfg(test)] diff --git a/hashes/src/hash160/mod.rs b/hashes/src/hash160/mod.rs index 3c457c1aac..fbb11e1d03 100644 --- a/hashes/src/hash160/mod.rs +++ b/hashes/src/hash160/mod.rs @@ -15,13 +15,16 @@ crate::internal_macros::general_hash_type! { "Output of the Bitcoin HASH160 hash function. (RIPEMD160(SHA256))" } -fn from_engine(e: HashEngine) -> Hash { - let sha2 = sha256::Hash::from_engine(e.0); - let rmd = ripemd160::Hash::hash(sha2.as_byte_array()); - - let mut ret = [0; 20]; - ret.copy_from_slice(rmd.as_byte_array()); - Hash(ret) +impl Hash { + /// Finalize a hash engine to produce a hash. + pub fn from_engine(e: HashEngine) -> Self { + let sha2 = sha256::Hash::from_engine(e.0); + let rmd = ripemd160::Hash::hash(sha2.as_byte_array()); + + let mut ret = [0; 20]; + ret.copy_from_slice(rmd.as_byte_array()); + Hash(ret) + } } /// Engine to compute HASH160 hash function. diff --git a/hashes/src/hmac/mod.rs b/hashes/src/hmac/mod.rs index 2d70253fca..aebe2e60e3 100644 --- a/hashes/src/hmac/mod.rs +++ b/hashes/src/hmac/mod.rs @@ -15,8 +15,9 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::{Hash, HashEngine}; /// A hash computed from a RFC 2104 HMAC. Parameterized by the underlying hash function. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Copy, Clone, PartialOrd, Ord, Hash)] #[repr(transparent)] +#[allow(clippy::derived_hash_with_manual_eq)] pub struct Hmac(T); impl str::FromStr for Hmac { @@ -24,6 +25,12 @@ impl str::FromStr for Hmac { fn from_str(s: &str) -> Result { Ok(Hmac(str::FromStr::from_str(s)?)) } } +impl PartialEq for Hmac { + fn eq(&self, other: &Self) -> bool { crate::cmp::fixed_time_eq(self.as_ref(), other.as_ref()) } +} + +impl Eq for Hmac {} + /// Pair of underlying hash engines, used for the inner and outer hash of HMAC. #[derive(Debug, Clone)] pub struct HmacEngine { diff --git a/hashes/src/internal_macros.rs b/hashes/src/internal_macros.rs index 5e9599322a..f3d2cecfb9 100644 --- a/hashes/src/internal_macros.rs +++ b/hashes/src/internal_macros.rs @@ -4,8 +4,9 @@ /// Adds trait impls to the type called `Hash` in the current scope. /// -/// Implpements various conversion traits as well as the [`crate::Hash`] trait. -/// Arguments: +/// Implements various conversion traits as well as the [`crate::Hash`] trait. +/// +/// # Parameters /// /// * `$bits` - number of bits this hash type has /// * `$reverse` - `bool` - `true` if the hash type should be displayed backwards, `false` @@ -14,10 +15,7 @@ /// /// Restrictions on usage: /// -/// * There must be a free-standing `fn from_engine(HashEngine) -> Hash` in the scope -/// * `fn internal_new([u8; $bits / 8]) -> Self` must exist on `Hash` -/// -/// `from_engine` obviously implements the finalization algorithm. +/// * The hash type must implement the `GeneralHash` trait. macro_rules! hash_trait_impls { ($bits:expr, $reverse:expr $(, $gen:ident: $gent:ident)*) => { $crate::impl_bytelike_traits!(Hash, { $bits / 8 } $(, $gen: $gent)*); @@ -55,14 +53,15 @@ pub(crate) use hash_trait_impls; /// The created type has a single field and will have all standard derives as well as an /// implementation of [`crate::Hash`]. /// -/// Arguments: +/// # Parameters /// /// * `$bits` - the number of bits of the hash type /// * `$reverse` - `true` if the hash should be displayed backwards, `false` otherwise /// * `$doc` - doc string to put on the type /// -/// The `from_engine` free-standing function is still required with this macro. See the doc of -/// [`hash_trait_impls`]. +/// Restrictions on usage: +/// +/// * The hash type must implement the `GeneralHash` trait. macro_rules! general_hash_type { ($bits:expr, $reverse:expr, $doc:literal) => { /// Hashes some bytes. @@ -92,9 +91,6 @@ macro_rules! general_hash_type { $crate::internal_macros::hash_type_no_default!($bits, $reverse, $doc); impl Hash { - /// Produces a hash from the current state of a given engine. - pub fn from_engine(e: HashEngine) -> Hash { from_engine(e) } - /// Constructs a new engine. pub fn engine() -> HashEngine { Default::default() } @@ -117,32 +113,25 @@ pub(crate) use general_hash_type; macro_rules! hash_type_no_default { ($bits:expr, $reverse:expr, $doc:literal) => { - #[doc = $doc] - #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] - #[repr(transparent)] - pub struct Hash([u8; $bits / 8]); + internals::transparent_newtype! { + #[doc = $doc] + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct Hash([u8; $bits / 8]); + + impl Hash { + /// Zero cost conversion between a fixed length byte array shared reference and + /// a shared reference to this Hash type. + pub fn from_bytes_ref(bytes: &_) -> &Self; + + /// Zero cost conversion between a fixed length byte array exclusive reference and + /// an exclusive reference to this Hash type. + pub fn from_bytes_mut(bytes: &mut _) -> &mut Self; + } + } impl Hash { - const fn internal_new(arr: [u8; $bits / 8]) -> Self { Hash(arr) } - /// Constructs a new hash from the underlying byte array. - pub const fn from_byte_array(bytes: [u8; $bits / 8]) -> Self { - Self::internal_new(bytes) - } - - /// Zero cost conversion between a fixed length byte array shared reference and - /// a shared reference to this Hash type. - pub fn from_bytes_ref(bytes: &[u8; $bits / 8]) -> &Self { - // Safety: Sound because Self is #[repr(transparent)] containing [u8; $bits / 8] - unsafe { &*(bytes as *const _ as *const Self) } - } - - /// Zero cost conversion between a fixed length byte array exclusive reference and - /// an exclusive reference to this Hash type. - pub fn from_bytes_mut(bytes: &mut [u8; $bits / 8]) -> &mut Self { - // Safety: Sound because Self is #[repr(transparent)] containing [u8; $bits / 8] - unsafe { &mut *(bytes as *mut _ as *mut Self) } - } + pub const fn from_byte_array(bytes: [u8; $bits / 8]) -> Self { Hash(bytes) } /// Copies a byte slice into a hash object. #[deprecated(since = "0.15.0", note = "use `from_byte_array` instead")] @@ -158,7 +147,7 @@ macro_rules! hash_type_no_default { } else { let mut ret = [0; $bits / 8]; ret.copy_from_slice(sl); - Ok(Self::internal_new(ret)) + Ok(Self::from_byte_array(ret)) } } diff --git a/hashes/src/lib.rs b/hashes/src/lib.rs index b3962214ca..3e8c41a6db 100644 --- a/hashes/src/lib.rs +++ b/hashes/src/lib.rs @@ -66,6 +66,7 @@ // Exclude lints we don't think are valuable. #![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134 #![allow(clippy::manual_range_contains)] // More readable than clippy's format. +#![allow(clippy::uninlined_format_args)] // Allow `format!("{}", x)`instead of enforcing `format!("{x}")` #[cfg(feature = "alloc")] extern crate alloc; diff --git a/hashes/src/macros.rs b/hashes/src/macros.rs index 2c9aa1bcf7..162af9a032 100644 --- a/hashes/src/macros.rs +++ b/hashes/src/macros.rs @@ -218,7 +218,7 @@ macro_rules! impl_debug_only_for_newtype { /// * `Borrow<[u8; $len]>` /// * `Borrow<[u8]>` /// -/// ## Parameters +/// # Parameters /// /// * `ty` - The bytelike type to implement the traits on. /// * `$len` - The number of bytes this type has. @@ -262,7 +262,7 @@ macro_rules! impl_bytelike_traits { /// /// (See also [`hex-conservative::fmt_hex_exact`].) /// -/// ## Parameters +/// # Parameters /// /// * `ty` - The bytelike type to implement the traits on. /// * `$len` - The number of bytes this type has. diff --git a/hashes/src/ripemd160/crypto.rs b/hashes/src/ripemd160/crypto.rs index 70e92f66d6..b7b733a8e3 100644 --- a/hashes/src/ripemd160/crypto.rs +++ b/hashes/src/ripemd160/crypto.rs @@ -229,7 +229,7 @@ impl HashEngine { round5: h_ordering 2, 3, 4, 0, 1; data_index 15; roll_shift 5; round5: h_ordering 1, 2, 3, 4, 0; data_index 13; roll_shift 6; - // Porallel Round 1; + // Parallel Round 1; par_round1: h_ordering 0, 1, 2, 3, 4; data_index 5; roll_shift 8; par_round1: h_ordering 4, 0, 1, 2, 3; data_index 14; roll_shift 9; par_round1: h_ordering 3, 4, 0, 1, 2; data_index 7; roll_shift 9; diff --git a/hashes/src/ripemd160/mod.rs b/hashes/src/ripemd160/mod.rs index bb2941455a..36f2b24169 100644 --- a/hashes/src/ripemd160/mod.rs +++ b/hashes/src/ripemd160/mod.rs @@ -20,31 +20,35 @@ crate::internal_macros::general_hash_type! { "Output of the RIPEMD160 hash function." } -#[cfg(not(hashes_fuzz))] -fn from_engine(mut e: HashEngine) -> Hash { - // pad buffer with a single 1-bit then all 0s, until there are exactly 8 bytes remaining - let n_bytes_hashed = e.bytes_hashed; - - let zeroes = [0; BLOCK_SIZE - 8]; - e.input(&[0x80]); - if crate::incomplete_block_len(&e) > zeroes.len() { - e.input(&zeroes); - } - let pad_length = zeroes.len() - incomplete_block_len(&e); - e.input(&zeroes[..pad_length]); - debug_assert_eq!(incomplete_block_len(&e), zeroes.len()); +impl Hash { + /// Finalize a hash engine to produce a hash. + #[cfg(not(hashes_fuzz))] + pub fn from_engine(mut e: HashEngine) -> Self { + // pad buffer with a single 1-bit then all 0s, until there are exactly 8 bytes remaining + let n_bytes_hashed = e.bytes_hashed; + + let zeroes = [0; BLOCK_SIZE - 8]; + e.input(&[0x80]); + if crate::incomplete_block_len(&e) > zeroes.len() { + e.input(&zeroes); + } + let pad_length = zeroes.len() - incomplete_block_len(&e); + e.input(&zeroes[..pad_length]); + debug_assert_eq!(incomplete_block_len(&e), zeroes.len()); - e.input(&(8 * n_bytes_hashed).to_le_bytes()); - debug_assert_eq!(incomplete_block_len(&e), 0); + e.input(&(8 * n_bytes_hashed).to_le_bytes()); + debug_assert_eq!(incomplete_block_len(&e), 0); - Hash(e.midstate()) -} + Hash(e.midstate()) + } -#[cfg(hashes_fuzz)] -fn from_engine(e: HashEngine) -> Hash { - let mut res = e.midstate(); - res[0] ^= (e.bytes_hashed & 0xff) as u8; - Hash(res) + /// Finalize a hash engine to produce a hash. + #[cfg(hashes_fuzz)] + pub fn from_engine(e: HashEngine) -> Self { + let mut res = e.midstate(); + res[0] ^= (e.bytes_hashed & 0xff) as u8; + Hash(res) + } } const BLOCK_SIZE: usize = 64; diff --git a/hashes/src/sha1/mod.rs b/hashes/src/sha1/mod.rs index 69ca308978..ca5e92acb8 100644 --- a/hashes/src/sha1/mod.rs +++ b/hashes/src/sha1/mod.rs @@ -20,23 +20,26 @@ crate::internal_macros::general_hash_type! { "Output of the SHA1 hash function." } -fn from_engine(mut e: HashEngine) -> Hash { - // pad buffer with a single 1-bit then all 0s, until there are exactly 8 bytes remaining - let n_bytes_hashed = e.bytes_hashed; - - let zeroes = [0; BLOCK_SIZE - 8]; - e.input(&[0x80]); - if incomplete_block_len(&e) > zeroes.len() { - e.input(&zeroes); - } - let pad_length = zeroes.len() - incomplete_block_len(&e); - e.input(&zeroes[..pad_length]); - debug_assert_eq!(incomplete_block_len(&e), zeroes.len()); +impl Hash { + /// Finalize a hash engine to produce a hash. + pub fn from_engine(mut e: HashEngine) -> Self { + // pad buffer with a single 1-bit then all 0s, until there are exactly 8 bytes remaining + let n_bytes_hashed = e.bytes_hashed; + + let zeroes = [0; BLOCK_SIZE - 8]; + e.input(&[0x80]); + if incomplete_block_len(&e) > zeroes.len() { + e.input(&zeroes); + } + let pad_length = zeroes.len() - incomplete_block_len(&e); + e.input(&zeroes[..pad_length]); + debug_assert_eq!(incomplete_block_len(&e), zeroes.len()); - e.input(&(8 * n_bytes_hashed).to_be_bytes()); - debug_assert_eq!(incomplete_block_len(&e), 0); + e.input(&(8 * n_bytes_hashed).to_be_bytes()); + debug_assert_eq!(incomplete_block_len(&e), 0); - Hash(e.midstate()) + Hash(e.midstate()) + } } const BLOCK_SIZE: usize = 64; diff --git a/hashes/src/sha256/crypto.rs b/hashes/src/sha256/crypto.rs index eea9ff7a04..ef4b0f25ce 100644 --- a/hashes/src/sha256/crypto.rs +++ b/hashes/src/sha256/crypto.rs @@ -80,7 +80,7 @@ mod fast_hash { } impl Midstate { - #[allow(clippy::identity_op)] // more readble + #[allow(clippy::identity_op)] // more readable const fn read_u32(bytes: &[u8], index: usize) -> u32 { ((bytes[index + 0] as u32) << 24) | ((bytes[index + 1] as u32) << 16) @@ -130,7 +130,7 @@ impl Midstate { if (bytes.len() % 64 <= 64 - 9) || (chunk + 2 == num_chunks) { buf[i] = 0x80; } - #[allow(clippy::identity_op)] // more readble + #[allow(clippy::identity_op)] // more readable #[allow(clippy::erasing_op)] if chunk + 1 == num_chunks { let bit_len = bytes.len() as u64 * 8; @@ -235,7 +235,7 @@ impl Midstate { } let mut output = [0u8; 32]; let mut i = 0; - #[allow(clippy::identity_op)] // more readble + #[allow(clippy::identity_op)] // more readable while i < 8 { output[i * 4 + 0] = (state[i + 0] >> 24) as u8; output[i * 4 + 1] = (state[i + 0] >> 16) as u8; diff --git a/hashes/src/sha256/mod.rs b/hashes/src/sha256/mod.rs index a45eb0bfee..39c3257be6 100644 --- a/hashes/src/sha256/mod.rs +++ b/hashes/src/sha256/mod.rs @@ -22,37 +22,6 @@ crate::internal_macros::general_hash_type! { "Output of the SHA256 hash function." } -#[cfg(not(hashes_fuzz))] -fn from_engine(mut e: HashEngine) -> Hash { - // pad buffer with a single 1-bit then all 0s, until there are exactly 8 bytes remaining - let n_bytes_hashed = e.bytes_hashed; - - let zeroes = [0; BLOCK_SIZE - 8]; - e.input(&[0x80]); - if incomplete_block_len(&e) > zeroes.len() { - e.input(&zeroes); - } - let pad_length = zeroes.len() - incomplete_block_len(&e); - e.input(&zeroes[..pad_length]); - debug_assert_eq!(incomplete_block_len(&e), zeroes.len()); - - e.input(&(8 * n_bytes_hashed).to_be_bytes()); - debug_assert_eq!(incomplete_block_len(&e), 0); - - Hash(e.midstate_unchecked().bytes) -} - -#[cfg(hashes_fuzz)] -fn from_engine(e: HashEngine) -> Hash { - let mut hash = e.midstate_unchecked().bytes; - if hash == [0; 32] { - // Assume sha256 is secure and never generate 0-hashes (which represent invalid - // secp256k1 secret keys, causing downstream application breakage). - hash[0] = 1; - } - Hash(hash) -} - const BLOCK_SIZE: usize = 64; /// Engine to compute SHA256 hash function. @@ -141,6 +110,39 @@ impl crate::HashEngine for HashEngine { } impl Hash { + /// Finalize a hash engine to obtain a hash. + #[cfg(not(hashes_fuzz))] + pub fn from_engine(mut e: HashEngine) -> Self { + // pad buffer with a single 1-bit then all 0s, until there are exactly 8 bytes remaining + let n_bytes_hashed = e.bytes_hashed; + + let zeroes = [0; BLOCK_SIZE - 8]; + e.input(&[0x80]); + if incomplete_block_len(&e) > zeroes.len() { + e.input(&zeroes); + } + let pad_length = zeroes.len() - incomplete_block_len(&e); + e.input(&zeroes[..pad_length]); + debug_assert_eq!(incomplete_block_len(&e), zeroes.len()); + + e.input(&(8 * n_bytes_hashed).to_be_bytes()); + debug_assert_eq!(incomplete_block_len(&e), 0); + + Hash(e.midstate_unchecked().bytes) + } + + /// Finalize a hash engine to obtain a hash. + #[cfg(hashes_fuzz)] + pub fn from_engine(e: HashEngine) -> Self { + let mut hash = e.midstate_unchecked().bytes; + if hash == [0; 32] { + // Assume sha256 is secure and never generate 0-hashes (which represent invalid + // secp256k1 secret keys, causing downstream application breakage). + hash[0] = 1; + } + Hash(hash) + } + /// Iterate the sha256 algorithm to turn a sha256 hash into a sha256d hash #[must_use] pub fn hash_again(&self) -> sha256d::Hash { sha256d::Hash::from_byte_array(hash(&self.0).0) } diff --git a/hashes/src/sha256d/mod.rs b/hashes/src/sha256d/mod.rs index 69bb8a72a3..add1935e18 100644 --- a/hashes/src/sha256d/mod.rs +++ b/hashes/src/sha256d/mod.rs @@ -10,13 +10,16 @@ crate::internal_macros::general_hash_type! { "Output of the SHA256d hash function." } -fn from_engine(e: HashEngine) -> Hash { - let sha2 = sha256::Hash::from_engine(e.0); - let sha2d = sha256::Hash::hash(sha2.as_byte_array()); - - let mut ret = [0; 32]; - ret.copy_from_slice(sha2d.as_byte_array()); - Hash(ret) +impl Hash { + /// Finalize a hash engine to produce a hash. + pub fn from_engine(e: HashEngine) -> Self { + let sha2 = sha256::Hash::from_engine(e.0); + let sha2d = sha256::Hash::hash(sha2.as_byte_array()); + + let mut ret = [0; 32]; + ret.copy_from_slice(sha2d.as_byte_array()); + Hash(ret) + } } /// Engine to compute SHA256d hash function. diff --git a/hashes/src/sha256t/mod.rs b/hashes/src/sha256t/mod.rs index d5acd5edfd..5b6d91db65 100644 --- a/hashes/src/sha256t/mod.rs +++ b/hashes/src/sha256t/mod.rs @@ -43,32 +43,27 @@ pub trait Tag { const MIDSTATE: sha256::Midstate; } -/// Output of the SHA256t hash function. -#[repr(transparent)] -pub struct Hash([u8; 32], PhantomData); +internals::transparent_newtype! { + /// Output of the SHA256t hash function. + pub struct Hash(PhantomData, [u8; 32]); + + impl Hash { + /// Zero cost conversion between a fixed length byte array shared reference and + /// a shared reference to this Hash type. + pub fn from_bytes_ref(bytes: &_) -> &Self; + + /// Zero cost conversion between a fixed length byte array exclusive reference and + /// an exclusive reference to this Hash type. + pub fn from_bytes_mut(bytes: &mut _) -> &mut Self; + } +} impl Hash where T: Tag, { - const fn internal_new(arr: [u8; 32]) -> Self { Hash(arr, PhantomData) } - /// Constructs a new hash from the underlying byte array. - pub const fn from_byte_array(bytes: [u8; 32]) -> Self { Self::internal_new(bytes) } - - /// Zero cost conversion between a fixed length byte array shared reference and - /// a shared reference to this Hash type. - pub fn from_bytes_ref(bytes: &[u8; 32]) -> &Self { - // Safety: Sound because Self is #[repr(transparent)] containing [u8; 32] - unsafe { &*(bytes as *const _ as *const Self) } - } - - /// Zero cost conversion between a fixed length byte array exclusive reference and - /// an exclusive reference to this Hash type. - pub fn from_bytes_mut(bytes: &mut [u8; 32]) -> &mut Self { - // Safety: Sound because Self is #[repr(transparent)] containing [u8; 32] - unsafe { &mut *(bytes as *mut _ as *mut Self) } - } + pub const fn from_byte_array(bytes: [u8; 32]) -> Self { Self(PhantomData, bytes) } /// Copies a byte slice into a hash object. #[deprecated(since = "0.15.0", note = "use `from_byte_array` instead")] @@ -86,7 +81,7 @@ where } /// Produces a hash from the current state of a given engine. - pub fn from_engine(e: HashEngine) -> Hash { + pub fn from_engine(e: HashEngine) -> Self { Hash::from_byte_array(sha256::Hash::from_engine(e.0).to_byte_array()) } @@ -117,10 +112,10 @@ where } /// Returns the underlying byte array. - pub const fn to_byte_array(self) -> [u8; 32] { self.0 } + pub const fn to_byte_array(self) -> [u8; 32] { self.1 } /// Returns a reference to the underlying byte array. - pub const fn as_byte_array(&self) -> &[u8; 32] { &self.0 } + pub const fn as_byte_array(&self) -> &[u8; 32] { &self.1 } } impl Copy for Hash {} @@ -128,7 +123,7 @@ impl Clone for Hash { fn clone(&self) -> Self { *self } } impl PartialEq for Hash { - fn eq(&self, other: &Hash) -> bool { self.0 == other.0 } + fn eq(&self, other: &Hash) -> bool { self.as_byte_array() == other.as_byte_array() } } impl Eq for Hash {} impl PartialOrd for Hash { @@ -137,10 +132,12 @@ impl PartialOrd for Hash { } } impl Ord for Hash { - fn cmp(&self, other: &Hash) -> cmp::Ordering { cmp::Ord::cmp(&self.0, &other.0) } + fn cmp(&self, other: &Hash) -> cmp::Ordering { + cmp::Ord::cmp(&self.as_byte_array(), &other.as_byte_array()) + } } impl core::hash::Hash for Hash { - fn hash(&self, h: &mut H) { self.0.hash(h) } + fn hash(&self, h: &mut H) { self.as_byte_array().hash(h) } } crate::internal_macros::hash_trait_impls!(256, false, T: Tag); diff --git a/hashes/src/sha384/mod.rs b/hashes/src/sha384/mod.rs index 3d6538af08..06d55e62eb 100644 --- a/hashes/src/sha384/mod.rs +++ b/hashes/src/sha384/mod.rs @@ -10,10 +10,13 @@ crate::internal_macros::general_hash_type! { "Output of the SHA384 hash function." } -fn from_engine(e: HashEngine) -> Hash { - let mut ret = [0; 48]; - ret.copy_from_slice(&sha512::from_engine(e.0).as_byte_array()[..48]); - Hash(ret) +impl Hash { + /// Finalize a hash engine to produce a hash. + pub fn from_engine(e: HashEngine) -> Self { + let mut ret = [0; 48]; + ret.copy_from_slice(&sha512::Hash::from_engine(e.0).as_byte_array()[..48]); + Hash(ret) + } } /// Engine to compute SHA384 hash function. diff --git a/hashes/src/sha512/mod.rs b/hashes/src/sha512/mod.rs index 31203aba4d..0f73f328e0 100644 --- a/hashes/src/sha512/mod.rs +++ b/hashes/src/sha512/mod.rs @@ -20,32 +20,36 @@ crate::internal_macros::general_hash_type! { "Output of the SHA512 hash function." } -#[cfg(not(hashes_fuzz))] -pub(crate) fn from_engine(mut e: HashEngine) -> Hash { - // pad buffer with a single 1-bit then all 0s, until there are exactly 16 bytes remaining - let n_bytes_hashed = e.bytes_hashed; - - let zeroes = [0; BLOCK_SIZE - 16]; - e.input(&[0x80]); - if incomplete_block_len(&e) > zeroes.len() { - e.input(&zeroes); - } - let pad_length = zeroes.len() - incomplete_block_len(&e); - e.input(&zeroes[..pad_length]); - debug_assert_eq!(incomplete_block_len(&e), zeroes.len()); +impl Hash { + /// Finalize a hash engine to produce a hash. + #[cfg(not(hashes_fuzz))] + pub fn from_engine(mut e: HashEngine) -> Self { + // pad buffer with a single 1-bit then all 0s, until there are exactly 16 bytes remaining + let n_bytes_hashed = e.bytes_hashed; + + let zeroes = [0; BLOCK_SIZE - 16]; + e.input(&[0x80]); + if incomplete_block_len(&e) > zeroes.len() { + e.input(&zeroes); + } + let pad_length = zeroes.len() - incomplete_block_len(&e); + e.input(&zeroes[..pad_length]); + debug_assert_eq!(incomplete_block_len(&e), zeroes.len()); - e.input(&[0; 8]); - e.input(&(8 * n_bytes_hashed).to_be_bytes()); - debug_assert_eq!(incomplete_block_len(&e), 0); + e.input(&[0; 8]); + e.input(&(8 * n_bytes_hashed).to_be_bytes()); + debug_assert_eq!(incomplete_block_len(&e), 0); - Hash(e.midstate()) -} + Hash(e.midstate()) + } -#[cfg(hashes_fuzz)] -pub(crate) fn from_engine(e: HashEngine) -> Hash { - let mut hash = e.midstate(); - hash[0] ^= 0xff; // Make this distinct from SHA-256 - Hash(hash) + /// Finalize a hash engine to produce a hash. + #[cfg(hashes_fuzz)] + pub fn from_engine(e: HashEngine) -> Self { + let mut hash = e.midstate(); + hash[0] ^= 0xff; // Make this distinct from SHA-256 + Hash(hash) + } } pub(crate) const BLOCK_SIZE: usize = 128; diff --git a/hashes/src/sha512_256/mod.rs b/hashes/src/sha512_256/mod.rs index cf12d46047..291eac9f78 100644 --- a/hashes/src/sha512_256/mod.rs +++ b/hashes/src/sha512_256/mod.rs @@ -15,10 +15,13 @@ crate::internal_macros::general_hash_type! { "Output of the SHA512/256 hash function.\n\nSHA512/256 is a hash function that uses the sha512 algorithm but it truncates the output to 256 bits. It has different initial constants than sha512 so it produces an entirely different hash compared to sha512. More information at ." } -fn from_engine(e: HashEngine) -> Hash { - let mut ret = [0; 32]; - ret.copy_from_slice(&sha512::from_engine(e.0).as_byte_array()[..32]); - Hash(ret) +impl Hash { + /// Finalize a hash engine to produce a hash. + pub fn from_engine(e: HashEngine) -> Self { + let mut ret = [0; 32]; + ret.copy_from_slice(&sha512::Hash::from_engine(e.0).as_byte_array()[..32]); + Hash(ret) + } } /// Engine to compute SHA512/256 hash function. diff --git a/hashes/src/siphash24/mod.rs b/hashes/src/siphash24/mod.rs index 50abe8575c..61f3dcf054 100644 --- a/hashes/src/siphash24/mod.rs +++ b/hashes/src/siphash24/mod.rs @@ -12,15 +12,6 @@ crate::internal_macros::hash_type_no_default! { "Output of the SipHash24 hash function." } -#[cfg(not(hashes_fuzz))] -fn from_engine(e: HashEngine) -> Hash { Hash::from_u64(Hash::from_engine_to_u64(e)) } - -#[cfg(hashes_fuzz)] -fn from_engine(e: HashEngine) -> Hash { - let state = e.state.clone(); - Hash::from_u64(state.v0 ^ state.v1 ^ state.v2 ^ state.v3) -} - macro_rules! compress { ($state:expr) => {{ compress!($state.v0, $state.v1, $state.v2, $state.v3) @@ -176,7 +167,14 @@ impl Hash { pub fn engine(k0: u64, k1: u64) -> HashEngine { HashEngine::with_keys(k0, k1) } /// Produces a hash from the current state of a given engine. - pub fn from_engine(e: HashEngine) -> Hash { from_engine(e) } + #[cfg(not(hashes_fuzz))] + pub fn from_engine(e: HashEngine) -> Self { Hash::from_u64(Hash::from_engine_to_u64(e)) } + + #[cfg(hashes_fuzz)] + pub fn from_engine(e: HashEngine) -> Self { + let state = e.state.clone(); + Hash::from_u64(state.v0 ^ state.v1 ^ state.v2 ^ state.v3) + } /// Hashes the given data with an engine with the provided keys. pub fn hash_with_keys(k0: u64, k1: u64, data: &[u8]) -> Hash { diff --git a/hashes/tests/api.rs b/hashes/tests/api.rs new file mode 100644 index 0000000000..a85eb55476 --- /dev/null +++ b/hashes/tests/api.rs @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Test the API surface of `units`. +//! +//! The point of these tests are to check the API surface as opposed to test the API functionality. +//! +//! ref: + +#![allow(dead_code)] +#![allow(unused_imports)] +// Exclude lints we don't think are valuable. +#![allow(clippy::uninlined_format_args)] // Allow `format!("{}", x)`instead of enforcing `format!("{x}")` + +// Import using module style e.g., `sha256::Hash`. +use bitcoin_hashes::{ + hash160, hash_newtype, hkdf, hmac, ripemd160, sha1, sha256, sha256d, sha256t, sha256t_tag, + sha384, sha512, sha512_256, siphash24, FromSliceError, Hash, HashEngine, +}; +// Import using type alias style e.g., `Sha256`. +use bitcoin_hashes::{ + Hash160, Hkdf, Hmac, HmacEngine, Ripemd160, Sha1, Sha256, Sha256d, Sha256t, Sha384, Sha512, + Sha512_256, Siphash24, +}; + +// Arbitrary midstate value; taken from as sha256t unit tests. +const TEST_MIDSTATE: [u8; 32] = [ + 156, 224, 228, 230, 124, 17, 108, 57, 56, 179, 202, 242, 195, 15, 80, 137, 211, 243, 147, 108, + 71, 99, 110, 96, 125, 179, 62, 234, 221, 198, 240, 201, +]; + +sha256t_tag! { + /// Test tag so we don't have to use generics. + #[derive(Debug)] + struct Tag = raw(TEST_MIDSTATE, 64); +} +hash_newtype! { + /// A concrete sha256t hash type so we don't have to use generics. + #[derive(Debug)] + struct TaggedHash(sha256t::Hash); +} + +/// All the hash types excluding `Hkdf`. +// `Hkdf` only implements `Copy` and `Clone` ATM - by design. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] // C-COMMON-TRAITS +// We check `Hkdf` implements `Debug` in the non-empty debug test below. +#[derive(Debug)] // All public types implement Debug (C-DEBUG). +struct Hashes { + a: hash160::Hash, + c: Hmac, + d: ripemd160::Hash, + e: sha1::Hash, + f: sha256::Hash, + g: sha256d::Hash, + h: TaggedHash, + i: sha384::Hash, + j: sha512::Hash, + k: sha512_256::Hash, + l: siphash24::Hash, +} + +impl Hashes { + fn new_sha256() -> Self { + let hmac = HmacEngine::::new(&[]).finalize(); + // `TaggedHash` is not a general hash but `Sha256` is. + let tagged = TaggedHash::from_byte_array(Sha256t::::hash(&[]).to_byte_array()); + let siphash = Siphash24::from_engine(siphash24::HashEngine::with_keys(0, 0)); + + Hashes { + a: Hash160::hash(&[]), + // b: hkdf, + c: hmac, + d: Ripemd160::hash(&[]), + e: Sha1::hash(&[]), + f: Sha256::hash(&[]), + g: Sha256d::hash(&[]), + h: tagged, + i: Sha384::hash(&[]), + j: Sha512::hash(&[]), + k: Sha512_256::hash(&[]), + l: siphash, + } + } +} + +/// All the hash engines. +#[derive(Clone)] // C-COMMON-TRAITS +#[derive(Debug)] // All public types implement Debug (C-DEBUG). +struct Engines { + a: hash160::HashEngine, + // We cannot derive `Debug` on a generic `HmacEngine` engine. + b: hmac::HmacEngine, + c: ripemd160::HashEngine, + d: sha1::HashEngine, + e: sha256::HashEngine, + f: sha256d::HashEngine, + g: sha256t::HashEngine, + h: sha384::HashEngine, + i: sha512::HashEngine, + j: sha512_256::HashEngine, + k: siphash24::HashEngine, +} + +impl Engines { + fn new_sha256() -> Self { + Engines { + a: hash160::HashEngine::new(), + b: hmac::HmacEngine::::new(&[]), + c: ripemd160::HashEngine::new(), + d: sha1::HashEngine::new(), + e: sha256::HashEngine::new(), + f: sha256d::HashEngine::new(), + g: sha256t::Hash::::engine(), + h: sha384::HashEngine::new(), + i: sha512::HashEngine::new(), + j: sha512_256::HashEngine::new(), + k: siphash24::HashEngine::with_keys(0, 0), + } + } +} + +/// Public structs that are not hashes, engines, or errors. +#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] // C-COMMON-TRAITS +#[derive(Debug)] // All public types implement Debug (C-DEBUG). +struct OtherStructs { + a: sha256::Midstate, + // There is no way to construct a `siphash24::State` so we cannot directly + // test it but `siphash24::HashEngine` includes one so `Engines` implicitly + // tests it (e.g. `Debug` and `Clone`). + // + // b: siphash24::State, + + // Don't worry about including a tag because its tested in `primitives`. +} + +impl OtherStructs { + fn new() -> Self { Self { a: sha256::Midstate::new(TEST_MIDSTATE, 0) } } +} + +/// All hash engine types that implement `Default`. +#[derive(Default)] +struct Default { + a: hash160::HashEngine, + b: ripemd160::HashEngine, + c: sha1::HashEngine, + d: sha256::HashEngine, + e: sha256d::HashEngine, + f: sha256t::HashEngine, + g: sha384::HashEngine, + h: sha512::HashEngine, + i: sha512_256::HashEngine, +} + +/// Hash types that require a key. +struct Keyed { + a: Hmac, + l: siphash24::Hash, +} + +/// A struct that includes all public error types. +// These derives are the policy of `rust-bitcoin` not Rust API guidelines. +#[derive(Debug, Clone, PartialEq, Eq)] // All public types implement Debug (C-DEBUG). +struct Errors { + a: FromSliceError, + b: hkdf::MaxLengthError, + c: sha256::MidstateError, +} + +#[test] +fn api_can_use_modules_from_crate_root() { + use bitcoin_hashes::{ + hash160, hkdf, hmac, ripemd160, sha1, sha256, sha256d, sha256t, sha384, sha512, sha512_256, + siphash24, + }; +} + +#[test] +fn api_can_use_alias_from_crate_root() { + use bitcoin_hashes::{ + Hash160, Hkdf, Hmac, Ripemd160, Sha1, Sha256, Sha256d, Sha256t, Sha384, Sha512, Sha512_256, + Siphash24, + }; +} + +// `Debug` representation is never empty (C-DEBUG-NONEMPTY). +#[test] +fn api_all_non_error_types_have_non_empty_debug() { + macro_rules! check_debug { + ($t:tt; $($field:tt),* $(,)?) => { + $( + let debug = format!("{:?}", $t.$field); + assert!(!debug.is_empty()); + )* + } + } + + let t = Hashes::::new_sha256(); + check_debug!(t; a, c, d, e, f, g, h, i, j, k, l); + + // This tests `Debug` on `Hkdf` but not for all `T: GeneralHash`. + let t = Hkdf::::new(&[], &[]); + let debug = format!("{:?}", t); + assert!(!debug.is_empty()); + + let t = Engines::new_sha256(); + check_debug!(t; a, c, d, e, f, g, h, i, j, k); + + let t = OtherStructs::new(); + check_debug!(t; a); +} + +#[test] +fn all_types_implement_send_sync() { + fn assert_send() {} + fn assert_sync() {} + + // Types are `Send` and `Sync` where possible (C-SEND-SYNC). + assert_send::>(); + assert_sync::>(); + assert_send::(); + assert_sync::(); + assert_send::(); + assert_sync::(); + + // Error types should implement the Send and Sync traits (C-GOOD-ERR). + assert_send::(); + assert_sync::(); +} diff --git a/hashes/tests/regression.rs b/hashes/tests/regression.rs index 31aeec5f80..dccff1714a 100644 --- a/hashes/tests/regression.rs +++ b/hashes/tests/regression.rs @@ -5,6 +5,8 @@ //! Test input data and expected hashes is the same as in `io/src/hash.rs`. #![cfg(feature = "hex")] +// Exclude lints we don't think are valuable. +#![allow(clippy::uninlined_format_args)] // Allow `format!("{}", x)`instead of enforcing `format!("{x}")` use bitcoin_hashes::{ hash160, ripemd160, sha1, sha256, sha256d, sha256t, sha384, sha512, sha512_256, siphash24, diff --git a/internals/build.rs b/internals/build.rs index 7ac6c70fd8..fff95c4b98 100644 --- a/internals/build.rs +++ b/internals/build.rs @@ -1,3 +1,6 @@ +// Exclude lints we don't think are valuable. +#![allow(clippy::uninlined_format_args)] // Allow `format!("{}", x)`instead of enforcing `format!("{x}")` + const MAX_USED_VERSION: u64 = 80; use std::io; diff --git a/internals/src/array.rs b/internals/src/array.rs index becbf6b64c..4142212b31 100644 --- a/internals/src/array.rs +++ b/internals/src/array.rs @@ -17,9 +17,7 @@ pub trait ArrayExt { /// Returns an item at given statically-known index. /// /// This is just like normal indexing except the check happens at compile time. - fn get_static(&self) -> &Self::Item { - &self.sub_array::()[0] - } + fn get_static(&self) -> &Self::Item { &self.sub_array::()[0] } /// Returns the first item in an array. /// @@ -30,17 +28,17 @@ pub trait ArrayExt { /// that this will not return `None` so trying to keep the `std` method around is pointless. /// Importing the trait will also cause compile failures - that's also intentional to expose /// the places where useless checks are made. - fn first(&self) -> &Self::Item { - self.get_static::<0>() - } + fn first(&self) -> &Self::Item { self.get_static::<0>() } - /// Splits the array into two, non-overlaping smaller arrays covering the entire range. + /// Splits the array into two, non-overlapping smaller arrays covering the entire range. /// /// This is almost equivalent to just calling [`sub_array`](Self::sub_array) twice, except it also /// checks that the arrays don't overlap and that they cover the full range. This is very useful /// for demonstrating correctness, especially when chained. Using this technique even revealed /// a bug in the past. ([#4195](https://github.com/rust-bitcoin/rust-bitcoin/issues/4195)) - fn split_array(&self) -> (&[Self::Item; LEFT], &[Self::Item; RIGHT]); + fn split_array( + &self, + ) -> (&[Self::Item; LEFT], &[Self::Item; RIGHT]); /// Splits the array into the first element and the remaining, one element shorter, array. /// @@ -84,7 +82,9 @@ impl ArrayExt for [T; N] { self[OFFSET..(OFFSET + LEN)].try_into().expect("this is also compiler-checked above") } - fn split_array(&self) -> (&[Self::Item; LEFT], &[Self::Item; RIGHT]) { + fn split_array( + &self, + ) -> (&[Self::Item; LEFT], &[Self::Item; RIGHT]) { #[allow(clippy::let_unit_value)] let _ = Hack2::::IS_FULL_RANGE; diff --git a/internals/src/array_vec.rs b/internals/src/array_vec.rs index 76d0490d5f..98bb3ea85f 100644 --- a/internals/src/array_vec.rs +++ b/internals/src/array_vec.rs @@ -212,7 +212,7 @@ mod verification { } } - #[kani::unwind(16)] // One grater than 15. + #[kani::unwind(16)] // One greater than 15. #[kani::proof] fn no_out_of_bounds_upto_cap() { const CAP: usize = 15; diff --git a/internals/src/lib.rs b/internals/src/lib.rs index c2e2ce7080..e86dfda690 100644 --- a/internals/src/lib.rs +++ b/internals/src/lib.rs @@ -15,6 +15,7 @@ // Exclude lints we don't think are valuable. #![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134 #![allow(clippy::manual_range_contains)] // More readable than clippy's format. +#![allow(clippy::uninlined_format_args)] // Allow `format!("{}", x)`instead of enforcing `format!("{x}")` #[cfg(feature = "alloc")] extern crate alloc; @@ -35,6 +36,12 @@ pub mod rust_version { include!(concat!(env!("OUT_DIR"), "/rust_version.rs")); } +#[doc(hidden)] +pub mod _export { + #[cfg(feature = "alloc")] + pub extern crate alloc; +} + pub mod array; pub mod array_vec; pub mod compact_size; diff --git a/internals/src/macros.rs b/internals/src/macros.rs index 69725e232a..ce0d412bcf 100644 --- a/internals/src/macros.rs +++ b/internals/src/macros.rs @@ -8,7 +8,7 @@ macro_rules! const_assert { ($x:expr $(; $message:expr)?) => { const _: () = { if !$x { - // We can't use formatting in const, only concating literals. + // We can't use formatting in const, only concatenating literals. panic!(concat!("assertion ", stringify!($x), " failed" $(, ": ", $message)?)) } }; @@ -37,3 +37,213 @@ macro_rules! impl_to_hex_from_lower_hex { } }; } + +/// Creates a transparent wrapper around an inner type and soundly implements reference casts. +/// +/// This macro takes care of several issues related to newtypes that need to allow casting their +/// inner types to themselves: +/// +/// * It makes sure to put repr(transparent) on the type +/// * It optionally implements conversions from `&`, `&mut`, `Box`, `Rc`, `Arc` +/// * It makes sure to put `#[inline]` on all of these conversions since they are trivial +/// * It makes sure the reference cast is const +/// * It makes sure the `Arc` conversion is conditioned on `target_has_atomic = "ptr"` +/// +/// Usage: just type the struct inside the macro as you would implementing it manually except leave +/// `#[repr(transparent)]` out. Then add an impl block for the just-defined type containing function +/// declarations that take a reference/smart pointer to `_` (use literal underscore; e.g. `&_` for +/// shared references) and return `Self` behind the appropriate "pointer" type. Do not write the +/// body, just semicolon. +/// +/// The `alloc` types MUST NOT have import paths and don't need imports. +#[macro_export] +macro_rules! transparent_newtype { + ( + $(#[$($struct_attr:tt)*])* + $vis:vis struct $newtype:tt$(<$gen:ident $(= $default:ty)?>)?($($fields:tt)+) $(where $($where_ty:ty: $bound:path),* $(,)?)?; + + impl$(<$gen2:tt>)? $newtype2:ident$(<$gen3:tt>)? { + $( + $(#[$($fn_attr:tt)*])* + $fn_vis:vis $(const)? fn $fn:ident($fn_arg_name:ident: $($fn_arg_ty:tt)+) -> $fn_ret_ty:ty; + )* + } + ) => { + $crate::_check_tts_eq!($newtype2, $newtype, "the type name in the impl block doesn't match the struct name"); + $( + // WARNING: renaming has to be disabled for soundness! + // If it weren't it'd be possible to make the type inside struct not match the one passed + // to functions. In principle we could also omit the generics but that'd be confusing for + // readers. + $crate::_check_tts_eq!($gen2, $gen, "the name of the left generic parameter in impl block doesn't match the one on struct"); + $crate::_check_tts_eq!($gen3, $gen, "the name of the right generic parameter in impl block doesn't match the one on struct"); + )? + $(#[$($struct_attr)*])* + #[repr(transparent)] + $vis struct $newtype$(<$gen $(= $default)?>)?($($fields)+) $(where $($where_ty: $bound),*)?; + + impl$(<$gen2>)? $newtype$(<$gen3>)? $(where $($where_ty: $bound),*)? { + $crate::_transparent_ref_conversions! { + $crate::_transparent_newtype_inner_type!($($fields)+); + $( + $(#[$($fn_attr)*])* + $fn_vis fn $fn($fn_arg_name: $($fn_arg_ty)+) -> $fn_ret_ty; + )+ + } + } + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! _transparent_ref_conversions { + ( + $inner:ty; + $( + $(#[$($fn_attr:tt)*])* + $fn_vis:vis $(const)? fn $fn:ident($fn_arg_name:ident: $($fn_arg_ty:tt)+) -> $fn_ret_ty:ty; + )+ + ) => { + $( + $crate::_transparent_ref_conversion! { + $inner; + $(#[$($fn_attr)*])* + $fn_vis fn $fn($fn_arg_name: $($fn_arg_ty)+) -> $fn_ret_ty; + } + )+ + } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! _transparent_ref_conversion { + ( + $inner:ty; + $(#[$($from_ref_attr:tt)*])* + $from_ref_vis:vis fn $from_ref:ident($from_ref_arg_name:ident: &_) -> $fn_ret_ty:ty; + ) => { + #[inline] + $(#[$($from_ref_attr)*])* + $from_ref_vis const fn $from_ref($from_ref_arg_name: &$inner) -> &Self { + // SAFETY: the pointer is created by casting a pointer that is pointing to an object + // with the same layout and validity invariants and the previous pointer was created + // directly from a reference. (Notice repr(transparent).) + // The lifetime of the input reference matches the lifetime of the returned reference. + unsafe { &*($from_ref_arg_name as *const $inner as *const Self) } + } + }; + ( + $inner:ty; + $(#[$($from_mut_attr:tt)*])* + $from_mut_vis:vis fn $from_mut:ident($from_mut_arg_name:ident: &mut _) -> $fn_ret_ty:ty; + ) => { + #[inline] + $(#[$($from_mut_attr)*])* + $from_mut_vis fn $from_mut($from_mut_arg_name: &mut $inner) -> &mut Self { + // SAFETY: the pointer is created by casting a pointer that is pointing to an object + // with the same layout and validity invariants and the previous pointer was created + // directly from a reference. (Notice repr(transparent).) + // The lifetime of the input reference matches the lifetime of the returned reference. + unsafe { &mut *($from_mut_arg_name as *mut $inner as *mut Self) } + } + }; + ( + $inner:ty; + $(#[$($from_box_attr:tt)*])* + $from_box_vis:vis fn $from_box:ident($from_box_arg_name:ident: Box<_>) -> $fn_ret_ty:ty; + ) => { + $crate::_emit_alloc! { + $(#[$($from_box_attr)*])* + #[inline] + $from_box_vis fn $from_box($from_box_arg_name: $crate::_export::alloc::boxed::Box<$inner>) -> $crate::_export::alloc::boxed::Box { + let ptr = $crate::_export::alloc::boxed::Box::into_raw($from_box_arg_name); + // SAFETY: the pointer is created by casting a pointer that is pointing to an object + // with the same layout and validity invariants and the previous pointer was created + // directly from box. (Notice repr(transparent).) + unsafe { $crate::_export::alloc::boxed::Box::from_raw(ptr as *mut Self) } + } + } + }; + + ( + $inner:ty; + $(#[$($from_rc_attr:tt)*])* + $from_rc_vis:vis fn $from_rc:ident($from_rc_arg_name:ident: Rc<_>) -> $fn_ret_ty:ty; + ) => { + $crate::_emit_alloc! { + $(#[$($from_rc_attr)*])* + #[inline] + $from_rc_vis fn $from_rc($from_rc_arg_name: $crate::_export::alloc::rc::Rc<$inner>) -> $crate::_export::alloc::rc::Rc { + let ptr = $crate::_export::alloc::rc::Rc::into_raw($from_rc_arg_name); + // SAFETY: the pointer is created by casting a pointer that is pointing to an object + // with the same layout and validity invariants and the previous pointer was created + // directly from box. (Notice repr(transparent).) + unsafe { $crate::_export::alloc::rc::Rc::from_raw(ptr as *mut Self) } + } + } + }; + + ( + $inner:ty; + $(#[$($from_arc_attr:tt)*])* + $from_arc_vis:vis fn $from_arc:ident($from_arc_arg_name:ident: Arc<_>) -> $fn_ret_ty:ty; + ) => { + $crate::_emit_alloc! { + $(#[$($from_arc_attr)*])* + #[cfg(target_has_atomic = "ptr")] + #[inline] + $from_arc_vis fn $from_arc($from_arc_arg_name: $crate::_export::alloc::sync::Arc<$inner>) -> $crate::_export::alloc::sync::Arc { + let ptr = $crate::_export::alloc::sync::Arc::into_raw($from_arc_arg_name); + // SAFETY: the pointer is created by casting a pointer that is pointing to an object + // with the same layout and validity invariants and the previous pointer was created + // directly from box. (Notice repr(transparent).) + unsafe { $crate::_export::alloc::sync::Arc::from_raw(ptr as *mut Self) } + } + } + } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! _check_tts_eq { + ($left:tt, $right:tt, $message:literal) => { + macro_rules! token_eq { + ($right) => {}; + ($any:tt) => { + compile_error!($message) + }; + } + token_eq!($left); + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! _transparent_newtype_inner_type { + ($(#[$($field_attr:tt)*])* $inner:ty) => { + $inner + }; + ($(#[$($phantom_attr:tt)*])* PhantomData<$phantom:ty>, $(#[$($field_attr:tt)*])* $inner:ty) => { + $inner + }; +} + +/// Emits given tokens only if the `alloc` feature **in this crate** is enabled. +/// +/// (The feature is currently enabled.) +#[cfg(feature = "alloc")] +#[doc(hidden)] +#[macro_export] +macro_rules! _emit_alloc { + ($($tokens:tt)*) => { $($tokens)* }; +} + +/// Emits given tokens only if the `alloc` feature **in this crate** is enabled. +/// +/// (The feature is currently disabled.) +#[cfg(not(feature = "alloc"))] +#[doc(hidden)] +#[macro_export] +macro_rules! _emit_alloc { + ($($tokens:tt)*) => {}; +} diff --git a/internals/src/slice.rs b/internals/src/slice.rs index b703238e45..6c91a4d2b4 100644 --- a/internals/src/slice.rs +++ b/internals/src/slice.rs @@ -9,9 +9,10 @@ pub trait SliceExt { /// /// Note that `N` must not be zero: /// - /// ```compile_fail + /// ```ignore + /// # use bitcoin_internals::slice::SliceExt; /// let slice = [1, 2, 3]; - /// let fail = slice.as_chunks::<0>(); + /// let _fail = slice.bitcoin_as_chunks::<0>(); // Fails to compile /// ``` fn bitcoin_as_chunks(&self) -> (&[[Self::Item; N]], &[Self::Item]); @@ -19,9 +20,10 @@ pub trait SliceExt { /// /// Note that `N` must not be zero: /// - /// ```compile_fail + /// ```ignore + /// # use bitcoin_internals::slice::SliceExt; /// let mut slice = [1, 2, 3]; - /// let fail = slice.as_chunks_mut::<0>(); + /// let _fail = slice.bitcoin_as_chunks_mut::<0>(); // Fails to compile /// ``` fn bitcoin_as_chunks_mut( &mut self, @@ -37,14 +39,18 @@ pub trait SliceExt { /// Returns `None` if the slice is shorter than `ARRAY_LEN` #[allow(clippy::type_complexity)] // it's not really complex and redefining would make it // harder to understand - fn split_first_chunk(&self) -> Option<(&[Self::Item; ARRAY_LEN], &[Self::Item])>; + fn split_first_chunk( + &self, + ) -> Option<(&[Self::Item; ARRAY_LEN], &[Self::Item])>; /// Splits the slice into a remainder and an array if it's long enough. /// /// Returns `None` if the slice is shorter than `ARRAY_LEN` #[allow(clippy::type_complexity)] // it's not really complex and redefining would make it // harder to understand - fn split_last_chunk(&self) -> Option<(&[Self::Item], &[Self::Item; ARRAY_LEN])>; + fn split_last_chunk( + &self, + ) -> Option<(&[Self::Item], &[Self::Item; ARRAY_LEN])>; } impl SliceExt for [T] { @@ -90,11 +96,16 @@ impl SliceExt for [T] { } fn get_array(&self, offset: usize) -> Option<&[Self::Item; ARRAY_LEN]> { - self.get(offset..(offset + ARRAY_LEN)) - .map(|slice| slice.try_into().expect("the arguments to `get` evaluate to the same length the return type uses")) + self.get(offset..(offset + ARRAY_LEN)).map(|slice| { + slice + .try_into() + .expect("the arguments to `get` evaluate to the same length the return type uses") + }) } - fn split_first_chunk(&self) -> Option<(&[Self::Item; ARRAY_LEN], &[Self::Item])> { + fn split_first_chunk( + &self, + ) -> Option<(&[Self::Item; ARRAY_LEN], &[Self::Item])> { if self.len() < ARRAY_LEN { return None; } @@ -102,12 +113,17 @@ impl SliceExt for [T] { Some((first.try_into().expect("we're passing `ARRAY_LEN` to `split_at` above"), remainder)) } - fn split_last_chunk(&self) -> Option<(&[Self::Item], &[Self::Item; ARRAY_LEN])> { + fn split_last_chunk( + &self, + ) -> Option<(&[Self::Item], &[Self::Item; ARRAY_LEN])> { if self.len() < ARRAY_LEN { return None; } let (remainder, last) = self.split_at(self.len() - ARRAY_LEN); - Some((remainder, last.try_into().expect("we're passing `self.len() - ARRAY_LEN` to `split_at` above"))) + Some(( + remainder, + last.try_into().expect("we're passing `self.len() - ARRAY_LEN` to `split_at` above"), + )) } } diff --git a/io/Cargo.toml b/io/Cargo.toml index f59c35e00c..07b38baa61 100644 --- a/io/Cargo.toml +++ b/io/Cargo.toml @@ -19,12 +19,12 @@ std = ["alloc", "hashes?/std"] alloc = ["hashes?/alloc"] [dependencies] -internals = { package = "bitcoin-internals", version = "0.4.0" } +internals = { package = "bitcoin-internals", path = "../internals" } -hashes = { package = "bitcoin_hashes", version = "0.16.0", default-features = false, optional = true } +hashes = { package = "bitcoin_hashes", path = "../hashes", default-features = false, optional = true } [dev-dependencies] -hashes = { package = "bitcoin_hashes", version = "0.16.0", default-features = false, features = ["hex"] } +hashes = { package = "bitcoin_hashes", path = "../hashes", default-features = false, features = ["hex"] } [package.metadata.docs.rs] all-features = true diff --git a/io/src/bridge.rs b/io/src/bridge.rs index fc49a07719..816820a2a5 100644 --- a/io/src/bridge.rs +++ b/io/src/bridge.rs @@ -1,34 +1,25 @@ // SPDX-License-Identifier: CC0-1.0 -#[cfg(feature = "alloc")] -use alloc::boxed::Box; - use internals::rust_version; -/// A bridging wrapper providing the I/O traits for types that already implement `std` I/O traits. -#[repr(transparent)] -#[derive(Debug)] -pub struct FromStd(T); +internals::transparent_newtype! { + /// A bridging wrapper providing the I/O traits for types that already implement `std` I/O traits. + #[derive(Debug)] + pub struct FromStd(T); -impl FromStd { - /// Wraps an I/O type. - #[inline] - pub const fn new(inner: T) -> Self { Self(inner) } + impl FromStd { + /// Wraps a mutable reference to I/O type. + pub fn new_mut(inner: &mut _) -> &mut Self; - /// Wraps a mutable reference to I/O type. - #[inline] - pub fn new_mut(inner: &mut T) -> &mut Self { - // SAFETY: the type is repr(transparent) and the lifetimes match - unsafe { &mut *(inner as *mut _ as *mut Self) } + /// Wraps a boxed I/O type. + pub fn new_boxed(inner: Box<_>) -> Box; } +} - /// Wraps a boxed I/O type. - #[cfg(feature = "alloc")] +impl FromStd { + /// Wraps an I/O type. #[inline] - pub fn new_boxed(inner: Box) -> Box { - // SAFETY: the type is repr(transparent) and the pointer is created from Box - unsafe { Box::from_raw(Box::into_raw(inner) as *mut Self) } - } + pub const fn new(inner: T) -> Self { Self(inner) } /// Returns the wrapped value. #[inline] @@ -117,30 +108,24 @@ impl std::io::Write for FromStd { fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { self.0.write_all(buf) } } -/// A bridging wrapper providing the std traits for types that already implement our traits. -#[repr(transparent)] -#[derive(Debug)] -pub struct ToStd(T); +internals::transparent_newtype! { + /// A bridging wrapper providing the std traits for types that already implement our traits. + #[derive(Debug)] + pub struct ToStd(T); -impl ToStd { - /// Wraps an I/O type. - #[inline] - pub const fn new(inner: T) -> Self { Self(inner) } + impl ToStd { + /// Wraps a mutable reference to I/O type. + pub fn new_mut(inner: &mut _) -> &mut Self; - /// Wraps a mutable reference to I/O type. - #[inline] - pub fn new_mut(inner: &mut T) -> &mut Self { - // SAFETY: the type is repr(transparent) and the lifetimes match - unsafe { &mut *(inner as *mut _ as *mut Self) } + /// Wraps a boxed I/O type. + pub fn new_boxed(inner: Box<_>) -> Box; } +} - /// Wraps a boxed I/O type. - #[cfg(feature = "alloc")] +impl ToStd { + /// Wraps an I/O type. #[inline] - pub fn new_boxed(inner: Box) -> Box { - // SAFETY: the type is repr(transparent) and the pointer is created from Box - unsafe { Box::from_raw(Box::into_raw(inner) as *mut Self) } - } + pub const fn new(inner: T) -> Self { Self(inner) } /// Returns the wrapped value. #[inline] diff --git a/io/src/lib.rs b/io/src/lib.rs index f92ec11f34..e5bf1e1eb4 100644 --- a/io/src/lib.rs +++ b/io/src/lib.rs @@ -24,6 +24,7 @@ // Exclude lints we don't think are valuable. #![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134 #![allow(clippy::manual_range_contains)] // More readable than clippy's format. +#![allow(clippy::uninlined_format_args)] // Allow `format!("{}", x)`instead of enforcing `format!("{x}")` #[cfg(feature = "alloc")] extern crate alloc; diff --git a/justfile b/justfile index 0be4cd940a..e3e2d37e3f 100644 --- a/justfile +++ b/justfile @@ -1,5 +1,7 @@ set positional-arguments +alias ulf := update-lock-files + default: @just --list diff --git a/nightly-version b/nightly-version index ba747383dc..0ed7b6b402 100644 --- a/nightly-version +++ b/nightly-version @@ -1 +1 @@ -nightly-2025-03-21 +nightly-2025-05-23 diff --git a/p2p/CHANGELOG.md b/p2p/CHANGELOG.md new file mode 100644 index 0000000000..4c310ad4be --- /dev/null +++ b/p2p/CHANGELOG.md @@ -0,0 +1,3 @@ +# 0.1.0 - 2025-05-27 + +* Initial release of the `github.com/rust-bitcoin/rust-bitcoin/p2p` crate as `bitcoin-p2p-messages`. diff --git a/p2p/Cargo.toml b/p2p/Cargo.toml new file mode 100644 index 0000000000..37166211c8 --- /dev/null +++ b/p2p/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "bitcoin-p2p-messages" +version = "0.1.0" +authors = ["Andrew Poelstra "] +license = "CC0-1.0" +repository = "https://github.com/rust-bitcoin/rust-bitcoin" +description = "Peer-to-peer messages defined by the Bitcoin protocol" +categories = ["cryptography::cryptocurrencies"] +keywords = ["bitcoin", "peer-to-peer"] +readme = "README.md" +edition = "2021" +rust-version = "1.63.0" +exclude = ["tests", "contrib"] + +[dependencies] + +[dev-dependencies] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/p2p/README.md b/p2p/README.md new file mode 100644 index 0000000000..f4fe096e40 --- /dev/null +++ b/p2p/README.md @@ -0,0 +1,12 @@ +# Rust Bitcoin Peer to Peer Message Types + +This crate provides data types used in the Bitcoin peer-to-peer protocol. + +## Minimum Supported Rust Version (MSRV) + +This library should always compile with any combination of features on **Rust 1.63.0**. + +## Licensing + +The code in this project is licensed under the [Creative Commons CC0 1.0 Universal license](LICENSE). +We use the [SPDX license list](https://spdx.org/licenses/) and [SPDX IDs](https://spdx.dev/ids/). diff --git a/p2p/contrib/test_vars.sh b/p2p/contrib/test_vars.sh new file mode 100644 index 0000000000..88e2d26e18 --- /dev/null +++ b/p2p/contrib/test_vars.sh @@ -0,0 +1,14 @@ +# No shebang, this file should not be executed. +# shellcheck disable=SC2148 +# +# 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="" + +# Test all these features without "std" enabled. +FEATURES_WITHOUT_STD="" + +# Run these examples. +EXAMPLES="" diff --git a/p2p/src/lib.rs b/p2p/src/lib.rs new file mode 100644 index 0000000000..bb9afc7d9d --- /dev/null +++ b/p2p/src/lib.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! # Rust Bitcoin Peer to Peer Message Types + +// Experimental features we need. +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +// Coding conventions. +#![warn(missing_docs)] +#![warn(deprecated_in_future)] +#![doc(test(attr(warn(unused))))] +// Pedantic lints that we enforce. +#![warn(clippy::return_self_not_must_use)] +// Exclude lints we don't think are valuable. +#![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134 +#![allow(clippy::manual_range_contains)] // More readable than clippy's format. +#![allow(clippy::uninlined_format_args)] // Allow `format!("{}", x)`instead of enforcing `format!("{x}")` diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index b8b0bdd4c5..7af43f83b7 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -15,19 +15,21 @@ rust-version = "1.63.0" exclude = ["tests", "contrib"] [features] -default = ["std"] -std = ["alloc", "hashes/std", "hex/std", "internals/std", "units/std"] -alloc = ["hashes/alloc", "hex/alloc", "internals/alloc", "units/alloc"] -serde = ["dep:serde", "hashes/serde", "internals/serde", "units/serde", "alloc"] +default = ["std", "hex"] +std = ["alloc", "hashes/std", "hex?/std", "internals/std", "units/std", "arrayvec/std"] +alloc = ["hashes/alloc", "hex?/alloc", "internals/alloc", "units/alloc"] +serde = ["dep:serde", "hashes/serde", "hex?/serde", "internals/serde", "units/serde", "alloc", "hex"] arbitrary = ["dep:arbitrary", "units/arbitrary"] +hex = ["dep:hex", "hashes/hex", "internals/hex"] [dependencies] -hashes = { package = "bitcoin_hashes", version = "0.16.0", default-features = false, features = ["hex"] } -hex = { package = "hex-conservative", version = "0.3.0", default-features = false } -internals = { package = "bitcoin-internals", version = "0.4.0", features = ["hex"] } -units = { package = "bitcoin-units", version = "0.2.0", default-features = false } +hashes = { package = "bitcoin_hashes", path = "../hashes", default-features = false } +internals = { package = "bitcoin-internals", path = "../internals" } +units = { package = "bitcoin-units", path = "../units", default-features = false } +arrayvec = { version = "0.7", default-features = false } arbitrary = { version = "1.4", optional = true } +hex = { package = "hex-conservative", version = "0.3.0", default-features = false, optional = true } serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"], optional = true } [dev-dependencies] diff --git a/primitives/src/block.rs b/primitives/src/block.rs index 632344420b..b01146f35b 100644 --- a/primitives/src/block.rs +++ b/primitives/src/block.rs @@ -47,7 +47,7 @@ pub trait Validation: sealed::Validation + Sync + Send + Sized + Unpin { /// /// [wiki-block]: https://en.bitcoin.it/wiki/Block /// -/// ### Bitcoin Core References +/// # Bitcoin Core References /// /// * [CBlock definition](https://github.com/bitcoin/bitcoin/blob/345457b542b6a980ccfbc868af0970a6f91d1b82/src/primitives/block.h#L62) #[cfg(feature = "alloc")] @@ -167,7 +167,7 @@ mod sealed { /// /// [Merkle tree]: https://en.wikipedia.org/wiki/Merkle_tree /// -/// ### Bitcoin Core References +/// # Bitcoin Core References /// /// * [CBlockHeader definition](https://github.com/bitcoin/bitcoin/blob/345457b542b6a980ccfbc868af0970a6f91d1b82/src/primitives/block.h#L20) #[derive(Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)] @@ -208,6 +208,28 @@ impl Header { } } +#[cfg(feature = "hex")] +impl fmt::Display for Header { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use fmt::Write as _; + use hex::DisplayHex as _; + + let mut buf = arrayvec::ArrayString::<160>::new(); + write!( + &mut buf, + "{}{}{}{}{}{}", + self.version.to_consensus().to_le_bytes().as_hex(), + self.prev_blockhash.as_byte_array().as_hex(), + self.merkle_root.as_byte_array().as_hex(), + self.time.to_u32().to_le_bytes().as_hex(), + self.bits.to_consensus().to_le_bytes().as_hex(), + self.nonce.to_le_bytes().as_hex(), + ) + .expect("total length of written objects is 160 characters"); + fmt::Display::fmt(&buf, f) + } +} + impl fmt::Debug for Header { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Header") @@ -241,7 +263,7 @@ impl From<&Header> for BlockHash { /// /// > When a block nVersion does not have top bits 001, it is treated as if all bits are 0 for the purposes of deployments. /// -/// ### Relevant BIPs +/// # Relevant BIPs /// /// * [BIP9 - Version bits with timeout and delay](https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki) (current usage) /// * [BIP34 - Block v2, Height in Coinbase](https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki) @@ -311,7 +333,10 @@ hashes::hash_newtype! { pub struct WitnessCommitment(sha256d::Hash); } +#[cfg(feature = "hex")] hashes::impl_hex_for_newtype!(BlockHash, WitnessCommitment); +#[cfg(not(feature = "hex"))] +hashes::impl_debug_only_for_newtype!(BlockHash, WitnessCommitment); #[cfg(feature = "serde")] hashes::impl_serde_for_newtype!(BlockHash, WitnessCommitment); @@ -533,4 +558,35 @@ mod tests { ); assert_eq!(format!("{:?}", header), expected); } + + #[test] + #[cfg(feature = "hex")] + fn header_display() { + let seconds: u32 = 1_653_195_600; // Arbitrary timestamp: May 22nd, 5am UTC. + + let header = Header { + version: Version::TWO, + prev_blockhash: BlockHash::from_byte_array([0xab; 32]), + merkle_root: TxMerkleNode::from_byte_array([0xcd; 32]), + time: BlockTime::from(seconds), + bits: CompactTarget::from_consensus(0xbeef), + nonce: 0xcafe, + }; + + let want = concat!( + "02000000", // version + "abababababababababababababababababababababababababababababababab", // prev_blockhash + "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", // merkle_root + "50c38962", // time + "efbe0000", // bits + "feca0000", // nonce + ); + assert_eq!(want.len(), 160); + assert_eq!(format!("{}", header), want); + + // Check how formatting options are handled. + let want = format!("{:.20}", want); + let got = format!("{:.20}", header); + assert_eq!(got, want); + } } diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index f9dafc05a2..2a22b6060f 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -16,6 +16,8 @@ #![warn(missing_docs)] #![warn(deprecated_in_future)] #![doc(test(attr(warn(unused))))] +// Exclude lints we don't think are valuable. +#![allow(clippy::uninlined_format_args)] // Allow `format!("{}", x)`instead of enforcing `format!("{x}")` #[cfg(feature = "alloc")] extern crate alloc; @@ -50,12 +52,16 @@ pub mod witness; #[doc(inline)] pub use units::{ amount::{self, Amount, SignedAmount}, - block::{BlockHeight, BlockInterval}, + block::{BlockHeight, BlockHeightInterval, BlockMtp, BlockMtpInterval}, fee_rate::{self, FeeRate}, time::{self, BlockTime}, weight::{self, Weight}, }; +#[deprecated(since = "TBD", note = "use `BlockHeightInterval` instead")] +#[doc(hidden)] +pub type BlockInterval = BlockHeightInterval; + #[doc(inline)] #[cfg(feature = "alloc")] pub use self::{ diff --git a/primitives/src/locktime/absolute.rs b/primitives/src/locktime/absolute.rs index ad03f5102b..808ff6c634 100644 --- a/primitives/src/locktime/absolute.rs +++ b/primitives/src/locktime/absolute.rs @@ -2,7 +2,7 @@ //! Provides type [`LockTime`] that implements the logic around `nLockTime`/`OP_CHECKLOCKTIMEVERIFY`. //! -//! There are two types of lock time: lock-by-blockheight and lock-by-blocktime, distinguished by +//! There are two types of lock time: lock-by-height and lock-by-time, distinguished by //! whether `LockTime < LOCKTIME_THRESHOLD`. use core::fmt; @@ -16,7 +16,11 @@ use crate::{absolute, Transaction}; #[rustfmt::skip] // Keep public re-exports separate. #[doc(inline)] -pub use units::locktime::absolute::{ConversionError, Height, ParseHeightError, ParseTimeError, Time, LOCK_TIME_THRESHOLD}; +pub use units::locktime::absolute::{ConversionError, Height, ParseHeightError, ParseTimeError, MedianTimePast, LOCK_TIME_THRESHOLD}; + +#[deprecated(since = "TBD", note = "use `MedianTimePast` instead")] +#[doc(hidden)] +pub type Time = MedianTimePast; /// An absolute lock time value, representing either a block height or a UNIX timestamp (seconds /// since epoch). @@ -24,7 +28,7 @@ pub use units::locktime::absolute::{ConversionError, Height, ParseHeightError, P /// Used for transaction lock time (`nLockTime` in Bitcoin Core and [`Transaction::lock_time`] /// in this library) and also for the argument to opcode `OP_CHECKLOCKTIMEVERIFY`. /// -/// ### Note on ordering +/// # Note on ordering /// /// Locktimes may be height- or time-based, and these metrics are incommensurate; there is no total /// ordering on locktimes. In order to compare locktimes, instead of using `<` or `>` we provide the @@ -34,7 +38,7 @@ pub use units::locktime::absolute::{ConversionError, Height, ParseHeightError, P /// it easy to store transactions in sorted data structures, and use the locktime's 32-bit integer /// consensus encoding to order it. /// -/// ### Relevant BIPs +/// # Relevant BIPs /// /// * [BIP-65 OP_CHECKLOCKTIMEVERIFY](https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki) /// * [BIP-113 Median time-past as endpoint for lock-time calculations](https://github.com/bitcoin/bips/blob/master/bip-0113.mediawiki) @@ -75,11 +79,11 @@ pub enum LockTime { /// use bitcoin_primitives::absolute; /// /// let seconds: u32 = 1653195600; // May 22nd, 5am UTC. - /// let n = absolute::LockTime::from_time(seconds).expect("valid time"); + /// let n = absolute::LockTime::from_mtp(seconds).expect("valid time"); /// assert!(n.is_block_time()); /// assert_eq!(n.to_consensus_u32(), seconds); /// ``` - Seconds(Time), + Seconds(MedianTimePast), } impl LockTime { @@ -141,9 +145,9 @@ impl LockTime { #[allow(clippy::missing_panics_doc)] pub fn from_consensus(n: u32) -> Self { if units::locktime::absolute::is_block_height(n) { - Self::Blocks(Height::from_consensus(n).expect("n is valid")) + Self::Blocks(Height::from_u32(n).expect("n is valid")) } else { - Self::Seconds(Time::from_consensus(n).expect("n is valid")) + Self::Seconds(MedianTimePast::from_u32(n).expect("n is valid")) } } @@ -166,18 +170,24 @@ impl LockTime { /// ``` #[inline] pub fn from_height(n: u32) -> Result { - let height = Height::from_consensus(n)?; + let height = Height::from_u32(n)?; Ok(LockTime::Blocks(height)) } - /// Constructs a new `LockTime` from `n`, expecting `n` to be a valid block time. + #[inline] + #[deprecated(since = "TBD", note = "use `from_mtp` instead")] + #[doc(hidden)] + pub fn from_time(n: u32) -> Result { Self::from_mtp(n) } + + /// Constructs a new `LockTime` from `n`, expecting `n` to be a median-time-past (MTP) + /// which is in range for a locktime. /// /// # Note /// - /// If the locktime is set to a timestamp `T`, - /// the transaction can be included in a block only if the median time past (MTP) of the - /// last 11 blocks is greater than `T`. - /// It is possible to broadcast the transaction once the MTP is greater than `T`.[see BIP-113] + /// If the locktime is set to an MTP `T`, the transaction can be included in a block only if + /// the MTP of last recent 11 blocks is greater than `T`. + /// + /// It is possible to broadcast the transaction once the MTP is greater than `T`. See BIP-113. /// /// [BIP-113 Median time-past as endpoint for lock-time calculations](https://github.com/bitcoin/bips/blob/master/bip-0113.mediawiki) /// @@ -187,12 +197,12 @@ impl LockTime { /// /// ```rust /// # use bitcoin_primitives::absolute; - /// assert!(absolute::LockTime::from_time(1653195600).is_ok()); - /// assert!(absolute::LockTime::from_time(741521).is_err()); + /// assert!(absolute::LockTime::from_mtp(1653195600).is_ok()); + /// assert!(absolute::LockTime::from_mtp(741521).is_err()); /// ``` #[inline] - pub fn from_time(n: u32) -> Result { - let time = Time::from_consensus(n)?; + pub fn from_mtp(n: u32) -> Result { + let time = MedianTimePast::from_u32(n)?; Ok(LockTime::Seconds(time)) } @@ -223,13 +233,21 @@ impl LockTime { /// is satisfied if a transaction with `nLockTime` ([`Transaction::lock_time`]) set to /// `height`/`time` is valid. /// + /// If `height` and `mtp` represent the current chain tip then a transaction with this + /// locktime can be broadcast for inclusion in the next block. + /// + /// If you do not have, or do not wish to calculate, both parameters consider using: + /// + /// * [`is_satisfied_by_height()`](absolute::LockTime::is_satisfied_by_height) + /// * [`is_satisfied_by_time()`](absolute::LockTime::is_satisfied_by_time) + /// /// # Examples /// /// ```no_run /// # use bitcoin_primitives::absolute; /// // Can be implemented if block chain data is available. /// fn get_height() -> absolute::Height { todo!("return the current block height") } - /// fn get_time() -> absolute::Time { todo!("return the current block time") } + /// fn get_time() -> absolute::MedianTimePast { todo!("return the current block MTP") } /// /// let n = absolute::LockTime::from_consensus(741521); // `n OP_CHEKCLOCKTIMEVERIFY`. /// if n.is_satisfied_by(get_height(), get_time()) { @@ -237,12 +255,43 @@ impl LockTime { /// } /// ```` #[inline] - pub fn is_satisfied_by(self, height: Height, time: Time) -> bool { + pub fn is_satisfied_by(self, height: Height, mtp: MedianTimePast) -> bool { + match self { + LockTime::Blocks(blocks) => blocks.is_satisfied_by(height), + LockTime::Seconds(time) => time.is_satisfied_by(mtp), + } + } + + /// Returns true if a transaction with this locktime can be spent in the next block. + /// + /// If `height` is the current block height of the chain then a transaction with this locktime + /// can be broadcast for inclusion in the next block. + /// + /// # Errors + /// + /// Returns an error if this lock is not lock-by-height. + #[inline] + pub fn is_satisfied_by_height(self, height: Height) -> Result { + use LockTime as L; + + match self { + L::Blocks(blocks) => Ok(blocks.is_satisfied_by(height)), + L::Seconds(time) => Err(IncompatibleHeightError { lock: time, incompatible: height }), + } + } + + /// Returns true if a transaction with this locktime can be included in the next block. + /// + /// # Errors + /// + /// Returns an error if this lock is not lock-by-time. + #[inline] + pub fn is_satisfied_by_time(self, mtp: MedianTimePast) -> Result { use LockTime as L; match self { - L::Blocks(n) => n <= height, - L::Seconds(n) => n <= time, + L::Seconds(time) => Ok(time.is_satisfied_by(mtp)), + L::Blocks(blocks) => Err(IncompatibleTimeError { lock: blocks, incompatible: mtp }), } } @@ -253,9 +302,15 @@ impl LockTime { /// two lock times (same unit) then the larger lock time being satisfied implies (in a /// mathematical sense) the smaller one being satisfied. /// - /// This function is useful if you wish to check a lock time against various other locks e.g., - /// filtering out locks which cannot be satisfied. Can also be used to remove the smaller value - /// of two `OP_CHECKLOCKTIMEVERIFY` operations within one branch of the script. + /// This function serves multiple purposes: + /// + /// * When evaluating `OP_CHECKLOCKTIMEVERIFY` the argument must be less than or equal to the + /// transactions nLockTime. If using this function to validate a script `self` is the argument + /// to `CLTV` and `other` is the transaction nLockTime. + /// + /// * If you wish to check a lock time against various other locks e.g., filtering out locks + /// which cannot be satisfied. Can also be used to remove the smaller value of two + /// `OP_CHECKLOCKTIMEVERIFY` operations within one branch of the script. /// /// # Examples /// @@ -304,8 +359,8 @@ impl LockTime { #[inline] pub fn to_consensus_u32(self) -> u32 { match self { - LockTime::Blocks(ref h) => h.to_consensus_u32(), - LockTime::Seconds(ref t) => t.to_consensus_u32(), + LockTime::Blocks(ref h) => h.to_u32(), + LockTime::Seconds(ref t) => t.to_u32(), } } } @@ -317,9 +372,9 @@ impl From for LockTime { fn from(h: Height) -> Self { LockTime::Blocks(h) } } -impl From