Skip to content

Commit 90a72cc

Browse files
committed
Merge commit 'refs/pull/1380/head' of https://github.com/BitBoxSwiss/bitbox02-firmware
2 parents 967939f + f6f5274 commit 90a72cc

File tree

4 files changed

+194
-11
lines changed

4 files changed

+194
-11
lines changed

CHANGELOG.md

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

99
### [Unreleased]
10+
- Ethereum: add confirmation screen for known networks, change base unit to ETH for Arbitrum and Optimism
11+
- Ethereum: add Base and Gnosis Chain to known networks
1012

1113
### 9.22.0
1214
- 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

+21-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,7 +82,21 @@ const PARAMS: &[Params] = &[
8282
bip44_coin: 60 + HARDENED,
8383
chain_id: 42161,
8484
name: "Arbitrum One",
85-
unit: "AETH",
85+
unit: "ETH",
86+
},
87+
Params {
88+
coin: None,
89+
bip44_coin: 60 + HARDENED,
90+
chain_id: 8453,
91+
name: "Base",
92+
unit: "ETH",
93+
},
94+
Params {
95+
coin: None,
96+
bip44_coin: 60 + HARDENED,
97+
chain_id: 100,
98+
name: "Gnosis Chain",
99+
unit: "xDAI",
86100
},
87101
];
88102

@@ -99,6 +113,11 @@ fn get(coin: Option<EthCoin>, chain_id: u64) -> Option<&'static Params> {
99113
})
100114
}
101115

116+
/// Check if the chain_id corresponds to a known network (to show an additional confirmations for).
117+
pub fn is_known_network(coin: Option<EthCoin>, chain_id: u64) -> bool {
118+
get(coin, chain_id).is_some()
119+
}
120+
102121
/// Get the chain parameters by `coin` or `chain_id`. If `chain_id` is non-zero, `coin` is
103122
/// ignored. If `coin` is None. `chain_id` alone is used.
104123
///

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

+167-7
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,16 @@ 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 networks
303+
if super::params::is_known_network(request.coin()?, request.chain_id()) {
304+
confirm::confirm(&confirm::Params {
305+
body: &format!("Sign transaction on\n\n{}", params.name),
306+
accept_is_nextarrow: true,
307+
..Default::default()
308+
})
309+
.await?;
310+
}
311+
302312
// Size limits.
303313
if request.nonce().len() > 16
304314
|| request.gas_limit().len() > 16
@@ -472,6 +482,11 @@ mod tests {
472482
const KEYPATH: &[u32] = &[44 + HARDENED, 60 + HARDENED, 0 + HARDENED, 0, 0];
473483

474484
mock(Data {
485+
ui_confirm_create: Some(Box::new(|params| {
486+
assert_eq!(params.body, "Sign transaction on\n\nEthereum");
487+
assert!(params.accept_is_nextarrow);
488+
true
489+
})),
475490
ui_transaction_address_create: Some(Box::new(|amount, address| {
476491
assert_eq!(amount, "0.530564 ETH");
477492
assert_eq!(address, "0x04F264Cf34440313B4A0192A352814FBe927b885");
@@ -546,6 +561,11 @@ mod tests {
546561
UI_COUNTER
547562
} {
548563
1 => {
564+
assert_eq!(params.body, "Sign transaction on\n\nEthereum");
565+
assert!(params.accept_is_nextarrow);
566+
true
567+
}
568+
2 => {
549569
assert_eq!(params.title, "High fee");
550570
assert_eq!(params.body, "The fee is 12.0%\nthe send amount.\nProceed?");
551571
assert!(params.longtouch);
@@ -575,7 +595,7 @@ mod tests {
575595
address_case: pb::EthAddressCase::Mixed as _,
576596
})))
577597
.is_ok());
578-
assert_eq!(unsafe { UI_COUNTER }, 1);
598+
assert_eq!(unsafe { UI_COUNTER }, 2);
579599
}
580600

581601
/// Test an EIP-1559 transaction with an unusually high fee.
@@ -598,6 +618,11 @@ mod tests {
598618
UI_COUNTER
599619
} {
600620
1 => {
621+
assert_eq!(params.body, "Sign transaction on\n\nEthereum");
622+
assert!(params.accept_is_nextarrow);
623+
true
624+
}
625+
2 => {
601626
assert_eq!(params.title, "High fee");
602627
assert_eq!(params.body, "The fee is 12.0%\nthe send amount.\nProceed?");
603628
assert!(params.longtouch);
@@ -627,7 +652,7 @@ mod tests {
627652
address_case: pb::EthAddressCase::Mixed as _,
628653
})))
629654
.is_ok());
630-
assert_eq!(unsafe { UI_COUNTER }, 1);
655+
assert_eq!(unsafe { UI_COUNTER }, 2);
631656
}
632657

