Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/descriptor/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// licenses.

//! Descriptor errors
use bitcoin::{BlockHash, Network};
use core::fmt;

/// Errors related to the parsing and usage of descriptors
Expand Down Expand Up @@ -44,6 +45,13 @@ pub enum Error {
Hex(bitcoin::hex::HexToBytesError),
/// The provided wallet descriptors are identical
ExternalAndInternalAreTheSame,
/// The provided genesis hash does not match the expected mainnet genesis hash
GenesisHashMismatch {
/// The configured network
network: Network,
/// The genesis hash that was provided
genesis_hash: BlockHash,
},
}

impl From<crate::keys::KeyError> for Error {
Expand Down Expand Up @@ -84,6 +92,13 @@ impl fmt::Display for Error {
Self::ExternalAndInternalAreTheSame => {
write!(f, "External and internal descriptors are the same")
}
Self::GenesisHashMismatch {
network,
genesis_hash,
} => write!(
f,
"Genesis hash {genesis_hash} does not match expected genesis hash for network {network}"
),
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions src/wallet/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ pub enum LoadError {
MissingGenesis,
/// Data loaded from persistence is missing descriptor.
MissingDescriptor(KeychainKind),
/// The network's mainnet genesis hash does not match the loaded genesis hash.
GenesisNetworkMismatch {
/// The loaded network.
network: Network,
/// The loaded genesis hash.
genesis_hash: BlockHash,
},
/// Data loaded is unexpected.
Mismatch(LoadMismatch),
}
Expand All @@ -51,6 +58,13 @@ impl fmt::Display for LoadError {
LoadError::MissingDescriptor(k) => {
write!(f, "loaded data is missing descriptor for {k} keychain")
}
LoadError::GenesisNetworkMismatch {
network,
genesis_hash,
} => write!(
f,
"network {network} is mainnet but loaded genesis hash {genesis_hash} does not match"
),
LoadError::Mismatch(e) => write!(f, "{e}"),
}
}
Expand Down
27 changes: 23 additions & 4 deletions src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use bitcoin::{
Address, Amount, Block, FeeRate, Network, NetworkKind, OutPoint, Psbt, ScriptBuf, Sequence,
SignedAmount, Transaction, TxOut, Txid, Weight, Witness, absolute,
consensus::encode::serialize,
constants::genesis_block,
constants::{ChainHash, genesis_block},
psbt,
secp256k1::Secp256k1,
sighash::{EcdsaSighashType, TapSighashType},
Expand Down Expand Up @@ -339,9 +339,20 @@ impl Wallet {
let secp = SecpCtx::new();
let network = params.network;
let network_kind = NetworkKind::from(network);
let genesis_hash = params
.genesis_hash
.unwrap_or(genesis_block(network).block_hash());
let genesis_hash = match params.genesis_hash {
Some(hash) => {
if network_kind.is_mainnet()
&& ChainHash::from_genesis_block_hash(hash) != ChainHash::BITCOIN
{
return Err(DescriptorError::GenesisHashMismatch {
network,
genesis_hash: hash,
});
}
hash
}
None => genesis_block(network).block_hash(),
};
let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash);

let (descriptor, mut descriptor_keymap) = (params.descriptor)(&secp, network_kind)?;
Expand Down Expand Up @@ -475,6 +486,14 @@ impl Wallet {
}));
}
}
if network_kind.is_mainnet()
&& ChainHash::from_genesis_block_hash(chain.genesis_hash()) != ChainHash::BITCOIN
{
return Err(LoadError::GenesisNetworkMismatch {
network,
genesis_hash: chain.genesis_hash(),
});
}
if let Some(exp_genesis_hash) = params.check_genesis_hash {
if chain.genesis_hash() != exp_genesis_hash {
return Err(LoadError::Mismatch(LoadMismatch::Genesis {
Expand Down
55 changes: 52 additions & 3 deletions tests/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::str::FromStr;
use std::sync::Arc;

use assert_matches::assert_matches;
use bdk_chain::{BlockId, CanonicalizationParams, ConfirmationBlockTime};
use bdk_chain::{BlockId, CanonicalizationParams, ConfirmationBlockTime, local_chain};
use bdk_wallet::KeychainKind;
use bdk_wallet::coin_selection;
use bdk_wallet::coin_selection::InsufficientFunds;
Expand All @@ -12,8 +12,8 @@ use bdk_wallet::psbt::PsbtUtils;
use bdk_wallet::signer::{SignOptions, SignerError, SignersContainer};
use bdk_wallet::test_utils::*;
use bdk_wallet::{
AddressInfo, Balance, FinalizeInputOutcome, IndexOutOfBoundsError, PersistedWallet, Update,
Wallet, WalletTx,
AddressInfo, Balance, ChangeSet, FinalizeInputOutcome, IndexOutOfBoundsError, LoadError,
LoadParams, PersistedWallet, Update, Wallet, WalletTx,
};
use bitcoin::constants::COINBASE_MATURITY;
use bitcoin::hashes::Hash;
Expand Down Expand Up @@ -53,6 +53,55 @@ fn test_error_external_and_internal_are_the_same() {
);
}

#[test]
fn test_error_create_mainnet_network_with_non_mainnet_genesis_hash() {
let external_desc = "tr(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642)";
let internal_desc = "tr(b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55)";
let testnet_genesis_hash = bitcoin::constants::genesis_block(Network::Testnet).block_hash();

let err = Wallet::create(external_desc, internal_desc)
.network(Network::Bitcoin)
.genesis_hash(testnet_genesis_hash)
.create_wallet_no_persist();

assert!(
matches!(
err,
Err(DescriptorError::GenesisHashMismatch {
network: Network::Bitcoin,
genesis_hash,
}) if genesis_hash == testnet_genesis_hash
),
"expected wallet creation to reject a mismatched mainnet network and genesis hash, got {err:?}",
);
}

#[test]
fn test_error_load_mainnet_network_with_non_mainnet_genesis_hash() {
let testnet_genesis_hash = bitcoin::constants::genesis_block(Network::Testnet).block_hash();

let changeset = ChangeSet {
network: Some(Network::Bitcoin),
local_chain: local_chain::ChangeSet {
blocks: [(0, Some(testnet_genesis_hash))].into(),
},
..Default::default()
};

let err = Wallet::load_with_params(changeset, LoadParams::default());

assert!(
matches!(
err,
Err(LoadError::GenesisNetworkMismatch {
network: Network::Bitcoin,
genesis_hash,
}) if genesis_hash == testnet_genesis_hash
),
"expected wallet loading to reject a mismatched mainnet network and genesis hash, got {err:?}"
);
}

#[test]
fn test_descriptor_checksum() {
let (wallet, _) = get_funded_wallet_wpkh();
Expand Down