From 9703d8d982e3bd158e39e40a8b14f488e23c3711 Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Thu, 9 Jul 2026 20:28:49 +0200 Subject: [PATCH] eth: loosen EIP-712 identifier validation Allow ':' in EIP-712 type names used by some dapps as a namespace separator Co-Authored-By: Tomas Vrba --- CHANGELOG.md | 3 ++ .../src/hww/api/ethereum/sign_typed_msg.rs | 49 +++++++++++++++++-- versions.json | 2 +- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83802bb3ce..9b3730fa4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ customers cannot upgrade their bootloader, its changes are recorded separately. ### [Unreleased] +### v9.26.4 +- Ethereum: allow ':' in EIP-712 type names, used by some dapps as a namespace separator + ### v9.26.3 - Security improvements 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 c6fe74f3da..56257bd14c 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 @@ -80,21 +80,28 @@ fn validate_identifier(name: &str) -> Result<(), Error> { } } +// Some deployed dapps use ':' as a namespace separator in struct type names. A colon +// cannot forge encodeType boundaries ('(', ')', ',', ' '), so it is safe to accept +// despite EIP-712's Solidity-identifier wording. Member names stay strict. +fn validate_type_name(name: &str) -> Result<(), Error> { + name.split(':').try_for_each(validate_identifier) +} + fn validate_member_type_identifiers(typ: &MemberType) -> Result<(), Error> { match DataType::try_from(typ.r#type)? { DataType::Unknown => Err(Error::InvalidInput), DataType::Array => { validate_member_type_identifiers(typ.array_type.as_ref().ok_or(Error::InvalidInput)?) } - DataType::Struct => validate_identifier(&typ.struct_name), + DataType::Struct => validate_type_name(&typ.struct_name), _ => Ok(()), } } fn validate_typed_msg_schema(types: &[StructType], primary_type: &str) -> Result<(), Error> { - validate_identifier(primary_type)?; + validate_type_name(primary_type)?; for typ in types { - validate_identifier(&typ.name)?; + validate_type_name(&typ.name)?; for member in &typ.members { validate_identifier(&member.name)?; validate_member_type_identifiers(member.r#type.as_ref().ok_or(Error::InvalidInput)?)?; @@ -1395,6 +1402,29 @@ mod tests { } } + #[test] + fn test_validate_type_name() { + for name in ["Msg", "Namespace:Type", "Foo:Bar:Baz", "_Type1"] { + assert_eq!(validate_type_name(name), Ok(())); + } + + for name in [ + "", + ":Type", + "Type:", + "Foo::Bar", + "Foo:123Bar", + "123Type", + "Bad\nType", + "Bad Type", + "Type(", + "Type,", + "Typé", + ] { + assert_eq!(validate_type_name(name), Err(Error::InvalidInput)); + } + } + #[test] fn test_validate_typed_msg_schema() { let valid_types = vec![ @@ -1416,6 +1446,19 @@ mod tests { ]; assert_eq!(validate_typed_msg_schema(&valid_types, "Msg"), Ok(())); + // Colons are allowed in type names, but not in member names. + let mut types = valid_types.clone(); + types[2].name = "Namespace:Msg".into(); + types[2].members[0] = mk_member("data", mk_struct_type("Inner")); + assert_eq!(validate_typed_msg_schema(&types, "Namespace:Msg"), Ok(())); + + let mut types = valid_types.clone(); + types[2].members[0] = mk_member("field:name", mk_type(DataType::String)); + assert_eq!( + validate_typed_msg_schema(&types, "Msg"), + Err(Error::InvalidInput) + ); + assert_eq!( validate_typed_msg_schema(&valid_types, "Bad\nType"), Err(Error::InvalidInput) diff --git a/versions.json b/versions.json index 7b6ba91f64..d43dc12bb0 100644 --- a/versions.json +++ b/versions.json @@ -1,4 +1,4 @@ { - "firmware": "v9.26.3", + "firmware": "v9.26.4", "bootloader": "v1.1.3" }