payment_request: remove "send to" screen
What changed, and why it matters
This commit removes an on-screen confirmation that told users which account would receive funds during a cryptocurrency swap. The remaining screen still shows the amount and destination coin type, but no longer shows the specific account number. The underlying cryptographic keypath validation is still performed, so the technical security check remains. The change is presented by the developer as a user-experience improvement, not as a security fix.
Review whether removing the account-level confirmation materially reduces user ability to detect account-substitution attacks during swaps. If the remaining 'SWAP' screen and keypath validation are considered sufficient, no action is needed; otherwise consider re-adding a non-interactive display of the destination account or a summary screen. Verify that downstream signing paths still enforce the validated keypath.
Security signals we found
UI confirmation step removed from a financial transaction flow
Destination account number no longer displayed to the user
Keypath validation retained according to commit message
Negative tests for missing/short destination derivations removed because the consuming code was removed
No explicit security framing by the vendor in commit or supplied references
Evidence from the diff
The patch removes the ‘Receive to’ UI confirmation in payment_request.rs for swap/coin-purchase memos. Previously user_verify() parsed the destination amount, derived the destination account number from the address derivation keypath, and prompted the user to confirm ‘Receive to:
Changed components
src/rust/bitbox02-rust/src/hww/api/payment_request.rssrc/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs (tests only)src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs (tests only)Inspect captured patch +19 / −148
diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
index 9804d12..eb534ca 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
@@ -3927,11 +3927,6 @@ mod tests {
body: "12.34567890 BTC\nto\n0.25 ETH".into(),
longtouch: false,
},
- Screen::Confirm {
- title: "Receive to".into(),
- body: "ETH account #1".into(),
- longtouch: false,
- },
Screen::Recipient {
recipient: "bc1q xven xven xven xven xven xven xven xven 2ymj t8".into(),
amount: "0.00006000 BTC".into(),
diff --git a/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs b/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs
index a8e114b..b8513fc 100644
--- a/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs
@@ -1174,11 +1174,6 @@ mod tests {
body: "0.530564 ETH\nto\n0.25 ETH".into(),
longtouch: false,
},
- Screen::Confirm {
- title: "Receive to".into(),
- body: "ETH account #1".into(),
- longtouch: false,
- },
Screen::TotalFee {
total: "0.53069 ETH".into(),
fee: "0.000126 ETH".into(),
@@ -1254,11 +1249,6 @@ mod tests {
body: "57 USDT\nto\n0.25 ETH".into(),
longtouch: false,
},
- Screen::Confirm {
- title: "Receive to".into(),
- body: "ETH account #1".into(),
- longtouch: false,
- },
Screen::TotalFee {
total: "57 USDT".into(),
fee: "0.0012658164 ETH".into(),
diff --git a/src/rust/bitbox02-rust/src/hww/api/payment_request.rs b/src/rust/bitbox02-rust/src/hww/api/payment_request.rs
index 7284710..339b647 100644
--- a/src/rust/bitbox02-rust/src/hww/api/payment_request.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/payment_request.rs
@@ -153,6 +153,7 @@ pub async fn user_verify(
"{displayed_source_amount}\nto\n{}",
coin_purchase_memo.amount
);
+ let _ = parse_coin_purchase_amount(&coin_purchase_memo.amount)?;
hal.ui()
.confirm(&ConfirmParams {
title: "SWAP",
@@ -162,48 +163,6 @@ pub async fn user_verify(
..Default::default()
})
.await?;
- let (_, destination_unit) = parse_coin_purchase_amount(&coin_purchase_memo.amount)?;
- let address_derivation = coin_purchase_memo
- .address_derivation
- .as_ref()
- .ok_or(Error::InvalidInput)?;
- let destination_account = match address_derivation {
- memo::coin_purchase_memo::AddressDerivation::Eth(eth) => {
- eth.keypath
- .get(2)
- .ok_or(Error::InvalidInput)?
- .checked_sub(util::bip32::HARDENED)
- .ok_or(Error::InvalidInput)?
- + 1
- }
- memo::coin_purchase_memo::AddressDerivation::Btc(btc) => {
- if !matches!(
- (coin_purchase_memo.coin_type, destination_unit),
- (0, "BTC") | (2, "LTC")
- ) {
- return Err(Error::InvalidInput);
- }
-
- let script_config =
- btc.script_config.as_ref().ok_or(Error::InvalidInput)?;
-
- script_config
- .keypath
- .get(2)
- .ok_or(Error::InvalidInput)?
- .checked_sub(util::bip32::HARDENED)
- .ok_or(Error::InvalidInput)?
- + 1
- }
- };
- hal.ui()
- .confirm(&ConfirmParams {
- title: "Receive to",
- body: &format!("{destination_unit} account #{destination_account}"),
- accept_is_nextarrow: true,
- ..Default::default()
- })
- .await?;
}
_ => return Err(Error::InvalidInput),
}
@@ -1447,11 +1406,6 @@ mod tests {
body: "0.25000000 BTC\nto\n0.25 ETH".into(),
longtouch: false,
},
- Screen::Confirm {
- title: "Receive to".into(),
- body: "ETH account #1".into(),
- longtouch: false,
- },
]
);
}
@@ -1501,11 +1455,6 @@ mod tests {
body: "0.25000000 BTC\nto\n0.25 LTC".into(),
longtouch: false,
},
- Screen::Confirm {
- title: "Receive to".into(),
- body: "LTC account #1".into(),
- longtouch: false,
- },
]
);
}
@@ -1614,86 +1563,23 @@ mod tests {
#[cfg(all(feature = "app-litecoin", feature = "app-ethereum"))]
#[async_test::test]
async fn test_user_verify_swap_invalid() {
- // Invalid swap requests that user_verify must reject because the
- // UI cannot render them safely.
- for payment_request in [
- // Missing destination derivation, so "Receive to" cannot be built.
- pb::BtcPaymentRequestRequest {
- recipient_name: "SWAPKIT (Provider)".into(),
- memos: vec![make_coin_purchase_memo(60, "0.25 ETH", "0x123", None)],
- nonce: vec![],
- total_amount: 25000000,
- signature: vec![],
- },
- // Destination keypath is too short to contain an account element.
- pb::BtcPaymentRequestRequest {
- recipient_name: "SWAPKIT (Provider)".into(),
- memos: vec![make_coin_purchase_memo(
- 60,
- "0.25 ETH",
- "0x123",
- Some(memo::coin_purchase_memo::AddressDerivation::Eth(
- memo::coin_purchase_memo::EthAddressDerivation {
- keypath: vec![44 + util::bip32::HARDENED, 60 + util::bip32::HARDENED],
- },
- )),
- )],
- nonce: vec![],
- total_amount: 25000000,
- signature: vec![],
- },
- // BTC-like derivation requires script_config.
- pb::BtcPaymentRequestRequest {
- recipient_name: "SWAPKIT (Provider)".into(),
- memos: vec![make_coin_purchase_memo(
- 0,
- "0.25 BTC",
- "bc1qdestination",
- Some(memo::coin_purchase_memo::AddressDerivation::Btc(
- memo::coin_purchase_memo::BtcAddressDerivation {
- script_config: None,
- },
- )),
- )],
- nonce: vec![],
- total_amount: 25000000,
- signature: vec![],
- },
- // BTC-like destination keypath is too short to contain an account element.
- pb::BtcPaymentRequestRequest {
- recipient_name: "SWAPKIT (Provider)".into(),
- memos: vec![make_coin_purchase_memo(
- 0,
- "0.25 BTC",
- "bc1qdestination",
- Some(dummy_btc_address_derivation(
- pb::btc_script_config::SimpleType::P2wpkh,
- &[84 + util::bip32::HARDENED, 0 + util::bip32::HARDENED],
- )),
- )],
- nonce: vec![],
- total_amount: 25000000,
- signature: vec![],
- },
- // Display amount must be exactly "<positive-decimal> <unit>".
- pb::BtcPaymentRequestRequest {
- recipient_name: "SWAPKIT (Provider)".into(),
- memos: vec![make_coin_purchase_memo(
- 60,
- "foo bar baz",
- "0x123",
- Some(dummy_eth_address_derivation(/*valid=*/ true)),
- )],
- nonce: vec![],
- total_amount: 25000000,
- signature: vec![],
- },
- ] {
- let mut mock_hal = TestingHal::new();
- assert_eq!(
- user_verify(&mut mock_hal, &payment_request, "0.25000000 BTC",).await,
- Err(Error::InvalidInput)
- );
- }
+ // Display amount must be exactly "<positive-decimal> <unit>".
+ let payment_request = pb::BtcPaymentRequestRequest {
+ recipient_name: "SWAPKIT (Provider)".into(),
+ memos: vec![make_coin_purchase_memo(
+ 60,
+ "foo bar baz",
+ "0x123",
+ Some(dummy_eth_address_derivation(/*valid=*/ true)),
+ )],
+ nonce: vec![],
+ total_amount: 25000000,
+ signature: vec![],
+ };
+ let mut mock_hal = TestingHal::new();
+ assert_eq!(
+ user_verify(&mut mock_hal, &payment_request, "0.25000000 BTC",).await,
+ Err(Error::InvalidInput),
+ );
}
}
Why this scored 28/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.