Skip to content

Commit 2ce66ac

Browse files
Reject silent payment addresses with clear error
Pasting a BIP352 silent payment address (sp1... / tsp1...) surfaces a generic "Invalid Address" error today, which is confusing because the string looks like a valid bech32m address. Match the SP prefixes up front in both AddressWithNetwork::try_new and Address::from_string and raise a dedicated SilentPaymentNotSupported variant so users learn the format is understood but blocked, not malformed. Refs #617
1 parent 76f3289 commit 2ce66ac

5 files changed

Lines changed: 60 additions & 4 deletions

File tree

android/app/src/main/java/org/bitcoinppl/cove/flows/SendFlow/SendFlowPresenter.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class SendFlowPresenter(
8080
is SendFlowException.EmptyAddress,
8181
is SendFlowException.InvalidAddress,
8282
is SendFlowException.WrongNetwork,
83+
is SendFlowException.SilentPaymentNotSupported,
8384
-> "Invalid Address"
8485

8586
is SendFlowException.InvalidNumber,
@@ -152,6 +153,9 @@ class SendFlowPresenter(
152153

153154
is SendFlowException.UnableToSaveUnsignedTransaction ->
154155
error.v1
156+
157+
is SendFlowException.SilentPaymentNotSupported ->
158+
"Sending to silent payment addresses (sp1...) is not yet supported. Support is coming soon."
155159
}
156160

157161
/**
@@ -171,6 +175,7 @@ class SendFlowPresenter(
171175
is SendFlowException.EmptyAddress,
172176
is SendFlowException.WrongNetwork,
173177
is SendFlowException.InvalidAddress,
178+
is SendFlowException.SilentPaymentNotSupported,
174179
-> {
175180
{
176181
alertState = null

ios/Cove/Flows/SendFlow/SendFlowPresenter.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ import SwiftUI
7171

7272
private func errorAlertTitle(_ error: SendFlowError) -> String {
7373
switch error {
74-
case .EmptyAddress, .InvalidAddress, .WrongNetwork:
74+
case .EmptyAddress, .InvalidAddress, .WrongNetwork, .SilentPaymentNotSupported:
7575
"Invalid Address"
7676
case .InvalidNumber, .ZeroAmount: "Invalid Amount"
7777
case .InsufficientFunds, .NoBalance: "Insufficient Funds"
@@ -129,6 +129,8 @@ import SwiftUI
129129
msg
130130
case let .UnableToSaveUnsignedTransaction(msg):
131131
msg
132+
case .SilentPaymentNotSupported:
133+
"Sending to silent payment addresses (sp1...) is not yet supported. Support is coming soon."
132134
}
133135
}
134136

@@ -145,7 +147,7 @@ import SwiftUI
145147
@ViewBuilder
146148
private func errorAlertButtons(_ error: SendFlowError) -> some View {
147149
switch error {
148-
case .EmptyAddress, .WrongNetwork, .InvalidAddress:
150+
case .EmptyAddress, .WrongNetwork, .InvalidAddress, .SilentPaymentNotSupported:
149151
Button("OK") {
150152
self.alertState = .none
151153
self.focusField = .address

rust/crates/cove-types/src/address.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ pub enum AddressError {
8484

8585
#[error("empty address")]
8686
EmptyAddress,
87+
88+
#[error("silent payment addresses (sp1...) are not yet supported for sending")]
89+
SilentPaymentNotSupported,
8790
}
8891

8992
impl Clone for AddressInfo {
@@ -169,6 +172,10 @@ impl AddressWithNetwork {
169172
pub fn try_new(str: &str) -> Result<Self, Error> {
170173
let (address_str, amount) = parse_bitcoin_uri(str)?;
171174

175+
if is_silent_payment_address(&address_str) {
176+
return Err(Error::SilentPaymentNotSupported);
177+
}
178+
172179
let address: BdkAddress<NetworkUnchecked> =
173180
address_str.parse().map_err(|_| Error::InvalidAddress)?;
174181

@@ -198,6 +205,12 @@ impl AddressWithNetwork {
198205
}
199206
}
200207

208+
fn is_silent_payment_address(s: &str) -> bool {
209+
let lower = s.trim().to_lowercase();
210+
// BIP352: mainnet SP addresses start with "sp1q", testnet/signet with "tsp1q"
211+
lower.starts_with("sp1q") || lower.starts_with("tsp1q")
212+
}
213+
201214
fn parse_bitcoin_uri(input: &str) -> Result<(String, Option<Amount>), Error> {
202215
let input = input.trim();
203216
if input.is_empty() {
@@ -275,6 +288,11 @@ impl Address {
275288
#[uniffi::constructor]
276289
pub fn from_string(address: &str, network: Network) -> Result<Self> {
277290
let address = address.trim();
291+
292+
if is_silent_payment_address(address) {
293+
return Err(Error::SilentPaymentNotSupported);
294+
}
295+
278296
let bdk_address = BdkAddress::from_str(address).map_err(|_| Error::InvalidAddress)?;
279297

280298
let bitcoin_network: bitcoin::Network = network.into();
@@ -492,6 +510,27 @@ mod tests {
492510
assert_eq!(amount, None);
493511
}
494512

513+
#[test]
514+
fn test_silent_payment_address_detection() {
515+
// mainnet SP address returns SilentPaymentNotSupported instead of InvalidAddress
516+
let result = AddressWithNetwork::try_new(
517+
"sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtsgpc7kytsk9w66xa3p4",
518+
);
519+
assert_eq!(result, Err(AddressError::SilentPaymentNotSupported));
520+
521+
// testnet SP address
522+
let result = AddressWithNetwork::try_new(
523+
"tsp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtsgpc7kytsk9w66xa3p4",
524+
);
525+
assert_eq!(result, Err(AddressError::SilentPaymentNotSupported));
526+
527+
// bitcoin: URI with SP address
528+
let result = AddressWithNetwork::try_new(
529+
"bitcoin:sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtsgpc7kytsk9w66xa3p4",
530+
);
531+
assert_eq!(result, Err(AddressError::SilentPaymentNotSupported));
532+
}
533+
495534
#[test]
496535
fn test_address_string_spaced_out() {
497536
let address = "bc1pkdj04w4lxsv570j5nsd249lqe4w4j608r2nq9997ruh0wv96cnksy5jeny";

rust/src/manager/send_flow_manager.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,8 +419,14 @@ impl RustSendFlowManager {
419419
pub fn validate_address(self: &Arc<Self>, display_alert: bool) -> bool {
420420
if self.state.lock().address.is_none() {
421421
if display_alert {
422-
let error =
423-
SendFlowError::InvalidAddress(self.state.lock().entering_address.clone());
422+
let (entering, network) = {
423+
let state = self.state.lock();
424+
(state.entering_address.clone(), state.metadata.network)
425+
};
426+
let error = match Address::from_string(&entering, network) {
427+
Err(e) => SendFlowError::from_address_error(e, entering),
428+
Ok(_) => SendFlowError::InvalidAddress(entering),
429+
};
424430
self.reconciler.send(Message::SetAlert(error.into()));
425431
}
426432

rust/src/manager/send_flow_manager/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ pub enum SendFlowError {
4646

4747
#[error("unable to get fee details: {0}")]
4848
UnableToGetFeeDetails(String),
49+
50+
#[error("sending to silent payment addresses is not yet supported")]
51+
SilentPaymentNotSupported,
4952
}
5053

5154
impl SendFlowError {
@@ -57,6 +60,7 @@ impl SendFlowError {
5760
Self::WrongNetwork { address, valid_for, current }
5861
}
5962

63+
AddressError::SilentPaymentNotSupported => Self::SilentPaymentNotSupported,
6064
_ => Self::InvalidAddress(address),
6165
}
6266
}

0 commit comments

Comments
 (0)