Skip to content

Commit

Permalink
bitcoin: Depend on tip of master
Browse files Browse the repository at this point in the history
  • Loading branch information
tcharding committed Mar 21, 2024
1 parent e4a5bff commit 007c38b
Show file tree
Hide file tree
Showing 14 changed files with 170 additions and 112 deletions.
35 changes: 31 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ edition = "2018"
[features]
default = ["std"]
std = ["bitcoin/std", "bitcoin/secp-recovery", "bech32/std"]
no-std = ["bitcoin/no-std", "bech32/alloc"]
no-std = ["bech32/alloc"]
compiler = []
trace = []

Expand All @@ -23,15 +23,15 @@ base64 = ["bitcoin/base64"]

[dependencies]
bech32 = { version = "0.11.0", default-features = false }
bitcoin = { version = "0.31.0", default-features = false }
bitcoin = { version = "0.32.0", default-features = false }

# Do NOT use this as a feature! Use the `serde` feature instead.
actual-serde = { package = "serde", version = "1.0.103", optional = true }

[dev-dependencies]
serde_test = "1.0.147"
bitcoin = { version = "0.31.0", features = ["base64"] }
secp256k1 = {version = "0.28.0", features = ["rand-std"]}
bitcoin = { version = "0.32.0", features = ["base64"] }
secp256k1 = {version = "0.29.0", features = ["rand-std"]}

[[example]]
name = "htlc"
Expand Down Expand Up @@ -68,3 +68,30 @@ required-features = ["std", "base64", "compiler"]
[workspace]
members = ["bitcoind-tests", "fuzz"]
exclude = ["embedded"]

[patch.crates-io.secp256k1]
path = "/home/tobin/build/github.com/tcharding/rust-secp256k1/test-bitcoin"

[patch.crates-io.bitcoind]
path = "/home/tobin/build/github.com/tcharding/bitcoind/test-bitcoin"

[patch.crates-io.bitcoincore-rpc]
path = "/home/tobin/build/github.com/tcharding/rust-bitcoincore-rpc/test-bitcoin/client"

[patch.crates-io.base58ck]
path = "/home/tobin/build/github.com/tcharding/rust-bitcoin/release/base58"

[patch.crates-io.bitcoin]
path = "/home/tobin/build/github.com/tcharding/rust-bitcoin/release/bitcoin"

[patch.crates-io.bitcoin_hashes]
path = "/home/tobin/build/github.com/tcharding/rust-bitcoin/release/hashes"

[patch.crates-io.bitcoin-internals]
path = "/home/tobin/build/github.com/tcharding/rust-bitcoin/release/internals"

[patch.crates-io.bitcoin-io]
path = "/home/tobin/build/github.com/tcharding/rust-bitcoin/release/io"

[patch.crates-io.bitcoin-units]
path = "/home/tobin/build/github.com/tcharding/rust-bitcoin/release/units"
4 changes: 2 additions & 2 deletions bitcoind-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ publish = false

