Skip to content

Commit 9649f90

Browse files
committed
eth: add confirmation screen for known L2s and sidechains
1 parent 34c7a3c commit 9649f90

File tree

4 files changed

+201
-5
lines changed

4 files changed

+201
-5
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ customers cannot upgrade their bootloader, its changes are recorded separately.
77
## Firmware
88

99
### [Unreleased]
10+
- Ethereum: add confirmation screen for known L2 networks and sidechains and change their unit to ETH
1011

1112
### 9.22.0
1213
- Update manufacturer HID descriptor to bitbox.swiss

py/send_message.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1014,16 +1014,18 @@ def _sign_eth_tx(self) -> None:
10141014
# pylint: disable=line-too-long
10151015

10161016
inp = input(
1017-
"Select one of: 1=normal; 2=erc20; 3=erc721; 4=unknown erc20; 5=large data field; 6=BSC; 7=unknown network; 8=eip1559: "
1017+
"Select one of: 1=normal; 2=erc20; 3=erc721; 4=unknown erc20; 5=large data field; 6=BSC; 7=unknown network; 8=eip1559; 9=Arbitrum: "
10181018
).strip()
10191019

10201020
chain_id = 1 # mainnet
10211021
if inp == "6":
10221022
chain_id = 56
10231023
elif inp == "7":
10241024
chain_id = 123456
1025+
elif inp == "9":
1026+
chain_id = 42161 # Arbitrum One
10251027

1026-
if inp in ("1", "6", "7"):
1028+
if inp in ("1", "6", "7", "9"):
10271029
# fmt: off
10281030
tx = bytes([0xf8, 0x6e, 0x82, 0x1f, 0xdc, 0x85, 0x01, 0x65, 0xa0, 0xbc, 0x00, 0x82, 0x52,
10291031
0x08, 0x94, 0x04, 0xf2, 0x64, 0xcf, 0x34, 0x44, 0x03, 0x13, 0xb4, 0xa0, 0x19, 0x2a,

src/rust/bitbox02-rust/src/hww/api/ethereum/params.rs

+12-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const PARAMS: &[Params] = &[
6161
bip44_coin: 60 + HARDENED,
6262
chain_id: 10,
6363
name: "Optimism",
64-
unit: "OETH",
64+
unit: "ETH",
6565
},
6666
Params {
6767
coin: None,
@@ -82,10 +82,20 @@ const PARAMS: &[Params] = &[
8282
bip44_coin: 60 + HARDENED,
8383
chain_id: 42161,
8484
name: "Arbitrum One",
85-
unit: "AETH",
85+
unit: "ETH",
8686
},
8787
];
8888

89+
/// Check if the chain_id corresponds to a known non-mainnet network.
90+
/// Returns true for specific L2s and sidechains we want to show confirmations for,
91+
/// false for mainnet (chain_id=1) and unknown networks.
92+
pub fn is_known_non_mainnet(chain_id: u64) -> bool {
93+
if chain_id == 1 {
94+
return false;
95+
}
96+
PARAMS.iter().any(|p| p.chain_id == chain_id)
97+
}
98+
8999
/// Get the chain parameters by `coin` or `chain_id`. If `chain_id` is non-zero, `coin` is ignored.
90100
fn get(coin: Option<EthCoin>, chain_id: u64) -> Option<&'static Params> {
91101
PARAMS.iter().find(|p| {

src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs

+184-1
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,17 @@ pub async fn process(request: &Transaction<'_>) -> Result<Response, Error> {
299299
}
300300
super::keypath::warn_unusual_keypath(&params, params.name, request.keypath()).await?;
301301

302+
// Show chain confirmation only for known non-mainnet networks
303+
if super::params::is_known_non_mainnet(params.chain_id) {
304+
confirm::confirm(&confirm::Params {
305+
title: "Confirm Chain",
306+
body: &format!("Sign transaction on\n{}", params.name),
307+
accept_is_nextarrow: true,
308+
..Default::default()
309+
})
310+
.await?;
311+
}
312+
302313
// Size limits.
303314
if request.nonce().len() > 16
304315
|| request.gas_limit().len() > 16
@@ -647,6 +658,11 @@ mod tests {
647658
assert_eq!(params.body, "Warning: unusual keypath m/44'/60'/0'/0/0. Proceed only if you know what you are doing.");
648659
true
649660
}
661+
2 => {
662+
assert_eq!(params.title, "Confirm Chain");
663+
assert_eq!(params.body, "Sign transaction on\nSepolia");
664+
true
665+
}
650666
_ => panic!("too many user confirmations"),
651667
}
652668
})),
@@ -681,7 +697,7 @@ mod tests {
681697
address_case: pb::EthAddressCase::Mixed as _,
682698
})))
683699
.unwrap();
684-
assert_eq!(unsafe { CONFIRM_COUNTER }, 1);
700+
assert_eq!(unsafe { CONFIRM_COUNTER }, 2);
685701
}
686702

