From ed6ff9c67b69b08e08e466102656b6765584dfce Mon Sep 17 00:00:00 2001 From: Antoine Poinsot Date: Tue, 20 Dec 2022 16:52:53 +0100 Subject: [PATCH] commands: check addresses' network when creating transactions --- src/commands/mod.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ src/jsonrpc/mod.rs | 1 + 2 files changed, 44 insertions(+) diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 878f727f..496fd872 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -47,6 +47,7 @@ pub enum CommandError { InvalidFeerate(/* sats/vb */ u64), UnknownOutpoint(bitcoin::OutPoint), AlreadySpent(bitcoin::OutPoint), + AddressNetwork(bitcoin::Address, /* Expected */ bitcoin::Network), InvalidOutputValue(bitcoin::Amount), InsufficientFunds( /* in value */ bitcoin::Amount, @@ -74,6 +75,11 @@ impl fmt::Display for CommandError { Self::InvalidFeerate(sats_vb) => write!(f, "Invalid feerate: {} sats/vb.", sats_vb), Self::AlreadySpent(op) => write!(f, "Coin at '{}' is already spent.", op), Self::UnknownOutpoint(op) => write!(f, "Unknown outpoint '{}'.", op), + Self::AddressNetwork(addr, expected) => write!( + f, + "Invalid network for address '{}'. Our network is '{}' but address is for '{}'.", + addr, expected, addr.network + ), Self::InvalidOutputValue(amount) => write!(f, "Invalid output value '{}'.", amount), Self::InsufficientFunds(in_val, out_val, feerate) => write!( f, @@ -188,6 +194,22 @@ impl DaemonControl { }; desc.derive(coin.derivation_index, &self.secp) } + + // Check whether this address is valid for the network we are operating on. + fn validate_address(&self, addr: &bitcoin::Address) -> Result<(), CommandError> { + // NOTE: signet uses testnet addresses + if addr.network == self.config.bitcoin_config.network + || (addr.network == bitcoin::Network::Testnet + && self.config.bitcoin_config.network == bitcoin::Network::Signet) + { + return Ok(()); + } + + Err(CommandError::AddressNetwork( + addr.clone(), + self.config.bitcoin_config.network, + )) + } } impl DaemonControl { @@ -338,6 +360,8 @@ impl DaemonControl { let mut txouts = Vec::with_capacity(destinations.len()); let mut psbt_outs = Vec::with_capacity(destinations.len()); for (address, value_sat) in destinations { + self.validate_address(address)?; + let amount = bitcoin::Amount::from_sat(*value_sat); check_output_value(amount)?; out_value = out_value.checked_add(amount).unwrap(); @@ -622,6 +646,7 @@ impl DaemonControl { if feerate_vb < 1 { return Err(CommandError::InvalidFeerate(feerate_vb)); } + self.validate_address(&address)?; let mut db_conn = self.db.connection(); // The transaction template. We'll fill-in the inputs afterward. @@ -951,6 +976,24 @@ mod tests { ))) ); + // If we ask to create an output for an address from another network, it will fail. + let invalid_addr = bitcoin::Address { + network: bitcoin::Network::Testnet, + payload: dummy_addr.payload.clone(), + }; + let invalid_destinations: HashMap = + [(invalid_addr.clone(), dummy_value)] + .iter() + .cloned() + .collect(); + assert_eq!( + control.create_spend(&invalid_destinations, &[dummy_op], 1), + Err(CommandError::AddressNetwork( + invalid_addr, + bitcoin::Network::Bitcoin + )) + ); + // If we ask for a large, but valid, output we won't get a change output. 95_000 because we // won't create an output lower than 5k sats. *destinations.get_mut(&dummy_addr).unwrap() = 95_000; diff --git a/src/jsonrpc/mod.rs b/src/jsonrpc/mod.rs index dddb35c7..85dfd0b2 100644 --- a/src/jsonrpc/mod.rs +++ b/src/jsonrpc/mod.rs @@ -156,6 +156,7 @@ impl From for Error { | commands::CommandError::UnknownOutpoint(..) | commands::CommandError::InvalidFeerate(..) | commands::CommandError::AlreadySpent(..) + | commands::CommandError::AddressNetwork(..) | commands::CommandError::InvalidOutputValue(..) | commands::CommandError::InsufficientFunds(..) | commands::CommandError::UnknownSpend(..)