633658
/// Standard ETH transaction on an unusual keypath (Sepolia on mainnet keypath)
@@ -647,6 +672,10 @@ mod tests {
647672
assert_eq!(params.body, "Warning: unusual keypath m/44'/60'/0'/0/0. Proceed only if you know what you are doing.");
648673
true
649674
}
675+
2 => {
676+
assert_eq!(params.body, "Sign transaction on\n\nSepolia");
677+
true
678+
}
650679
_ => panic!("too many user confirmations"),
651680
}
652681
})),
@@ -681,7 +710,7 @@ mod tests {
681710
address_case: pb::EthAddressCase::Mixed as _,
682711
})))
683712
.unwrap();
684-
assert_eq!(unsafe { CONFIRM_COUNTER }, 1);
713+
assert_eq!(unsafe { CONFIRM_COUNTER }, 2);
685714
}
686715

687716
/// Standard ETH transaction with an unknown data field.
@@ -692,8 +721,12 @@ mod tests {
692721
mock(Data {
693722
ui_confirm_create: Some(Box::new(|params| {
694723
match unsafe { CONFIRM_COUNTER } {
695-
0 | 1 => assert_eq!(params.title, "Unknown\ncontract"),
696-
2 => {
724+
0 => {
725+
assert_eq!(params.body, "Sign transaction on\n\nEthereum");
726+
assert!(params.accept_is_nextarrow);
727+
}
728+
1 | 2 => assert_eq!(params.title, "Unknown\ncontract"),
729+
3 => {
697730
assert_eq!(params.title, "Transaction\ndata");
698731
assert_eq!(params.body, "666f6f20626172"); // "foo bar" in hex.
699732
assert!(params.scrollable);
@@ -748,8 +781,12 @@ mod tests {
748781
mock(Data {
749782
ui_confirm_create: Some(Box::new(|params| {
750783
match unsafe { CONFIRM_COUNTER } {
751-
0 | 1 => assert_eq!(params.title, "Unknown\ncontract"),
752-
2 => {
784+
0 => {
785+
assert_eq!(params.body, "Sign transaction on\n\nEthereum");
786+
assert!(params.accept_is_nextarrow);
787+
}
788+
1 | 2 => assert_eq!(params.title, "Unknown\ncontract"),
789+
3 => {
753790
assert_eq!(params.title, "Transaction\ndata");
754791
assert_eq!(params.body, "666f6f20626172"); // "foo bar" in hex.
755792
assert!(params.scrollable);
@@ -803,6 +840,11 @@ mod tests {
803840
const KEYPATH: &[u32] = &[44 + HARDENED, 60 + HARDENED, 0 + HARDENED, 0, 0];
804841

805842
mock(Data {
843+
ui_confirm_create: Some(Box::new(|params| {
844+
assert_eq!(params.body, "Sign transaction on\n\nEthereum");
845+
assert!(params.accept_is_nextarrow);
846+
true
847+
})),
806848
ui_transaction_address_create: Some(Box::new(|amount, address| {
807849
assert_eq!(amount, "57 USDT");
808850
assert_eq!(address, "0xE6CE0a092A99700CD4ccCcBb1fEDc39Cf53E6330");
@@ -863,6 +905,11 @@ mod tests {
863905
const KEYPATH: &[u32] = &[44 + HARDENED, 60 + HARDENED, 0 + HARDENED, 0, 0];
864906

865907
mock(Data {
908+
ui_confirm_create: Some(Box::new(|params| {
909+
assert_eq!(params.body, "Sign transaction on\n\nEthereum");
910+
assert!(params.accept_is_nextarrow);
911+
true
912+
})),
866913
ui_transaction_address_create: Some(Box::new(|amount, address| {
867914
assert_eq!(amount, "Unknown token");
868915
assert_eq!(address, "0x857B3D969eAcB775a9f79cabc62Ec4bB1D1cd60e");
@@ -938,6 +985,7 @@ mod tests {
938985
{
939986
// Check that the above is valid before making invalid variants.
940987
mock(Data {
988+
ui_confirm_create: Some(Box::new(|_| true)),
941989
ui_transaction_address_create: Some(Box::new(|_, _| true)),
942990
ui_transaction_fee_create: Some(Box::new(|_, _, _| true)),
943991
..Default::default()
@@ -1006,9 +1054,30 @@ mod tests {
10061054
);
10071055
}
10081056

1057+
{
1058+
// User rejects chain confirmation
1059+
mock(Data {
1060+
ui_confirm_create: Some(Box::new(|params| {
1061+
assert_eq!(params.body, "Sign transaction on\n\nEthereum");
1062+
assert!(params.accept_is_nextarrow);
1063+
false
1064+
})),
1065+
..Default::default()
1066+
});
1067+
assert_eq!(
1068+
block_on(process(&Transaction::Legacy(&valid_request))),
1069+
Err(Error::UserAbort)
1070+
);
1071+
}
1072+
10091073
{
10101074
// User rejects recipient/value.
10111075
mock(Data {
1076+
ui_confirm_create: Some(Box::new(|params| {
1077+
assert_eq!(params.body, "Sign transaction on\n\nEthereum");
1078+
assert!(params.accept_is_nextarrow);
1079+
true
1080+
})),
10121081
ui_transaction_address_create: Some(Box::new(|amount, address| {
10131082
assert_eq!(amount, "0.530564 ETH");
10141083
assert_eq!(address, "0x04F264Cf34440313B4A0192A352814FBe927b885");
@@ -1024,6 +1093,11 @@ mod tests {
10241093
{
10251094
// User rejects total/fee.
10261095
mock(Data {
1096+
ui_confirm_create: Some(Box::new(|params| {
1097+
assert_eq!(params.body, "Sign transaction on\n\nEthereum");
1098+
assert!(params.accept_is_nextarrow);
1099+
true
1100+
})),
10271101
ui_transaction_address_create: Some(Box::new(|amount, address| {
10281102
assert_eq!(amount, "0.530564 ETH");
10291103
assert_eq!(address, "0x04F264Cf34440313B4A0192A352814FBe927b885");
@@ -1045,6 +1119,7 @@ mod tests {
10451119
{
10461120
// Keystore locked.
10471121
mock(Data {
1122+
ui_confirm_create: Some(Box::new(|_| true)),
10481123
ui_transaction_address_create: Some(Box::new(|_, _| true)),
10491124
ui_transaction_fee_create: Some(Box::new(|_, _, _| true)),
10501125
..Default::default()
@@ -1077,6 +1152,7 @@ mod tests {
10771152
{
10781153
// Check that the above is valid before making invalid variants.
10791154
mock(Data {
1155+
ui_confirm_create: Some(Box::new(|_| true)),
10801156
ui_transaction_address_create: Some(Box::new(|_, _| true)),
10811157
ui_transaction_fee_create: Some(Box::new(|_, _, _| true)),
10821158
..Default::default()
@@ -1175,4 +1251,88 @@ mod tests {
11751251
);
11761252
assert_eq!(unsafe { CONFIRM_COUNTER }, 4);
11771253
}
1254+
1255+
/// Test that the chain confirmation screen appears for known non-mainnet networks.
1256+
#[test]
1257+
pub fn test_chain_confirmation() {
1258+
const KEYPATH: &[u32] = &[44 + HARDENED, 60 + HARDENED, 0 + HARDENED, 0, 0];
1259+
static mut CONFIRM_COUNTER: u32 = 0;
1260+
// Test with Arbitrum (chain_id 42161)
1261+
mock(Data {
1262+
ui_confirm_create: Some(Box::new(|params| {
1263+
unsafe {
1264+
if CONFIRM_COUNTER == 0 {
1265+
assert_eq!(params.body, "Sign transaction on\n\nArbitrum One");
1266+
CONFIRM_COUNTER += 1;
1267+
}
1268+
}
1269+
true
1270+
})),
1271+
// Skip checking these details
1272+
ui_transaction_address_create: Some(Box::new(|_, _| true)),
1273+
ui_transaction_fee_create: Some(Box::new(|_, _, _| true)),
1274+
..Default::default()
1275+
});
1276+
mock_unlocked();
1277+
1278+
block_on(process(&Transaction::Legacy(&pb::EthSignRequest {
1279+
coin: pb::EthCoin::Eth as _,
1280+
keypath: KEYPATH.to_vec(),
1281+
nonce: b"\x1f\xdc".to_vec(),
1282+
gas_price: b"\x01\x65\xa0\xbc\x00".to_vec(),
1283+
gas_limit: b"\x52\x08".to_vec(),
1284+
recipient:
1285+
b"\x04\xf2\x64\xcf\x34\x44\x03\x13\xb4\xa0\x19\x2a\x35\x28\x14\xfb\xe9\x27\xb8\x85"
1286+
.to_vec(),
1287+
value: b"\x07\x5c\xf1\x25\x9e\x9c\x40\x00".to_vec(),
1288+
data: b"".to_vec(),
1289+
host_nonce_commitment: None,
1290+
chain_id: 42161,
1291+
address_case: pb::EthAddressCase::Mixed as _,
1292+
})))
1293+
.unwrap();
1294+
assert_eq!(unsafe { CONFIRM_COUNTER }, 1);
1295+
}
1296+
1297+
/// Test that EIP-1559 transactions also get the chain confirmation screen
1298+
#[test]
1299+
pub fn test_chain_confirmation_for_eip1559() {
1300+
const KEYPATH: &[u32] = &[44 + HARDENED, 60 + HARDENED, 0 + HARDENED, 0, 0];
1301+
static mut CONFIRM_COUNTER: u32 = 0;
1302+
1303+
// Test with Polygon network (chain_id 137)
1304+
mock(Data {
1305+
ui_confirm_create: Some(Box::new(|params| {
1306+
unsafe {
1307+
if CONFIRM_COUNTER == 0 {
1308+
assert_eq!(params.body, "Sign transaction on\n\nPolygon");
1309+
CONFIRM_COUNTER += 1;
1310+
}
1311+
}
1312+
true
1313+
})),
1314+
ui_transaction_address_create: Some(Box::new(|_, _| true)),
1315+
ui_transaction_fee_create: Some(Box::new(|_, _, _| true)),
1316+
..Default::default()
1317+
});
1318+
mock_unlocked();
1319+
1320+
block_on(process(&Transaction::Eip1559(&pb::EthSignEip1559Request {
1321+
keypath: KEYPATH.to_vec(),
1322+
nonce: b"\x1f\xdc".to_vec(),
1323+
max_priority_fee_per_gas: b"\x3b\x9a\xca\x00".to_vec(),
1324+
max_fee_per_gas: b"\x01\x65\xa0\xbc\x00".to_vec(),
1325+
gas_limit: b"\x52\x08".to_vec(),
1326+
recipient:
1327+
b"\x04\xf2\x64\xcf\x34\x44\x03\x13\xb4\xa0\x19\x2a\x35\x28\x14\xfb\xe9\x27\xb8\x85"
1328+
.to_vec(),
1329+
value: b"\x07\x5c\xf1\x25\x9e\x9c\x40\x00".to_vec(),
1330+
data: b"".to_vec(),
1331+
host_nonce_commitment: None,
1332+
chain_id: 137,
1333+
address_case: pb::EthAddressCase::Mixed as _,
1334+
})))
1335+
.unwrap();
1336+
assert_eq!(unsafe { CONFIRM_COUNTER }, 1);
1337+
}
11781338
}

0 commit comments

Comments
 (0)