Skip to content

Commit 2543155

Browse files
committed
feat: add onchain address validation for network-specific addresses
1 parent 5abb42f commit 2543155

File tree

3 files changed

+48
-2
lines changed

3 files changed

+48
-2
lines changed

src/error.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use bdk_chain::local_chain::CannotConnectError as BdkChainConnectionError;
1010
use bdk_chain::tx_graph::CalculateFeeError as BdkChainCalculateFeeError;
1111
use bdk_wallet::error::CreateTxError as BdkCreateTxError;
1212
use bdk_wallet::signer::SignerError as BdkSignerError;
13+
use bitcoin::Network;
1314

1415
use std::fmt;
1516

@@ -120,6 +121,13 @@ pub enum Error {
120121
LiquiditySourceUnavailable,
121122
/// The given operation failed due to the LSP's required opening fee being too high.
122123
LiquidityFeeTooHigh,
124+
/// The given address is invalid.
125+
InvalidAddressFormat,
126+
///Address belongs to the wrong network
127+
InvalidNetworkAddress {
128+
/// The expected network.
129+
expected: Network
130+
},
123131
}
124132

125133
impl fmt::Display for Error {
@@ -193,6 +201,8 @@ impl fmt::Display for Error {
193201
Self::LiquidityFeeTooHigh => {
194202
write!(f, "The given operation failed due to the LSP's required opening fee being too high.")
195203
},
204+
Self::InvalidAddressFormat => write!(f, "The given address is invalid."),
205+
Self::InvalidNetworkAddress { expected } => write!(f, "The given address is invalid. Expected network: {:?}", expected),
196206
}
197207
}
198208
}

src/payment/onchain.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ use crate::logger::{log_info, LdkLogger, Logger};
1313
use crate::types::{ChannelManager, Wallet};
1414
use crate::wallet::OnchainSendAmount;
1515

16-
use bitcoin::{Address, Txid};
16+
use bitcoin::address::NetworkUnchecked;
17+
use bitcoin::{Address, Network, Txid};
1718

19+
use std::str::FromStr;
1820
use std::sync::{Arc, RwLock};
1921

2022
#[cfg(not(feature = "uniffi"))]
@@ -80,12 +82,33 @@ impl OnchainPayment {
8082
return Err(Error::NotRunning);
8183
}
8284

85+
let validated_address = self.parse_and_validate_address(self.config.network, &address.to_string())?;
86+
8387
let cur_anchor_reserve_sats =
8488
crate::total_anchor_channels_reserve_sats(&self.channel_manager, &self.config);
8589
let send_amount =
8690
OnchainSendAmount::ExactRetainingReserve { amount_sats, cur_anchor_reserve_sats };
8791
let fee_rate_opt = maybe_map_fee_rate_opt!(fee_rate);
88-
self.wallet.send_to_address(address, send_amount, fee_rate_opt)
92+
self.wallet.send_to_address(&validated_address, send_amount, fee_rate_opt)
93+
}
94+
95+
/// Validates a Bitcoin address is properly formatted and matches the expected network.
96+
///
97+
/// Returns `Ok(Address)` if valid, or:
98+
/// - `InvalidAddressFormat` if malformed
99+
/// - `InvalidNetworkAddress` if valid but wrong network
100+
///
101+
/// # Example
102+
/// ```
103+
/// let address = "tb1q..."; // Testnet address
104+
/// parse_and_validate_address(Network::Testnet, address)?;
105+
pub fn parse_and_validate_address(&self, network: Network, address: &str) -> Result<Address, Error> {
106+
Address::<NetworkUnchecked>::from_str(address)
107+
.map_err(|_| Error::InvalidAddressFormat)?
108+
.require_network(network)
109+
.map_err(|_| Error::InvalidNetworkAddress {
110+
expected: network,
111+
})
89112
}
90113

91114
/// Send an on-chain payment to the given address, draining the available funds.

tests/integration_tests_rust.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,12 @@ use bitcoincore_rpc::RpcApi;
3232

3333
use bitcoin::hashes::Hash;
3434
use bitcoin::Amount;
35+
use bitcoin::address::NetworkUnchecked;
36+
use bitcoin::Address;
3537
use lightning_invoice::{Bolt11InvoiceDescription, Description};
3638
use log::LevelFilter;
3739

40+
use std::str::FromStr;
3841
use std::sync::Arc;
3942

4043
#[test]
@@ -294,6 +297,9 @@ fn onchain_send_receive() {
294297

295298
let addr_a = node_a.onchain_payment().new_address().unwrap();
296299
let addr_b = node_b.onchain_payment().new_address().unwrap();
300+
let static_address = "tb1q0d40e5rta4fty63z64gztf8c3v20cvet6v2jdh";
301+
let unchecked_address = Address::<NetworkUnchecked>::from_str(static_address).unwrap();
302+
let addr_c = unchecked_address.assume_checked();
297303

298304
let premine_amount_sat = 1_100_000;
299305
premine_and_distribute_funds(
@@ -358,6 +364,13 @@ fn onchain_send_receive() {
358364
node_a.onchain_payment().send_to_address(&addr_b, expected_node_a_balance + 1, None)
359365
);
360366

367+
assert_eq!(
368+
Err(NodeError::InvalidNetworkAddress {
369+
expected: node_a.config().network,
370+
}),
371+
node_a.onchain_payment().send_to_address(&addr_c, expected_node_a_balance + 1, None)
372+
);
373+
361374
let amount_to_send_sats = 54321;
362375
let txid =
363376
node_b.onchain_payment().send_to_address(&addr_a, amount_to_send_sats, None).unwrap();

0 commit comments

Comments
 (0)