687703
/// Standard ETH transaction with an unknown data field.
@@ -1175,4 +1191,171 @@ mod tests {
11751191
);
11761192
assert_eq!(unsafe { CONFIRM_COUNTER }, 4);
11771193
}
1194+
1195+
/// Test that the chain confirmation screen appears for known non-mainnet networks.
1196+
#[test]
1197+
pub fn test_chain_confirmation_for_l2_networks() {
1198+
const KEYPATH: &[u32] = &[44 + HARDENED, 60 + HARDENED, 0 + HARDENED, 0, 0];
1199+
static mut CONFIRM_COUNTER: u32 = 0;
1200+
// Test with Arbitrum (chain_id 42161)
1201+
mock(Data {
1202+
ui_confirm_create: Some(Box::new(|params| {
1203+
unsafe {
1204+
if CONFIRM_COUNTER == 0 {
1205+
assert_eq!(params.title, "Confirm Chain");
1206+
assert_eq!(params.body, "Sign transaction on\nArbitrum One");
1207+
CONFIRM_COUNTER += 1;
1208+
}
1209+
}
1210+
true
1211+
})),
1212+
// Skip checking these details
1213+
ui_transaction_address_create: Some(Box::new(|_, _| true)),
1214+
ui_transaction_fee_create: Some(Box::new(|_, _, _| true)),
1215+
..Default::default()
1216+
});
1217+
mock_unlocked();
1218+
1219+
block_on(process(&Transaction::Legacy(&pb::EthSignRequest {
1220+
coin: pb::EthCoin::Eth as _,
1221+
keypath: KEYPATH.to_vec(),
1222+
nonce: b"\x1f\xdc".to_vec(),
1223+
gas_price: b"\x01\x65\xa0\xbc\x00".to_vec(),
1224+
gas_limit: b"\x52\x08".to_vec(),
1225+
recipient:
1226+
b"\x04\xf2\x64\xcf\x34\x44\x03\x13\xb4\xa0\x19\x2a\x35\x28\x14\xfb\xe9\x27\xb8\x85"
1227+
.to_vec(),
1228+
value: b"\x07\x5c\xf1\x25\x9e\x9c\x40\x00".to_vec(),
1229+
data: b"".to_vec(),
1230+
host_nonce_commitment: None,
1231+
chain_id: 42161,
1232+
address_case: pb::EthAddressCase::Mixed as _,
1233+
})))
1234+
.unwrap();
1235+
assert_eq!(unsafe { CONFIRM_COUNTER }, 1);
1236+
}
1237+
1238+
/// Test that EIP-1559 transactions also get the chain confirmation screen for L2 networks
1239+
#[test]
1240+
pub fn test_chain_confirmation_for_eip1559() {
1241+
const KEYPATH: &[u32] = &[44 + HARDENED, 60 + HARDENED, 0 + HARDENED, 0, 0];
1242+
static mut CONFIRM_COUNTER: u32 = 0;
1243+
1244+
// Test with Polygon network (chain_id 137)
1245+
mock(Data {
1246+
ui_confirm_create: Some(Box::new(|params| {
1247+
unsafe {
1248+
if CONFIRM_COUNTER == 0 {
1249+
assert_eq!(params.title, "Confirm Chain");
1250+
assert_eq!(params.body, "Sign transaction on\nPolygon");
1251+
CONFIRM_COUNTER += 1;
1252+
}
1253+
}
1254+
true
1255+
})),
1256+
ui_transaction_address_create: Some(Box::new(|_, _| true)),
1257+
ui_transaction_fee_create: Some(Box::new(|_, _, _| true)),
1258+
..Default::default()
1259+
});
1260+
mock_unlocked();
1261+
1262+
block_on(process(&Transaction::Eip1559(&pb::EthSignEip1559Request {
1263+
keypath: KEYPATH.to_vec(),
1264+
nonce: b"\x1f\xdc".to_vec(),
1265+
max_priority_fee_per_gas: b"\x3b\x9a\xca\x00".to_vec(),
1266+
max_fee_per_gas: b"\x01\x65\xa0\xbc\x00".to_vec(),
1267+
gas_limit: b"\x52\x08".to_vec(),
1268+
recipient:
1269+
b"\x04\xf2\x64\xcf\x34\x44\x03\x13\xb4\xa0\x19\x2a\x35\x28\x14\xfb\xe9\x27\xb8\x85"
1270+
.to_vec(),
1271+
value: b"\x07\x5c\xf1\x25\x9e\x9c\x40\x00".to_vec(),
1272+
data: b"".to_vec(),
1273+
host_nonce_commitment: None,
1274+
chain_id: 137,
1275+
address_case: pb::EthAddressCase::Mixed as _,
1276+
})))
1277+
.unwrap();
1278+
assert_eq!(unsafe { CONFIRM_COUNTER }, 1);
1279+
}
1280+
1281+
/// Test that unknown networks and mainnet do NOT get the chain confirmation screen
1282+
#[test]
1283+
pub fn test_no_chain_confirmation_for_unknown_networks() {
1284+
const KEYPATH: &[u32] = &[44 + HARDENED, 60 + HARDENED, 0 + HARDENED, 0, 0];
1285+
static mut CONFIRM_COUNTER: u32 = 0;
1286+
1287+
// Test with an unknown network (chain_id 999999)
1288+
mock(Data {
1289+
ui_confirm_create: Some(Box::new(|params| {
1290+
unsafe {
1291+
// Only the warning confirmations should appear, not chain confirmation
1292+
if params.title == "Confirm Chain" {
1293+
CONFIRM_COUNTER += 1;
1294+
}
1295+
}
1296+
true
1297+
})),
1298+
ui_transaction_address_create: Some(Box::new(|_, _| true)),
1299+
ui_transaction_fee_create: Some(Box::new(|_, _, _| true)),
1300+
..Default::default()
1301+
});
1302+
mock_unlocked();
1303+
1304+
block_on(process(&Transaction::Legacy(&pb::EthSignRequest {
1305+
coin: pb::EthCoin::Eth as _,
1306+
keypath: KEYPATH.to_vec(),
1307+
nonce: b"\x1f\xdc".to_vec(),
1308+
gas_price: b"\x01\x65\xa0\xbc\x00".to_vec(),
1309+
gas_limit: b"\x52\x08".to_vec(),
1310+
recipient:
1311+
b"\x04\xf2\x64\xcf\x34\x44\x03\x13\xb4\xa0\x19\x2a\x35\x28\x14\xfb\xe9\x27\xb8\x85"
1312+
.to_vec(),
1313+
value: b"\x07\x5c\xf1\x25\x9e\x9c\x40\x00".to_vec(),
1314+
data: b"".to_vec(),
1315+
host_nonce_commitment: None,
1316+
chain_id: 999999,
1317+
address_case: pb::EthAddressCase::Mixed as _,
1318+
})))
1319+
.unwrap();
1320+
1321+
assert_eq!(unsafe { CONFIRM_COUNTER }, 0);
1322+
1323+
// Reset counter for mainnet test
1324+
unsafe { CONFIRM_COUNTER = 0 };
1325+
1326+
// Test with Ethereum mainnet (chain_id 1)
1327+
mock(Data {
1328+
ui_confirm_create: Some(Box::new(|params| {
1329+
unsafe {
1330+
if params.title == "Confirm Chain" {
1331+
CONFIRM_COUNTER += 1;
1332+
}
1333+
}
1334+
true
1335+
})),
1336+
ui_transaction_address_create: Some(Box::new(|_, _| true)),
1337+
ui_transaction_fee_create: Some(Box::new(|_, _, _| true)),
1338+
..Default::default()
1339+
});
1340+
mock_unlocked();
1341+
1342+
block_on(process(&Transaction::Legacy(&pb::EthSignRequest {
1343+
coin: pb::EthCoin::Eth as _,
1344+
keypath: KEYPATH.to_vec(),
1345+
nonce: b"\x1f\xdc".to_vec(),
1346+
gas_price: b"\x01\x65\xa0\xbc\x00".to_vec(),
1347+
gas_limit: b"\x52\x08".to_vec(),
1348+
recipient:
1349+
b"\x04\xf2\x64\xcf\x34\x44\x03\x13\xb4\xa0\x19\x2a\x35\x28\x14\xfb\xe9\x27\xb8\x85"
1350+
.to_vec(),
1351+
value: b"\x07\x5c\xf1\x25\x9e\x9c\x40\x00".to_vec(),
1352+
data: b"".to_vec(),
1353+
host_nonce_commitment: None,
1354+
chain_id: 1,
1355+
address_case: pb::EthAddressCase::Mixed as _,
1356+
})))
1357+
.unwrap();
1358+
1359+
assert_eq!(unsafe { CONFIRM_COUNTER }, 0);
1360+
}
11781361
}

0 commit comments

Comments
 (0)