diff --git a/bitcoin/CHANGELOG.md b/bitcoin/CHANGELOG.md index 85d0dcc5e5..0ce2481f92 100644 --- a/bitcoin/CHANGELOG.md +++ b/bitcoin/CHANGELOG.md @@ -1,7 +1,16 @@ -All notable changes to this project will be documented in this file. +# 0.32.7 - 2025-07-30 -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +- Backport - Use `_u32` in `FeeRate` constructor instead of `_unchecked` [#4552](https://github.com/rust-bitcoin/rust-bitcoin/pull/4552) +- Backport - Add support for pay to anchor outputs [#4691](https://github.com/rust-bitcoin/rust-bitcoin/pull/4691) +- Backport - Remove `non_exhaustive` from `Network` [#4658](https://github.com/rust-bitcoin/rust-bitcoin/pull/4658) + +# 0.32.6 - 2025-05-06 + +- Backport - Fix `is_invalid_use_of_sighash_single()` incompatibility with Bitcoin Core [#4122](https://github.com/rust-bitcoin/rust-bitcoin/pull/4122) +- Backport - Backport witness fixes [#4101](https://github.com/rust-bitcoin/rust-bitcoin/pull/4101) +- Backport - bip32: Return error when attempting to derive past maximum depth [#4434](https://github.com/rust-bitcoin/rust-bitcoin/pull/4434) +- Backport - Add `XOnlyPublicKey` support for PSBT key retrieval and improve Taproot signing [#4443](https://github.com/rust-bitcoin/rust-bitcoin/pull/4443) +- Backport - Add methods to retrieve inner types [#4450](https://github.com/rust-bitcoin/rust-bitcoin/pull/4450) # 0.32.5-doge.1 - 2025-11-10 @@ -16,38 +25,38 @@ Initial release of the rust-dogecoin crate, a fork of rust-bitcoin adapted for D - **Scrypt Proof-of-Work**: Add scrypt-based PoW validation (`block_hash_with_scrypt()` and `validate_pow_with_scrypt()`) to support Dogecoin's PoW algorithm. - **Difficulty Adjustment Algorithms**: - - Pre-Digishield algorithm (blocks 0-144,999) with variable transition thresholds based on block height ranges - - Digishield algorithm (blocks 145,000+) - - Methods `min_transition_threshold_dogecoin()` and `max_transition_threshold_dogecoin()` + - Pre-Digishield algorithm (blocks 0-144,999) with variable transition thresholds based on block height ranges + - Digishield algorithm (blocks 145,000+) + - Methods `min_transition_threshold_dogecoin()` and `max_transition_threshold_dogecoin()` - **Generic Network Messages**: Make `RawNetworkMessage` and `NetworkMessage` generic over `Header` and `Block` types to support AuxPoW blocks - Updated license to Apache-2.0 for new Dogecoin-specific code ### New Dogecoin module (`bitcoin/src/dogecoin/`) - **Core Types** (`mod.rs`): - - `Header`: Dogecoin block header with optional AuxPoW data - - `Block`: Block structure supporting both legacy and AuxPoW blocks - - `Network`: Dogecoin mainnet, Testnet, Regtest - - Helper methods for AuxPoW bit detection, chain ID extraction, and legacy block identification + - `Header`: Dogecoin block header with optional AuxPoW data + - `Block`: Block structure supporting both legacy and AuxPoW blocks + - `Network`: Dogecoin mainnet, Testnet, Regtest + - Helper methods for AuxPoW bit detection, chain ID extraction, and legacy block identification - **AuxPoW Support** (`auxpow.rs`): - - AuxPow validation (coinbase script validation, merkle branch verification, chain ID checks) - - Error types for AuxPow validation failures + - AuxPow validation (coinbase script validation, merkle branch verification, chain ID checks) + - Error types for AuxPow validation failures - **Consensus Parameters** (`params.rs`): - - Dogecoin PoW parameters and methods: target spacing, max attainable target, pow target timespan - - Digishield and AuxPoW activation height - - Chain ID for merged mining - - BIP activation heights + - Dogecoin PoW parameters and methods: target spacing, max attainable target, pow target timespan + - Digishield and AuxPoW activation height + - Chain ID for merged mining + - BIP activation heights - **Constants** (`constants.rs`): - - Genesis block definitions for mainnet, testnet, and regtest - - Prefixes for P2PKH and P2SH addresses + - Genesis block definitions for mainnet, testnet, and regtest + - Prefixes for P2PKH and P2SH addresses - **Address Handling** (`address/`): - - Base58 address encoding/decoding with Dogecoin-specific prefixes - - Support for P2PKH and P2SH address types - - Network validation and address parsing + - Base58 address encoding/decoding with Dogecoin-specific prefixes + - Support for P2PKH and P2SH address types + - Network validation and address parsing # 0.32.5 - 2024-11-27 diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml index cc8d31d9e1..20a6649e71 100644 --- a/bitcoin/Cargo.toml +++ b/bitcoin/Cargo.toml @@ -83,4 +83,4 @@ required-features = ["std", "rand-std", "bitcoinconsensus"] name = "sighash" [lints.rust] -unexpected_cfgs = { level = "deny", check-cfg = ['cfg(bench)', 'cfg(fuzzing)', 'cfg(kani)', 'cfg(mutate)', 'cfg(rust_v_1_60)'] } +unexpected_cfgs = { level = "deny", check-cfg = ['cfg(bench)', 'cfg(fuzzing)', 'cfg(kani)', 'cfg(mutate)', 'cfg(rust_v_1_60)', 'cfg(rust_v_1_61)'] } diff --git a/bitcoin/examples/sign-tx-taproot.rs b/bitcoin/examples/sign-tx-taproot.rs index 3221c560ce..9dae0b2e0f 100644 --- a/bitcoin/examples/sign-tx-taproot.rs +++ b/bitcoin/examples/sign-tx-taproot.rs @@ -72,7 +72,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, &tweaked.to_inner()); + let signature = secp.sign_schnorr(&msg, tweaked.as_keypair()); // Update the witness stack. let signature = bitcoin::taproot::Signature { signature, sighash_type }; diff --git a/bitcoin/examples/taproot-psbt.rs b/bitcoin/examples/taproot-psbt.rs index b5db05ab89..62f9431960 100644 --- a/bitcoin/examples/taproot-psbt.rs +++ b/bitcoin/examples/taproot-psbt.rs @@ -734,7 +734,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/mod.rs b/bitcoin/src/address/mod.rs index 7c546ae786..c05f32ac87 100644 --- a/bitcoin/src/address/mod.rs +++ b/bitcoin/src/address/mod.rs @@ -74,6 +74,8 @@ pub enum AddressType { P2wsh, /// Pay to taproot. P2tr, + /// Pay to anchor. + P2a } impl fmt::Display for AddressType { @@ -84,6 +86,7 @@ impl fmt::Display for AddressType { AddressType::P2wpkh => "p2wpkh", AddressType::P2wsh => "p2wsh", AddressType::P2tr => "p2tr", + AddressType::P2a => "p2a", }) } } @@ -97,6 +100,7 @@ impl FromStr for AddressType { "p2wpkh" => Ok(AddressType::P2wpkh), "p2wsh" => Ok(AddressType::P2wsh), "p2tr" => Ok(AddressType::P2tr), + "p2a" => Ok(AddressType::P2a), _ => Err(UnknownAddressTypeError(s.to_owned())), } } @@ -496,6 +500,8 @@ impl Address { Some(AddressType::P2wsh) } else if program.is_p2tr() { Some(AddressType::P2tr) + } else if program.is_p2a() { + Some(AddressType::P2a) } else { None }, @@ -1367,4 +1373,22 @@ mod tests { } } } + + #[test] + fn pay_to_anchor_address_regtest() { + // Verify that p2a uses the expected address for regtest. + // This test-vector is borrowed from the bitcoin source code. + let address_str = "bcrt1pfeesnyr2tx"; + + let script = ScriptBuf::new_p2a(); + let address_unchecked = address_str.parse().unwrap(); + let address = Address::from_script(&script, Network::Regtest).unwrap(); + assert_eq!(address.as_unchecked(), &address_unchecked); + assert_eq!(address.to_string(), address_str); + + // Verify that the address is considered standard + // 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/bip32.rs b/bitcoin/src/bip32.rs index f15d121bf9..ebc0dde2c0 100644 --- a/bitcoin/src/bip32.rs +++ b/bitcoin/src/bip32.rs @@ -178,6 +178,9 @@ impl ChildNumber { /// Returns the child number that is a single increment from this one. 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), @@ -481,6 +484,10 @@ pub type KeySource = (Fingerprint, DerivationPath); pub enum Error { /// A pk->pk derivation was attempted on a hardened key CannotDeriveFromHardenedKey, + /// Attempted to derive a child of depth 256 or higher. + /// + /// There is no way to encode such xkeys. + MaximumDepthExceeded, /// A secp256k1 error occurred Secp256k1(secp256k1::Error), /// A child number was provided that was out of range @@ -512,6 +519,7 @@ impl fmt::Display for Error { match *self { CannotDeriveFromHardenedKey => f.write_str("cannot derive hardened key from public key"), + MaximumDepthExceeded => f.write_str("cannot derive child of depth 256 or higher"), 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), @@ -540,6 +548,7 @@ impl std::error::Error for Error { Hex(ref e) => Some(e), InvalidBase58PayloadLength(ref e) => Some(e), CannotDeriveFromHardenedKey + | MaximumDepthExceeded | InvalidChildNumber(_) | InvalidChildNumberFormat | InvalidDerivationPathFormat @@ -636,7 +645,7 @@ impl Xpriv { Ok(Xpriv { network: self.network, - depth: self.depth + 1, + depth: self.depth.checked_add(1).ok_or(Error::MaximumDepthExceeded)?, parent_fingerprint: self.fingerprint(secp), child_number: i, private_key: tweaked, @@ -768,7 +777,7 @@ impl Xpub { Ok(Xpub { network: self.network, - depth: self.depth + 1, + depth: self.depth.checked_add(1).ok_or(Error::MaximumDepthExceeded)?, parent_fingerprint: self.fingerprint(), child_number: i, public_key: tweaked, diff --git a/bitcoin/src/blockdata/script/owned.rs b/bitcoin/src/blockdata/script/owned.rs index d7189488c6..829ead8459 100644 --- a/bitcoin/src/blockdata/script/owned.rs +++ b/bitcoin/src/blockdata/script/owned.rs @@ -8,7 +8,7 @@ use secp256k1::{Secp256k1, Verification}; use crate::blockdata::opcodes::all::*; use crate::blockdata::opcodes::{self, Opcode}; -use crate::blockdata::script::witness_program::WitnessProgram; +use crate::blockdata::script::witness_program::{WitnessProgram, P2A_PROGRAM}; use crate::blockdata::script::witness_version::WitnessVersion; use crate::blockdata::script::{ opcode_to_verify, Builder, Instruction, PushBytes, Script, ScriptHash, WScriptHash, @@ -130,6 +130,11 @@ impl ScriptBuf { ScriptBuf::new_witness_program_unchecked(WitnessVersion::V1, output_key.serialize()) } + /// Generates pay to anchor output. + pub fn new_p2a() -> Self { + ScriptBuf::new_witness_program_unchecked(WitnessVersion::V1, P2A_PROGRAM) + } + /// Generates P2WSH-type of scriptPubkey with a given [`WitnessProgram`]. pub fn new_witness_program(witness_program: &WitnessProgram) -> Self { Builder::new() @@ -141,14 +146,15 @@ impl ScriptBuf { /// Generates P2WSH-type of scriptPubkey with a given [`WitnessVersion`] and the program bytes. /// Does not do any checks on version or program length. /// - /// Convenience method used by `new_p2wpkh`, `new_p2wsh`, `new_p2tr`, and `new_p2tr_tweaked`. + /// Convenience method used by `new_p2wpkh`, `new_p2wsh`, `new_p2tr`, and `new_p2tr_tweaked`, + /// and `new_p2a`. pub(crate) fn new_witness_program_unchecked>( version: WitnessVersion, program: T, ) -> Self { let program = program.as_ref(); debug_assert!(program.len() >= 2 && program.len() <= 40); - // In segwit v0, the program must be 20 or 32 bytes long. + // In SegWit v0, the program must be either 20 (P2WPKH) bytes or 32 (P2WSH) bytes 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/blockdata/script/witness_program.rs b/bitcoin/src/blockdata/script/witness_program.rs index 9b76becc86..2cf7101f23 100644 --- a/bitcoin/src/blockdata/script/witness_program.rs +++ b/bitcoin/src/blockdata/script/witness_program.rs @@ -24,6 +24,9 @@ pub const MIN_SIZE: usize = 2; /// The maximum byte size of a segregated witness program. pub const MAX_SIZE: usize = 40; +/// The P2A program which is given by 0x4e73. +pub(crate) const P2A_PROGRAM: [u8;2] = [78, 115]; + /// The segregated witness program. /// /// The segregated witness program is technically only the program bytes _excluding_ the witness @@ -90,16 +93,23 @@ impl WitnessProgram { merkle_root: Option, ) -> Self { 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) } /// Creates a pay to taproot address from a pre-tweaked output key. 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) } + internals::const_tools::cond_const! { + /// Constructs a new pay to anchor address + pub const(in rust_v_1_61 = "1.61") fn p2a() -> Self { + WitnessProgram { version: WitnessVersion::V1, program: ArrayVec::from_slice(&P2A_PROGRAM)} + } + } + /// Returns the witness program version. pub fn version(&self) -> WitnessVersion { self.version } @@ -123,6 +133,11 @@ 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. + pub fn is_p2a(&self) -> bool { + self.version == WitnessVersion::V1 && self.program == P2A_PROGRAM + } } /// Witness program error. diff --git a/bitcoin/src/blockdata/witness.rs b/bitcoin/src/blockdata/witness.rs index 0737a678a1..77f426d65d 100644 --- a/bitcoin/src/blockdata/witness.rs +++ b/bitcoin/src/blockdata/witness.rs @@ -14,7 +14,7 @@ use crate::consensus::encode::{Error, MAX_VEC_SIZE}; use crate::consensus::{Decodable, Encodable, WriteExt}; use crate::crypto::ecdsa; use crate::prelude::*; -use crate::taproot::{self, TAPROOT_ANNEX_PREFIX}; +use crate::taproot::{self, LeafScript, LeafVersion, TAPROOT_ANNEX_PREFIX, TAPROOT_CONTROL_BASE_SIZE, TAPROOT_LEAF_MASK}; use crate::{Script, VarInt}; /// The Witness is the data used to unlock bitcoin since the [segwit upgrade]. @@ -395,61 +395,69 @@ impl Witness { self.element_at(pos) } - /// Get Tapscript following BIP341 rules regarding accounting for an annex. + /// Get leaf script following BIP341 rules regarding accounting for an annex. + /// + /// This method is broken: it's called `tapscript` but it's actually returning a leaf script. + /// We're not going to fix it because someone might be relying on it thinking leaf script and + /// tapscript are the same thing (they are not). Instead, this is deprecated and will be + /// removed in the next breaking release. You need to use `taproot_leaf_script` and if you + /// intended to use it as leaf script, just access the `script` field of the returned type. If + /// you intended tapscript specifically you have to check the version first and bail if it's not + /// `LeafVersion::TapScript`. /// /// This does not guarantee that this represents a P2TR [`Witness`]. It /// merely gets the second to last or third to last element depending on - /// the first byte of the last element being equal to 0x50. See - /// [Script::is_p2tr](crate::blockdata::script::Script::is_p2tr) to - /// check whether this is actually a Taproot witness. + /// the first byte of the last element being equal to 0x50. + /// + /// See [`Script::is_p2tr`] to check whether this is actually a Taproot witness. + #[deprecated = "use `taproot_leaf_script` and check leaf version, if applicable"] pub fn tapscript(&self) -> Option<&Script> { - if self.is_empty() { - return None; - } + match P2TrSpend::from_witness(self) { + // Note: the method is named "tapscript" but historically it was actually returning + // leaf script. This is broken but we now keep the behavior the same to not subtly + // break someone. + Some(P2TrSpend::Script { leaf_script, .. }) => Some(leaf_script), + _ => None, + } + } - if self.taproot_annex().is_some() { - self.third_to_last().map(Script::from_bytes) - } else { - self.second_to_last().map(Script::from_bytes) - } + /// Returns the leaf script with its version but without the merkle proof. + /// + /// This does not guarantee that this represents a P2TR [`Witness`]. It + /// merely gets the second to last or third to last element depending on + /// the first byte of the last element being equal to 0x50 and the associated + /// version. + pub 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, }) + }, + _ => None, + } } /// 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](crate::blockdata::script::Script::is_p2tr) to - /// check whether this is actually a Taproot witness. + /// byte of the last element being equal to 0x50. + /// + /// See [`Script::is_p2tr`] to check whether this is actually a Taproot witness. pub fn taproot_control_block(&self) -> Option<&[u8]> { - if self.is_empty() { - return None; - } - - if self.taproot_annex().is_some() { - self.second_to_last() - } else { - self.last() - } + match P2TrSpend::from_witness(self) { + Some(P2TrSpend::Script { control_block, .. }) => Some(control_block), + _ => None, + } } /// Get the taproot annex following BIP341 rules. /// - /// This does not guarantee that this represents a P2TR [`Witness`]. See - /// [Script::is_p2tr](crate::blockdata::script::Script::is_p2tr) to - /// check whether this is actually a Taproot witness. + /// This does not guarantee that this represents a P2TR [`Witness`]. + /// + /// See [`Script::is_p2tr`] to check whether this is actually a Taproot witness. pub fn taproot_annex(&self) -> Option<&[u8]> { - self.last().and_then(|last| { - // From BIP341: - // If there are at least two witness elements, and the first byte of - // the last element is 0x50, this last element is called annex a - // and is removed from the witness stack. - if self.len() >= 2 && last.first() == Some(&TAPROOT_ANNEX_PREFIX) { - Some(last) - } else { - None - } - }) + P2TrSpend::from_witness(self)?.annex() } /// Get the p2wsh witness script following BIP141 rules. @@ -468,6 +476,88 @@ impl Index for Witness { fn index(&self, index: usize) -> &Self::Output { self.nth(index).expect("Out of Bounds") } } +/// Represents a possible Taproot spend. +/// +/// Taproot can be spent as key spend or script spend and, depending on which it is, different data +/// is in the witness. This type helps representing that data more cleanly when parsing the witness +/// because there are a lot of conditions that make reasoning hard. It's better to parse it at one +/// place and pass it along. +/// +/// This type is so far private but it could be published eventually. The design is geared towards +/// it but it's not fully finished. +enum P2TrSpend<'a> { + Key { + // This field is technically present in witness in case of key spend but none of our code + // uses it yet. Rather than deleting it, it's kept here commented as documentation and as + // an easy way to add it if anything needs it - by just uncommenting. + // signature: &'a [u8], + annex: Option<&'a [u8]>, + }, + Script { + leaf_script: &'a Script, + control_block: &'a [u8], + annex: Option<&'a [u8]>, + }, +} + +impl<'a> P2TrSpend<'a> { + /// 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 + /// (without knowing the output), so it doesn't attempt to check anything other than what is + /// required for the program to not crash. + /// + /// In other words, if the caller is certain that the witness is a valid p2tr spend (e.g. + /// obtained from Bitcoin Core) then it's OK to unwrap this but not vice versa - `Some` does + /// not imply correctness. + fn from_witness(witness: &'a Witness) -> Option { + // BIP341 says: + // If there are at least two witness elements, and the first byte of + // the last element is 0x50, this last element is called annex a + // and is removed from the witness stack. + // + // However here we're not removing anything, so we have to adjust the numbers to account + // for the fact that annex is still there. + match witness.len() { + 0 => None, + 1 => Some(P2TrSpend::Key { /* signature: witness.last().expect("len > 0") ,*/ annex: None }), + 2 if witness.last().expect("len > 0").starts_with(&[TAPROOT_ANNEX_PREFIX]) => { + let spend = P2TrSpend::Key { + // signature: witness.second_to_last().expect("len > 1"), + annex: witness.last(), + }; + Some(spend) + }, + // 2 => this is script spend without annex - same as when there are 3+ elements and the + // 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 spend = P2TrSpend::Script { + leaf_script: Script::from_bytes(witness.third_to_last().expect("len > 2")), + control_block: witness.second_to_last().expect("len > 1"), + annex: witness.last(), + }; + Some(spend) + }, + _ => { + let spend = P2TrSpend::Script { + leaf_script: Script::from_bytes(witness.second_to_last().expect("len > 1")), + control_block: witness.last().expect("len > 0"), + annex: None, + }; + Some(spend) + }, + } + } + + fn annex(&self) -> Option<&'a [u8]> { + match self { + P2TrSpend::Key { annex, .. } => *annex, + P2TrSpend::Script { annex, .. } => *annex, + } + } +} + impl<'a> Iterator for Iter<'a> { type Item = &'a [u8]; @@ -777,25 +867,56 @@ mod test { assert_eq!(witness_annex.tapscript(), None); } + #[test] + fn get_taproot_leaf_script() { + let tapscript = hex!("deadbeef"); + let control_block = hex!("c0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + // annex starting with 0x50 causes the branching logic. + let annex = hex!("50"); + + let witness_vec = vec![tapscript.clone(), control_block.clone()]; + let witness_vec_annex = vec![tapscript.clone(), control_block, annex]; + + let witness_serialized: Vec = serialize(&witness_vec); + let witness_serialized_annex: Vec = serialize(&witness_vec_annex); + + let witness = deserialize::(&witness_serialized[..]).unwrap(); + let witness_annex = deserialize::(&witness_serialized_annex[..]).unwrap(); + + let expected_leaf_script = LeafScript { + version: LeafVersion::TapScript, + script: Script::from_bytes(&tapscript), + }; + + // With or without annex, the tapscript should be returned. + assert_eq!(witness.taproot_leaf_script().unwrap(), expected_leaf_script); + assert_eq!(witness_annex.taproot_leaf_script().unwrap(), expected_leaf_script); + } + #[test] fn test_get_control_block() { let tapscript = hex!("deadbeef"); let control_block = hex!("02"); // annex starting with 0x50 causes the branching logic. let annex = hex!("50"); + let signature = vec![0xff; 64]; let witness_vec = vec![tapscript.clone(), control_block.clone()]; - let witness_vec_annex = vec![tapscript.clone(), control_block.clone(), annex]; + let witness_vec_annex = vec![tapscript.clone(), control_block.clone(), annex.clone()]; + let witness_vec_key_spend_annex = vec![signature, annex]; let witness_serialized: Vec = serialize(&witness_vec); let witness_serialized_annex: Vec = serialize(&witness_vec_annex); + let witness_serialized_key_spend_annex: Vec = serialize(&witness_vec_key_spend_annex); let witness = deserialize::(&witness_serialized[..]).unwrap(); let witness_annex = deserialize::(&witness_serialized_annex[..]).unwrap(); + let witness_key_spend_annex = deserialize::(&witness_serialized_key_spend_annex[..]).unwrap(); // 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!(witness_key_spend_annex.taproot_control_block().is_none()) } #[test] diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs index f6a5a4735d..3ac16a596a 100644 --- a/bitcoin/src/crypto/key.rs +++ b/bitcoin/src/crypto/key.rs @@ -496,6 +496,20 @@ impl PrivateKey { inner: secp256k1::SecretKey::from_slice(&data[1..33])?, }) } + + /// 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 + /// with specific public key formats and BIP-340 requirements. + #[inline] + pub fn negate(&self) -> Self { + PrivateKey { + compressed: self.compressed, + network: self.network, + inner: self.inner.negate(), + } + } } impl fmt::Display for PrivateKey { @@ -841,9 +855,18 @@ impl TweakedPublicKey { TweakedPublicKey(key) } - /// Returns the underlying public key. + #[doc(hidden)] + #[deprecated(since="0.32.6", note="use to_x_only_public_key() instead")] pub fn to_inner(self) -> XOnlyPublicKey { self.0 } + /// 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 } + /// Serialize 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. @@ -860,9 +883,17 @@ impl TweakedKeypair { #[inline] pub fn dangerous_assume_tweaked(pair: Keypair) -> TweakedKeypair { TweakedKeypair(pair) } + #[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_inner(self) -> Keypair { self.0 } + 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] diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs index f27dbb27ba..294dac70e6 100644 --- a/bitcoin/src/crypto/sighash.rs +++ b/bitcoin/src/crypto/sighash.rs @@ -400,6 +400,16 @@ impl EcdsaSighashType { } } + /// Checks if the sighash type is [`Self::Single`] or [`Self::SinglePlusAnyoneCanPay`]. + /// + /// This matches Bitcoin Core's behavior where SIGHASH_SINGLE bug check is based on the base + /// type (after masking with 0x1f), regardless of the ANYONECANPAY flag. + /// + /// See: + pub fn is_single(&self) -> bool { + matches!(self, Self::Single | Self::SinglePlusAnyoneCanPay) + } + /// Creates a [`EcdsaSighashType`] from a raw `u32`. /// /// **Note**: this replicates consensus behaviour, for current standardness rules correctness @@ -1316,7 +1326,7 @@ impl std::error::Error for AnnexError { fn is_invalid_use_of_sighash_single(sighash: u32, input_index: usize, outputs_len: usize) -> bool { let ty = EcdsaSighashType::from_consensus(sighash); - ty == EcdsaSighashType::Single && input_index >= outputs_len + ty.is_single() && input_index >= outputs_len } /// Result of [`SighashCache::legacy_encode_signing_data_to`]. @@ -1453,8 +1463,6 @@ mod tests { #[test] fn sighash_single_bug() { - const SIGHASH_SINGLE: u32 = 3; - // We need a tx with more inputs than outputs. let tx = Transaction { version: transaction::Version::ONE, @@ -1465,10 +1473,16 @@ mod tests { let script = ScriptBuf::new(); let cache = SighashCache::new(&tx); - let got = cache.legacy_signature_hash(1, &script, SIGHASH_SINGLE).expect("sighash"); - let want = LegacySighash::from_slice(&UINT256_ONE).unwrap(); + let sighash_single = 3; + let got = cache.legacy_signature_hash(1, &script, sighash_single).expect("sighash"); + let want = LegacySighash::from_byte_array(UINT256_ONE); + assert_eq!(got, want); - assert_eq!(got, want) + // https://github.com/rust-bitcoin/rust-bitcoin/issues/4112 + let sighash_single = 131; + let got = cache.legacy_signature_hash(1, &script, sighash_single).expect("sighash"); + let want = LegacySighash::from_byte_array(UINT256_ONE); + assert_eq!(got, want); } #[test] diff --git a/bitcoin/src/network.rs b/bitcoin/src/network.rs index 3b0341f204..3b60fd98d1 100644 --- a/bitcoin/src/network.rs +++ b/bitcoin/src/network.rs @@ -59,11 +59,19 @@ impl From for NetworkKind { } /// The cryptocurrency network to act on. +/// +/// This is an exhaustive enum, meaning that we cannot add any future networks without defining a +/// new, incompatible version of this type. If you are using this type directly and wish to support the +/// new network, this will be a breaking change to your APIs and likely require changes in your code. +/// +/// If you are concerned about forward compatibility, consider using `T: Into` instead of +/// this type as a parameter to functions in your public API, or directly using the `Params` type. +// For extensive discussion on the usage of `non_exhaustive` please see: +// https://github.com/rust-bitcoin/rust-bitcoin/issues/2225 #[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] #[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] -#[non_exhaustive] pub enum Network { /// Mainnet Bitcoin. Bitcoin, diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs index 3d428fdac9..4089087f21 100644 --- a/bitcoin/src/psbt/mod.rs +++ b/bitcoin/src/psbt/mod.rs @@ -21,7 +21,7 @@ use std::collections::{HashMap, HashSet}; use internals::write_err; use secp256k1::{Keypair, Message, Secp256k1, Signing, Verification}; -use crate::bip32::{self, KeySource, Xpriv, Xpub}; +use crate::bip32::{self, DerivationPath, KeySource, Xpriv, Xpub}; use crate::blockdata::transaction::{self, Transaction, TxOut}; use crate::crypto::key::{PrivateKey, PublicKey}; use crate::crypto::{ecdsa, taproot}; @@ -423,6 +423,8 @@ impl Psbt { k.get_key(KeyRequest::Bip32(key_source.clone()), secp) { secret_key + } else if let Ok(Some(sk)) = k.get_key(KeyRequest::XOnlyPubkey(xonly), secp) { + sk } else { continue; }; @@ -442,7 +444,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, &key_pair); @@ -730,6 +732,8 @@ pub enum KeyRequest { Pubkey(PublicKey), /// Request a private key using BIP-32 fingerprint and derivation path. Bip32(KeySource), + /// Request a private key using the associated x-only public key. + XOnlyPubkey(XOnlyPublicKey), } /// Trait to get a private key from a key request, key is then used to sign an input. @@ -760,10 +764,18 @@ impl GetKey for Xpriv { ) -> Result, Self::Error> { match key_request { KeyRequest::Pubkey(_) => Err(GetKeyError::NotSupported), + KeyRequest::XOnlyPubkey(_) => Err(GetKeyError::NotSupported), KeyRequest::Bip32((fingerprint, path)) => { let key = if self.fingerprint(secp) == fingerprint { let k = self.derive_priv(secp, &path)?; Some(k.to_priv()) + } 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_priv(secp, &path)?; + Some(k.to_priv()) } else { None }; @@ -803,18 +815,11 @@ impl GetKey for $set { key_request: KeyRequest, secp: &Secp256k1 ) -> Result, Self::Error> { - match key_request { - KeyRequest::Pubkey(_) => Err(GetKeyError::NotSupported), - KeyRequest::Bip32((fingerprint, path)) => { - for xpriv in self.iter() { - if xpriv.parent_fingerprint == fingerprint { - let k = xpriv.derive_priv(secp, &path)?; - return Ok(Some(k.to_priv())); - } - } - Ok(None) - } - } + // OK to stop at the first error because Xpriv::get_key() can only fail + // if this isn't a KeyRequest::Bip32, which would fail for all Xprivs. + self.iter() + .find_map(|xpriv| xpriv.get_key(key_request.clone(), secp).transpose()) + .transpose() } }}} impl_get_key_for_set!(BTreeSet); @@ -822,7 +827,7 @@ impl_get_key_for_set!(BTreeSet); impl_get_key_for_set!(HashSet); #[rustfmt::skip] -macro_rules! impl_get_key_for_map { +macro_rules! impl_get_key_for_pubkey_map { ($map:ident) => { impl GetKey for $map { @@ -835,13 +840,67 @@ impl GetKey for $map { ) -> Result, Self::Error> { 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 key = self.get(&pubkey_even).cloned(); + + if key.is_some() { + return Ok(key); + } + + let pubkey_odd = PublicKey::new(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)); + } + + Ok(None) + }, + KeyRequest::Bip32(_) => Err(GetKeyError::NotSupported), + } + } +}}} +impl_get_key_for_pubkey_map!(BTreeMap); +#[cfg(feature = "std")] +impl_get_key_for_pubkey_map!(HashMap); + +#[rustfmt::skip] +macro_rules! impl_get_key_for_xonly_map { + ($map:ident) => { + +impl GetKey for $map { + type Error = GetKeyError; + + fn get_key( + &self, + key_request: KeyRequest, + secp: &Secp256k1, + ) -> Result, Self::Error> { + match key_request { + 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), } } }}} -impl_get_key_for_map!(BTreeMap); +impl_get_key_for_xonly_map!(BTreeMap); #[cfg(feature = "std")] -impl_get_key_for_map!(HashMap); +impl_get_key_for_xonly_map!(HashMap); /// Errors when getting a key. #[derive(Debug, Clone, PartialEq, Eq)] @@ -1208,7 +1267,14 @@ mod tests { use hashes::{hash160, ripemd160, sha256, Hash}; use hex::{test_hex_unwrap as hex, FromHex}; #[cfg(feature = "rand-std")] - use secp256k1::{All, SecretKey}; + use { + crate::bip32::{DerivationPath, Fingerprint}, + crate::key::WPubkeyHash, + crate::locktime, + crate::witness_version::WitnessVersion, + crate::WitnessProgram, + secp256k1::{All, SecretKey}, + }; use super::*; use crate::bip32::ChildNumber; @@ -2138,7 +2204,43 @@ mod tests { } #[test] - fn test_fee() { + #[cfg(feature = "rand-std")] + fn pubkey_map_get_key_negates_odd_parity_keys() { + use crate::psbt::{GetKey, KeyRequest}; + + let (mut priv_key, mut pk, secp) = gen_keys(); + let (xonly, parity) = pk.inner.x_only_public_key(); + + let mut pubkey_map: HashMap = HashMap::new(); + + if parity == secp256k1::Parity::Even { + priv_key = PrivateKey { + compressed: priv_key.compressed, + network: priv_key.network, + inner: priv_key.inner.negate(), + }; + pk = priv_key.public_key(&secp); + } + + pubkey_map.insert(pk, priv_key); + + let req_result = pubkey_map.get_key(KeyRequest::XOnlyPubkey(xonly), &secp).unwrap(); + + let retrieved_key = req_result.unwrap(); + + let retrieved_pub_key = retrieved_key.public_key(&secp); + let (retrieved_xonly, retrieved_parity) = retrieved_pub_key.inner.x_only_public_key(); + + assert_eq!(xonly, retrieved_xonly); + assert_eq!( + retrieved_parity, + secp256k1::Parity::Even, + "Key should be normalized to have even parity, even when original had odd parity" + ); + } + + #[test] + fn fee() { let output_0_val = Amount::from_sat(99_999_699); let output_1_val = Amount::from_sat(100_000_000); let prev_output_val = Amount::from_sat(200_000_000); @@ -2248,11 +2350,73 @@ mod tests { #[test] #[cfg(feature = "rand-std")] - fn sign_psbt() { - use crate::bip32::{DerivationPath, Fingerprint}; - use crate::witness_version::WitnessVersion; - use crate::{WPubkeyHash, WitnessProgram}; + fn hashmap_can_sign_taproot() { + let (priv_key, pk, secp) = gen_keys(); + let internal_key: XOnlyPublicKey = pk.inner.into(); + + let tx = Transaction { + version: transaction::Version::TWO, + lock_time: locktime::absolute::LockTime::ZERO, + input: vec![TxIn::default()], + output: vec![TxOut { value: Amount::ZERO, script_pubkey: ScriptBuf::new() }], + }; + + let mut psbt = Psbt::from_unsigned_tx(tx).unwrap(); + psbt.inputs[0].tap_internal_key = Some(internal_key); + psbt.inputs[0].witness_utxo = Some(transaction::TxOut { + value: Amount::from_sat(10), + script_pubkey: ScriptBuf::new_p2tr(&secp, internal_key, None), + }); + + let mut key_map: HashMap = HashMap::new(); + key_map.insert(pk, priv_key); + let key_source = (Fingerprint::default(), DerivationPath::default()); + let mut tap_key_origins = std::collections::BTreeMap::new(); + tap_key_origins.insert(internal_key, (vec![], key_source)); + psbt.inputs[0].tap_key_origins = tap_key_origins; + + let signing_keys = psbt.sign(&key_map, &secp).unwrap(); + assert_eq!(signing_keys.len(), 1); + assert_eq!(signing_keys[&0], SigningKeys::Schnorr(vec![internal_key])); + } + + #[test] + #[cfg(feature = "rand-std")] + fn xonly_hashmap_can_sign_taproot() { + let (priv_key, pk, secp) = gen_keys(); + let internal_key: XOnlyPublicKey = pk.inner.into(); + + let tx = Transaction { + version: transaction::Version::TWO, + lock_time: locktime::absolute::LockTime::ZERO, + input: vec![TxIn::default()], + output: vec![TxOut { value: Amount::ZERO, script_pubkey: ScriptBuf::new() }], + }; + + let mut psbt = Psbt::from_unsigned_tx(tx).unwrap(); + psbt.inputs[0].tap_internal_key = Some(internal_key); + psbt.inputs[0].witness_utxo = Some(transaction::TxOut { + value: Amount::from_sat(10), + script_pubkey: ScriptBuf::new_p2tr(&secp, internal_key, None), + }); + + let mut xonly_key_map: HashMap = HashMap::new(); + xonly_key_map.insert(internal_key, priv_key); + + let key_source = (Fingerprint::default(), DerivationPath::default()); + let mut tap_key_origins = std::collections::BTreeMap::new(); + tap_key_origins.insert(internal_key, (vec![], key_source)); + psbt.inputs[0].tap_key_origins = tap_key_origins; + + let signing_keys = psbt.sign(&xonly_key_map, &secp).unwrap(); + assert_eq!(signing_keys.len(), 1); + assert_eq!(signing_keys[&0], SigningKeys::Schnorr(vec![internal_key])); + } + + #[test] + #[cfg(feature = "rand-std")] + fn sign_psbt() { let unsigned_tx = Transaction { version: transaction::Version::TWO, lock_time: absolute::LockTime::ZERO, diff --git a/bitcoin/src/taproot/mod.rs b/bitcoin/src/taproot/mod.rs index 43d3cca47b..f44e318acb 100644 --- a/bitcoin/src/taproot/mod.rs +++ b/bitcoin/src/taproot/mod.rs @@ -158,7 +158,16 @@ pub const TAPROOT_CONTROL_BASE_SIZE: usize = 33; pub const TAPROOT_CONTROL_MAX_SIZE: usize = TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * TAPROOT_CONTROL_MAX_NODE_COUNT; -// type alias for versioned tap script corresponding merkle proof +/// The leaf script with its version. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct LeafScript { + /// The version of the script. + pub version: LeafVersion, + /// The script, usually `ScriptBuf` or `&Script`. + pub script: S, +} + +// type alias for versioned tap script corresponding Merkle proof type ScriptMerkleProofMap = BTreeMap<(ScriptBuf, LeafVersion), BTreeSet>; /// Represents taproot spending information. @@ -1547,7 +1556,7 @@ mod test { let control_block = ControlBlock::decode(&Vec::::from_hex(control_block_hex).unwrap()).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] @@ -1654,7 +1663,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 )) } @@ -1729,7 +1738,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 )) } @@ -1845,7 +1854,7 @@ mod test { 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/units/src/fee_rate.rs b/units/src/fee_rate.rs index 9dcb88ddbe..da686fdb23 100644 --- a/units/src/fee_rate.rs +++ b/units/src/fee_rate.rs @@ -37,10 +37,10 @@ impl FeeRate { /// Minimum fee rate required to broadcast a transaction. /// /// The value matches the default Bitcoin Core policy at the time of library release. - pub const BROADCAST_MIN: FeeRate = FeeRate::from_sat_per_vb_unchecked(1); + pub const BROADCAST_MIN: FeeRate = FeeRate::from_sat_per_vb_u32(1); /// Fee rate used to compute dust amount. - pub const DUST: FeeRate = FeeRate::from_sat_per_vb_unchecked(3); + pub const DUST: FeeRate = FeeRate::from_sat_per_vb_u32(3); /// Constructs `FeeRate` from satoshis per 1000 weight units. pub const fn from_sat_per_kwu(sat_kwu: u64) -> Self { FeeRate(sat_kwu) } @@ -57,7 +57,14 @@ impl FeeRate { Some(FeeRate(sat_vb.checked_mul(1000 / 4)?)) } + /// Constructs a new [`FeeRate`] from satoshis per virtual bytes. + pub const fn from_sat_per_vb_u32(sat_vb: u32) -> Self { + let sat_vb = sat_vb as u64; // No `Into` in const context. + FeeRate(sat_vb * (1000 / 4)) + } + /// Constructs `FeeRate` from satoshis per virtual bytes without overflow check. + #[deprecated(since = "0.32.7", note = "use from_sat_per_vb_u32 instead")] pub const fn from_sat_per_vb_unchecked(sat_vb: u64) -> Self { FeeRate(sat_vb * (1000 / 4)) } /// Returns raw fee rate. @@ -168,16 +175,17 @@ mod tests { fn fee_rate_from_sat_per_vb_overflow_test() { let fee_rate = FeeRate::from_sat_per_vb(u64::MAX); assert!(fee_rate.is_none()); - } + } #[test] - fn from_sat_per_vb_unchecked_test() { - let fee_rate = FeeRate::from_sat_per_vb_unchecked(10); + fn from_sat_per_vb_u32() { + let fee_rate = FeeRate::from_sat_per_vb_u32(10); assert_eq!(FeeRate(2500), fee_rate); } #[test] #[cfg(debug_assertions)] + #[allow(deprecated)] // Keep test until we remove the function. #[should_panic] fn from_sat_per_vb_unchecked_panic_test() { FeeRate::from_sat_per_vb_unchecked(u64::MAX); }