Skip to content

Commit fdfbec1

Browse files
committed
eth: add streaming support for large data for EIP-712 typed data
1 parent 574c05a commit fdfbec1

File tree

6 files changed

+123
-3
lines changed

6 files changed

+123
-3
lines changed

CHANGELOG-npm.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Changelog
22

33
## [Unreleased]
4-
- eth: add support for streaming transactions with large data
4+
- eth: add support for streaming transactions and EIP-712 typed data with large data
55
- eth: add optional `useAntiklepto` argument to `ethSignTypedMessage()` (set to `false` for
66
deterministic typed-message signatures, firmware >=9.26.0)
77

CHANGELOG-rust.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Changelog
22

33
## [Unreleased]
4-
- eth: add support for streaming transactions with large data
4+
- eth: add support for streaming transactions and EIP-712 typed data with large data
55
- eth: add `use_antiklepto` toggle to `eth_sign_typed_message()` (set `false` for deterministic
66
typed-message signatures, firmware >=9.26.0)
77

messages/eth.proto

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ message ETHTypedMessageValueResponse {
144144

145145
message ETHTypedMessageValueRequest {
146146
bytes value = 1;
147+
// If non-zero, value should be empty and data will be streamed via
148+
// DataRequestChunk/DataResponseChunk.
149+
uint32 data_length = 2;
147150
}
148151

149152
message ETHRequest {

src/eth.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -701,11 +701,18 @@ impl<R: Runtime> PairedBitBox<R> {
701701
.await?;
702702
while let pb::eth_response::Response::TypedMsgValue(typed_msg_value) = &response {
703703
let value = get_value(typed_msg_value, &msg).map_err(Error::EthTypedMessage)?;
704+
let use_streaming = value.len() > STREAMING_THRESHOLD;
704705
response = self
705706
.query_proto_eth(pb::eth_request::Request::TypedMsgValue(
706-
pb::EthTypedMessageValueRequest { value },
707+
pb::EthTypedMessageValueRequest {
708+
value: if use_streaming { vec![] } else { value.clone() },
709+
data_length: if use_streaming { value.len() as u32 } else { 0 },
710+
},
707711
))
708712
.await?;
713+
if use_streaming {
714+
response = self.handle_eth_data_streaming(&value, response).await?;
715+
}
709716
}
710717
let mut signature = if use_antiklepto {
711718
self.handle_antiklepto(&response, host_nonce.unwrap())

src/shiftcrypto.bitbox02.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1973,6 +1973,10 @@ pub mod eth_typed_message_value_response {
19731973
pub struct EthTypedMessageValueRequest {
19741974
#[prost(bytes = "vec", tag = "1")]
19751975
pub value: ::prost::alloc::vec::Vec<u8>,
1976+
/// If non-zero, value should be empty and data will be streamed via
1977+
/// DataRequestChunk/DataResponseChunk.
1978+
#[prost(uint32, tag = "2")]
1979+
pub data_length: u32,
19761980
}
19771981
#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))]
19781982
#[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))]

tests/test_eth.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,28 @@ fn eip1559_sighash(tx: &EIP1559Transaction) -> [u8; 32] {
103103
keccak256(&prefixed)
104104
}
105105

106+
fn eip712_sighash(primary_type: &str, data_type: &str, data: &[u8]) -> [u8; 32] {
107+
let domain_type_hash = keccak256(b"EIP712Domain(string name)");
108+
let name_hash = keccak256(b"Test");
109+
let mut domain_input = Vec::new();
110+
domain_input.extend_from_slice(&domain_type_hash);
111+
domain_input.extend_from_slice(&name_hash);
112+
let domain_separator = keccak256(&domain_input);
113+
114+
let type_hash = keccak256(format!("{primary_type}({data_type} data)").as_bytes());
115+
let data_hash = keccak256(data);
116+
let mut struct_input = Vec::new();
117+
struct_input.extend_from_slice(&type_hash);
118+
struct_input.extend_from_slice(&data_hash);
119+
let struct_hash = keccak256(&struct_input);
120+
121+
let mut sig_input = Vec::new();
122+
sig_input.extend_from_slice(b"\x19\x01");
123+
sig_input.extend_from_slice(&domain_separator);
124+
sig_input.extend_from_slice(&struct_hash);
125+
keccak256(&sig_input)
126+
}
127+
106128
fn verify_eth_signature(sighash: &[u8; 32], signature: &[u8; 65]) {
107129
let secp = secp256k1::Secp256k1::new();
108130
let path: bitcoin::bip32::DerivationPath = "m/44'/60'/0'/0/0".parse().unwrap();
@@ -312,3 +334,87 @@ async fn test_eth_sign_typed_message_antiklepto_disabled() {
312334
})
313335
.await
314336
}
337+
338+
#[tokio::test]
339+
async fn test_eth_sign_typed_message_streaming_bytes() {
340+
test_initialized_simulators(async |paired_bitbox| {
341+
if !semver::VersionReq::parse(">=9.26.0")
342+
.unwrap()
343+
.matches(paired_bitbox.version())
344+
{
345+
return;
346+
}
347+
348+
let large_bytes_hex = "aa".repeat(10000);
349+
let msg = format!(
350+
r#"{{
351+
"types": {{
352+
"EIP712Domain": [
353+
{{ "name": "name", "type": "string" }}
354+
],
355+
"Msg": [
356+
{{ "name": "data", "type": "bytes" }}
357+
]
358+
}},
359+
"primaryType": "Msg",
360+
"domain": {{
361+
"name": "Test"
362+
}},
363+
"message": {{
364+
"data": "0x{large_bytes_hex}"
365+
}}
366+
}}"#
367+
);
368+
369+
let signature = paired_bitbox
370+
.eth_sign_typed_message(1, &"m/44'/60'/0'/0/0".try_into().unwrap(), &msg, false)
371+
.await
372+
.unwrap();
373+
assert_eq!(signature.len(), 65);
374+
let sighash = eip712_sighash("Msg", "bytes", &vec![0xaa; 10000]);
375+
verify_eth_signature(&sighash, &signature);
376+
})
377+
.await
378+
}
379+
380+
#[tokio::test]
381+
async fn test_eth_sign_typed_message_streaming_string() {
382+
test_initialized_simulators(async |paired_bitbox| {
383+
if !semver::VersionReq::parse(">=9.26.0")
384+
.unwrap()
385+
.matches(paired_bitbox.version())
386+
{
387+
return;
388+
}
389+
390+
let large_string = "a".repeat(10000);
391+
let msg = format!(
392+
r#"{{
393+
"types": {{
394+
"EIP712Domain": [
395+
{{ "name": "name", "type": "string" }}
396+
],
397+
"Msg": [
398+
{{ "name": "data", "type": "string" }}
399+
]
400+
}},
401+
"primaryType": "Msg",
402+
"domain": {{
403+
"name": "Test"
404+
}},
405+
"message": {{
406+
"data": "{large_string}"
407+
}}
408+
}}"#
409+
);
410+
411+
let signature = paired_bitbox
412+
.eth_sign_typed_message(1, &"m/44'/60'/0'/0/0".try_into().unwrap(), &msg, false)
413+
.await
414+
.unwrap();
415+
assert_eq!(signature.len(), 65);
416+
let sighash = eip712_sighash("Msg", "string", "a".repeat(10000).as_bytes());
417+
verify_eth_signature(&sighash, &signature);
418+
})
419+
.await
420+
}

0 commit comments

Comments
 (0)