Compare commits
38 Commits
release/0.
...
release/0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd64d12835 | ||
|
|
7e8e9921a9 | ||
|
|
31fa4f723a | ||
|
|
c741df746b | ||
|
|
3205f0c16d | ||
|
|
5f0870a741 | ||
|
|
5a483472c1 | ||
|
|
8d4cc3920a | ||
|
|
14bc9c0e35 | ||
|
|
2451c00268 | ||
|
|
4cad18bbca | ||
|
|
634a0575cb | ||
|
|
d3d07564f2 | ||
|
|
0b768d6f0b | ||
|
|
ec9aefac6b | ||
|
|
d72aa7ebc0 | ||
|
|
99930af12e | ||
|
|
d6e730f18a | ||
|
|
d1e5b87bfc | ||
|
|
c101dea460 | ||
|
|
9ddd502538 | ||
|
|
a5d345fff2 | ||
|
|
11dcc14374 | ||
|
|
4c5ceaff14 | ||
|
|
b5fcddcf1a | ||
|
|
d570ff2c65 | ||
|
|
21c96c9c81 | ||
|
|
c51d544932 | ||
|
|
5e56c3b3c1 | ||
|
|
235961a934 | ||
|
|
df905a8d5e | ||
|
|
8b68cf9546 | ||
|
|
150f4d6f41 | ||
|
|
1c95ca33a8 | ||
|
|
108edc3a6b | ||
|
|
f99a6b9f43 | ||
|
|
aedbc8c97d | ||
|
|
e9bbb8724f |
4
.github/workflows/cont_integration.yml
vendored
4
.github/workflows/cont_integration.yml
vendored
@@ -173,8 +173,8 @@ jobs:
|
|||||||
- name: Check fmt
|
- name: Check fmt
|
||||||
run: cargo fmt --all -- --config format_code_in_doc_comments=true --check
|
run: cargo fmt --all -- --config format_code_in_doc_comments=true --check
|
||||||
|
|
||||||
test_harware_wallet:
|
test_hardware_wallet:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
rust:
|
rust:
|
||||||
|
|||||||
2
.github/workflows/nightly_docs.yml
vendored
2
.github/workflows/nightly_docs.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
target
|
target
|
||||||
key: nightly-docs-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
|
key: nightly-docs-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
|
||||||
- name: Set default toolchain
|
- name: Set default toolchain
|
||||||
run: rustup default nightly-2022-01-25
|
run: rustup default nightly-2022-12-14
|
||||||
- name: Set profile
|
- name: Set profile
|
||||||
run: rustup set profile minimal
|
run: rustup set profile minimal
|
||||||
- name: Update toolchain
|
- name: Update toolchain
|
||||||
|
|||||||
29
Cargo.toml
29
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk"
|
name = "bdk"
|
||||||
version = "0.24.0"
|
version = "0.26.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
|
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
|
||||||
homepage = "https://bitcoindevkit.org"
|
homepage = "https://bitcoindevkit.org"
|
||||||
@@ -23,7 +23,7 @@ rand = "^0.8"
|
|||||||
# Optional dependencies
|
# Optional dependencies
|
||||||
sled = { version = "0.34", optional = true }
|
sled = { version = "0.34", optional = true }
|
||||||
electrum-client = { version = "0.12", optional = true }
|
electrum-client = { version = "0.12", optional = true }
|
||||||
esplora-client = { version = "0.2", default-features = false, optional = true }
|
esplora-client = { version = "0.3", default-features = false, optional = true }
|
||||||
rusqlite = { version = "0.27.0", optional = true }
|
rusqlite = { version = "0.27.0", optional = true }
|
||||||
ahash = { version = "0.7.6", optional = true }
|
ahash = { version = "0.7.6", optional = true }
|
||||||
futures = { version = "0.3", optional = true }
|
futures = { version = "0.3", optional = true }
|
||||||
@@ -31,7 +31,7 @@ async-trait = { version = "0.1", optional = true }
|
|||||||
rocksdb = { version = "0.14", default-features = false, features = ["snappy"], optional = true }
|
rocksdb = { version = "0.14", default-features = false, features = ["snappy"], optional = true }
|
||||||
cc = { version = ">=1.0.64", optional = true }
|
cc = { version = ">=1.0.64", optional = true }
|
||||||
socks = { version = "0.3", optional = true }
|
socks = { version = "0.3", optional = true }
|
||||||
hwi = { version = "0.3.0", optional = true }
|
hwi = { version = "0.4.0", optional = true, features = [ "use-miniscript"] }
|
||||||
|
|
||||||
bip39 = { version = "1.0.1", optional = true }
|
bip39 = { version = "1.0.1", optional = true }
|
||||||
bitcoinconsensus = { version = "0.19.0-3", optional = true }
|
bitcoinconsensus = { version = "0.19.0-3", optional = true }
|
||||||
@@ -41,7 +41,7 @@ bitcoincore-rpc = { version = "0.16", optional = true }
|
|||||||
|
|
||||||
# Platform-specific dependencies
|
# Platform-specific dependencies
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
tokio = { version = "1", features = ["rt"] }
|
tokio = { version = "1", features = ["rt", "macros"] }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
getrandom = "0.2"
|
getrandom = "0.2"
|
||||||
@@ -109,6 +109,7 @@ env_logger = "0.7"
|
|||||||
electrsd = "0.21"
|
electrsd = "0.21"
|
||||||
# Move back to importing from rust-bitcoin once https://github.com/rust-bitcoin/rust-bitcoin/pull/1342 is released
|
# Move back to importing from rust-bitcoin once https://github.com/rust-bitcoin/rust-bitcoin/pull/1342 is released
|
||||||
base64 = "^0.13"
|
base64 = "^0.13"
|
||||||
|
assert_matches = "1.5.0"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "compact_filters_balance"
|
name = "compact_filters_balance"
|
||||||
@@ -138,6 +139,26 @@ name = "hardware_signer"
|
|||||||
path = "examples/hardware_signer.rs"
|
path = "examples/hardware_signer.rs"
|
||||||
required-features = ["electrum", "hardware-signer"]
|
required-features = ["electrum", "hardware-signer"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "electrum_backend"
|
||||||
|
path = "examples/electrum_backend.rs"
|
||||||
|
required-features = ["electrum"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "esplora_backend_synchronous"
|
||||||
|
path = "examples/esplora_backend_synchronous.rs"
|
||||||
|
required-features = ["use-esplora-ureq"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "esplora_backend_asynchronous"
|
||||||
|
path = "examples/esplora_backend_asynchronous.rs"
|
||||||
|
required-features = ["use-esplora-reqwest", "reqwest-default-tls", "async-interface"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "mnemonic_to_descriptors"
|
||||||
|
path = "examples/mnemonic_to_descriptors.rs"
|
||||||
|
required-features = ["all-keys"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["macros"]
|
members = ["macros"]
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -68,12 +68,13 @@ fn main() -> Result<(), bdk::Error> {
|
|||||||
```rust
|
```rust
|
||||||
use bdk::{Wallet, database::MemoryDatabase};
|
use bdk::{Wallet, database::MemoryDatabase};
|
||||||
use bdk::wallet::AddressIndex::New;
|
use bdk::wallet::AddressIndex::New;
|
||||||
|
use bdk::bitcoin::Network;
|
||||||
|
|
||||||
fn main() -> Result<(), bdk::Error> {
|
fn main() -> Result<(), bdk::Error> {
|
||||||
let wallet = Wallet::new(
|
let wallet = Wallet::new(
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||||
bitcoin::Network::Testnet,
|
Network::Testnet,
|
||||||
MemoryDatabase::default(),
|
MemoryDatabase::default(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -96,14 +97,15 @@ use bdk::electrum_client::Client;
|
|||||||
use bdk::wallet::AddressIndex::New;
|
use bdk::wallet::AddressIndex::New;
|
||||||
|
|
||||||
use base64;
|
use base64;
|
||||||
use bitcoin::consensus::serialize;
|
use bdk::bitcoin::consensus::serialize;
|
||||||
|
use bdk::bitcoin::Network;
|
||||||
|
|
||||||
fn main() -> Result<(), bdk::Error> {
|
fn main() -> Result<(), bdk::Error> {
|
||||||
let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?);
|
let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?);
|
||||||
let wallet = Wallet::new(
|
let wallet = Wallet::new(
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||||
bitcoin::Network::Testnet,
|
Network::Testnet,
|
||||||
MemoryDatabase::default(),
|
MemoryDatabase::default(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -133,20 +135,21 @@ fn main() -> Result<(), bdk::Error> {
|
|||||||
use bdk::{Wallet, SignOptions, database::MemoryDatabase};
|
use bdk::{Wallet, SignOptions, database::MemoryDatabase};
|
||||||
|
|
||||||
use base64;
|
use base64;
|
||||||
use bitcoin::consensus::deserialize;
|
use bdk::bitcoin::consensus::deserialize;
|
||||||
|
use bdk::bitcoin::Network;
|
||||||
|
|
||||||
fn main() -> Result<(), bdk::Error> {
|
fn main() -> Result<(), bdk::Error> {
|
||||||
let wallet = Wallet::new(
|
let wallet = Wallet::new(
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
|
"wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
|
||||||
Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
|
Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
|
||||||
bitcoin::Network::Testnet,
|
Network::Testnet,
|
||||||
MemoryDatabase::default(),
|
MemoryDatabase::default(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let psbt = "...";
|
let psbt = "...";
|
||||||
let mut psbt = deserialize(&base64::decode(psbt).unwrap())?;
|
let mut psbt = deserialize(&base64::decode(psbt).unwrap())?;
|
||||||
|
|
||||||
let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
|
let _finalized = wallet.sign(&mut psbt, SignOptions::default())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
87
examples/electrum_backend.rs
Normal file
87
examples/electrum_backend.rs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use bdk::bitcoin::util::bip32::ExtendedPrivKey;
|
||||||
|
use bdk::bitcoin::Network;
|
||||||
|
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
|
||||||
|
use bdk::database::MemoryDatabase;
|
||||||
|
use bdk::template::Bip84;
|
||||||
|
use bdk::wallet::export::FullyNodedExport;
|
||||||
|
use bdk::{KeychainKind, SyncOptions, Wallet};
|
||||||
|
|
||||||
|
use bdk::electrum_client::Client;
|
||||||
|
use bdk::wallet::AddressIndex;
|
||||||
|
use bitcoin::util::bip32;
|
||||||
|
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
use crate::utils::tx::build_signed_tx;
|
||||||
|
|
||||||
|
/// This will create a wallet from an xpriv and get the balance by connecting to an Electrum server.
|
||||||
|
/// If enough amount is available, this will send a transaction to an address.
|
||||||
|
/// Otherwise, this will display a wallet address to receive funds.
|
||||||
|
///
|
||||||
|
/// This can be run with `cargo run --example electrum_backend` in the root folder.
|
||||||
|
fn main() {
|
||||||
|
let network = Network::Testnet;
|
||||||
|
|
||||||
|
let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy";
|
||||||
|
|
||||||
|
let electrum_url = "ssl://electrum.blockstream.info:60002";
|
||||||
|
|
||||||
|
run(&network, electrum_url, xpriv);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet<MemoryDatabase> {
|
||||||
|
Wallet::new(
|
||||||
|
Bip84(*xpriv, KeychainKind::External),
|
||||||
|
Some(Bip84(*xpriv, KeychainKind::Internal)),
|
||||||
|
*network,
|
||||||
|
MemoryDatabase::default(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(network: &Network, electrum_url: &str, xpriv: &str) {
|
||||||
|
let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap();
|
||||||
|
|
||||||
|
// Apparently it works only with Electrs (not EletrumX)
|
||||||
|
let blockchain = ElectrumBlockchain::from(Client::new(electrum_url).unwrap());
|
||||||
|
|
||||||
|
let wallet = create_wallet(network, &xpriv);
|
||||||
|
|
||||||
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
|
||||||
|
let address = wallet.get_address(AddressIndex::New).unwrap().address;
|
||||||
|
|
||||||
|
println!("address: {}", address);
|
||||||
|
|
||||||
|
let balance = wallet.get_balance().unwrap();
|
||||||
|
|
||||||
|
println!("Available coins in BDK wallet : {} sats", balance);
|
||||||
|
|
||||||
|
if balance.confirmed > 6500 {
|
||||||
|
// the wallet sends the amount to itself.
|
||||||
|
let recipient_address = wallet
|
||||||
|
.get_address(AddressIndex::New)
|
||||||
|
.unwrap()
|
||||||
|
.address
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let amount = 5359;
|
||||||
|
|
||||||
|
let tx = build_signed_tx(&wallet, &recipient_address, amount);
|
||||||
|
|
||||||
|
blockchain.broadcast(&tx).unwrap();
|
||||||
|
|
||||||
|
println!("tx id: {}", tx.txid());
|
||||||
|
} else {
|
||||||
|
println!("Insufficient Funds. Fund the wallet with the address above");
|
||||||
|
}
|
||||||
|
|
||||||
|
let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true)
|
||||||
|
.map_err(ToString::to_string)
|
||||||
|
.map_err(bdk::Error::Generic)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!("------\nWallet Backup: {}", export.to_string());
|
||||||
|
}
|
||||||
93
examples/esplora_backend_asynchronous.rs
Normal file
93
examples/esplora_backend_asynchronous.rs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use bdk::blockchain::Blockchain;
|
||||||
|
use bdk::{
|
||||||
|
blockchain::esplora::EsploraBlockchain,
|
||||||
|
database::MemoryDatabase,
|
||||||
|
template::Bip84,
|
||||||
|
wallet::{export::FullyNodedExport, AddressIndex},
|
||||||
|
KeychainKind, SyncOptions, Wallet,
|
||||||
|
};
|
||||||
|
use bitcoin::{
|
||||||
|
util::bip32::{self, ExtendedPrivKey},
|
||||||
|
Network,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
use crate::utils::tx::build_signed_tx;
|
||||||
|
|
||||||
|
/// This will create a wallet from an xpriv and get the balance by connecting to an Esplora server,
|
||||||
|
/// using non blocking asynchronous calls with `reqwest`.
|
||||||
|
/// If enough amount is available, this will send a transaction to an address.
|
||||||
|
/// Otherwise, this will display a wallet address to receive funds.
|
||||||
|
///
|
||||||
|
/// This can be run with `cargo run --no-default-features --features="use-esplora-reqwest, reqwest-default-tls, async-interface" --example esplora_backend_asynchronous`
|
||||||
|
/// in the root folder.
|
||||||
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
async fn main() {
|
||||||
|
let network = Network::Signet;
|
||||||
|
|
||||||
|
let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy";
|
||||||
|
|
||||||
|
let esplora_url = "https://explorer.bc-2.jp/api";
|
||||||
|
|
||||||
|
run(&network, esplora_url, xpriv).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet<MemoryDatabase> {
|
||||||
|
Wallet::new(
|
||||||
|
Bip84(*xpriv, KeychainKind::External),
|
||||||
|
Some(Bip84(*xpriv, KeychainKind::Internal)),
|
||||||
|
*network,
|
||||||
|
MemoryDatabase::default(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(network: &Network, esplora_url: &str, xpriv: &str) {
|
||||||
|
let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap();
|
||||||
|
|
||||||
|
let blockchain = EsploraBlockchain::new(esplora_url, 20);
|
||||||
|
|
||||||
|
let wallet = create_wallet(network, &xpriv);
|
||||||
|
|
||||||
|
wallet
|
||||||
|
.sync(&blockchain, SyncOptions::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let address = wallet.get_address(AddressIndex::New).unwrap().address;
|
||||||
|
|
||||||
|
println!("address: {}", address);
|
||||||
|
|
||||||
|
let balance = wallet.get_balance().unwrap();
|
||||||
|
|
||||||
|
println!("Available coins in BDK wallet : {} sats", balance);
|
||||||
|
|
||||||
|
if balance.confirmed > 10500 {
|
||||||
|
// the wallet sends the amount to itself.
|
||||||
|
let recipient_address = wallet
|
||||||
|
.get_address(AddressIndex::New)
|
||||||
|
.unwrap()
|
||||||
|
.address
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let amount = 9359;
|
||||||
|
|
||||||
|
let tx = build_signed_tx(&wallet, &recipient_address, amount);
|
||||||
|
|
||||||
|
let _ = blockchain.broadcast(&tx);
|
||||||
|
|
||||||
|
println!("tx id: {}", tx.txid());
|
||||||
|
} else {
|
||||||
|
println!("Insufficient Funds. Fund the wallet with the address above");
|
||||||
|
}
|
||||||
|
|
||||||
|
let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true)
|
||||||
|
.map_err(ToString::to_string)
|
||||||
|
.map_err(bdk::Error::Generic)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!("------\nWallet Backup: {}", export.to_string());
|
||||||
|
}
|
||||||
89
examples/esplora_backend_synchronous.rs
Normal file
89
examples/esplora_backend_synchronous.rs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use bdk::blockchain::Blockchain;
|
||||||
|
use bdk::{
|
||||||
|
blockchain::esplora::EsploraBlockchain,
|
||||||
|
database::MemoryDatabase,
|
||||||
|
template::Bip84,
|
||||||
|
wallet::{export::FullyNodedExport, AddressIndex},
|
||||||
|
KeychainKind, SyncOptions, Wallet,
|
||||||
|
};
|
||||||
|
use bitcoin::{
|
||||||
|
util::bip32::{self, ExtendedPrivKey},
|
||||||
|
Network,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
use crate::utils::tx::build_signed_tx;
|
||||||
|
|
||||||
|
/// This will create a wallet from an xpriv and get the balance by connecting to an Esplora server,
|
||||||
|
/// using blocking calls with `ureq`.
|
||||||
|
/// If enough amount is available, this will send a transaction to an address.
|
||||||
|
/// Otherwise, this will display a wallet address to receive funds.
|
||||||
|
///
|
||||||
|
/// This can be run with `cargo run --features=use-esplora-ureq --example esplora_backend_synchronous`
|
||||||
|
/// in the root folder.
|
||||||
|
fn main() {
|
||||||
|
let network = Network::Signet;
|
||||||
|
|
||||||
|
let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy";
|
||||||
|
|
||||||
|
let esplora_url = "https://explorer.bc-2.jp/api";
|
||||||
|
|
||||||
|
run(&network, esplora_url, xpriv);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet<MemoryDatabase> {
|
||||||
|
Wallet::new(
|
||||||
|
Bip84(*xpriv, KeychainKind::External),
|
||||||
|
Some(Bip84(*xpriv, KeychainKind::Internal)),
|
||||||
|
*network,
|
||||||
|
MemoryDatabase::default(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(network: &Network, esplora_url: &str, xpriv: &str) {
|
||||||
|
let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap();
|
||||||
|
|
||||||
|
let blockchain = EsploraBlockchain::new(esplora_url, 20);
|
||||||
|
|
||||||
|
let wallet = create_wallet(network, &xpriv);
|
||||||
|
|
||||||
|
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||||
|
|
||||||
|
let address = wallet.get_address(AddressIndex::New).unwrap().address;
|
||||||
|
|
||||||
|
println!("address: {}", address);
|
||||||
|
|
||||||
|
let balance = wallet.get_balance().unwrap();
|
||||||
|
|
||||||
|
println!("Available coins in BDK wallet : {} sats", balance);
|
||||||
|
|
||||||
|
if balance.confirmed > 10500 {
|
||||||
|
// the wallet sends the amount to itself.
|
||||||
|
let recipient_address = wallet
|
||||||
|
.get_address(AddressIndex::New)
|
||||||
|
.unwrap()
|
||||||
|
.address
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let amount = 9359;
|
||||||
|
|
||||||
|
let tx = build_signed_tx(&wallet, &recipient_address, amount);
|
||||||
|
|
||||||
|
blockchain.broadcast(&tx).unwrap();
|
||||||
|
|
||||||
|
println!("tx id: {}", tx.txid());
|
||||||
|
} else {
|
||||||
|
println!("Insufficient Funds. Fund the wallet with the address above");
|
||||||
|
}
|
||||||
|
|
||||||
|
let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true)
|
||||||
|
.map_err(ToString::to_string)
|
||||||
|
.map_err(bdk::Error::Generic)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!("------\nWallet Backup: {}", export.to_string());
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ use bdk::bitcoin::{Address, Network};
|
|||||||
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
|
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
|
||||||
use bdk::database::MemoryDatabase;
|
use bdk::database::MemoryDatabase;
|
||||||
use bdk::hwi::{types::HWIChain, HWIClient};
|
use bdk::hwi::{types::HWIChain, HWIClient};
|
||||||
|
use bdk::miniscript::{Descriptor, DescriptorPublicKey};
|
||||||
use bdk::signer::SignerOrdering;
|
use bdk::signer::SignerOrdering;
|
||||||
use bdk::wallet::{hardwaresigner::HWISigner, AddressIndex};
|
use bdk::wallet::{hardwaresigner::HWISigner, AddressIndex};
|
||||||
use bdk::{FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet};
|
use bdk::{FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet};
|
||||||
@@ -23,26 +24,27 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
println!("Hold tight, I'm connecting to your hardware wallet...");
|
println!("Hold tight, I'm connecting to your hardware wallet...");
|
||||||
|
|
||||||
// Listing all the available hardware wallet devices...
|
// Listing all the available hardware wallet devices...
|
||||||
let devices = HWIClient::enumerate()?;
|
let mut devices = HWIClient::enumerate()?;
|
||||||
let first_device = devices
|
if devices.is_empty() {
|
||||||
.first()
|
panic!("No devices found. Either plug in a hardware wallet, or start a simulator.");
|
||||||
.expect("No devices found. Either plug in a hardware wallet, or start a simulator.");
|
}
|
||||||
|
let first_device = devices.remove(0)?;
|
||||||
// ...and creating a client out of the first one
|
// ...and creating a client out of the first one
|
||||||
let client = HWIClient::get_client(first_device, true, HWIChain::Test)?;
|
let client = HWIClient::get_client(&first_device, true, HWIChain::Test)?;
|
||||||
println!("Look what I found, a {}!", first_device.model);
|
println!("Look what I found, a {}!", first_device.model);
|
||||||
|
|
||||||
// Getting the HW's public descriptors
|
// Getting the HW's public descriptors
|
||||||
let descriptors = client.get_descriptors(None)?;
|
let descriptors = client.get_descriptors::<Descriptor<DescriptorPublicKey>>(None)?;
|
||||||
println!(
|
println!(
|
||||||
"The hardware wallet's descriptor is: {}",
|
"The hardware wallet's descriptor is: {}",
|
||||||
descriptors.receive[0]
|
descriptors.receive[0]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Creating a custom signer from the device
|
// Creating a custom signer from the device
|
||||||
let custom_signer = HWISigner::from_device(first_device, HWIChain::Test)?;
|
let custom_signer = HWISigner::from_device(&first_device, HWIChain::Test)?;
|
||||||
let mut wallet = Wallet::new(
|
let mut wallet = Wallet::new(
|
||||||
&descriptors.receive[0],
|
descriptors.receive[0].clone(),
|
||||||
Some(&descriptors.internal[0]),
|
Some(descriptors.internal[0].clone()),
|
||||||
Network::Testnet,
|
Network::Testnet,
|
||||||
MemoryDatabase::default(),
|
MemoryDatabase::default(),
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
60
examples/mnemonic_to_descriptors.rs
Normal file
60
examples/mnemonic_to_descriptors.rs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
|
||||||
|
//
|
||||||
|
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
|
||||||
|
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
|
||||||
|
// You may not use this file except in accordance with one or both of these
|
||||||
|
// licenses.
|
||||||
|
|
||||||
|
use bdk::bitcoin::secp256k1::Secp256k1;
|
||||||
|
use bdk::bitcoin::util::bip32::DerivationPath;
|
||||||
|
use bdk::bitcoin::Network;
|
||||||
|
use bdk::descriptor;
|
||||||
|
use bdk::descriptor::IntoWalletDescriptor;
|
||||||
|
use bdk::keys::bip39::{Language, Mnemonic, WordCount};
|
||||||
|
use bdk::keys::{GeneratableKey, GeneratedKey};
|
||||||
|
use bdk::miniscript::Tap;
|
||||||
|
use bdk::Error as BDK_Error;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
/// This example demonstrates how to generate a mnemonic phrase
|
||||||
|
/// using BDK and use that to generate a descriptor string.
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let secp = Secp256k1::new();
|
||||||
|
|
||||||
|
// In this example we are generating a 12 words mnemonic phrase
|
||||||
|
// but it is also possible generate 15, 18, 21 and 24 words
|
||||||
|
// using their respective `WordCount` variant.
|
||||||
|
let mnemonic: GeneratedKey<_, Tap> =
|
||||||
|
Mnemonic::generate((WordCount::Words12, Language::English))
|
||||||
|
.map_err(|_| BDK_Error::Generic("Mnemonic generation error".to_string()))?;
|
||||||
|
|
||||||
|
println!("Mnemonic phrase: {}", *mnemonic);
|
||||||
|
let mnemonic_with_passphrase = (mnemonic, None);
|
||||||
|
|
||||||
|
// define external and internal derivation key path
|
||||||
|
let external_path = DerivationPath::from_str("m/86h/0h/0h/0").unwrap();
|
||||||
|
let internal_path = DerivationPath::from_str("m/86h/0h/0h/1").unwrap();
|
||||||
|
|
||||||
|
// generate external and internal descriptor from mnemonic
|
||||||
|
let (external_descriptor, ext_keymap) =
|
||||||
|
descriptor!(tr((mnemonic_with_passphrase.clone(), external_path)))?
|
||||||
|
.into_wallet_descriptor(&secp, Network::Testnet)?;
|
||||||
|
let (internal_descriptor, int_keymap) =
|
||||||
|
descriptor!(tr((mnemonic_with_passphrase, internal_path)))?
|
||||||
|
.into_wallet_descriptor(&secp, Network::Testnet)?;
|
||||||
|
|
||||||
|
println!("tpub external descriptor: {}", external_descriptor);
|
||||||
|
println!("tpub internal descriptor: {}", internal_descriptor);
|
||||||
|
println!(
|
||||||
|
"tprv external descriptor: {}",
|
||||||
|
external_descriptor.to_string_with_secret(&ext_keymap)
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"tprv internal descriptor: {}",
|
||||||
|
internal_descriptor.to_string_with_secret(&int_keymap)
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
30
examples/utils/mod.rs
Normal file
30
examples/utils/mod.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
pub(crate) mod tx {
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use bdk::{database::BatchDatabase, SignOptions, Wallet};
|
||||||
|
use bitcoin::{Address, Transaction};
|
||||||
|
|
||||||
|
pub fn build_signed_tx<D: BatchDatabase>(
|
||||||
|
wallet: &Wallet<D>,
|
||||||
|
recipient_address: &str,
|
||||||
|
amount: u64,
|
||||||
|
) -> Transaction {
|
||||||
|
// Create a transaction builder
|
||||||
|
let mut tx_builder = wallet.build_tx();
|
||||||
|
|
||||||
|
let to_address = Address::from_str(recipient_address).unwrap();
|
||||||
|
|
||||||
|
// Set recipient of the transaction
|
||||||
|
tx_builder.set_recipients(vec![(to_address.script_pubkey(), amount)]);
|
||||||
|
|
||||||
|
// Finalise the transaction and extract PSBT
|
||||||
|
let (mut psbt, _) = tx_builder.finish().unwrap();
|
||||||
|
|
||||||
|
// Sign the above psbt with signing option
|
||||||
|
wallet.sign(&mut psbt, SignOptions::default()).unwrap();
|
||||||
|
|
||||||
|
// Extract the final transaction
|
||||||
|
psbt.extract_tx()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -178,7 +178,8 @@ impl_from!(boxed rpc::RpcBlockchain, AnyBlockchain, Rpc, #[cfg(feature = "rpc")]
|
|||||||
/// "type" : "electrum",
|
/// "type" : "electrum",
|
||||||
/// "url" : "ssl://electrum.blockstream.info:50002",
|
/// "url" : "ssl://electrum.blockstream.info:50002",
|
||||||
/// "retry": 2,
|
/// "retry": 2,
|
||||||
/// "stop_gap": 20
|
/// "stop_gap": 20,
|
||||||
|
/// "validate_domain": true
|
||||||
/// }"#,
|
/// }"#,
|
||||||
/// )
|
/// )
|
||||||
/// .unwrap();
|
/// .unwrap();
|
||||||
@@ -190,6 +191,7 @@ impl_from!(boxed rpc::RpcBlockchain, AnyBlockchain, Rpc, #[cfg(feature = "rpc")]
|
|||||||
/// socks5: None,
|
/// socks5: None,
|
||||||
/// timeout: None,
|
/// timeout: None,
|
||||||
/// stop_gap: 20,
|
/// stop_gap: 20,
|
||||||
|
/// validate_domain: true,
|
||||||
/// })
|
/// })
|
||||||
/// );
|
/// );
|
||||||
/// # }
|
/// # }
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ impl CfSync {
|
|||||||
|
|
||||||
let resp = peer.get_cf_headers(0x00, start_height as u32, stop_hash)?;
|
let resp = peer.get_cf_headers(0x00, start_height as u32, stop_hash)?;
|
||||||
|
|
||||||
assert!(resp.previous_filter_header == checkpoint);
|
assert_eq!(resp.previous_filter_header, checkpoint);
|
||||||
status =
|
status =
|
||||||
self.cf_store
|
self.cf_store
|
||||||
.advance_to_cf_headers(index, checkpoint, resp.filter_hashes)?;
|
.advance_to_cf_headers(index, checkpoint, resp.filter_hashes)?;
|
||||||
|
|||||||
@@ -281,9 +281,11 @@ impl<'a, 'b, D: Database> TxCache<'a, 'b, D> {
|
|||||||
.client
|
.client
|
||||||
.batch_transaction_get(need_fetch.clone())
|
.batch_transaction_get(need_fetch.clone())
|
||||||
.map_err(Error::Electrum)?;
|
.map_err(Error::Electrum)?;
|
||||||
for (tx, _txid) in txs.into_iter().zip(need_fetch) {
|
let mut txs: HashMap<_, _> = txs.into_iter().map(|tx| (tx.txid(), tx)).collect();
|
||||||
debug_assert_eq!(*_txid, tx.txid());
|
for txid in need_fetch {
|
||||||
self.cache.insert(tx.txid(), tx);
|
if let Some(tx) = txs.remove(txid) {
|
||||||
|
self.cache.insert(*txid, tx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,6 +312,8 @@ pub struct ElectrumBlockchainConfig {
|
|||||||
pub timeout: Option<u8>,
|
pub timeout: Option<u8>,
|
||||||
/// Stop searching addresses for transactions after finding an unused gap of this length
|
/// Stop searching addresses for transactions after finding an unused gap of this length
|
||||||
pub stop_gap: usize,
|
pub stop_gap: usize,
|
||||||
|
/// Validate the domain when using SSL
|
||||||
|
pub validate_domain: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigurableBlockchain for ElectrumBlockchain {
|
impl ConfigurableBlockchain for ElectrumBlockchain {
|
||||||
@@ -321,6 +325,7 @@ impl ConfigurableBlockchain for ElectrumBlockchain {
|
|||||||
.retry(config.retry)
|
.retry(config.retry)
|
||||||
.timeout(config.timeout)?
|
.timeout(config.timeout)?
|
||||||
.socks5(socks5)?
|
.socks5(socks5)?
|
||||||
|
.validate_domain(config.validate_domain)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Ok(ElectrumBlockchain {
|
Ok(ElectrumBlockchain {
|
||||||
@@ -415,6 +420,7 @@ mod test {
|
|||||||
retry: 0,
|
retry: 0,
|
||||||
timeout: None,
|
timeout: None,
|
||||||
stop_gap: stop_gap,
|
stop_gap: stop_gap,
|
||||||
|
validate_domain: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,17 @@ static MIGRATIONS: &[&str] = &[
|
|||||||
"CREATE TABLE utxos (value INTEGER, keychain TEXT, vout INTEGER, txid BLOB, script BLOB, is_spent BOOLEAN DEFAULT 0);",
|
"CREATE TABLE utxos (value INTEGER, keychain TEXT, vout INTEGER, txid BLOB, script BLOB, is_spent BOOLEAN DEFAULT 0);",
|
||||||
"INSERT INTO utxos SELECT value, keychain, vout, txid, script, is_spent FROM utxos_old;",
|
"INSERT INTO utxos SELECT value, keychain, vout, txid, script, is_spent FROM utxos_old;",
|
||||||
"DROP TABLE utxos_old;",
|
"DROP TABLE utxos_old;",
|
||||||
"CREATE UNIQUE INDEX idx_utxos_txid_vout ON utxos(txid, vout);"
|
"CREATE UNIQUE INDEX idx_utxos_txid_vout ON utxos(txid, vout);",
|
||||||
|
// Fix issue https://github.com/bitcoindevkit/bdk/issues/801: drop duplicated script_pubkeys
|
||||||
|
"ALTER TABLE script_pubkeys RENAME TO script_pubkeys_old;",
|
||||||
|
"DROP INDEX idx_keychain_child;",
|
||||||
|
"DROP INDEX idx_script;",
|
||||||
|
"CREATE TABLE script_pubkeys (keychain TEXT, child INTEGER, script BLOB);",
|
||||||
|
"CREATE INDEX idx_keychain_child ON script_pubkeys(keychain, child);",
|
||||||
|
"CREATE INDEX idx_script ON script_pubkeys(script);",
|
||||||
|
"CREATE UNIQUE INDEX idx_script_pks_unique ON script_pubkeys(keychain, child);",
|
||||||
|
"INSERT OR REPLACE INTO script_pubkeys SELECT keychain, child, script FROM script_pubkeys_old;",
|
||||||
|
"DROP TABLE script_pubkeys_old;"
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Sqlite database stored on filesystem
|
/// Sqlite database stored on filesystem
|
||||||
@@ -88,7 +98,7 @@ impl SqliteDatabase {
|
|||||||
child: u32,
|
child: u32,
|
||||||
script: &[u8],
|
script: &[u8],
|
||||||
) -> Result<i64, Error> {
|
) -> Result<i64, Error> {
|
||||||
let mut statement = self.connection.prepare_cached("INSERT INTO script_pubkeys (keychain, child, script) VALUES (:keychain, :child, :script)")?;
|
let mut statement = self.connection.prepare_cached("INSERT OR REPLACE INTO script_pubkeys (keychain, child, script) VALUES (:keychain, :child, :script)")?;
|
||||||
statement.execute(named_params! {
|
statement.execute(named_params! {
|
||||||
":keychain": keychain,
|
":keychain": keychain,
|
||||||
":child": child,
|
":child": child,
|
||||||
@@ -1096,4 +1106,42 @@ pub mod test {
|
|||||||
fn test_check_descriptor_checksum() {
|
fn test_check_descriptor_checksum() {
|
||||||
crate::database::test::test_check_descriptor_checksum(get_database());
|
crate::database::test::test_check_descriptor_checksum(get_database());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Issue 801: https://github.com/bitcoindevkit/bdk/issues/801
|
||||||
|
#[test]
|
||||||
|
fn test_unique_spks() {
|
||||||
|
use crate::bitcoin::hashes::hex::FromHex;
|
||||||
|
use crate::database::*;
|
||||||
|
|
||||||
|
let mut db = get_database();
|
||||||
|
|
||||||
|
let script = Script::from(
|
||||||
|
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
|
||||||
|
);
|
||||||
|
let path = 42;
|
||||||
|
let keychain = KeychainKind::External;
|
||||||
|
|
||||||
|
for _ in 0..100 {
|
||||||
|
db.set_script_pubkey(&script, keychain, path).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut statement = db
|
||||||
|
.connection
|
||||||
|
.prepare_cached(
|
||||||
|
"select keychain,child,count(child) from script_pubkeys group by keychain,child;",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut rows = statement.query([]).unwrap();
|
||||||
|
while let Some(row) = rows.next().unwrap() {
|
||||||
|
let keychain: String = row.get(0).unwrap();
|
||||||
|
let child: u32 = row.get(1).unwrap();
|
||||||
|
let count: usize = row.get(2).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
count, 1,
|
||||||
|
"keychain={}, child={}, count={}",
|
||||||
|
keychain, child, count
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ pub fn get_checksum(desc: &str) -> Result<String, DescriptorError> {
|
|||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::descriptor::calc_checksum;
|
use crate::descriptor::calc_checksum;
|
||||||
|
use assert_matches::assert_matches;
|
||||||
|
|
||||||
// test calc_checksum() function; it should return the same value as Bitcoin Core
|
// test calc_checksum() function; it should return the same value as Bitcoin Core
|
||||||
#[test]
|
#[test]
|
||||||
@@ -155,16 +156,16 @@ mod test {
|
|||||||
assert_eq!(calc_checksum(desc).unwrap(), "lasegmfs");
|
assert_eq!(calc_checksum(desc).unwrap(), "lasegmfs");
|
||||||
|
|
||||||
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc26";
|
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc26";
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
calc_checksum(desc).err(),
|
calc_checksum(desc),
|
||||||
Some(DescriptorError::InvalidDescriptorChecksum)
|
Err(DescriptorError::InvalidDescriptorChecksum)
|
||||||
));
|
);
|
||||||
|
|
||||||
let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)#lasegmsf";
|
let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)#lasegmsf";
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
calc_checksum(desc).err(),
|
calc_checksum(desc),
|
||||||
Some(DescriptorError::InvalidDescriptorChecksum)
|
Err(DescriptorError::InvalidDescriptorChecksum)
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -172,9 +173,9 @@ mod test {
|
|||||||
let sparkle_heart = unsafe { std::str::from_utf8_unchecked(&[240, 159, 146, 150]) };
|
let sparkle_heart = unsafe { std::str::from_utf8_unchecked(&[240, 159, 146, 150]) };
|
||||||
let invalid_desc = format!("wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", sparkle_heart);
|
let invalid_desc = format!("wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", sparkle_heart);
|
||||||
|
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
calc_checksum(&invalid_desc).err(),
|
calc_checksum(&invalid_desc),
|
||||||
Some(DescriptorError::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart.as_bytes()[0]
|
Err(DescriptorError::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart.as_bytes()[0]
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ use bitcoin::util::{psbt, taproot};
|
|||||||
use bitcoin::{secp256k1, PublicKey, XOnlyPublicKey};
|
use bitcoin::{secp256k1, PublicKey, XOnlyPublicKey};
|
||||||
use bitcoin::{Network, TxOut};
|
use bitcoin::{Network, TxOut};
|
||||||
|
|
||||||
use miniscript::descriptor::{DefiniteDescriptorKey, DescriptorType, InnerXKey, SinglePubKey};
|
use miniscript::descriptor::{
|
||||||
|
DefiniteDescriptorKey, DescriptorSecretKey, DescriptorType, InnerXKey, SinglePubKey,
|
||||||
|
};
|
||||||
pub use miniscript::{
|
pub use miniscript::{
|
||||||
descriptor::DescriptorXKey, descriptor::KeyMap, descriptor::Wildcard, Descriptor,
|
descriptor::DescriptorXKey, descriptor::KeyMap, descriptor::Wildcard, Descriptor,
|
||||||
DescriptorPublicKey, Legacy, Miniscript, ScriptContext, Segwitv0,
|
DescriptorPublicKey, Legacy, Miniscript, ScriptContext, Segwitv0,
|
||||||
@@ -240,14 +242,34 @@ impl IntoWalletDescriptor for DescriptorTemplateOut {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.2.contains(&network) {
|
let (desc, keymap, networks) = self;
|
||||||
|
|
||||||
|
if !networks.contains(&network) {
|
||||||
return Err(DescriptorError::Key(KeyError::InvalidNetwork));
|
return Err(DescriptorError::Key(KeyError::InvalidNetwork));
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixup the network for keys that need it
|
// fixup the network for keys that need it in the descriptor
|
||||||
let translated = self.0.translate_pk(&mut Translator { network })?;
|
let translated = desc.translate_pk(&mut Translator { network })?;
|
||||||
|
// ...and in the key map
|
||||||
|
let fixed_keymap = keymap
|
||||||
|
.into_iter()
|
||||||
|
.map(|(mut k, mut v)| {
|
||||||
|
match (&mut k, &mut v) {
|
||||||
|
(DescriptorPublicKey::XPub(xpub), DescriptorSecretKey::XPrv(xprv)) => {
|
||||||
|
xpub.xkey.network = network;
|
||||||
|
xprv.xkey.network = network;
|
||||||
|
}
|
||||||
|
(_, DescriptorSecretKey::Single(key)) => {
|
||||||
|
key.key.network = network;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
Ok((translated, self.1))
|
(k, v)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok((translated, fixed_keymap))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,6 +581,7 @@ impl DescriptorMeta for ExtendedDescriptor {
|
|||||||
mod test {
|
mod test {
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use bitcoin::consensus::encode::deserialize;
|
use bitcoin::consensus::encode::deserialize;
|
||||||
use bitcoin::hashes::hex::FromHex;
|
use bitcoin::hashes::hex::FromHex;
|
||||||
use bitcoin::secp256k1::Secp256k1;
|
use bitcoin::secp256k1::Secp256k1;
|
||||||
@@ -682,23 +705,40 @@ mod test {
|
|||||||
|
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
|
|
||||||
let xpub = bip32::ExtendedPubKey::from_str("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL").unwrap();
|
let xprv = bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K3c3gF1DUWpWNr2SG2XrG8oYPpqYh7hoWsJy9NjabErnzriJPpnGHyKz5NgdXmq1KVbqS1r4NXdCoKitWg5e86zqXHa8kxyB").unwrap();
|
||||||
let path = bip32::DerivationPath::from_str("m/0").unwrap();
|
let path = bip32::DerivationPath::from_str("m/0").unwrap();
|
||||||
|
|
||||||
// here `to_descriptor_key` will set the valid networks for the key to only mainnet, since
|
// here `to_descriptor_key` will set the valid networks for the key to only mainnet, since
|
||||||
// we are using an "xpub"
|
// we are using an "xpub"
|
||||||
let key = (xpub, path).into_descriptor_key().unwrap();
|
let key = (xprv, path.clone()).into_descriptor_key().unwrap();
|
||||||
// override it with any. this happens in some key conversions, like bip39
|
// override it with any. this happens in some key conversions, like bip39
|
||||||
let key = key.override_valid_networks(any_network());
|
let key = key.override_valid_networks(any_network());
|
||||||
|
|
||||||
// make a descriptor out of it
|
// make a descriptor out of it
|
||||||
let desc = crate::descriptor!(wpkh(key)).unwrap();
|
let desc = crate::descriptor!(wpkh(key)).unwrap();
|
||||||
// this should convert the key that supports "any_network" to the right network (testnet)
|
// this should convert the key that supports "any_network" to the right network (testnet)
|
||||||
let (wallet_desc, _) = desc
|
let (wallet_desc, keymap) = desc
|
||||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(wallet_desc.to_string(), "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)#y8p7e8kk");
|
let mut xprv_testnet = xprv;
|
||||||
|
xprv_testnet.network = Network::Testnet;
|
||||||
|
|
||||||
|
let xpub_testnet = bip32::ExtendedPubKey::from_priv(&secp, &xprv_testnet);
|
||||||
|
let desc_pubkey = DescriptorPublicKey::XPub(DescriptorXKey {
|
||||||
|
xkey: xpub_testnet,
|
||||||
|
origin: None,
|
||||||
|
derivation_path: path,
|
||||||
|
wildcard: Wildcard::Unhardened,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(wallet_desc.to_string(), "wpkh(tpubD6NzVbkrYhZ4XtJzoDja5snUjBNQRP5B3f4Hyn1T1x6PVPxzzVjvw6nJx2D8RBCxog9GEVjZoyStfepTz7TtKoBVdkCtnc7VCJh9dD4RAU9/0/*)#a3svx0ha");
|
||||||
|
assert_eq!(
|
||||||
|
keymap
|
||||||
|
.get(&desc_pubkey)
|
||||||
|
.map(|key| key.to_public(&secp).unwrap()),
|
||||||
|
Some(desc_pubkey)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// test IntoWalletDescriptor trait from &str with and without checksum appended
|
// test IntoWalletDescriptor trait from &str with and without checksum appended
|
||||||
@@ -724,17 +764,11 @@ mod test {
|
|||||||
|
|
||||||
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
|
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
|
||||||
.into_wallet_descriptor(&secp, Network::Testnet);
|
.into_wallet_descriptor(&secp, Network::Testnet);
|
||||||
assert!(matches!(
|
assert_matches!(desc, Err(DescriptorError::InvalidDescriptorChecksum));
|
||||||
desc.err(),
|
|
||||||
Some(DescriptorError::InvalidDescriptorChecksum)
|
|
||||||
));
|
|
||||||
|
|
||||||
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
|
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
|
||||||
.into_wallet_descriptor(&secp, Network::Testnet);
|
.into_wallet_descriptor(&secp, Network::Testnet);
|
||||||
assert!(matches!(
|
assert_matches!(desc, Err(DescriptorError::InvalidDescriptorChecksum));
|
||||||
desc.err(),
|
|
||||||
Some(DescriptorError::InvalidDescriptorChecksum)
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// test IntoWalletDescriptor trait from &str with keys from right and wrong network
|
// test IntoWalletDescriptor trait from &str with keys from right and wrong network
|
||||||
@@ -768,17 +802,11 @@ mod test {
|
|||||||
|
|
||||||
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
|
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
|
||||||
.into_wallet_descriptor(&secp, Network::Bitcoin);
|
.into_wallet_descriptor(&secp, Network::Bitcoin);
|
||||||
assert!(matches!(
|
assert_matches!(desc, Err(DescriptorError::Key(KeyError::InvalidNetwork)));
|
||||||
desc.err(),
|
|
||||||
Some(DescriptorError::Key(KeyError::InvalidNetwork))
|
|
||||||
));
|
|
||||||
|
|
||||||
let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
|
let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
|
||||||
.into_wallet_descriptor(&secp, Network::Bitcoin);
|
.into_wallet_descriptor(&secp, Network::Bitcoin);
|
||||||
assert!(matches!(
|
assert_matches!(desc, Err(DescriptorError::Key(KeyError::InvalidNetwork)));
|
||||||
desc.err(),
|
|
||||||
Some(DescriptorError::Key(KeyError::InvalidNetwork))
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// test IntoWalletDescriptor trait from the output of the descriptor!() macro
|
// test IntoWalletDescriptor trait from the output of the descriptor!() macro
|
||||||
@@ -812,11 +840,7 @@ mod test {
|
|||||||
let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0'/1/2/*)";
|
let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0'/1/2/*)";
|
||||||
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
|
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert_matches!(result, Err(DescriptorError::HardenedDerivationXpub));
|
||||||
assert!(matches!(
|
|
||||||
result.unwrap_err(),
|
|
||||||
DescriptorError::HardenedDerivationXpub
|
|
||||||
));
|
|
||||||
|
|
||||||
let descriptor = "wsh(multi(2,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*))";
|
let descriptor = "wsh(multi(2,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*))";
|
||||||
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
|
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
|
||||||
|
|||||||
@@ -1139,6 +1139,7 @@ mod test {
|
|||||||
use crate::descriptor::policy::SatisfiableItem::{EcdsaSignature, Multisig, Thresh};
|
use crate::descriptor::policy::SatisfiableItem::{EcdsaSignature, Multisig, Thresh};
|
||||||
use crate::keys::{DescriptorKey, IntoDescriptorKey};
|
use crate::keys::{DescriptorKey, IntoDescriptorKey};
|
||||||
use crate::wallet::signer::SignersContainer;
|
use crate::wallet::signer::SignersContainer;
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use bitcoin::secp256k1::Secp256k1;
|
use bitcoin::secp256k1::Secp256k1;
|
||||||
use bitcoin::util::bip32;
|
use bitcoin::util::bip32;
|
||||||
use bitcoin::Network;
|
use bitcoin::Network;
|
||||||
@@ -1182,8 +1183,8 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
|
assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
|
||||||
assert!(matches!(&policy.contribution, Satisfaction::None));
|
assert_matches!(&policy.contribution, Satisfaction::None);
|
||||||
|
|
||||||
let desc = descriptor!(wpkh(prvkey)).unwrap();
|
let desc = descriptor!(wpkh(prvkey)).unwrap();
|
||||||
let (wallet_desc, keymap) = desc
|
let (wallet_desc, keymap) = desc
|
||||||
@@ -1195,10 +1196,8 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
|
assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
|
||||||
assert!(
|
assert_matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None);
|
||||||
matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2 pub keys descriptor, required 2 prv keys
|
// 2 pub keys descriptor, required 2 prv keys
|
||||||
@@ -1217,19 +1216,16 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
|
||||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
|
|
||||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
|
||||||
);
|
);
|
||||||
// TODO should this be "Satisfaction::None" since we have no prv keys?
|
// TODO should this be "Satisfaction::None" since we have no prv keys?
|
||||||
// TODO should items and conditions not be empty?
|
// TODO should items and conditions not be empty?
|
||||||
assert!(
|
assert_matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
|
||||||
matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
|
|
||||||
&& m == &2usize
|
&& m == &2usize
|
||||||
&& items.is_empty()
|
&& items.is_empty()
|
||||||
&& conditions.is_empty()
|
&& conditions.is_empty()
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1248,18 +1244,15 @@ mod test {
|
|||||||
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(
|
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
|
||||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
|
|
||||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
|
||||||
matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
|
|
||||||
&& m == &2usize
|
&& m == &2usize
|
||||||
&& items.len() == 1
|
&& items.len() == 1
|
||||||
&& conditions.contains_key(&0)
|
&& conditions.contains_key(&0)
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1281,18 +1274,15 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
|
||||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
|
|
||||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
|
||||||
);
|
);
|
||||||
assert!(
|
assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
|
||||||
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
|
|
||||||
&& m == &1
|
&& m == &1
|
||||||
&& items.len() == 2
|
&& items.len() == 2
|
||||||
&& conditions.contains_key(&vec![0])
|
&& conditions.contains_key(&vec![0])
|
||||||
&& conditions.contains_key(&vec![1])
|
&& conditions.contains_key(&vec![1])
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1313,18 +1303,15 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2
|
||||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2
|
|
||||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
|
||||||
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items.len() == 2
|
&& items.len() == 2
|
||||||
&& conditions.contains_key(&vec![0,1])
|
&& conditions.contains_key(&vec![0,1])
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1345,8 +1332,8 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
|
assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
|
||||||
assert!(matches!(&policy.contribution, Satisfaction::None));
|
assert_matches!(&policy.contribution, Satisfaction::None);
|
||||||
|
|
||||||
let desc = descriptor!(wpkh(prvkey)).unwrap();
|
let desc = descriptor!(wpkh(prvkey)).unwrap();
|
||||||
let (wallet_desc, keymap) = desc
|
let (wallet_desc, keymap) = desc
|
||||||
@@ -1358,10 +1345,8 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(matches!(policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == fingerprint));
|
assert_matches!(policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == fingerprint);
|
||||||
assert!(
|
assert_matches!(policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None);
|
||||||
matches!(policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// single key, 1 prv and 1 pub key descriptor, required 1 prv keys
|
// single key, 1 prv and 1 pub key descriptor, required 1 prv keys
|
||||||
@@ -1382,18 +1367,15 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(policy.item, Multisig { keys, threshold } if threshold == 1
|
||||||
matches!(policy.item, Multisig { keys, threshold } if threshold == 1
|
|
||||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
|
||||||
);
|
);
|
||||||
assert!(
|
assert_matches!(policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == 2
|
||||||
matches!(policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == 2
|
|
||||||
&& m == 1
|
&& m == 1
|
||||||
&& items.len() == 2
|
&& items.len() == 2
|
||||||
&& conditions.contains_key(&vec![0])
|
&& conditions.contains_key(&vec![0])
|
||||||
&& conditions.contains_key(&vec![1])
|
&& conditions.contains_key(&vec![1])
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1425,18 +1407,14 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.item, Thresh { items, threshold } if items.len() == 3 && threshold == &2);
|
||||||
matches!(&policy.item, Thresh { items, threshold } if items.len() == 3 && threshold == &2)
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &3
|
||||||
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &3
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items.len() == 3
|
&& items.len() == 3
|
||||||
&& conditions.get(&vec![0,1]).unwrap().iter().next().unwrap().csv.is_none()
|
&& conditions.get(&vec![0,1]).unwrap().iter().next().unwrap().csv.is_none()
|
||||||
&& conditions.get(&vec![0,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
|
&& conditions.get(&vec![0,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
|
||||||
&& conditions.get(&vec![1,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
|
&& conditions.get(&vec![1,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1599,11 +1577,9 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
//println!("{}", serde_json::to_string(&policy_alice_psbt).unwrap());
|
//println!("{}", serde_json::to_string(&policy_alice_psbt).unwrap());
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy_alice_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
|
||||||
matches!(&policy_alice_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items == &vec![0]
|
&& items == &vec![0]
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let psbt = Psbt::from_str(BOB_SIGNED_PSBT).unwrap();
|
let psbt = Psbt::from_str(BOB_SIGNED_PSBT).unwrap();
|
||||||
@@ -1613,11 +1589,9 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
//println!("{}", serde_json::to_string(&policy_bob_psbt).unwrap());
|
//println!("{}", serde_json::to_string(&policy_bob_psbt).unwrap());
|
||||||
|
|
||||||
assert!(
|
assert_matches!(&policy_bob_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
|
||||||
matches!(&policy_bob_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items == &vec![1]
|
&& items == &vec![1]
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let psbt = Psbt::from_str(ALICE_BOB_SIGNED_PSBT).unwrap();
|
let psbt = Psbt::from_str(ALICE_BOB_SIGNED_PSBT).unwrap();
|
||||||
@@ -1625,11 +1599,9 @@ mod test {
|
|||||||
.extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
|
.extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(
|
assert_matches!(&policy_alice_bob_psbt.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &2
|
||||||
matches!(&policy_alice_bob_psbt.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &2
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items == &vec![0, 1]
|
&& items == &vec![0, 1]
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1673,11 +1645,9 @@ mod test {
|
|||||||
.extract_policy(&signers_container, build_sat, &secp)
|
.extract_policy(&signers_container, build_sat, &secp)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(
|
assert_matches!(&policy.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
|
||||||
matches!(&policy.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items.is_empty()
|
&& items.is_empty()
|
||||||
)
|
|
||||||
);
|
);
|
||||||
//println!("{}", serde_json::to_string(&policy).unwrap());
|
//println!("{}", serde_json::to_string(&policy).unwrap());
|
||||||
|
|
||||||
@@ -1691,11 +1661,9 @@ mod test {
|
|||||||
.extract_policy(&signers_container, build_sat_expired, &secp)
|
.extract_policy(&signers_container, build_sat_expired, &secp)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(
|
assert_matches!(&policy_expired.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
|
||||||
matches!(&policy_expired.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items == &vec![0]
|
&& items == &vec![0]
|
||||||
)
|
|
||||||
);
|
);
|
||||||
//println!("{}", serde_json::to_string(&policy_expired).unwrap());
|
//println!("{}", serde_json::to_string(&policy_expired).unwrap());
|
||||||
|
|
||||||
@@ -1711,11 +1679,9 @@ mod test {
|
|||||||
.extract_policy(&signers_container, build_sat_expired_signed, &secp)
|
.extract_policy(&signers_container, build_sat_expired_signed, &secp)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(
|
assert_matches!(&policy_expired_signed.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &3
|
||||||
matches!(&policy_expired_signed.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &3
|
|
||||||
&& m == &2
|
&& m == &2
|
||||||
&& items == &vec![0, 1]
|
&& items == &vec![0, 1]
|
||||||
)
|
|
||||||
);
|
);
|
||||||
//println!("{}", serde_json::to_string(&policy_expired_signed).unwrap());
|
//println!("{}", serde_json::to_string(&policy_expired_signed).unwrap());
|
||||||
}
|
}
|
||||||
@@ -1790,12 +1756,8 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(policy.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
|
||||||
matches!(policy.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2)
|
assert_matches!(policy.contribution, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![1]);
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
matches!(policy.contribution, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![1])
|
|
||||||
);
|
|
||||||
|
|
||||||
let alice_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(alice_fing));
|
let alice_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(alice_fing));
|
||||||
let bob_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(bob_fing));
|
let bob_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(bob_fing));
|
||||||
@@ -1887,19 +1849,11 @@ mod test {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert_matches!(policy_unsigned.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
|
||||||
matches!(policy_unsigned.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2)
|
assert_matches!(policy_unsigned.satisfaction, Satisfaction::Partial { n: 2, m: 1, items, .. } if items.is_empty());
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
matches!(policy_unsigned.satisfaction, Satisfaction::Partial { n: 2, m: 1, items, .. } if items.is_empty())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(
|
assert_matches!(policy_signed.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
|
||||||
matches!(policy_signed.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2)
|
assert_matches!(policy_signed.satisfaction, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![0, 1]);
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
matches!(policy_signed.satisfaction, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![0, 1])
|
|
||||||
);
|
|
||||||
|
|
||||||
let satisfied_items = match policy_signed.item {
|
let satisfied_items = match policy_signed.item {
|
||||||
SatisfiableItem::Thresh { items, .. } => items,
|
SatisfiableItem::Thresh { items, .. } => items,
|
||||||
|
|||||||
@@ -235,14 +235,17 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
|
|||||||
/// )?;
|
/// )?;
|
||||||
///
|
///
|
||||||
/// assert_eq!(wallet.get_address(New)?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
|
/// assert_eq!(wallet.get_address(New)?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
|
||||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#xgaaevjx");
|
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#cfhumdqz");
|
||||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Bip44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
pub struct Bip44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
||||||
|
|
||||||
impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
|
impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
|
||||||
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||||
P2Pkh(legacy::make_bipxx_public(44, self.0, self.1, self.2)?).build(network)
|
P2Pkh(legacy::make_bipxx_public(
|
||||||
|
44, self.0, self.1, self.2, network,
|
||||||
|
)?)
|
||||||
|
.build(network)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,14 +314,17 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
|
|||||||
/// )?;
|
/// )?;
|
||||||
///
|
///
|
||||||
/// assert_eq!(wallet.get_address(New)?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
|
/// assert_eq!(wallet.get_address(New)?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
|
||||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49'/0'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#gsmdv4xr");
|
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#3tka9g0q");
|
||||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Bip49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
pub struct Bip49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
||||||
|
|
||||||
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
|
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
|
||||||
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||||
P2Wpkh_P2Sh(segwit_v0::make_bipxx_public(49, self.0, self.1, self.2)?).build(network)
|
P2Wpkh_P2Sh(segwit_v0::make_bipxx_public(
|
||||||
|
49, self.0, self.1, self.2, network,
|
||||||
|
)?)
|
||||||
|
.build(network)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,14 +393,17 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
|
|||||||
/// )?;
|
/// )?;
|
||||||
///
|
///
|
||||||
/// assert_eq!(wallet.get_address(New)?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
|
/// assert_eq!(wallet.get_address(New)?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
|
||||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#nkk5dtkg");
|
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#dhu402yv");
|
||||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Bip84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
pub struct Bip84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
||||||
|
|
||||||
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
|
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
|
||||||
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||||
P2Wpkh(segwit_v0::make_bipxx_public(84, self.0, self.1, self.2)?).build(network)
|
P2Wpkh(segwit_v0::make_bipxx_public(
|
||||||
|
84, self.0, self.1, self.2, network,
|
||||||
|
)?)
|
||||||
|
.build(network)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,6 +449,7 @@ macro_rules! expand_make_bipxx {
|
|||||||
key: K,
|
key: K,
|
||||||
parent_fingerprint: bip32::Fingerprint,
|
parent_fingerprint: bip32::Fingerprint,
|
||||||
keychain: KeychainKind,
|
keychain: KeychainKind,
|
||||||
|
network: Network,
|
||||||
) -> Result<impl IntoDescriptorKey<$ctx>, DescriptorError> {
|
) -> Result<impl IntoDescriptorKey<$ctx>, DescriptorError> {
|
||||||
let derivation_path: bip32::DerivationPath = match keychain {
|
let derivation_path: bip32::DerivationPath = match keychain {
|
||||||
KeychainKind::External => vec![bip32::ChildNumber::from_normal_idx(0)?].into(),
|
KeychainKind::External => vec![bip32::ChildNumber::from_normal_idx(0)?].into(),
|
||||||
@@ -448,7 +458,10 @@ macro_rules! expand_make_bipxx {
|
|||||||
|
|
||||||
let source_path = bip32::DerivationPath::from(vec![
|
let source_path = bip32::DerivationPath::from(vec![
|
||||||
bip32::ChildNumber::from_hardened_idx(bip)?,
|
bip32::ChildNumber::from_hardened_idx(bip)?,
|
||||||
bip32::ChildNumber::from_hardened_idx(0)?,
|
match network {
|
||||||
|
Network::Bitcoin => bip32::ChildNumber::from_hardened_idx(0)?,
|
||||||
|
_ => bip32::ChildNumber::from_hardened_idx(1)?,
|
||||||
|
},
|
||||||
bip32::ChildNumber::from_hardened_idx(0)?,
|
bip32::ChildNumber::from_hardened_idx(0)?,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -470,6 +483,7 @@ mod test {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::descriptor::{DescriptorError, DescriptorMeta};
|
use crate::descriptor::{DescriptorError, DescriptorMeta};
|
||||||
use crate::keys::ValidNetworks;
|
use crate::keys::ValidNetworks;
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use bitcoin::network::constants::Network::Regtest;
|
use bitcoin::network::constants::Network::Regtest;
|
||||||
use miniscript::descriptor::{DescriptorPublicKey, KeyMap};
|
use miniscript::descriptor::{DescriptorPublicKey, KeyMap};
|
||||||
use miniscript::Descriptor;
|
use miniscript::Descriptor;
|
||||||
@@ -488,9 +502,9 @@ mod test {
|
|||||||
if let ExtendedDescriptor::Pkh(pkh) = xdesc.0 {
|
if let ExtendedDescriptor::Pkh(pkh) = xdesc.0 {
|
||||||
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().into();
|
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().into();
|
||||||
let purpose = path.get(0).unwrap();
|
let purpose = path.get(0).unwrap();
|
||||||
assert!(matches!(purpose, Hardened { index: 44 }));
|
assert_matches!(purpose, Hardened { index: 44 });
|
||||||
let coin_type = path.get(1).unwrap();
|
let coin_type = path.get(1).unwrap();
|
||||||
assert!(matches!(coin_type, Hardened { index: 0 }));
|
assert_matches!(coin_type, Hardened { index: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
let tprvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
|
let tprvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
|
||||||
@@ -502,9 +516,9 @@ mod test {
|
|||||||
if let ExtendedDescriptor::Pkh(pkh) = tdesc.0 {
|
if let ExtendedDescriptor::Pkh(pkh) = tdesc.0 {
|
||||||
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().into();
|
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().into();
|
||||||
let purpose = path.get(0).unwrap();
|
let purpose = path.get(0).unwrap();
|
||||||
assert!(matches!(purpose, Hardened { index: 44 }));
|
assert_matches!(purpose, Hardened { index: 44 });
|
||||||
let coin_type = path.get(1).unwrap();
|
let coin_type = path.get(1).unwrap();
|
||||||
assert!(matches!(coin_type, Hardened { index: 1 }));
|
assert_matches!(coin_type, Hardened { index: 1 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -256,6 +256,9 @@ pub extern crate rusqlite;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod testutils;
|
pub mod testutils;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
extern crate assert_matches;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub(crate) mod error;
|
pub(crate) mod error;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pub trait ConfigurableBlockchainTester<B: ConfigurableBlockchain>: Sized {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs all avaliable tests.
|
/// Runs all available tests.
|
||||||
fn run(&self) {
|
fn run(&self) {
|
||||||
let test_client = &mut TestClient::default();
|
let test_client = &mut TestClient::default();
|
||||||
|
|
||||||
|
|||||||
117
src/types.rs
117
src/types.rs
@@ -247,6 +247,20 @@ pub struct TransactionDetails {
|
|||||||
pub confirmation_time: Option<BlockTime>,
|
pub confirmation_time: Option<BlockTime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for TransactionDetails {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for TransactionDetails {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.confirmation_time
|
||||||
|
.cmp(&other.confirmation_time)
|
||||||
|
.then_with(|| self.txid.cmp(&other.txid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Block height and timestamp of a block
|
/// Block height and timestamp of a block
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
|
||||||
pub struct BlockTime {
|
pub struct BlockTime {
|
||||||
@@ -256,6 +270,20 @@ pub struct BlockTime {
|
|||||||
pub timestamp: u64,
|
pub timestamp: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for BlockTime {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for BlockTime {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.height
|
||||||
|
.cmp(&other.height)
|
||||||
|
.then_with(|| self.timestamp.cmp(&other.timestamp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// **DEPRECATED**: Confirmation time of a transaction
|
/// **DEPRECATED**: Confirmation time of a transaction
|
||||||
///
|
///
|
||||||
/// The structure has been renamed to `BlockTime`
|
/// The structure has been renamed to `BlockTime`
|
||||||
@@ -334,6 +362,95 @@ impl std::iter::Sum for Balance {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use bitcoin::hashes::Hash;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sort_block_time() {
|
||||||
|
let block_time_a = BlockTime {
|
||||||
|
height: 100,
|
||||||
|
timestamp: 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
let block_time_b = BlockTime {
|
||||||
|
height: 100,
|
||||||
|
timestamp: 110,
|
||||||
|
};
|
||||||
|
|
||||||
|
let block_time_c = BlockTime {
|
||||||
|
height: 0,
|
||||||
|
timestamp: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut vec = vec![
|
||||||
|
block_time_a.clone(),
|
||||||
|
block_time_b.clone(),
|
||||||
|
block_time_c.clone(),
|
||||||
|
];
|
||||||
|
vec.sort();
|
||||||
|
let expected = vec![block_time_c, block_time_a, block_time_b];
|
||||||
|
|
||||||
|
assert_eq!(vec, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sort_tx_details() {
|
||||||
|
let block_time_a = BlockTime {
|
||||||
|
height: 100,
|
||||||
|
timestamp: 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
let block_time_b = BlockTime {
|
||||||
|
height: 0,
|
||||||
|
timestamp: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let tx_details_a = TransactionDetails {
|
||||||
|
transaction: None,
|
||||||
|
txid: Txid::from_inner([0; 32]),
|
||||||
|
received: 0,
|
||||||
|
sent: 0,
|
||||||
|
fee: None,
|
||||||
|
confirmation_time: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let tx_details_b = TransactionDetails {
|
||||||
|
transaction: None,
|
||||||
|
txid: Txid::from_inner([0; 32]),
|
||||||
|
received: 0,
|
||||||
|
sent: 0,
|
||||||
|
fee: None,
|
||||||
|
confirmation_time: Some(block_time_a),
|
||||||
|
};
|
||||||
|
|
||||||
|
let tx_details_c = TransactionDetails {
|
||||||
|
transaction: None,
|
||||||
|
txid: Txid::from_inner([0; 32]),
|
||||||
|
received: 0,
|
||||||
|
sent: 0,
|
||||||
|
fee: None,
|
||||||
|
confirmation_time: Some(block_time_b.clone()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let tx_details_d = TransactionDetails {
|
||||||
|
transaction: None,
|
||||||
|
txid: Txid::from_inner([1; 32]),
|
||||||
|
received: 0,
|
||||||
|
sent: 0,
|
||||||
|
fee: None,
|
||||||
|
confirmation_time: Some(block_time_b),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut vec = vec![
|
||||||
|
tx_details_a.clone(),
|
||||||
|
tx_details_b.clone(),
|
||||||
|
tx_details_c.clone(),
|
||||||
|
tx_details_d.clone(),
|
||||||
|
];
|
||||||
|
vec.sort();
|
||||||
|
let expected = vec![tx_details_a, tx_details_c, tx_details_d, tx_details_b];
|
||||||
|
|
||||||
|
assert_eq!(vec, expected)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_store_feerate_in_const() {
|
fn can_store_feerate_in_const() {
|
||||||
|
|||||||
@@ -102,11 +102,11 @@ use crate::{error::Error, Utxo};
|
|||||||
use bitcoin::consensus::encode::serialize;
|
use bitcoin::consensus::encode::serialize;
|
||||||
use bitcoin::Script;
|
use bitcoin::Script;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
#[cfg(test)]
|
|
||||||
use rand::{rngs::StdRng, SeedableRng};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
@@ -671,6 +671,7 @@ impl BranchAndBoundCoinSelection {
|
|||||||
optional_utxos.shuffle(&mut thread_rng());
|
optional_utxos.shuffle(&mut thread_rng());
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
{
|
{
|
||||||
|
use rand::{rngs::StdRng, SeedableRng};
|
||||||
let seed = [0; 32];
|
let seed = [0; 32];
|
||||||
let mut rng: StdRng = SeedableRng::from_seed(seed);
|
let mut rng: StdRng = SeedableRng::from_seed(seed);
|
||||||
optional_utxos.shuffle(&mut rng);
|
optional_utxos.shuffle(&mut rng);
|
||||||
@@ -1522,24 +1523,22 @@ mod test {
|
|||||||
let database = MemoryDatabase::default();
|
let database = MemoryDatabase::default();
|
||||||
let drain_script = Script::default();
|
let drain_script = Script::default();
|
||||||
|
|
||||||
let err = BranchAndBoundCoinSelection::default()
|
let selection = BranchAndBoundCoinSelection::default().coin_select(
|
||||||
.coin_select(
|
&database,
|
||||||
&database,
|
vec![],
|
||||||
vec![],
|
utxos,
|
||||||
utxos,
|
FeeRate::from_sat_per_vb(10.0),
|
||||||
FeeRate::from_sat_per_vb(10.0),
|
500_000,
|
||||||
500_000,
|
&drain_script,
|
||||||
&drain_script,
|
);
|
||||||
)
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
err,
|
selection,
|
||||||
Error::InsufficientFunds {
|
Err(Error::InsufficientFunds {
|
||||||
available: 300_000,
|
available: 300_000,
|
||||||
..
|
..
|
||||||
}
|
})
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1552,24 +1551,22 @@ mod test {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.partition(|u| matches!(u, WeightedUtxo { utxo, .. } if utxo.txout().value < 1000));
|
.partition(|u| matches!(u, WeightedUtxo { utxo, .. } if utxo.txout().value < 1000));
|
||||||
|
|
||||||
let err = BranchAndBoundCoinSelection::default()
|
let selection = BranchAndBoundCoinSelection::default().coin_select(
|
||||||
.coin_select(
|
&database,
|
||||||
&database,
|
required,
|
||||||
required,
|
optional,
|
||||||
optional,
|
FeeRate::from_sat_per_vb(10.0),
|
||||||
FeeRate::from_sat_per_vb(10.0),
|
500_000,
|
||||||
500_000,
|
&drain_script,
|
||||||
&drain_script,
|
);
|
||||||
)
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
err,
|
selection,
|
||||||
Error::InsufficientFunds {
|
Err(Error::InsufficientFunds {
|
||||||
available: 300_010,
|
available: 300_010,
|
||||||
..
|
..
|
||||||
}
|
})
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1578,23 +1575,21 @@ mod test {
|
|||||||
let database = MemoryDatabase::default();
|
let database = MemoryDatabase::default();
|
||||||
let drain_script = Script::default();
|
let drain_script = Script::default();
|
||||||
|
|
||||||
let err = BranchAndBoundCoinSelection::default()
|
let selection = BranchAndBoundCoinSelection::default().coin_select(
|
||||||
.coin_select(
|
&database,
|
||||||
&database,
|
utxos,
|
||||||
utxos,
|
vec![],
|
||||||
vec![],
|
FeeRate::from_sat_per_vb(10_000.0),
|
||||||
FeeRate::from_sat_per_vb(10_000.0),
|
500_000,
|
||||||
500_000,
|
&drain_script,
|
||||||
&drain_script,
|
);
|
||||||
)
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
err,
|
selection,
|
||||||
Error::InsufficientFunds {
|
Err(Error::InsufficientFunds {
|
||||||
available: 300_010,
|
available: 300_010,
|
||||||
..
|
..
|
||||||
}
|
})
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,15 +134,11 @@ impl FullyNodedExport {
|
|||||||
let blockheight = match wallet.database.borrow().iter_txs(false) {
|
let blockheight = match wallet.database.borrow().iter_txs(false) {
|
||||||
_ if !include_blockheight => 0,
|
_ if !include_blockheight => 0,
|
||||||
Err(_) => 0,
|
Err(_) => 0,
|
||||||
Ok(txs) => {
|
Ok(txs) => txs
|
||||||
let mut heights = txs
|
.into_iter()
|
||||||
.into_iter()
|
.filter_map(|tx| tx.confirmation_time.map(|c| c.height))
|
||||||
.map(|tx| tx.confirmation_time.map(|c| c.height).unwrap_or(0))
|
.min()
|
||||||
.collect::<Vec<_>>();
|
.unwrap_or(0),
|
||||||
heights.sort_unstable();
|
|
||||||
|
|
||||||
*heights.last().unwrap_or(&0)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let export = FullyNodedExport {
|
let export = FullyNodedExport {
|
||||||
@@ -249,6 +245,22 @@ mod test {
|
|||||||
fee: Some(500),
|
fee: Some(500),
|
||||||
confirmation_time: Some(BlockTime {
|
confirmation_time: Some(BlockTime {
|
||||||
timestamp: 12345678,
|
timestamp: 12345678,
|
||||||
|
height: 5001,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
db.set_tx(&TransactionDetails {
|
||||||
|
transaction: None,
|
||||||
|
txid: Txid::from_str(
|
||||||
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
received: 25_000,
|
||||||
|
sent: 0,
|
||||||
|
fee: Some(300),
|
||||||
|
confirmation_time: Some(BlockTime {
|
||||||
|
timestamp: 12345677,
|
||||||
height: 5000,
|
height: 5000,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -24,9 +24,12 @@
|
|||||||
//! # use std::sync::Arc;
|
//! # use std::sync::Arc;
|
||||||
//! #
|
//! #
|
||||||
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
//! let devices = HWIClient::enumerate()?;
|
//! let mut devices = HWIClient::enumerate()?;
|
||||||
//! let first_device = devices.first().expect("No devices found!");
|
//! if devices.is_empty() {
|
||||||
//! let custom_signer = HWISigner::from_device(first_device, HWIChain::Test)?;
|
//! panic!("No devices found!");
|
||||||
|
//! }
|
||||||
|
//! let first_device = devices.remove(0)?;
|
||||||
|
//! let custom_signer = HWISigner::from_device(&first_device, HWIChain::Test)?;
|
||||||
//!
|
//!
|
||||||
//! # let mut wallet = Wallet::new(
|
//! # let mut wallet = Wallet::new(
|
||||||
//! # "",
|
//! # "",
|
||||||
|
|||||||
@@ -1851,6 +1851,7 @@ pub fn get_funded_wallet(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod test {
|
pub(crate) mod test {
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use bitcoin::{util::psbt, Network, PackedLockTime, Sequence};
|
use bitcoin::{util::psbt, Network, PackedLockTime, Sequence};
|
||||||
|
|
||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
@@ -4425,11 +4426,9 @@ pub(crate) mod test {
|
|||||||
result.is_err(),
|
result.is_err(),
|
||||||
"Signing should have failed because the TX uses non-standard sighashes"
|
"Signing should have failed because the TX uses non-standard sighashes"
|
||||||
);
|
);
|
||||||
assert!(
|
assert_matches!(
|
||||||
matches!(
|
result,
|
||||||
result.unwrap_err(),
|
Err(Error::Signer(SignerError::NonStandardSighash)),
|
||||||
Error::Signer(SignerError::NonStandardSighash)
|
|
||||||
),
|
|
||||||
"Signing failed with the wrong error type"
|
"Signing failed with the wrong error type"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -4912,16 +4911,10 @@ pub(crate) mod test {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
assert!(
|
assert_matches!(
|
||||||
result.is_err(),
|
result,
|
||||||
"Signing should have failed because the witness_utxo is missing"
|
Err(Error::Signer(SignerError::MissingWitnessUtxo)),
|
||||||
);
|
"Signing should have failed with the correct error because the witness_utxo is missing"
|
||||||
assert!(
|
|
||||||
matches!(
|
|
||||||
result.unwrap_err(),
|
|
||||||
Error::Signer(SignerError::MissingWitnessUtxo)
|
|
||||||
),
|
|
||||||
"Signing failed with the wrong error type"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// restore the witness_utxo
|
// restore the witness_utxo
|
||||||
@@ -4935,9 +4928,9 @@ pub(crate) mod test {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(result.is_ok(), "Signing should have worked");
|
assert_matches!(
|
||||||
assert!(
|
result,
|
||||||
result.unwrap(),
|
Ok(true),
|
||||||
"Should finalize the input since we can produce signatures"
|
"Should finalize the input since we can produce signatures"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -5262,11 +5255,9 @@ pub(crate) mod test {
|
|||||||
result.is_err(),
|
result.is_err(),
|
||||||
"Signing should have failed because the TX uses non-standard sighashes"
|
"Signing should have failed because the TX uses non-standard sighashes"
|
||||||
);
|
);
|
||||||
assert!(
|
assert_matches!(
|
||||||
matches!(
|
result,
|
||||||
result.unwrap_err(),
|
Err(Error::Signer(SignerError::NonStandardSighash)),
|
||||||
Error::Signer(SignerError::NonStandardSighash)
|
|
||||||
),
|
|
||||||
"Signing failed with the wrong error type"
|
"Signing failed with the wrong error type"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -5282,11 +5273,9 @@ pub(crate) mod test {
|
|||||||
result.is_err(),
|
result.is_err(),
|
||||||
"Signing should have failed because the witness_utxo is missing"
|
"Signing should have failed because the witness_utxo is missing"
|
||||||
);
|
);
|
||||||
assert!(
|
assert_matches!(
|
||||||
matches!(
|
result,
|
||||||
result.unwrap_err(),
|
Err(Error::Signer(SignerError::MissingWitnessUtxo)),
|
||||||
Error::Signer(SignerError::MissingWitnessUtxo)
|
|
||||||
),
|
|
||||||
"Signing failed with the wrong error type"
|
"Signing failed with the wrong error type"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -5367,26 +5356,26 @@ pub(crate) mod test {
|
|||||||
builder
|
builder
|
||||||
.add_recipient(addr.script_pubkey(), balance.immature / 2)
|
.add_recipient(addr.script_pubkey(), balance.immature / 2)
|
||||||
.current_height(confirmation_time);
|
.current_height(confirmation_time);
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
builder.finish().unwrap_err(),
|
builder.finish(),
|
||||||
Error::InsufficientFunds {
|
Err(Error::InsufficientFunds {
|
||||||
needed: _,
|
needed: _,
|
||||||
available: 0
|
available: 0
|
||||||
}
|
})
|
||||||
));
|
);
|
||||||
|
|
||||||
// Still unspendable...
|
// Still unspendable...
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
builder
|
builder
|
||||||
.add_recipient(addr.script_pubkey(), balance.immature / 2)
|
.add_recipient(addr.script_pubkey(), balance.immature / 2)
|
||||||
.current_height(not_yet_mature_time);
|
.current_height(not_yet_mature_time);
|
||||||
assert!(matches!(
|
assert_matches!(
|
||||||
builder.finish().unwrap_err(),
|
builder.finish(),
|
||||||
Error::InsufficientFunds {
|
Err(Error::InsufficientFunds {
|
||||||
needed: _,
|
needed: _,
|
||||||
available: 0
|
available: 0
|
||||||
}
|
})
|
||||||
));
|
);
|
||||||
|
|
||||||
// ...Now the coinbase is mature :)
|
// ...Now the coinbase is mature :)
|
||||||
let sync_time = SyncTime {
|
let sync_time = SyncTime {
|
||||||
@@ -5428,10 +5417,7 @@ pub(crate) mod test {
|
|||||||
|
|
||||||
builder.add_recipient(addr.script_pubkey(), 0);
|
builder.add_recipient(addr.script_pubkey(), 0);
|
||||||
|
|
||||||
assert!(matches!(
|
assert_matches!(builder.finish(), Err(Error::OutputBelowDustLimit(0)));
|
||||||
builder.finish().unwrap_err(),
|
|
||||||
Error::OutputBelowDustLimit(0)
|
|
||||||
));
|
|
||||||
|
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
|
|
||||||
@@ -5546,11 +5532,14 @@ pub(crate) mod test {
|
|||||||
use hwi::types::HWIChain;
|
use hwi::types::HWIChain;
|
||||||
use hwi::HWIClient;
|
use hwi::HWIClient;
|
||||||
|
|
||||||
let devices = HWIClient::enumerate().unwrap();
|
let mut devices = HWIClient::enumerate().unwrap();
|
||||||
let device = devices.first().expect("No devices found");
|
if devices.is_empty() {
|
||||||
let client = HWIClient::get_client(device, true, HWIChain::Regtest).unwrap();
|
panic!("No devices found!");
|
||||||
let descriptors = client.get_descriptors(None).unwrap();
|
}
|
||||||
let custom_signer = HWISigner::from_device(device, HWIChain::Regtest).unwrap();
|
let device = devices.remove(0).unwrap();
|
||||||
|
let client = HWIClient::get_client(&device, true, HWIChain::Regtest).unwrap();
|
||||||
|
let descriptors = client.get_descriptors::<String>(None).unwrap();
|
||||||
|
let custom_signer = HWISigner::from_device(&device, HWIChain::Regtest).unwrap();
|
||||||
|
|
||||||
let (mut wallet, _, _) = get_funded_wallet(&descriptors.internal[0]);
|
let (mut wallet, _, _) = get_funded_wallet(&descriptors.internal[0]);
|
||||||
wallet.add_signer(
|
wallet.add_signer(
|
||||||
|
|||||||
@@ -998,6 +998,7 @@ mod signers_container_tests {
|
|||||||
use crate::descriptor;
|
use crate::descriptor;
|
||||||
use crate::descriptor::IntoWalletDescriptor;
|
use crate::descriptor::IntoWalletDescriptor;
|
||||||
use crate::keys::{DescriptorKey, IntoDescriptorKey};
|
use crate::keys::{DescriptorKey, IntoDescriptorKey};
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use bitcoin::secp256k1::{All, Secp256k1};
|
use bitcoin::secp256k1::{All, Secp256k1};
|
||||||
use bitcoin::util::bip32;
|
use bitcoin::util::bip32;
|
||||||
use bitcoin::Network;
|
use bitcoin::Network;
|
||||||
@@ -1067,17 +1068,17 @@ mod signers_container_tests {
|
|||||||
signers.add_external(id2.clone(), SignerOrdering(2), signer2.clone());
|
signers.add_external(id2.clone(), SignerOrdering(2), signer2.clone());
|
||||||
signers.add_external(id3.clone(), SignerOrdering(3), signer3.clone());
|
signers.add_external(id3.clone(), SignerOrdering(3), signer3.clone());
|
||||||
|
|
||||||
assert!(matches!(signers.find(id1), Some(signer) if is_equal(signer, &signer1)));
|
assert_matches!(signers.find(id1), Some(signer) if is_equal(signer, &signer1));
|
||||||
assert!(matches!(signers.find(id2), Some(signer) if is_equal(signer, &signer2)));
|
assert_matches!(signers.find(id2), Some(signer) if is_equal(signer, &signer2));
|
||||||
assert!(matches!(signers.find(id3.clone()), Some(signer) if is_equal(signer, &signer3)));
|
assert_matches!(signers.find(id3.clone()), Some(signer) if is_equal(signer, &signer3));
|
||||||
|
|
||||||
// The `signer4` has the same ID as `signer3` but lower ordering.
|
// The `signer4` has the same ID as `signer3` but lower ordering.
|
||||||
// It should be found by `id3` instead of `signer3`.
|
// It should be found by `id3` instead of `signer3`.
|
||||||
signers.add_external(id3.clone(), SignerOrdering(2), signer4.clone());
|
signers.add_external(id3.clone(), SignerOrdering(2), signer4.clone());
|
||||||
assert!(matches!(signers.find(id3), Some(signer) if is_equal(signer, &signer4)));
|
assert_matches!(signers.find(id3), Some(signer) if is_equal(signer, &signer4));
|
||||||
|
|
||||||
// Can't find anything with ID that doesn't exist
|
// Can't find anything with ID that doesn't exist
|
||||||
assert!(matches!(signers.find(id_nonexistent), None));
|
assert_matches!(signers.find(id_nonexistent), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ impl_error!(bitcoinconsensus::Error, Consensus, VerifyError);
|
|||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::database::{BatchOperations, MemoryDatabase};
|
use crate::database::{BatchOperations, MemoryDatabase};
|
||||||
|
use assert_matches::assert_matches;
|
||||||
use bitcoin::consensus::encode::deserialize;
|
use bitcoin::consensus::encode::deserialize;
|
||||||
use bitcoin::hashes::hex::FromHex;
|
use bitcoin::hashes::hex::FromHex;
|
||||||
use bitcoin::{Transaction, Txid};
|
use bitcoin::{Transaction, Txid};
|
||||||
@@ -137,9 +138,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let result = verify_tx(&signed_tx, &database, &blockchain);
|
let result = verify_tx(&signed_tx, &database, &blockchain);
|
||||||
assert!(result.is_err(), "Should fail with missing input tx");
|
assert_matches!(result, Err(VerifyError::MissingInputTx(txid)) if txid == prev_tx.txid(),
|
||||||
assert!(
|
|
||||||
matches!(result, Err(VerifyError::MissingInputTx(txid)) if txid == prev_tx.txid()),
|
|
||||||
"Error should be a `MissingInputTx` error"
|
"Error should be a `MissingInputTx` error"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -147,9 +146,9 @@ mod test {
|
|||||||
database.set_raw_tx(&prev_tx).unwrap();
|
database.set_raw_tx(&prev_tx).unwrap();
|
||||||
|
|
||||||
let result = verify_tx(&unsigned_tx, &database, &blockchain);
|
let result = verify_tx(&unsigned_tx, &database, &blockchain);
|
||||||
assert!(result.is_err(), "Should fail since the TX is unsigned");
|
assert_matches!(
|
||||||
assert!(
|
result,
|
||||||
matches!(result, Err(VerifyError::Consensus(_))),
|
Err(VerifyError::Consensus(_)),
|
||||||
"Error should be a `Consensus` error"
|
"Error should be a `Consensus` error"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user