From 068c3f24e96f2571630dbe44db445084d60a6cb2 Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Tue, 25 Feb 2025 07:40:46 +0800 Subject: [PATCH 01/18] Fix `is_invalid_use_of_sighash_single()` incompatibility with Bitcoin Core --- bitcoin/src/crypto/sighash.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs index f27dbb27ba..17cc0a37cd 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`]. From 18c2cad578fd4383411f1b958caa90217d78d590 Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Tue, 25 Feb 2025 07:38:36 +0800 Subject: [PATCH 02/18] Add test for sighash_single_bug incompatility fix --- bitcoin/src/crypto/sighash.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs index 17cc0a37cd..294dac70e6 100644 --- a/bitcoin/src/crypto/sighash.rs +++ b/bitcoin/src/crypto/sighash.rs @@ -1463,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, @@ -1475,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] From 39e280a2b61fb10900c483485dbceb5296b87020 Mon Sep 17 00:00:00 2001 From: Martin Habovstiak Date: Fri, 21 Feb 2025 14:59:55 +0100 Subject: [PATCH 03/18] Fix key/script spend detection in `Witness` The `taproot_control_block` did not properly detect whether it deals with script spend or key spend. As a result, if key spend with annex was used it'd return the first element (the signature) as if it was a control block. Further, the conditions identifying which kind of spend it was were repeated multiple times but behaved subtly differently making only `taproot_control_block` buggy but the other places confusing. To resolve these issues this change adds a `P2TrSpend` enum that represents a parsed witness and has a single method doing all the parsing. The other methods can then be trivially implemented by matching on that type. This way only one place needs to be verified and the parsing code is more readable since it uses one big `match` to handle all possibilities. The downside of this is a potential perf impact if the parsing code doesn't get inlined since the common parsing code has to shuffle around data that the caller is not intersted in. I don't think this will be a problem but if it will I suppose it will be solvable (e.g. by using `#[inline(always)]`). The enum also looks somewhat nice and perhaps downstream consumers could make use of it. This change does not expose it yet but is written such that after exposing it the API would be (mostly) idiomatic. Closes #4097 --- bitcoin/src/blockdata/witness.rs | 141 ++++++++++++++++++++++--------- 1 file changed, 103 insertions(+), 38 deletions(-) diff --git a/bitcoin/src/blockdata/witness.rs b/bitcoin/src/blockdata/witness.rs index 0737a678a1..024418a0e5 100644 --- a/bitcoin/src/blockdata/witness.rs +++ b/bitcoin/src/blockdata/witness.rs @@ -399,57 +399,40 @@ impl Witness { /// /// 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. pub fn tapscript(&self) -> Option<&Script> { - if self.is_empty() { - return None; - } - - if self.taproot_annex().is_some() { - self.third_to_last().map(Script::from_bytes) - } else { - self.second_to_last().map(Script::from_bytes) - } + 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, + } } /// 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 +451,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]; From 74138d56b1a8bdeb0c9ce0084c630f4193a03dd3 Mon Sep 17 00:00:00 2001 From: Martin Habovstiak Date: Fri, 21 Feb 2025 15:34:39 +0100 Subject: [PATCH 04/18] Add a test case checking `taproot_control_block` The previous commit fixed a bug when `taproot_control_block` returned `Some` on key-spends. This adds a test case for it which succeeds when applied after the previous commit and fails if applied before it. --- bitcoin/src/blockdata/witness.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bitcoin/src/blockdata/witness.rs b/bitcoin/src/blockdata/witness.rs index 024418a0e5..de36c15eb0 100644 --- a/bitcoin/src/blockdata/witness.rs +++ b/bitcoin/src/blockdata/witness.rs @@ -848,19 +848,24 @@ mod test { 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] From 730baeb4e82a75b3e542124b0b34bfc325734f8b Mon Sep 17 00:00:00 2001 From: Martin Habovstiak Date: Fri, 21 Feb 2025 16:33:00 +0100 Subject: [PATCH 05/18] Add `taproot_leaf_script` methood to `Witness` We already have `tapscript` method on `Witness` which is broken because it doesn't check that the leaf script is a tapscript, however that behavior might have been intended by some consumers who want to inspect the script independent of the version. To resolve the confusion, we're going to add a new method that returns both the leaf script and, to avoid forgetting version check, also the leaf version. This doesn't touch the `tapscript` method yet to make backporting of this commit easier. It's also worth noting that leaf script is often used together with version. To make passing them around easier it'd be helpful to use a separate type. Thus this also adds a public POD type containing the script and the version. In anticipation of if being usable in different APIs it's also generic over the script type. Similarly to the `tapscript` method, this also only adds the type and doesn't change other functions to use it yet. Only the newly added `taproot_leaf_script` method uses it now. This is a part of #4073 --- bitcoin/src/blockdata/witness.rs | 44 +++++++++++++++++++++++++++++++- bitcoin/src/taproot/mod.rs | 11 +++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/bitcoin/src/blockdata/witness.rs b/bitcoin/src/blockdata/witness.rs index de36c15eb0..6d159e0ae7 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]. @@ -412,6 +412,22 @@ impl Witness { } } + /// 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 @@ -842,6 +858,32 @@ 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"); diff --git a/bitcoin/src/taproot/mod.rs b/bitcoin/src/taproot/mod.rs index 60ce28ee09..656a3ccbfb 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. From 9e87bc5b2cb39b6b7079507abfc0c1603a13d381 Mon Sep 17 00:00:00 2001 From: Martin Habovstiak Date: Fri, 21 Feb 2025 18:09:29 +0100 Subject: [PATCH 06/18] Deprecate the `Witness::tapscript` method Now that an alternative exists we can deprecate the method with an expalantion of what's going on. --- bitcoin/src/blockdata/witness.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/bitcoin/src/blockdata/witness.rs b/bitcoin/src/blockdata/witness.rs index 6d159e0ae7..77f426d65d 100644 --- a/bitcoin/src/blockdata/witness.rs +++ b/bitcoin/src/blockdata/witness.rs @@ -395,13 +395,22 @@ 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`] 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> { match P2TrSpend::from_witness(self) { // Note: the method is named "tapscript" but historically it was actually returning From 315750deb3a74f82f28c9b388f0f57a38ebd3e62 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Sat, 3 May 2025 13:08:15 +0000 Subject: [PATCH 07/18] bip32: return error when attempting to derive past maximum depth The corresponding PR on master is #4387, but this doesn't really resemble that PR. Rather than changing all the error enums, this just adds a new variant to the #[non_exhaustive] bip32::Error enum and returns that when adding 1 to 255 when deriving child keys. This is therefore not an API break and can be released in a minor version. Although it does change the error return on private derivation from a "succeeds except with negligible probability" to "there is an error path you may need to check for". Fixes #4308 --- bitcoin/src/bip32.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) 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, From b75b2e36496674d2dd9964c726dbdd50b3d1ed01 Mon Sep 17 00:00:00 2001 From: Nadav Ivgi Date: Fri, 13 Sep 2024 11:28:17 +0300 Subject: [PATCH 08/18] Fix GetKey for sets to properly compare the fingerprint --- bitcoin/src/psbt/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs index 3d428fdac9..7c42d6dd34 100644 --- a/bitcoin/src/psbt/mod.rs +++ b/bitcoin/src/psbt/mod.rs @@ -807,7 +807,7 @@ impl GetKey for $set { KeyRequest::Pubkey(_) => Err(GetKeyError::NotSupported), KeyRequest::Bip32((fingerprint, path)) => { for xpriv in self.iter() { - if xpriv.parent_fingerprint == fingerprint { + if xpriv.fingerprint(secp) == fingerprint { let k = xpriv.derive_priv(secp, &path)?; return Ok(Some(k.to_priv())); } From d005ddd5249909bd8919a169abb5c7d6d2386ec1 Mon Sep 17 00:00:00 2001 From: Nadav Ivgi Date: Fri, 13 Sep 2024 11:35:32 +0300 Subject: [PATCH 09/18] Refactor GetKey for sets to internally use Xpriv::get_key() --- bitcoin/src/psbt/mod.rs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs index 7c42d6dd34..7fd497b9dc 100644 --- a/bitcoin/src/psbt/mod.rs +++ b/bitcoin/src/psbt/mod.rs @@ -803,18 +803,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.fingerprint(secp) == 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); From 2858b6cf801def1800a2cfd3287a38633d194a49 Mon Sep 17 00:00:00 2001 From: Nadav Ivgi Date: Fri, 13 Sep 2024 11:46:07 +0300 Subject: [PATCH 10/18] Support GetKey where the Xpriv is a direct child of the looked up KeySource --- bitcoin/src/psbt/mod.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs index 7fd497b9dc..69a0cf1cbf 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}; @@ -764,6 +764,13 @@ impl GetKey for Xpriv { 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 }; From 95eb2556b93da25a402e82eab1e032a0ececb67e Mon Sep 17 00:00:00 2001 From: Erick Cestari Date: Fri, 14 Mar 2025 10:25:28 -0300 Subject: [PATCH 11/18] Add XOnlyPublicKey support for PSBT key retrieval and improve Taproot signing This commit enhances PSBT signing functionality by: 1. Added new KeyRequest::XOnlyPubkey variant to support direct retrieval using XOnly public keys 2. Implemented GetKey for HashMap for more efficient Taproot key management 3. Modified HashMap implementation to handle XOnlyPublicKey requests by checking both even and odd parity variants These changes allow for more flexible key management in Taproot transactions. Specifically, wallet implementations can now store keys indexed by either PublicKey or XOnlyPublicKey and successfully sign PSBTs with Taproot inputs. Added tests for both implementations to verify correct behavior. Added test for odd parity key retrieval. Closes #4150 --- bitcoin/src/crypto/key.rs | 14 +++ bitcoin/src/psbt/mod.rs | 182 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 187 insertions(+), 9 deletions(-) diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs index f6a5a4735d..c65b432bf4 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 { diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs index 69a0cf1cbf..278d11ddfd 100644 --- a/bitcoin/src/psbt/mod.rs +++ b/bitcoin/src/psbt/mod.rs @@ -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; }; @@ -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,6 +764,7 @@ 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)?; @@ -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, From c67adcd64e9b425f2787629d50ecbcce465e1f5e Mon Sep 17 00:00:00 2001 From: Shing Him Ng Date: Sun, 20 Apr 2025 13:19:15 -0500 Subject: [PATCH 12/18] backport: Add methods to retrieve inner types Backport #4373, authored by Shing Him Ng. Original gitlog: For TweakedKeypair, `to_inner` is also renamed to `to_keypair` to maintain consistency. Similarly, `to_inner` is renamed to `to_x_only_pubkey` for TweakedPublicKey Co-authored-by: Shing Him Ng --- bitcoin/examples/sign-tx-taproot.rs | 2 +- bitcoin/examples/taproot-psbt.rs | 2 +- .../src/blockdata/script/witness_program.rs | 4 ++-- bitcoin/src/crypto/key.rs | 21 +++++++++++++++++-- bitcoin/src/psbt/mod.rs | 2 +- bitcoin/src/taproot/mod.rs | 8 +++---- 6 files changed, 28 insertions(+), 11 deletions(-) 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/blockdata/script/witness_program.rs b/bitcoin/src/blockdata/script/witness_program.rs index 9b76becc86..7a661f21c0 100644 --- a/bitcoin/src/blockdata/script/witness_program.rs +++ b/bitcoin/src/blockdata/script/witness_program.rs @@ -90,13 +90,13 @@ 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) } diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs index f6a5a4735d..a44a21b2e4 100644 --- a/bitcoin/src/crypto/key.rs +++ b/bitcoin/src/crypto/key.rs @@ -841,9 +841,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 +869,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/psbt/mod.rs b/bitcoin/src/psbt/mod.rs index 3d428fdac9..695af00114 100644 --- a/bitcoin/src/psbt/mod.rs +++ b/bitcoin/src/psbt/mod.rs @@ -442,7 +442,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); diff --git a/bitcoin/src/taproot/mod.rs b/bitcoin/src/taproot/mod.rs index 656a3ccbfb..54576c5786 100644 --- a/bitcoin/src/taproot/mod.rs +++ b/bitcoin/src/taproot/mod.rs @@ -1556,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] @@ -1663,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 )) } @@ -1738,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 )) } @@ -1854,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); From 916982a025d12ab98c9c878d33aae60cfb98102b Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 6 May 2025 09:21:12 +1000 Subject: [PATCH 13/18] bitcoin: Bump version to 0.32.6 In preparation for release bump the version, add a changelog entry, and update the lock files. --- Cargo-minimal.lock | 2 +- Cargo-recent.lock | 2 +- bitcoin/CHANGELOG.md | 8 ++++++++ bitcoin/Cargo.toml | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index f28cbe79f0..a335ef716a 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -47,7 +47,7 @@ dependencies = [ [[package]] name = "bitcoin" -version = "0.32.5" +version = "0.32.6" dependencies = [ "base58ck", "base64", diff --git a/Cargo-recent.lock b/Cargo-recent.lock index dde2e6b318..65a7f1f370 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -46,7 +46,7 @@ dependencies = [ [[package]] name = "bitcoin" -version = "0.32.5" +version = "0.32.6" dependencies = [ "base58ck", "base64", diff --git a/bitcoin/CHANGELOG.md b/bitcoin/CHANGELOG.md index 1c5b03aae5..c02599a3a7 100644 --- a/bitcoin/CHANGELOG.md +++ b/bitcoin/CHANGELOG.md @@ -1,3 +1,11 @@ +# 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 - 2024-11-27 - Backport - Re-export `bech32` crate [#3662](https://github.com/rust-bitcoin/rust-bitcoin/pull/3662) diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml index 70594e093d..7cc56d1245 100644 --- a/bitcoin/Cargo.toml +++ b/bitcoin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bitcoin" -version = "0.32.5" +version = "0.32.6" authors = ["Andrew Poelstra "] license = "CC0-1.0" repository = "https://github.com/rust-bitcoin/rust-bitcoin/" From c7b20f411ad03232a9b68455f7856638fe0bd413 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Sat, 24 May 2025 11:54:09 +1000 Subject: [PATCH 14/18] backport: Use _u32 in FeeRate constructor instead of _unchecked Manually backport #4538. If we use a `u32` then the constructor no longer panics. 32 bits is plenty for an sane usage. --- units/src/fee_rate.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) 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); } From c2481e4e824fddd86cef50d73f97ad590be2c55f Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 8 Jul 2025 15:38:57 +1000 Subject: [PATCH 15/18] backport: Add support for pay to anchor outputs Manually backport PR #4111. Of note, here we put `new_p2a` on `ScriptBuf` instead of on the `script::Builder` because that seems to be where all the other `new_foo` methods are in this release. Note the `WitnessProgram::p2a` is conditionally const on Rust `v1.61` because MSRV is only `v1.56.1`. From the original patch: Add support for the newly created Pay2Anchor output-type. See https://github.com/bitcoin/bitcoin/pull/30352 --- bitcoin/Cargo.toml | 2 +- bitcoin/src/address/mod.rs | 24 +++++++++++++++++++ bitcoin/src/blockdata/script/owned.rs | 12 +++++++--- .../src/blockdata/script/witness_program.rs | 15 ++++++++++++ 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml index 7cc56d1245..dcfd8959ed 100644 --- a/bitcoin/Cargo.toml +++ b/bitcoin/Cargo.toml @@ -81,4 +81,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/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/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 7a661f21c0..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 @@ -100,6 +103,13 @@ impl WitnessProgram { 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. From 3cf4a916e3599f2ede002d9358c7776571dcc70c Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 2 Jul 2025 09:36:47 +1000 Subject: [PATCH 16/18] Remove non_exhausive from Network Issue #2225 is long and has many valid opposing opinions. The main argument for having non_exhaustive is that it helps future proof the ecosystem at the cost of pain now. The main argument against having non_exhaustive is why have pain now when adding a network is so rare that having pain then is ok. At the end of the thread Andrew posts: > I continue to think we should have an exhaustive enum, with a bunch of > documentation about how to use it properly. I am warming up to the > "don't have an enum, just have rules for defining your own" but I think > this would be needless work for people who just want to grab an > off-the-shelf set of networks or people who want to make their own enum > but want to see an example of how to do it first. In order to make some forward progress lets remove the `non_exhaustive` now and backport this change to 0.32, 0.31, an 0.30. Later we can add, and release in 0.33, whatever forward protection / libapocalyse protection we want to add. This removes the pain now and gives us a path to prevent future pain - that should keep all parties happy. --- bitcoin/src/network.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bitcoin/src/network.rs b/bitcoin/src/network.rs index f16f2d5c7f..8df48b0bef 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, From 571cd7f33ec82e7febd708d0e5984b6367f7a361 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 30 Jul 2025 11:10:31 +1000 Subject: [PATCH 17/18] bitcoin: Bump version to 0.32.7 In preparation for release bump the version, add a changelog entry, and update the lock files. --- Cargo-minimal.lock | 2 +- Cargo-recent.lock | 2 +- bitcoin/CHANGELOG.md | 6 ++++++ bitcoin/Cargo.toml | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index a335ef716a..8b47f1e59c 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -47,7 +47,7 @@ dependencies = [ [[package]] name = "bitcoin" -version = "0.32.6" +version = "0.32.7" dependencies = [ "base58ck", "base64", diff --git a/Cargo-recent.lock b/Cargo-recent.lock index 65a7f1f370..9f49d4c0c0 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -46,7 +46,7 @@ dependencies = [ [[package]] name = "bitcoin" -version = "0.32.6" +version = "0.32.7" dependencies = [ "base58ck", "base64", diff --git a/bitcoin/CHANGELOG.md b/bitcoin/CHANGELOG.md index c02599a3a7..caa0de7267 100644 --- a/bitcoin/CHANGELOG.md +++ b/bitcoin/CHANGELOG.md @@ -1,3 +1,9 @@ +# 0.32.7 - 2025-07-30 + +- 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) diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml index dcfd8959ed..1dc142e0ad 100644 --- a/bitcoin/Cargo.toml +++ b/bitcoin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bitcoin" -version = "0.32.6" +version = "0.32.7" authors = ["Andrew Poelstra "] license = "CC0-1.0" repository = "https://github.com/rust-bitcoin/rust-bitcoin/" From f835efce5b1974b7ee0d49a5c45f8e3d419317c6 Mon Sep 17 00:00:00 2001 From: Mathieu Ducroux Date: Tue, 23 Dec 2025 14:31:32 +0100 Subject: [PATCH 18/18] update lock files --- Cargo-minimal.lock | 155 +++++++++++++++++++++++++++++++++++++++++++-- Cargo-recent.lock | 147 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 291 insertions(+), 11 deletions(-) diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index 8b47f1e59c..c345dc31f0 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -46,8 +46,8 @@ dependencies = [ ] [[package]] -name = "bitcoin" -version = "0.32.7" +name = "bitcoin-dogecoin" +version = "0.32.5-doge.1" dependencies = [ "base58ck", "base64", @@ -62,6 +62,7 @@ dependencies = [ "hex_lit", "mutagen", "ordered", + "scrypt", "secp256k1", "serde", "serde_json", @@ -72,7 +73,7 @@ dependencies = [ name = "bitcoin-fuzz" version = "0.0.1" dependencies = [ - "bitcoin", + "bitcoin-dogecoin", "honggfuzz", "serde", "serde_cbor", @@ -121,6 +122,15 @@ dependencies = [ "cc", ] +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "byteorder" version = "1.3.0" @@ -139,19 +149,75 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + [[package]] name = "dyn-clone" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0da518043f6481364cd454be81dfe096cfd3f82daa1466f4946d24ea325b0941" +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4" dependencies = [ - "cfg-if", + "cfg-if 0.1.2", "libc", "wasi", ] @@ -177,6 +243,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "honggfuzz" version = "0.5.55" @@ -188,6 +263,15 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "itoa" version = "0.4.3" @@ -208,9 +292,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.64" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dfca3d9957906e8d1e6a0b641dc9a59848e793f1da2165889fd4f62d10d79c" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "memmap2" @@ -260,6 +344,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f0642533dea0bb58bd5cae31bafc1872429f0f12ac8c61fe2b4ba44f80b959b" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "ppv-lite86" version = "0.2.8" @@ -339,6 +433,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "schemars" version = "0.8.3" @@ -350,6 +453,17 @@ dependencies = [ "serde_json", ] +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2", + "salsa20", + "sha2", +] + [[package]] name = "secp256k1" version = "0.29.0" @@ -428,6 +542,23 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if 1.0.4", + "cpufeatures", + "digest", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.109" @@ -439,12 +570,24 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" diff --git a/Cargo-recent.lock b/Cargo-recent.lock index 9f49d4c0c0..a0d92c7466 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -45,8 +45,8 @@ dependencies = [ ] [[package]] -name = "bitcoin" -version = "0.32.7" +name = "bitcoin-dogecoin" +version = "0.32.5-doge.1" dependencies = [ "base58ck", "base64", @@ -61,6 +61,7 @@ dependencies = [ "hex_lit", "mutagen", "ordered", + "scrypt", "secp256k1", "serde", "serde_json", @@ -71,7 +72,7 @@ dependencies = [ name = "bitcoin-fuzz" version = "0.0.1" dependencies = [ - "bitcoin", + "bitcoin-dogecoin", "honggfuzz", "serde", "serde_cbor", @@ -120,6 +121,15 @@ dependencies = [ "cc", ] +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "byteorder" version = "1.4.3" @@ -138,12 +148,62 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + [[package]] name = "dyn-clone" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.9" @@ -176,6 +236,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "honggfuzz" version = "0.5.55" @@ -187,6 +256,15 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "itoa" version = "1.0.6" @@ -207,9 +285,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.142" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "memmap2" @@ -259,6 +337,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f0642533dea0bb58bd5cae31bafc1872429f0f12ac8c61fe2b4ba44f80b959b" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -328,6 +416,15 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "schemars" version = "0.8.12" @@ -339,6 +436,17 @@ dependencies = [ "serde_json", ] +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2", + "salsa20", + "sha2", +] + [[package]] name = "secp256k1" version = "0.29.0" @@ -417,6 +525,23 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.109" @@ -428,12 +553,24 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1"