Ui: Display address in groups of 4 characters
What changed, and why it matters
This commit only changes how wallet addresses are shown on the BitBox02 screen. It inserts spaces every four characters (for example, a Bitcoin address becomes "bc1q k5f9 em9q ..." instead of one long string). There is no code that changes security logic, cryptography, or how transactions are approved. It is a user-experience improvement to make long addresses easier to read and compare.
No security action needed. Treat as a normal UX improvement. Reviewers may optionally verify that the formatter does not truncate or alter the actual address returned to the host, which the diff confirms is unchanged.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch adds a utility util::strings::format_address that groups address strings into 4-character blocks separated by spaces. It then calls this formatter (plus coin-specific wrappers for Cardano and Ethereum that preserve their human-readable prefixes) before passing the result to on-device confirmation screens. The underlying address derivation, validation, signing, and response values are unchanged. All modifications are in UI presentation code and in corresponding unit-test expectations.
Changed components
On-device UI address display for Bitcoin, Litecoin, Cardano, and Ethereumutil::strings Rust moduleInspect captured patch +215 / −78
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d8ecfca..c2e5979 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ customers cannot upgrade their bootloader, its changes are recorded separately.
- Improve experience of moving back and forth when entering password characters
- Ethereum: add data streaming support for transactions with large (>6144 bytes) data
- Ethereum: allow EIP-712 typed message signing without anti-klepto host nonce commitment
+- Improve address display by grouping addresses into groups of 4 characters
### v9.25.0
- BitBox02 Nova: improved password stretching algorithm
diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin.rs
index e7f4ab5..a2057a2 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin.rs
@@ -172,9 +172,10 @@ async fn address_simple(
) -> Result<Response, Error> {
let address = derive_address_simple(hal, coin, simple_type, keypath)?;
if display {
+ let address_formatted = util::strings::format_address(&address);
let confirm_params = ConfirmParams {
title: params::get(coin).name,
- body: &address,
+ body: &address_formatted,
scrollable: true,
..Default::default()
};
@@ -212,10 +213,11 @@ pub async fn address_multisig(
)?
.address(coin_params)?;
if display {
+ let address_formatted = util::strings::format_address(&address);
hal.ui()
.confirm(&ConfirmParams {
title,
- body: &address,
+ body: &address_formatted,
scrollable: true,
..Default::default()
})
@@ -252,10 +254,11 @@ async fn address_policy(
let address =
common::Payload::from_policy(coin_params, &parsed, keypath)?.address(coin_params)?;
if display {
+ let address_formatted = util::strings::format_address(&address);
hal.ui()
.confirm(&ConfirmParams {
title,
- body: &address,
+ body: &address_formatted,
scrollable: true,
..Default::default()
})
@@ -330,7 +333,6 @@ mod tests {
use pb::btc_script_config::multisig::ScriptType as MultisigScriptType;
use util::bb02_async::block_on;
use util::bip32::HARDENED;
-
#[test]
pub fn test_xpub() {
struct Test<'a> {
@@ -589,6 +591,7 @@ mod tests {
keypath: &'a [u32],
simple_type: SimpleType,
expected_address: &'a str,
+ expected_displayed_address: &'a str,
expected_display_title: &'a str,
}
@@ -600,6 +603,7 @@ mod tests {
keypath: &[49 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0],
simple_type: SimpleType::P2wpkhP2sh,
expected_address: "3BaL6XecvLAidPToUDhXo1zxD99ZUrErpd",
+ expected_displayed_address: "3BaL 6Xec vLAi dPTo UDhX o1zx D99Z UrEr pd",
expected_display_title: "Bitcoin",
},
Test {
@@ -608,6 +612,7 @@ mod tests {
keypath: &[49 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 1],
simple_type: SimpleType::P2wpkhP2sh,
expected_address: "3QRfCGEJVzvR1HN13kxB7xkuUtdEvG2orZ",
+ expected_displayed_address: "3QRf CGEJ VzvR 1HN1 3kxB 7xku UtdE vG2o rZ",
expected_display_title: "Bitcoin",
},
Test {
@@ -616,6 +621,7 @@ mod tests {
keypath: &[49 + HARDENED, 0 + HARDENED, 1 + HARDENED, 1, 100],
simple_type: SimpleType::P2wpkhP2sh,
expected_address: "39r7CFVo1wpb3mxQfkG6yYyxMAfqAmZMhA",
+ expected_displayed_address: "39r7 CFVo 1wpb 3mxQ fkG6 yYyx MAfq AmZM hA",
expected_display_title: "Bitcoin",
},
// BTC P2WPKH
@@ -625,6 +631,7 @@ mod tests {
keypath: &[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0],
simple_type: SimpleType::P2wpkh,
expected_address: "bc1qk5f9em9qc8yfpks8ngfg3h8h02n2e3yeqdyhpt",
+ expected_displayed_address: "bc1q k5f9 em9q c8yf pks8 ngfg 3h8h 02n2 e3ye qdyh pt",
expected_display_title: "Bitcoin",
},
Test {
@@ -633,6 +640,7 @@ mod tests {
keypath: &[84 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 1],
simple_type: SimpleType::P2wpkh,
expected_address: "bc1qtn7feuj7juxkzf48zfxtngrcyqyns9f4ska7hg",
+ expected_displayed_address: "bc1q tn7f euj7 juxk zf48 zfxt ngrc yqyn s9f4 ska7 hg",
expected_display_title: "Bitcoin",
},
Test {
@@ -641,6 +649,7 @@ mod tests {
keypath: &[84 + HARDENED, 0 + HARDENED, 1 + HARDENED, 1, 100],
simple_type: SimpleType::P2wpkh,
expected_address: "bc1qarhxx6daqetewkjwz9p6y78a28ygxm2vndhdas",
+ expected_displayed_address: "bc1q arhx x6da qete wkjw z9p6 y78a 28yg xm2v ndhd as",
expected_display_title: "Bitcoin",
},
// BTC P2TR
@@ -651,6 +660,7 @@ mod tests {
keypath: &[86 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 0],
simple_type: SimpleType::P2tr,
expected_address: "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
+ expected_displayed_address: "bc1p 5cyx nuxm euwu vkwf em96 lqzs zd02 n6xd cjrs 20ca c6yq jjwu dpxq kedr cr",
expected_display_title: "Bitcoin",
},
Test {
@@ -659,6 +669,7 @@ mod tests {
keypath: &[86 + HARDENED, 0 + HARDENED, 0 + HARDENED, 0, 1],
simple_type: SimpleType::P2tr,
expected_address: "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
+ expected_displayed_address: "bc1p 4qhj n9zd vkux 4e44 uhx8 tc55 attv tyu3 58ku tcqk udyc celu 0was 9fqz wh",
expected_display_title: "Bitcoin",
},
Test {
@@ -667,6 +678,7 @@ mod tests {
keypath: &[86 + HARDENED, 0 + HARDENED, 0 + HARDENED, 1, 0],
simple_type: SimpleType::P2tr,
expected_address: "bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7",
+ expected_displayed_address: "bc1p 3qkh fews 2uk4 4qtv auqy r2tt dsw7 svhk l9nk m9s9 c3x4 ax5h 60wq wruh k7",
expected_display_title: "Bitcoin",
},
// TBTC P2WPKH-P2SH
@@ -676,6 +688,7 @@ mod tests {
keypath: &[49 + HARDENED, 1 + HARDENED, 0 + HARDENED, 0, 0],
simple_type: SimpleType::P2wpkhP2sh,
expected_address: "2N5Tjwx5Htk7gLbv7nWqXUgpg5K2Uf4TacQ",
+ expected_displayed_address: "2N5T jwx5 Htk7 gLbv 7nWq XUgp g5K2 Uf4T acQ",
expected_display_title: "BTC Testnet",
},
// TBTC P2WPKH
@@ -685,6 +698,7 @@ mod tests {
keypath: &[84 + HARDENED, 1 + HARDENED, 0 + HARDENED, 0, 0],
simple_type: SimpleType::P2wpkh,
expected_address: "tb1qnlyrq9pshg0v0lsuudjgga4nvmjxhcvketqwdg",
+ expected_displayed_address: "tb1q nlyr q9ps hg0v 0lsu udjg ga4n vmjx hcvk etqw dg",
expected_display_title: "BTC Testnet",
},
// RBTC P2WPKH
@@ -694,6 +708,7 @@ mod tests {
keypath: &[84 + HARDENED, 1 + HARDENED, 0 + HARDENED, 0, 0],
simple_type: SimpleType::P2wpkh,
expected_address: "bcrt1qnlyrq9pshg0v0lsuudjgga4nvmjxhcvkmzer6p",
+ expected_displayed_address: "bcrt 1qnl yrq9 pshg 0v0l suud jgga 4nvm jxhc vkmz er6p",
expected_display_title: "BTC Regtest",
},
// LTC P2WPKH-P2SH
@@ -703,6 +718,7 @@ mod tests {
keypath: &[49 + HARDENED, 2 + HARDENED, 0 + HARDENED, 0, 0],
simple_type: SimpleType::P2wpkhP2sh,
expected_address: "MMmYgSH7fbTPnfdi1vTejMJyY7rKY4j9qv",
+ expected_displayed_address: "MMmY gSH7 fbTP nfdi 1vTe jMJy Y7rK Y4j9 qv",
expected_display_title: "Litecoin",
},
Test {
@@ -711,6 +727,7 @@ mod tests {
keypath: &[49 + HARDENED, 2 + HARDENED, 0 + HARDENED, 0, 1],
simple_type: SimpleType::P2wpkhP2sh,
expected_address: "M7wA8gBLL4SBiwQ1muQeKcG6naYqWcaUHg",
+ expected_displayed_address: "M7wA 8gBL L4SB iwQ1 muQe KcG6 naYq WcaU Hg",
expected_display_title: "Litecoin",
},
Test {
@@ -719,6 +736,7 @@ mod tests {
keypath: &[49 + HARDENED, 2 + HARDENED, 1 + HARDENED, 1, 100],
simple_type: SimpleType::P2wpkhP2sh,
expected_address: "MPBnihMP2JYjPtBnLxGydqvaALBsc5ALTG",
+ expected_displayed_address: "MPBn ihMP 2JYj PtBn LxGy dqva ALBs c5AL TG",
expected_display_title: "Litecoin",
},
// LTC P2WPKH
@@ -728,6 +746,7 @@ mod tests {
keypath: &[84 + HARDENED, 2 + HARDENED, 0 + HARDENED, 0, 0],
simple_type: SimpleType::P2wpkh,
expected_address: "ltc1q7598y6mzud5fka043vs4vkx7zktvppxffsf7e3",
+ expected_displayed_address: "ltc1 q759 8y6m zud5 fka0 43vs 4vkx 7zkt vppx ffsf 7e3",
expected_display_title: "Litecoin",
},
Test {
@@ -736,6 +755,7 @@ mod tests {
keypath: &[84 + HARDENED, 2 + HARDENED, 0 + HARDENED, 0, 1],
simple_type: SimpleType::P2wpkh,
expected_address: "ltc1qtgjfu2ltg4slmksv27awmh6h2pccvsth4mw2w9",
+ expected_displayed_address: "ltc1 qtgj fu2l tg4s lmks v27a wmh6 h2pc cvst h4mw 2w9",
expected_display_title: "Litecoin",
},
Test {
@@ -744,6 +764,7 @@ mod tests {
keypath: &[84 + HARDENED, 2 + HARDENED, 1 + HARDENED, 1, 100],
simple_type: SimpleType::P2wpkh,
expected_address: "ltc1qwsz89auhpezjfllq9y9qegpfgdwpw5vesppsz0",
+ expected_displayed_address: "ltc1 qwsz 89au hpez jfll q9y9 qegp fgdw pw5v espp sz0",
expected_display_title: "Litecoin",
},
// TLTC P2WPKH-P2SH
@@ -753,6 +774,7 @@ mod tests {
keypath: &[49 + HARDENED, 1 + HARDENED, 0 + HARDENED, 0, 0],
simple_type: SimpleType::P2wpkhP2sh,
expected_address: "2N5Tjwx5Htk7gLbv7nWqXUgpg5K2Uf4TacQ",
+ expected_displayed_address: "2N5T jwx5 Htk7 gLbv 7nWq XUgp g5K2 Uf4T acQ",
expected_display_title: "LTC Testnet",
},
// TLTC P2WPKH
@@ -762,6 +784,7 @@ mod tests {
keypath: &[84 + HARDENED, 1 + HARDENED, 0 + HARDENED, 0, 0],
simple_type: SimpleType::P2wpkh,
expected_address: "tltc1qnlyrq9pshg0v0lsuudjgga4nvmjxhcvkqrzsap",
+ expected_displayed_address: "tltc 1qnl yrq9 pshg 0v0l suud jgga 4nvm jxhc vkqr zsap",
expected_display_title: "LTC Testnet",
},
] {
@@ -797,7 +820,7 @@ mod tests {
mock_hal.ui.screens,
vec![Screen::Confirm {
title: test.expected_display_title.into(),
- body: test.expected_address.into(),
+ body: test.expected_displayed_address.into(),
longtouch: false,
},]
);
@@ -856,6 +879,7 @@ mod tests {
keypath: &'a [u32],
script_type: MultisigScriptType,
expected_address: &'a str,
+ expected_displayed_address: &'a str,
}
let tests = &[
/* P2WSH */
@@ -878,6 +902,7 @@ mod tests {
],
script_type: MultisigScriptType::P2wsh,
expected_address: "bc1q2fhgukymf0caaqrhfxrdju4wm94wwrch2ukntl5fuc0faz8zm49q0h6ss8",
+ expected_displayed_address: "bc1q 2fhg ukym f0ca aqrh fxrd ju4w m94w wrch 2ukn tl5f uc0f az8z m49q 0h6s s8",
},
Test {
coin: BtcCoin::Tbtc,
@@ -898,6 +923,7 @@ mod tests {
],
script_type: MultisigScriptType::P2wsh,
expected_address: "tb1qw2scxk3zq0znr4ug9xkf3n7nfjsc8ldvemrm9dxjpl847zyu6afsfjjy28",
+ expected_displayed_address: "tb1q w2sc xk3z q0zn r4ug 9xkf 3n7n fjsc 8ldv emrm 9dxj pl84 7zyu 6afs fjjy 28",
},
Test {
coin: BtcCoin::Tbtc,
@@ -931,6 +957,7 @@ mod tests {
],
script_type: MultisigScriptType::P2wsh,
expected_address: "tb1qndz49j0arp8g6jc8vcrgf9ugrsw96a0j5d7vqcun6jev6rlv47jsv99y5m",
+ expected_displayed_address: "tb1q ndz4 9j0a rp8g 6jc8 vcrg f9ug rsw9 6a0j 5d7v qcun 6jev 6rlv 47js v99y 5m",
},
// An arbitrary "non-standard" keypath
Test {
@@ -945,6 +972,7 @@ mod tests {
keypath: &[45 + HARDENED, 1, 2],
script_type: MultisigScriptType::P2wsh,
expected_address: "bc1qtsvlhzltl05etjjeqh00urwttu6ep4xn3c0ccndz77unttut9h0qvrcs04",
+ expected_displayed_address: "bc1q tsvl hzlt l05e tjje qh00 urwt tu6e p4xn 3c0c cndz 77un ttut 9h0q vrcs 04",
},
/* P2WSH-P2SH */
Test {
@@ -966,6 +994,7 @@ mod tests {
],
script_type: MultisigScriptType::P2wshP2sh,
expected_address: "3BKdK5c2kcFrNmmJbMAeWuveaoYDB4BYvu",
+ expected_displayed_address: "3BKd K5c2 kcFr NmmJ bMAe Wuve aoYD B4BY vu",
},
/* P2WSH-P2SH Nunchuk keypath */
Test {
@@ -980,6 +1009,7 @@ mod tests {
keypath: &[48 + HARDENED, 0 + HARDENED, 0 + HARDENED, 1, 0],
script_type: MultisigScriptType::P2wshP2sh,
expected_address: "341hw7cuzpf2AtSuXupX5Pu3tkkXv24bvo",
+ expected_displayed_address: "341h w7cu zpf2 AtSu XupX 5Pu3 tkkX v24b vo",
},
];
for test in tests.iter() {
@@ -1034,7 +1064,7 @@ mod tests {
},
Screen::Confirm {
title: "Receive to".into(),
- body: test.expected_address.into(),
+ body: test.expected_displayed_address.into(),
longtouch: false,
},
]
@@ -1085,6 +1115,7 @@ mod tests {
keys: &'a [pb::KeyOriginInfo],
keypath: &'a [u32],
expected_address: &'a str,
+ expected_displayed_address: &'a str,
}
let tests = &[
Test {
@@ -1100,6 +1131,7 @@ mod tests {
0,
],
expected_address: "bc1q9n0nxanmarawjpj2xz0echuhk4a2qga99xpn0nrpgfv2vv9279vsvrh6rj",
+ expected_displayed_address: "bc1q 9n0n xanm araw jpj2 xz0e chuh k4a2 qga9 9xpn 0nrp gfv2 vv92 79vs vrh6 rj",
},
Test {
coin: BtcCoin::Tbtc,
@@ -1114,6 +1146,7 @@ mod tests {
0,
],
expected_address: "tb1qvq2793p7nyuxzqn5ts3kgqywxn9kj277skyvtz895gf7urfdxenqvq39sp",
+ expected_displayed_address: "tb1q vq27 93p7 nyux zqn5 ts3k gqyw xn9k j277 skyv tz89 5gf7 urfd xenq vq39 sp",
},
Test {
coin: BtcCoin::Tbtc,
@@ -1128,6 +1161,7 @@ mod tests {
0,
],
expected_address: "tb1qeah5dqvya674w60ce6d3gk2xy7n8n0g4weztlywdd6zhu0csdv7s8yynr3",
+ expected_displayed_address: "tb1q eah5 dqvy a674 w60c e6d3 gk2x y7n8 n0g4 wezt lywd d6zh u0cs dv7s 8yyn r3",
},
Test {
coin: BtcCoin::Tbtc,
@@ -1142,6 +1176,7 @@ mod tests {
0,
],
expected_address: "tb1qeyetg3vgjvrgax0c5z70yuev3egdtxvv870jvzn235agtqe0l3gqytjrmc",
+ expected_displayed_address: "tb1q eyet g3vg jvrg ax0c 5z70 yuev 3egd txvv 870j vzn2 35ag tqe0 l3gq ytjr mc",
},
Test {
coin: BtcCoin::Tbtc,
@@ -1156,6 +1191,7 @@ mod tests {
5,
],
expected_address: "tb1qkfpeqx87pwjruet9c2xt88n6k47mz9q9m5jt77906780qrv4sl4sr5m72q",
+ expected_displayed_address: "tb1q kfpe qx87 pwjr uet9 c2xt 88n6 k47m z9q9 m5jt 7790 6780 qrv4 sl4s r5m7 2q",
},
];
for test in tests {
diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signmsg.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signmsg.rs
index 12b1033..b717e3d 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signmsg.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signmsg.rs
@@ -51,6 +51,7 @@ pub async fn process(
// Keypath and script_config are validated in address_simple().
let address = super::derive_address_simple(hal, coin, simple_type, keypath)?;
+ let address_formatted = util::strings::format_address(&address);
let basic_info = format!("Coin: {}", super::params::get(coin).name);
let confirm_params = ConfirmParams {
@@ -63,7 +64,7 @@ pub async fn process(
let confirm_params = ConfirmParams {
title: "Address",
- body: &address,
+ body: &address_formatted,
scrollable: true,
accept_is_nextarrow: true,
..Default::default()
@@ -167,7 +168,7 @@ mod tests {
},
Screen::Confirm {
title: "Address".into(),
- body: "bc1qk5f9em9qc8yfpks8ngfg3h8h02n2e3yeqdyhpt".into(),
+ body: "bc1q k5f9 em9q c8yf pks8 ngfg 3h8h 02n2 e3ye qdyh pt".into(),
longtouch: false,
},
Screen::Confirm {
@@ -206,7 +207,7 @@ mod tests {
},
Screen::Confirm {
title: "Address".into(),
- body: "tb1qnlyrq9pshg0v0lsuudjgga4nvmjxhcvketqwdg".into(),
+ body: "tb1q nlyr q9ps hg0v 0lsu udjg ga4n vmjx hcvk etqw dg".into(),
longtouch: false,
},
Screen::Confirm {
@@ -250,7 +251,7 @@ mod tests {
},
Screen::Confirm {
title: "Address".into(),
- body: "3BaL6XecvLAidPToUDhXo1zxD99ZUrErpd".into(),
+ body: "3BaL 6Xec vLAi dPTo UDhX o1zx D99Z UrEr pd".into(),
longtouch: false,
},
Screen::Confirm {
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 85c0f8e..9634360 100644
--- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs
@@ -1015,13 +1015,15 @@ async fn _process(
None
};
+ let address_formatted = util::strings::format_address(&address()?);
+ let recipient = if let Some(prefix) = prefix {
+ format!("{}: {}", prefix, address_formatted)
+ } else {
+ address_formatted
+ };
hal.ui()
.verify_recipient(
- &(if let Some(prefix) = prefix {
- format!("{}: {}", prefix, address()?)
- } else {
- address()?
- }),
+ &recipient,
&format_amount(coin_params, format_unit, tx_output.value)?,
)
.await?;
@@ -1307,7 +1309,6 @@ mod tests {
_ => panic!("wrong response type"),
}
}
-
struct TxInput {
input: pb::BtcSignInputRequest,
prevtx_version: u32,
@@ -1954,60 +1955,56 @@ mod tests {
vec![
match coin {
pb::BtcCoin::Btc => Screen::Recipient {
- recipient: "12ZEw5Hcv1hTb6YUQJ69y1V7uhcoDz92PH".into(),
+ recipient: "12ZE w5Hc v1hT b6YU QJ69 y1V7 uhco Dz92 PH".into(),
amount: match format_unit {
FormatUnit::Default => "1.00000000 BTC".into(),
FormatUnit::Sat => "100000000 sat".into(),
},
},
pb::BtcCoin::Ltc => Screen::Recipient {
- recipient: "LLnCCHbSzfwWquEdaS5TF2Yt7uz5Qb1SZ1".into(),
+ recipient: "LLnC CHbS zfwW quEd aS5T F2Yt 7uz5 Qb1S Z1".into(),
amount: "1.00000000 LTC".into(),
},
_ => panic!("unexpected coin"),
},
match coin {
pb::BtcCoin::Btc => Screen::Recipient {
- recipient: "34oVnh4gNviJGMnNvgquMeLAxvXJuaRVMZ".into(),
+ recipient: "34oV nh4g NviJ GMnN vgqu MeLA xvXJ uaRV MZ".into(),
amount: match format_unit {
FormatUnit::Default => "12.34567890 BTC".into(),
FormatUnit::Sat => "1234567890 sat".into(),
},
},
pb::BtcCoin::Ltc => Screen::Recipient {
- recipient: "MB1e6aUeL3Zj4s4H2ZqFBHaaHd7kvvzTco".into(),
+ recipient: "MB1e 6aUe L3Zj 4s4H 2ZqF BHaa Hd7k vvzT co".into(),
amount: "12.34567890 LTC".into(),
},
_ => panic!("unexpected coin"),
},
match coin {
pb::BtcCoin::Btc => Screen::Recipient {
- recipient: "bc1qxvenxvenxvenxvenxvenxvenxvenxven2ymjt8".into(),
+ recipient: "bc1q xven xven xven xven xven xven xven xven 2ymj t8".into(),
amount: match format_unit {
FormatUnit::Default => "0.00006000 BTC".into(),
FormatUnit::Sat => "6000 sat".into(),
},
},
pb::BtcCoin::Ltc => Screen::Recipient {
- recipient: "ltc1qxvenxvenxvenxvenxvenxvenxvenxvenwcpknh".into(),
+ recipient: "ltc1 qxve nxve nxve nxve nxve nxve nxve nxve nwcp knh".into(),
amount: "0.00006000 LTC".into(),
},
_ => panic!("unexpected coin"),
},
match coin {
pb::BtcCoin::Btc => Screen::Recipient {
- recipient:
- "bc1qg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zqd8sxw4"
- .into(),
+ recipient: "bc1q g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zq d8sx w4".into(),
amount: match format_unit {
FormatUnit::Default => "0.00007000 BTC".into(),
FormatUnit::Sat => "7000 sat".into(),
},
},
pb::BtcCoin::Ltc => Screen::Recipient {
- recipient:
- "ltc1qg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zqwr7k5s"
- .into(),
+ recipient: "ltc1 qg3z yg3z yg3z yg3z yg3z yg3z yg3z yg3z yg3z yg3z yg3z yg3z yg3z qwr7 k5s".into(),
amount: "0.00007000 LTC".into(),
},
_ => panic!("unexpected coin"),
@@ -2528,7 +2525,9 @@ mod tests {
assert_eq!(
mock_hal.ui.screens[0],
Screen::Recipient {
- recipient: "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr".into(),
+ recipient:
+ "bc1p 5cyx nuxm euwu vkwf em96 lqzs zd02 n6xd cjrs 20ca c6yq jjwu dpxq kedr cr"
+ .into(),
amount: "1.00000000 BTC".into(),
}
);
@@ -2600,7 +2599,7 @@ mod tests {
assert_eq!(
mock_hal.ui.screens[0],
Screen::Recipient {
- recipient: "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv".into(),
+ recipient: "sp1q qgst e7k9 hx0q ftg6 qmwl kqtw uy6c ycya vzmz j85c 6qdf hjdp djtd gqju exzk 6mur w56s uy3e 0rd2 cgqv ycxt tddw svgx e2us fpxu mr70 xc9p kqwv".into(),
amount: "1.00000000 BTC".into(),
}
);
@@ -2621,8 +2620,7 @@ mod tests {
assert_eq!(
mock_hal.ui.screens[4],
Screen::Recipient {
- recipient: "This BitBox (same account): bc1qnu4x8dlrx6dety47gehf4uhk5tj3q7yhywgry6"
- .into(),
+ recipient: "This BitBox (same account): bc1q nu4x 8dlr x6de ty47 gehf 4uhk 5tj3 q7yh ywgr y6".into(),
amount: "0.00000100 BTC".into(),
}
);
@@ -2678,8 +2676,7 @@ mod tests {
assert_eq!(
mock_hal.ui.screens[4],
Screen::Recipient {
- recipient: "This BitBox (account #21): bc1qr9t2u35gzrtznzv6n99f2dj37j9msfffv78cv2"
- .into(),
+ recipient: "This BitBox (account #21): bc1q r9t2 u35g zrtz nzv6 n99f 2dj3 7j9m sfff v78c v2".into(),
amount: "0.00000100 BTC".into(),
}
);
@@ -2965,8 +2962,7 @@ mod tests {
longtouch: false,
},
Screen::Recipient {
- recipient: "tb1qtxyqynfxwsk8f5gu8v5g8e6hs3njtglkywhvyztk6v8znvx5kddsmhuve2"
- .into(),
+ recipient: "tb1q txyq ynfx wsk8 f5gu 8v5g 8e6h s3nj tglk ywhv yztk 6v8z nvx5 kdds mhuv e2".into(),
amount: "0.00090000 TBTC".into(),
},
Screen::Confirm {
@@ -3306,8 +3302,7 @@ mod tests {
longtouch: false,
},
Screen::Recipient {
- recipient: "tb1qtxyqynfxwsk8f5gu8v5g8e6hs3njtglkywhvyztk6v8znvx5kddsmhuve2"
- .into(),
+ recipient: "tb1q txyq ynfx wsk8 f5gu 8v5g 8e6h s3nj tglk ywhv yztk 6v8z nvx5 kdds mhuv e2".into(),
amount: "0.00090000 TBTC".into(),
},
Screen::Confirm {
@@ -3495,8 +3490,7 @@ mod tests {
longtouch: false,
},
Screen::Recipient {
- recipient: "tb1qtxyqynfxwsk8f5gu8v5g8e6hs3njtglkywhvyztk6v8znvx5kddsmhuve2"
- .into(),
+ recipient: "tb1q txyq ynfx wsk8 f5gu 8v5g 8e6h s3nj tglk ywhv yztk 6v8z nvx5 kdds mhuv e2".into(),
amount: "0.00090000 TBTC".into(),
},
Screen::Confirm {
@@ -3731,7 +3725,7 @@ mod tests {
mock_hal.ui.screens,
vec![
Screen::Recipient {
- recipient: "12ZEw5Hcv1hTb6YUQJ69y1V7uhcoDz92PH".into(),
+ recipient: "12ZE w5Hc v1hT b6YU QJ69 y1V7 uhco Dz92 PH".into(),
amount: "1.00000000 BTC".into(),
},
// Payment request
@@ -3755,12 +3749,11 @@ mod tests {
longtouch: false,
},
Screen::Recipient {
- recipient: "bc1qxvenxvenxvenxvenxvenxvenxvenxven2ymjt8".into(),
+ recipient: "bc1q xven xven xven xven xven xven xven xven 2ymj t8".into(),
amount: "0.00006000 BTC".into(),
},
Screen::Recipient {
- recipient: "bc1qg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zqd8sxw4"
- .into(),
+ recipient: "bc1q g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zy g3zq d8sx w4".into(),
amount: "0.00007000 BTC".into(),
},
Screen::Confirm {
diff --git a/src/rust/bitbox02-rust/src/hww/api/cardano/address.rs b/src/rust/bitbox02-rust/src/hww/api/cardano/address.rs
index ade0a7d..9669dc0 100644
--- a/src/rust/bitbox02-rust/src/hww/api/cardano/address.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/cardano/address.rs
@@ -359,6 +359,24 @@ pub fn validate_and_encode_payment_address(
}
}
+/// Formats a Cardano address for display in blocks of 4 chars.
+/// Bech32 payment prefixes `addr1` and `addr_test1` are preserved and followed by a space.
+pub fn format_display_address(address: &str) -> String {
+ if let Some(rest) = address.strip_prefix("addr_test1") {
+ if rest.is_empty() {
+ return address.into();
+ }
+ return format!("addr_test1 {}", util::strings::format_address(rest));
+ }
+ if let Some(rest) = address.strip_prefix("addr1") {
+ if rest.is_empty() {
+ return address.into();
+ }
+ return format!("addr1 {}", util::strings::format_address(rest));
+ }
+ util::strings::format_address(address)
+}
+
pub async fn process(
hal: &mut impl crate::hal::Hal,
request: &pb::CardanoAddressRequest,
@@ -376,10 +394,11 @@ pub async fn process(
let encoded_address = validate_and_encode_payment_address(hal, params, script_config, None)?;
if request.display {
+ let displayed_address = format_display_address(&encoded_address);
hal.ui()
.confirm(&ConfirmParams {
title: params.name,
- body: &encoded_address,
+ body: &displayed_address,
scrollable: true,
..Default::default()
})
@@ -401,6 +420,26 @@ mod tests {
use util::bb02_async::block_on;
use util::bip32::HARDENED;
+ #[test]
+ fn test_format_display_address() {
+ assert_eq!(
+ format_display_address(
+ "addr1q90tlskd4mh5kncmul7vx887j30tjtfgvap5n0g0rf9qqc7znmndrdhe7rwvqkw5c7mqnp4a3yflnvu6kff7l5dungvqmvu6hs"
+ ),
+ "addr1 q90t lskd 4mh5 kncm ul7v x887 j30t jtfg vap5 n0g0 rf9q qc7z nmnd rdhe 7rwv qkw5 c7mq np4a 3yfl nvu6 kff7 l5du ngvq mvu6 hs",
+ );
+ assert_eq!(
+ format_display_address(
+ "addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgs68faae"
+ ),
+ "addr_test1 qz2f xv2u myht tkxy xp8x 0dlp dt3k 6cwn g5px j3jh sydz er3n 0d3v llmy qwsx 5wkt cd8c c3sq 835l u7dr v2xw l2wy wfgs 68fa ae",
+ );
+ assert_eq!(
+ format_display_address("Ae2tdPwUPEZFRbyhz3cpfC2CumGzNkFBN2L42rcUc2yjQpEkxDbkPodpMAi"),
+ "Ae2t dPwU PEZF Rbyh z3cp fC2C umGz NkFB N2L4 2rcU c2yj QpEk xDbk Podp MAi",
+ );
+ }
+
#[test]
fn test_decode_payment_address() {
// See https://github.com/cardano-foundation/CIPs/blob/0081c890995ff94618145ae5beb7f288c029a86a/CIP-0019/CIP-0019.md#test-vectors
@@ -563,6 +602,7 @@ mod tests {
#[test]
fn test_process_confirm() {
const EXPECTED: &str = "addr1q90tlskd4mh5kncmul7vx887j30tjtfgvap5n0g0rf9qqc7znmndrdhe7rwvqkw5c7mqnp4a3yflnvu6kff7l5dungvqmvu6hs";
+ const EXPECTED_DISPLAYED: &str = "addr1 q90t lskd 4mh5 kncm ul7v x887 j30t jtfg vap5 n0g0 rf9q qc7z nmnd rdhe 7rwv qkw5 c7mq np4a 3yfl nvu6 kff7 l5du ngvq mvu6 hs";
mock_unlocked();
let mut mock_hal = TestingHal::new();
@@ -586,7 +626,7 @@ mod tests {
mock_hal.ui.screens,
vec![Screen::Confirm {
title: "Cardano".into(),
- body: EXPECTED.into(),
+ body: EXPECTED_DISPLAYED.into(),
longtouch: false,
},]
);
diff --git a/src/rust/bitbox02-rust/src/hww/api/cardano/sign_transaction.rs b/src/rust/bitbox02-rust/src/hww/api/cardano/sign_transaction.rs
index 66b89aa..2525b7f 100644
--- a/src/rust/bitbox02-rust/src/hww/api/cardano/sign_transaction.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/cardano/sign_transaction.rs
@@ -239,8 +239,10 @@ async fn _process(
},
None => {
let formatted_value = format_value(params, output.value);
+ let displayed_address =
+ super::address::format_display_address(&output.encoded_address);
hal.ui()
- .verify_recipient(&output.encoded_address, &formatted_value)
+ .verify_recipient(&displayed_address, &formatted_value)
.await?;
total += output.value;
@@ -441,9 +443,10 @@ mod tests {
}]
})
);
- const RECIPIENT1: &str = "addr1q9qfllpxg2vu4lq6rnpel4pvpp5xnv3kvvgtxk6k6wp4ff89xrhu8jnu3p33vnctc9eklee5dtykzyag5penc6dcmakqsqqgpt";
- const RECIPIENT2: &str = "Ae2tdPwUPEZFRbyhz3cpfC2CumGzNkFBN2L42rcUc2yjQpEkxDbkPodpMAi";
- const RECIPIENT3: &str = "DdzFFzCqrhtC3C4UY8YFaEyDALJmFAwhx4Kggk3eae3BT9PhymMjzCVYhQE753BH1Rp3LXfVkVaD1FHT4joSBq7Y8rcXbbVWoxkqB7gy";
+ const RECIPIENT1: &str = "addr1 q9qf llpx g2vu 4lq6 rnpe l4pv pp5x nv3k vvgt xk6k 6wp4 ff89 xrhu 8jnu 3p33 vnct c9ek lee5 dtyk zyag 5pen c6dc makq sqqg pt";
+ const RECIPIENT2: &str =
+ "Ae2t dPwU PEZF Rbyh z3cp fC2C umGz NkFB N2L4 2rcU c2yj QpEk xDbk Podp MAi";
+ const RECIPIENT3: &str = "DdzF FzCq rhtC 3C4U Y8YF aEyD ALJm FAwh x4Kg gk3e ae3B T9Ph ymMj zCVY hQE7 53BH 1Rp3 LXfV kVaD 1FHT 4joS Bq7Y 8rcX bbVW oxkq B7gy";
assert_eq!(
mock_hal.ui.screens,
vec![
@@ -949,7 +952,7 @@ mod tests {
longtouch: false
},
Screen::Recipient {
- recipient: "addr1q9qfllpxg2vu4lq6rnpel4pvpp5xnv3kvvgtxk6k6wp4ff89xrhu8jnu3p33vnctc9eklee5dtykzyag5penc6dcmakqsqqgpt".into(),
+ recipient: "addr1 q9qf llpx g2vu 4lq6 rnpe l4pv pp5x nv3k vvgt xk6k 6wp4 ff89 xrhu 8jnu 3p33 vnct c9ek lee5 dtyk zyag 5pen c6dc makq sqqg pt".into(),
amount: "1 ADA".into()
},
Screen::TotalFee {
@@ -1149,7 +1152,7 @@ mod tests {
mock_hal.ui.screens,
vec![
Screen::Recipient {
- recipient: "addr1q9qfllpxg2vu4lq6rnpel4pvpp5xnv3kvvgtxk6k6wp4ff89xrhu8jnu3p33vnctc9eklee5dtykzyag5penc6dcmakqsqqgpt".into(),
+ recipient: "addr1 q9qf llpx g2vu 4lq6 rnpe l4pv pp5x nv3k vvgt xk6k 6wp4 ff89 xrhu 8jnu 3p33 vnct c9ek lee5 dtyk zyag 5pen c6dc makq sqqg pt".into(),
amount: "1 ADA".into()
},
Screen::Confirm {
diff --git a/src/rust/bitbox02-rust/src/hww/api/ethereum/address.rs b/src/rust/bitbox02-rust/src/hww/api/ethereum/address.rs
index 56518f6..ce4e098 100644
--- a/src/rust/bitbox02-rust/src/hww/api/ethereum/address.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/ethereum/address.rs
@@ -48,6 +48,21 @@ pub fn from_pubkey(pubkey_uncompressed: &[u8; 65]) -> String {
)
}
+/// Formats an Ethereum address for display in blocks of 4 chars.
+/// If the input starts with `0x`/`0X`, the prefix is preserved and followed by a space.
+pub fn format_display_address(address: &str) -> String {
+ if let Some(rest) = address
+ .strip_prefix("0x")
+ .or_else(|| address.strip_prefix("0X"))
+ {
+ if rest.is_empty() {
+ return address.into();
+ }
+ return format!("{} {}", &address[..2], util::strings::format_address(rest));
+ }
+ util::strings::format_address(address)
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -133,4 +148,20 @@ mod tests {
assert_eq!(from_pubkey(pubkey), expected_address);
}
}
+
+ #[test]
+ fn test_format_display_address() {
+ assert_eq!(
+ format_display_address("0x773A77b9D32589be03f9132AF759e294f7851be9"),
+ "0x 773A 77b9 D325 89be 03f9 132A F759 e294 f785 1be9"
+ );
+ assert_eq!(
+ format_display_address("0X773A77b9D32589be03f9132AF759e294f7851be9"),
+ "0X 773A 77b9 D325 89be 03f9 132A F759 e294 f785 1be9"
+ );
+ assert_eq!(
+ format_display_address("773A77b9D32589be03f9132AF759e294f7851be9"),
+ "773A 77b9 D325 89be 03f9 132A F759 e294 f785 1be9"
+ );
+ }
}
diff --git a/src/rust/bitbox02-rust/src/hww/api/ethereum/pubrequest.rs b/src/rust/bitbox02-rust/src/hww/api/ethereum/pubrequest.rs
index 8c8501e..921c152 100644
--- a/src/rust/bitbox02-rust/src/hww/api/ethereum/pubrequest.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/ethereum/pubrequest.rs
@@ -33,6 +33,7 @@ async fn process_address(
let address = super::derive_address(hal, &request.keypath)?;
if request.display {
+ let address_display = super::address::format_display_address(&address);
let title = match erc20_params {
Some(erc20_params) => format!("{}\n{}", params.name, erc20_params.unit),
None => params.name.into(),
@@ -42,7 +43,7 @@ async fn process_address(
.confirm(&ConfirmParams {
title: &title,
title_autowrap: true,
- body: &address,
+ body: &address_display,
scrollable: true,
..Default::default()
})
@@ -134,6 +135,7 @@ mod tests {
#[test]
pub fn test_process_address() {
const ADDRESS: &str = "0x773A77b9D32589be03f9132AF759e294f7851be9";
+ const DISPLAY_ADDRESS: &str = "0x 773A 77b9 D325 89be 03f9 132A F759 e294 f785 1be9";
let request = pb::EthPubRequest {
output_type: OutputType::Address as _,
@@ -176,7 +178,7 @@ mod tests {
mock_hal.ui.screens,
vec![Screen::Confirm {
title: "Ethereum".into(),
- body: ADDRESS.into(),
+ body: DISPLAY_ADDRESS.into(),
longtouch: false,
}]
);
@@ -210,7 +212,7 @@ mod tests {
},
Screen::Confirm {
title: "Sepolia".into(),
- body: ADDRESS.into(),
+ body: DISPLAY_ADDRESS.into(),
longtouch: false,
},
]
@@ -269,6 +271,7 @@ mod tests {
#[test]
pub fn test_process_erc20_address() {
const ADDRESS: &str = "0x773A77b9D32589be03f9132AF759e294f7851be9";
+ const DISPLAY_ADDRESS: &str = "0x 773A 77b9 D325 89be 03f9 132A F759 e294 f785 1be9";
const CONTRACT_ADDRESS: [u8; 20] = hex!("dac17f958d2ee523a2206206994597c13d831ec7");
let request = pb::EthPubRequest {
@@ -312,7 +315,7 @@ mod tests {
mock_hal.ui.screens,
vec![Screen::Confirm {
title: "Ethereum\nUSDT".into(),
- body: ADDRESS.into(),
+ body: DISPLAY_ADDRESS.into(),
longtouch: false,
}]
);
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 03d4374..3e9085c 100644
--- a/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs
@@ -222,6 +222,7 @@ async fn verify_erc20_transaction(
let erc20_params = erc20_params::get(params.chain_id, parse_recipient(request.recipient())?);
let formatted_fee = parse_fee(request, params).format();
let recipient_address = super::address::from_pubkey_hash(&erc20_recipient, request.case()?);
+ let recipient_address_display = super::address::format_display_address(&recipient_address);
let (formatted_value, formatted_total) = match erc20_params {
Some(erc20_params) => {
let value = Amount {
@@ -237,7 +238,7 @@ async fn verify_erc20_transaction(
None => ("Unknown token".into(), "Unknown amount".into()),
};
hal.ui()
- .verify_recipient(&recipient_address, &formatted_value)
+ .verify_recipient(&recipient_address_display, &formatted_value)
.await?;
transaction::verify_total_fee_maybe_warn(hal, &formatted_total, &formatted_fee, None).await?;
Ok(())
@@ -311,13 +312,14 @@ async fn verify_standard_transaction(
}
let address = super::address::from_pubkey_hash(&recipient, request.case()?);
+ let address_display = super::address::format_display_address(&address);
let amount = Amount {
unit: params.unit,
decimals: WEI_DECIMALS,
value: BigUint::from_bytes_be(request.value()),
};
hal.ui()
- .verify_recipient(&address, &amount.format())
+ .verify_recipient(&address_display, &amount.format())
.await?;
let fee = parse_fee(request, params);
@@ -575,7 +577,7 @@ mod tests {
longtouch: false,
},
Screen::Recipient {
- recipient: "0x04F264Cf34440313B4A0192A352814FBe927b885".into(),
+ recipient: "0x 04F2 64Cf 3444 0313 B4A0 192A 3528 14FB e927 b885".into(),
amount: "0.530564 ETH".into(),
},
Screen::TotalFee {
@@ -676,7 +678,7 @@ mod tests {
longtouch: false,
},
Screen::Recipient {
- recipient: "0x04F264Cf34440313B4A0192A352814FBe927b885".into(),
+ recipient: "0x 04F2 64Cf 3444 0313 B4A0 192A 3528 14FB e927 b885".into(),
amount: "0.530564 ETH".into(),
},
Screen::TotalFee {
@@ -736,7 +738,7 @@ mod tests {
longtouch: false,
},
Screen::Recipient {
- recipient: "0x04F264Cf34440313B4A0192A352814FBe927b885".into(),
+ recipient: "0x 04F2 64Cf 3444 0313 B4A0 192A 3528 14FB e927 b885".into(),
amount: "0.530564 ETH".into(),
},
Screen::TotalFee {
@@ -797,7 +799,7 @@ mod tests {
longtouch: false,
},
Screen::Recipient {
- recipient: "0x04F264Cf34440313B4A0192A352814FBe927b885".into(),
+ recipient: "0x 04F2 64Cf 3444 0313 B4A0 192A 3528 14FB e927 b885".into(),
amount: "0.530564 SEPETH".into(),
},
Screen::TotalFee {
@@ -864,7 +866,7 @@ mod tests {
longtouch: false
},
Screen::Recipient {
- recipient: "0x04F264Cf34440313B4A0192A352814FBe927b885".into(),
+ recipient: "0x 04F2 64Cf 3444 0313 B4A0 192A 3528 14FB e927 b885".into(),
amount: "0.530564 ETH".into()
},
Screen::TotalFee {
@@ -931,7 +933,7 @@ mod tests {
longtouch: false
},
Screen::Recipient {
- recipient: "0x04F264Cf34440313B4A0192A352814FBe927b885".into(),
+ recipient: "0x 04F2 64Cf 3444 0313 B4A0 192A 3528 14FB e927 b885".into(),
amount: "0.530564 ETH".into()
},
Screen::TotalFee {
@@ -960,7 +962,7 @@ mod tests {
longtouch: false,
},
Screen::Recipient {
- recipient: "0xE6CE0a092A99700CD4ccCcBb1fEDc39Cf53E6330".into(),
+ recipient: "0x E6CE 0a09 2A99 700C D4cc CcBb 1fED c39C f53E 6330".into(),
amount: "57 USDT".into(),
},
Screen::TotalFee {
@@ -1034,7 +1036,7 @@ mod tests {
longtouch: false,
},
Screen::Recipient {
- recipient: "0x857B3D969eAcB775a9f79cabc62Ec4bB1D1cd60e".into(),
+ recipient: "0x 857B 3D96 9eAc B775 a9f7 9cab c62E c4bB 1D1c d60e".into(),
amount: "Unknown token".into(),
},
Screen::TotalFee {
@@ -1218,7 +1220,7 @@ mod tests {
longtouch: false,
},
Screen::Recipient {
- recipient: "0x04F264Cf34440313B4A0192A352814FBe927b885".into(),
+ recipient: "0x 04F2 64Cf 3444 0313 B4A0 192A 3528 14FB e927 b885".into(),
amount: "0.530564 ETH".into(),
},
Screen::TotalFee {
@@ -1358,7 +1360,7 @@ mod tests {
longtouch: false
},
Screen::Recipient {
- recipient: "0x04F264Cf34440313B4A0192A352814FBe927b885".into(),
+ recipient: "0x 04F2 64Cf 3444 0313 B4A0 192A 3528 14FB e927 b885".into(),
amount: "0.530564 ".into(),
},
Screen::TotalFee {
@@ -1618,7 +1620,7 @@ mod tests {
longtouch: false,
},
Screen::Recipient {
- recipient: "0x112233445566778899AabbcCDDeEFF0011223344".into(),
+ recipient: "0x 1122 3344 5566 7788 99Aa bbcC DDeE FF00 1122 3344".into(),
amount: "0 ETH".into(),
},
Screen::TotalFee {
@@ -1691,7 +1693,7 @@ mod tests {
longtouch: false,
},
Screen::Recipient {
- recipient: "0x112233445566778899AabbcCDDeEFF0011223344".into(),
+ recipient: "0x 1122 3344 5566 7788 99Aa bbcC DDeE FF00 1122 3344".into(),
amount: "0 ETH".into(),
},
Screen::TotalFee {
diff --git a/src/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rs b/src/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rs
index 3b82576..6776f71 100644
--- a/src/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rs
@@ -237,10 +237,8 @@ fn encode_value(typ: &MemberType, value: Vec<u8>) -> Result<(Vec<u8>, String), E
} else {
return Err(Error::InvalidInput);
};
- (
- encoded,
- String::from_utf8(value).or(Err(Error::InvalidInput))?,
- )
+ let value_str = String::from_utf8(value).or(Err(Error::InvalidInput))?;
+ (encoded, super::address::format_display_address(&value_str))
}
DataType::String => {
if !util::ascii::is_printable_ascii(&value, util::ascii::Charset::AllNewline) {
@@ -1010,7 +1008,8 @@ mod tests {
},
Screen::Confirm {
title: "Domain (4/4)".into(),
- body: "verifyingContract: 0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC".into(),
+ body: "verifyingContract: 0x CcCC cccc CCCC cCCC CCCc CcCc cCcC CCcC cccc cccC"
+ .into(),
longtouch: false,
},
]
@@ -1107,13 +1106,13 @@ mod tests {
("Domain (3/4)", "chainId: 1"),
(
"Domain (4/4)",
- "verifyingContract: 0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
+ "verifyingContract: 0x CcCC cccc CCCC cCCC CCCc CcCc cCcC CCcC cccc cccC",
),
("Message (1/23)", "str: str"),
("Message (2/23)", "emptyArray: (empty list)"),
(
"Message (3/23)",
- "name_address: 0xa21A16EC22a940990922220E4ab5bF4C2310F556",
+ "name_address: 0x a21A 16EC 22a9 4099 0922 220E 4ab5 bF4C 2310 F556",
),
("Message (4/23)", "name_string: list with 6 elements"),
("Message (4/23)", "name_string[1/6]: "),
diff --git a/src/rust/bitbox02-rust/src/hww/api/ethereum/signmsg.rs b/src/rust/bitbox02-rust/src/hww/api/ethereum/signmsg.rs
index fb5b4cc..289e5d0 100644
--- a/src/rust/bitbox02-rust/src/hww/api/ethereum/signmsg.rs
+++ b/src/rust/bitbox02-rust/src/hww/api/ethereum/signmsg.rs
@@ -102,7 +102,7 @@ mod tests {
const KEYPATH: &[u32] = &[44 + HARDENED, 60 + HARDENED, 0 + HARDENED, 0, 0];
const MESSAGE: &str = "message";
- const EXPECTED_ADDRESS: &str = "0x773A77b9D32589be03f9132AF759e294f7851be9";
+ const EXPECTED_ADDRESS: &str = "0x 773A 77b9 D325 89be 03f9 132A F759 e294 f785 1be9";
#[test]
pub fn test_process() {
diff --git a/src/rust/util/src/strings.rs b/src/rust/util/src/strings.rs
index 6a04e01..73953dd 100644
--- a/src/rust/util/src/strings.rs
+++ b/src/rust/util/src/strings.rs
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
extern crate alloc;
+use alloc::string::String;
use alloc::vec::Vec;
/// Parses a utf-8 string out of a null terminated buffer. Returns `Err(())` if there
@@ -58,6 +59,24 @@ pub fn str_to_cstr_vec_zeroizing(
Ok(result)
}
+/// Formats an address-like string into blocks of 4 chars separated by spaces.
+pub fn format_address(input: &str) -> String {
+ const GROUP_SIZE: usize = 4;
+ const SEPARATOR: char = ' ';
+
+ if input.is_empty() {
+ return input.into();
+ }
+ let mut result = String::with_capacity(input.len() + input.len() / GROUP_SIZE);
+ for (index, ch) in input.chars().enumerate() {
+ if index > 0 && index % GROUP_SIZE == 0 {
+ result.push(SEPARATOR);
+ }
+ result.push(ch);
+ }
+ result
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -153,4 +172,13 @@ mod tests {
);
assert_eq!(str_to_cstr_vec_zeroizing("te\0st"), Err(()));
}
+
+ #[test]
+ fn test_format_address() {
+ assert_eq!(format_address(""), "");
+ assert_eq!(format_address("abc"), "abc");
+ assert_eq!(format_address("abcd"), "abcd");
+ assert_eq!(format_address("abcde"), "abcd e");
+ assert_eq!(format_address("abcdefghijkl"), "abcd efgh ijkl");
+ }
}
Why this scored 15/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.