Merge remote-tracking branch 'agent/benma-agent/warn-eip712-array-truncation'
What changed, and why it matters
This change updates how the BitBox02 hardware wallet warns users when displaying very long member names in Ethereum typed-data (EIP-712) array confirmations. It replaces a direct confirmation call with a shared helper that adds a truncation warning screen. The practical effect is to make sure users see a 'Warning' screen before a long field name is shown, reducing the chance that a maliciously crafted field name could hide or spoof important confirmation details.
Review the confirm::confirm_value helper to ensure the truncation warning is consistently applied to all EIP-712 confirmation paths, and verify that MAX_CONFIRM_BODY_SIZE is appropriate for the device screen. No urgent action is required beyond normal review.
Security signals we found
UI confirmation flow change for EIP-712 array members
Use of shared confirm_value helper that emits truncation warning
New regression test for long member-name truncation warning
No cryptographic or signing logic changed
Evidence from the diff
The commit modifies sign_typed_msg.rs so that array member confirmations go through confirm::confirm_value() instead of calling hal.ui().confirm() directly. The confirm_value helper is already used elsewhere to insert a truncation warning screen when a confirmation body exceeds MAX_CONFIRM_BODY_SIZE. A new test verifies that when an EIP-712 array member name is exactly MAX_CONFIRM_BODY_SIZE characters long, the UI flow contains a ‘Warning’ screen before the array screen. The change is defensive: it does not alter hashing or signing logic, only the presentation/confirmation flow for array fields.
Changed components
src/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rsEIP-712 typed-data signing confirmation UIInspect captured patch +45 / −7
### src/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rs
@@ -521,8 +521,9 @@ async fn hash_array(
let array_type = member_type.array_type.as_ref().ok_or(Error::InvalidInput)?;
- hal.ui()
- .confirm(&ConfirmParams {
+ confirm::confirm_value(
+ hal,
+ &ConfirmParams {
title: &format!(
"{}{}",
confirm_title(context.root_object),
@@ -540,8 +541,9 @@ async fn hash_array(
scrollable: true,
accept_is_nextarrow: true,
..Default::default()
- })
- .await?;
+ },
+ )
+ .await?;
let mut hasher = sha3::Keccak256::new();
let mut child_path = context.path.to_vec();
@@ -950,6 +952,7 @@ mod tests {
}
async fn run_single_message_typed_msg(
+ member_name: &str,
member_type: MemberType,
message_obj: Object<'static>,
) -> TestingHal<'static> {
@@ -961,7 +964,7 @@ mod tests {
},
StructType {
name: "Msg".into(),
- members: vec![mk_member("data", member_type)],
+ members: vec![mk_member(member_name, member_type)],
},
],
"Msg",
@@ -988,11 +991,17 @@ mod tests {
async fn run_single_string_message(message: String) -> TestingHal<'static> {
let message = Box::leak(message.into_boxed_str());
- run_single_message_typed_msg(mk_type(DataType::String), Object::String(message)).await
+ run_single_message_typed_msg("data", mk_type(DataType::String), Object::String(message))
+ .await
}
async fn run_single_streaming_bytes_message(data: Vec<u8>) -> TestingHal<'static> {
- run_single_message_typed_msg(mk_type(DataType::Bytes), Object::StreamingBytes(data)).await
+ run_single_message_typed_msg(
+ "data",
+ mk_type(DataType::Bytes),
+ Object::StreamingBytes(data),
+ )
+ .await
}
/// A utility structure to build domain/message objects for testing.
@@ -1461,6 +1470,35 @@ mod tests {
);
}
+ #[async_test::test]
+ async fn test_hash_array_truncation_warning() {
+ let member_name = "a".repeat(MAX_CONFIRM_BODY_SIZE);
+ let mock_hal = run_single_message_typed_msg(
+ &member_name,
+ mk_arr_type(mk_type(DataType::String)),
+ Object::List(vec![]),
+ )
+ .await;
+
+ assert_eq!(mock_hal.ui.screens.len(), 3);
+ assert_eq!(
+ mock_hal.ui.screens[1],
+ Screen::Confirm {
+ title: "Warning".into(),
+ body: confirm::TRUNCATION_WARNING_BODY.into(),
+ longtouch: false,
+ }
+ );
+ assert_eq!(
+ mock_hal.ui.screens[2],
+ Screen::Confirm {
+ title: "Message (1/1)".into(),
+ body: format!("{member_name}: (empty list)"),
+ longtouch: false,
+ }
+ );
+ }
+
#[async_test::test]
async fn test_streaming_bytes_show_display_size_and_truncated_body() {
let data: Vec<u8> = (0u8..=255).cycle().take(10_000).collect();Why this scored 38/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.