[dependencies]
miniscript = {path = "../"}
bitcoind = { version = "0.34.0" }
bitcoind = { version = "0.35.0" }
actual-rand = { package = "rand", version = "0.8.4"}
secp256k1 = {version = "0.28.0", features = ["rand-std"]}
secp256k1 = {version = "0.29.0", features = ["rand-std"]}
4 changes: 2 additions & 2 deletions examples/sign_multisig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,14 @@ fn list_of_three_arbitrary_public_keys() -> Vec<bitcoin::PublicKey> {
// a valid signature for this transaction; Miniscript does not verify the validity.
fn random_signature_from_the_blockchain() -> ecdsa::Signature {
ecdsa::Signature {
sig: secp256k1::ecdsa::Signature::from_str(
signature: secp256k1::ecdsa::Signature::from_str(
"3045\
0221\
00f7c3648c390d87578cd79c8016940aa8e3511c4104cb78daa8fb8e429375efc1\
0220\
531d75c136272f127a5dc14acc0722301cbddc222262934151f140da345af177",
)
.unwrap(),
hash_ty: bitcoin::sighash::EcdsaSighashType::All,
sighash_type: bitcoin::sighash::EcdsaSighashType::All,
}
}
4 changes: 2 additions & 2 deletions examples/verify_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ fn main() {

let iter = interpreter.iter_custom(Box::new(|key_sig: &KeySigPair| {
let (pk, ecdsa_sig) = key_sig.as_ecdsa().expect("Ecdsa Sig");
ecdsa_sig.hash_ty == bitcoin::sighash::EcdsaSighashType::All
ecdsa_sig.sighash_type == bitcoin::sighash::EcdsaSighashType::All
&& secp
.verify_ecdsa(&message, &ecdsa_sig.sig, &pk.inner)
.verify_ecdsa(&message, &ecdsa_sig.signature, &pk.inner)
.is_ok()
}));

Expand Down
85 changes: 42 additions & 43 deletions src/descriptor/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use core::str::FromStr;
use std::error;

use bitcoin::bip32::{self, XKeyIdentifier};
use bitcoin::hashes::hex::FromHex;
use bitcoin::hashes::{hash160, ripemd160, sha256, Hash, HashEngine};
use bitcoin::key::XOnlyPublicKey;
use bitcoin::secp256k1::{Secp256k1, Signing, Verification};
Expand Down Expand Up @@ -906,21 +905,21 @@ impl<K: InnerXKey> DescriptorXKey<K> {
/// assert_eq!(
/// xpub.matches(&(
/// bip32::Fingerprint::from_str("d34db33f").or(Err(()))?,
/// bip32::DerivationPath::from_str("m/44'/0'/0'/1/42").or(Err(()))?
/// bip32::DerivationPath::from_str("44'/0'/0'/1/42").or(Err(()))?
/// ), &ctx),
/// Some(bip32::DerivationPath::from_str("m/44'/0'/0'/1").or(Err(()))?)
/// Some(bip32::DerivationPath::from_str("44'/0'/0'/1").or(Err(()))?)
/// );
/// assert_eq!(
/// xpub.matches(&(
/// bip32::Fingerprint::from_str("ffffffff").or(Err(()))?,
/// bip32::DerivationPath::from_str("m/44'/0'/0'/1/42").or(Err(()))?
/// bip32::DerivationPath::from_str("44'/0'/0'/1/42").or(Err(()))?
/// ), &ctx),
/// None
/// );
/// assert_eq!(
/// xpub.matches(&(
/// bip32::Fingerprint::from_str("d34db33f").or(Err(()))?,
/// bip32::DerivationPath::from_str("m/44'/0'/0'/100/0").or(Err(()))?
/// bip32::DerivationPath::from_str("44'/0'/0'/100/0").or(Err(()))?
/// ), &ctx),
/// None
/// );
Expand Down Expand Up @@ -1234,17 +1233,17 @@ mod test {
fn test_wildcard() {
let public_key = DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2").unwrap();
assert_eq!(public_key.master_fingerprint().to_string(), "abcdef00");
assert_eq!(public_key.full_derivation_path().unwrap().to_string(), "m/0'/1'/2");
assert_eq!(public_key.full_derivation_path().unwrap().to_string(), "0'/1'/2");
assert!(!public_key.has_wildcard());

let public_key = DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/*").unwrap();
assert_eq!(public_key.master_fingerprint().to_string(), "abcdef00");
assert_eq!(public_key.full_derivation_path().unwrap().to_string(), "m/0'/1'");
assert_eq!(public_key.full_derivation_path().unwrap().to_string(), "0'/1'");
assert!(public_key.has_wildcard());

let public_key = DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/*h").unwrap();
assert_eq!(public_key.master_fingerprint().to_string(), "abcdef00");
assert_eq!(public_key.full_derivation_path().unwrap().to_string(), "m/0'/1'");
assert_eq!(public_key.full_derivation_path().unwrap().to_string(), "0'/1'");
assert!(public_key.has_wildcard());
}

Expand All @@ -1256,32 +1255,32 @@ mod test {
let public_key = secret_key.to_public(&secp).unwrap();
assert_eq!(public_key.to_string(), "[2cbe2a6d/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2");
assert_eq!(public_key.master_fingerprint().to_string(), "2cbe2a6d");
assert_eq!(public_key.full_derivation_path().unwrap().to_string(), "m/0'/1'/2");
assert_eq!(public_key.full_derivation_path().unwrap().to_string(), "0'/1'/2");
assert!(!public_key.has_wildcard());

let secret_key = DescriptorSecretKey::from_str("tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/0'/1'/2'").unwrap();
let public_key = secret_key.to_public(&secp).unwrap();
assert_eq!(public_key.to_string(), "[2cbe2a6d/0'/1'/2']tpubDDPuH46rv4dbFtmF6FrEtJEy1CvLZonyBoVxF6xsesHdYDdTBrq2mHhm8AbsPh39sUwL2nZyxd6vo4uWNTU9v4t893CwxjqPnwMoUACLvMV");
assert_eq!(public_key.master_fingerprint().to_string(), "2cbe2a6d");
assert_eq!(public_key.full_derivation_path().unwrap().to_string(), "m/0'/1'/2'");
assert_eq!(public_key.full_derivation_path().unwrap().to_string(), "0'/1'/2'");

let secret_key = DescriptorSecretKey::from_str("tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/0/1/2").unwrap();
let public_key = secret_key.to_public(&secp).unwrap();
assert_eq!(public_key.to_string(), "tpubD6NzVbkrYhZ4WQdzxL7NmJN7b85ePo4p6RSj9QQHF7te2RR9iUeVSGgnGkoUsB9LBRosgvNbjRv9bcsJgzgBd7QKuxDm23ZewkTRzNSLEDr/0/1/2");
assert_eq!(public_key.master_fingerprint().to_string(), "2cbe2a6d");
assert_eq!(public_key.full_derivation_path().unwrap().to_string(), "m/0/1/2");
assert_eq!(public_key.full_derivation_path().unwrap().to_string(), "0/1/2");

let secret_key = DescriptorSecretKey::from_str("[aabbccdd]tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/0/1/2").unwrap();
let public_key = secret_key.to_public(&secp).unwrap();
assert_eq!(public_key.to_string(), "[aabbccdd]tpubD6NzVbkrYhZ4WQdzxL7NmJN7b85ePo4p6RSj9QQHF7te2RR9iUeVSGgnGkoUsB9LBRosgvNbjRv9bcsJgzgBd7QKuxDm23ZewkTRzNSLEDr/0/1/2");
assert_eq!(public_key.master_fingerprint().to_string(), "aabbccdd");
assert_eq!(public_key.full_derivation_path().unwrap().to_string(), "m/0/1/2");
assert_eq!(public_key.full_derivation_path().unwrap().to_string(), "0/1/2");

let secret_key = DescriptorSecretKey::from_str("[aabbccdd/90']tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/0'/1'/2").unwrap();
let public_key = secret_key.to_public(&secp).unwrap();
assert_eq!(public_key.to_string(), "[aabbccdd/90'/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2");
assert_eq!(public_key.master_fingerprint().to_string(), "aabbccdd");
assert_eq!(public_key.full_derivation_path().unwrap().to_string(), "m/90'/0'/1'/2");
assert_eq!(public_key.full_derivation_path().unwrap().to_string(), "90'/0'/1'/2");
}

#[test]
Expand Down Expand Up @@ -1323,10 +1322,10 @@ mod test {
assert_eq!(
xpub.derivation_paths.paths(),
&vec![
bip32::DerivationPath::from_str("m/2/0").unwrap(),
bip32::DerivationPath::from_str("m/2/1").unwrap(),
bip32::DerivationPath::from_str("m/2/42").unwrap(),
bip32::DerivationPath::from_str("m/2/9854").unwrap()
bip32::DerivationPath::from_str("2/0").unwrap(),
bip32::DerivationPath::from_str("2/1").unwrap(),
bip32::DerivationPath::from_str("2/42").unwrap(),
bip32::DerivationPath::from_str("2/9854").unwrap()
],
);
assert_eq!(
Expand All @@ -1338,9 +1337,9 @@ mod test {
assert_eq!(
xpub.derivation_paths.paths(),
&vec![
bip32::DerivationPath::from_str("m/2/0/0/5/10").unwrap(),
bip32::DerivationPath::from_str("m/2/1/0/5/10").unwrap(),
bip32::DerivationPath::from_str("m/2/9854/0/5/10").unwrap()
bip32::DerivationPath::from_str("2/0/0/5/10").unwrap(),
bip32::DerivationPath::from_str("2/1/0/5/10").unwrap(),
bip32::DerivationPath::from_str("2/9854/0/5/10").unwrap()
],
);
assert_eq!(
Expand All @@ -1353,9 +1352,9 @@ mod test {
assert_eq!(
xpub.derivation_paths.paths(),
&vec![
bip32::DerivationPath::from_str("m/2/0/3456/9876").unwrap(),
bip32::DerivationPath::from_str("m/2/1/3456/9876").unwrap(),
bip32::DerivationPath::from_str("m/2/9854/3456/9876").unwrap()
bip32::DerivationPath::from_str("2/0/3456/9876").unwrap(),
bip32::DerivationPath::from_str("2/1/3456/9876").unwrap(),
bip32::DerivationPath::from_str("2/9854/3456/9876").unwrap()
],
);
assert_eq!(
Expand All @@ -1368,8 +1367,8 @@ mod test {
assert_eq!(
xpub.derivation_paths.paths(),
&vec![
bip32::DerivationPath::from_str("m/0").unwrap(),
bip32::DerivationPath::from_str("m/1").unwrap(),
bip32::DerivationPath::from_str("0").unwrap(),
bip32::DerivationPath::from_str("1").unwrap(),
],
);
assert_eq!(
Expand All @@ -1383,8 +1382,8 @@ mod test {
assert_eq!(
xpub.derivation_paths.paths(),
&vec![
bip32::DerivationPath::from_str("m/9478'/0'/8'").unwrap(),
bip32::DerivationPath::from_str("m/9478h/1h/8h").unwrap(),
bip32::DerivationPath::from_str("9478'/0'/8'").unwrap(),
bip32::DerivationPath::from_str("9478h/1h/8h").unwrap(),
],
);
assert_eq!(
Expand All @@ -1399,8 +1398,8 @@ mod test {
assert_eq!(
desc_key.full_derivation_paths(),
vec![
bip32::DerivationPath::from_str("m/0'/1'/9478'/0'/8'").unwrap(),
bip32::DerivationPath::from_str("m/0'/1'/9478'/1/8'").unwrap(),
bip32::DerivationPath::from_str("0'/1'/9478'/0'/8'").unwrap(),
bip32::DerivationPath::from_str("0'/1'/9478'/1/8'").unwrap(),
],
);
assert_eq!(desc_key.into_single_keys(), vec![DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/9478'/0'/8h/*'").unwrap(), DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/9478'/1/8h/*'").unwrap()]);
Expand All @@ -1410,10 +1409,10 @@ mod test {
assert_eq!(
xprv.derivation_paths.paths(),
&vec![
bip32::DerivationPath::from_str("m/2/0").unwrap(),
bip32::DerivationPath::from_str("m/2/1").unwrap(),
bip32::DerivationPath::from_str("m/2/42").unwrap(),
bip32::DerivationPath::from_str("m/2/9854").unwrap()
bip32::DerivationPath::from_str("2/0").unwrap(),
bip32::DerivationPath::from_str("2/1").unwrap(),
bip32::DerivationPath::from_str("2/42").unwrap(),
bip32::DerivationPath::from_str("2/9854").unwrap()
],
);
assert_eq!(
Expand All @@ -1424,9 +1423,9 @@ mod test {
assert_eq!(
xprv.derivation_paths.paths(),
&vec![
bip32::DerivationPath::from_str("m/2/0/0/5/10").unwrap(),
bip32::DerivationPath::from_str("m/2/1/0/5/10").unwrap(),
bip32::DerivationPath::from_str("m/2/9854/0/5/10").unwrap()
bip32::DerivationPath::from_str("2/0/0/5/10").unwrap(),
bip32::DerivationPath::from_str("2/1/0/5/10").unwrap(),
bip32::DerivationPath::from_str("2/9854/0/5/10").unwrap()
],
);
assert_eq!(
Expand All @@ -1438,9 +1437,9 @@ mod test {
assert_eq!(
xprv.derivation_paths.paths(),
&vec![
bip32::DerivationPath::from_str("m/2/0/3456/9876").unwrap(),
bip32::DerivationPath::from_str("m/2/1/3456/9876").unwrap(),
bip32::DerivationPath::from_str("m/2/9854/3456/9876").unwrap()
bip32::DerivationPath::from_str("2/0/3456/9876").unwrap(),
bip32::DerivationPath::from_str("2/1/3456/9876").unwrap(),
bip32::DerivationPath::from_str("2/9854/3456/9876").unwrap()
],
);
assert_eq!(
Expand All @@ -1452,8 +1451,8 @@ mod test {
assert_eq!(
xprv.derivation_paths.paths(),
&vec![
bip32::DerivationPath::from_str("m/0").unwrap(),
bip32::DerivationPath::from_str("m/1").unwrap(),
bip32::DerivationPath::from_str("0").unwrap(),
bip32::DerivationPath::from_str("1").unwrap(),
],
);
assert_eq!(
Expand All @@ -1465,8 +1464,8 @@ mod test {
assert_eq!(
xprv.derivation_paths.paths(),
&vec![
bip32::DerivationPath::from_str("m/9478'/0'/8'").unwrap(),
bip32::DerivationPath::from_str("m/9478h/1h/8h").unwrap(),
bip32::DerivationPath::from_str("9478'/0'/8'").unwrap(),
bip32::DerivationPath::from_str("9478h/1h/8h").unwrap(),
],
);
assert_eq!(
Expand Down
8 changes: 4 additions & 4 deletions src/descriptor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1266,8 +1266,8 @@ mod tests {
) -> Option<bitcoin::ecdsa::Signature> {
if *pk == self.pk {
Some(bitcoin::ecdsa::Signature {
sig: self.sig,
hash_ty: bitcoin::sighash::EcdsaSighashType::All,
signature: self.sig,
sighash_type: bitcoin::sighash::EcdsaSighashType::All,
})
} else {
None
Expand Down Expand Up @@ -1533,11 +1533,11 @@ mod tests {

satisfier.insert(
a,
bitcoin::ecdsa::Signature { sig: sig_a, hash_ty: EcdsaSighashType::All },
bitcoin::ecdsa::Signature { signature: sig_a, sighash_type: EcdsaSighashType::All },
);
satisfier.insert(
b,
bitcoin::ecdsa::Signature { sig: sig_b, hash_ty: EcdsaSighashType::All },
bitcoin::ecdsa::Signature { signature: sig_b, sighash_type: EcdsaSighashType::All },
);

satisfier
Expand Down
14 changes: 10 additions & 4 deletions src/descriptor/segwitv0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,15 +371,21 @@ impl<Pk: MiniscriptKey> Wpkh<Pk> {
impl<Pk: MiniscriptKey + ToPublicKey> Wpkh<Pk> {
/// Obtains the corresponding script pubkey for this descriptor.
pub fn script_pubkey(&self) -> ScriptBuf {
let addr = Address::p2wpkh(&self.pk.to_public_key(), Network::Bitcoin)
.expect("wpkh descriptors have compressed keys");
use core::convert::TryFrom;
let pk = self.pk.to_public_key();
let compressed = bitcoin::key::CompressedPublicKey::try_from(pk).expect("TODO: Handle compressed key");

let addr = Address::p2wpkh(&compressed, Network::Bitcoin);
addr.script_pubkey()
}

/// Obtains the corresponding script pubkey for this descriptor.
pub fn address(&self, network: Network) -> Address {
Address::p2wpkh(&self.pk.to_public_key(), network)
.expect("Rust Miniscript types don't allow uncompressed pks in segwit descriptors")
use core::convert::TryFrom;
let pk = self.pk.to_public_key();
let compressed = bitcoin::key::CompressedPublicKey::try_from(pk).expect("TODO: Handle compressed key");

Address::p2wpkh(&compressed, network)
}

/// Obtains the underlying miniscript for this descriptor.
Expand Down
6 changes: 3 additions & 3 deletions src/interpreter/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ pub enum Error {
/// Schnorr Signature error
SchnorrSig(bitcoin::taproot::SigFromSliceError),
/// Errors in signature hash calculations
SighashError(bitcoin::sighash::Error),
SighashError(bitcoin::sighash::InvalidSighashTypeError),
/// Taproot Annex Unsupported
TapAnnexUnsupported,
/// An uncompressed public key was encountered in a context where it is
Expand Down Expand Up @@ -242,8 +242,8 @@ impl From<secp256k1::Error> for Error {
}

#[doc(hidden)]
impl From<bitcoin::sighash::Error> for Error {
fn from(e: bitcoin::sighash::Error) -> Error { Error::SighashError(e) }
impl From<bitcoin::sighash::InvalidSighashTypeError> for Error {
fn from(e: bitcoin::sighash::InvalidSighashTypeError) -> Error { Error::SighashError(e) }
}

#[doc(hidden)]
Expand Down
Loading

0 comments on commit 007c38b

Please sign in to comment.