From 0fd4e4f9bd6696eb36e814f0aed6c579bc7f3f53 Mon Sep 17 00:00:00 2001 From: Tomas Vrba Date: Wed, 4 Mar 2026 10:06:12 +0100 Subject: [PATCH 1/3] fix: fix build-protos script broken by license header make build-protos fails with unresolved module or unlinked crate prost_build because rust-script can't find the embedded cargo manifest in scripts/build-protos.rs. Root cause: Commit 6453995 (Dec 2025, "license: add SPDX headers") added // SPDX-License-Identifier: Apache-2.0 to the top of build-protos.rs, before the //! ```cargo doc-comment block. rust-script's manifest parser uses regex ^\s*(/\*!|//(!|/)) without multiline mode, so ^ only matches at position 0 of the file. The regular comment at position 0 prevents the regex from finding the doc-comment manifest, causing the [dependencies] section in the generated Cargo.toml to be empty. --- scripts/build-protos.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build-protos.rs b/scripts/build-protos.rs index 9c1fcd6..3d9e14a 100644 --- a/scripts/build-protos.rs +++ b/scripts/build-protos.rs @@ -1,11 +1,11 @@ -// SPDX-License-Identifier: Apache-2.0 - //! ```cargo //! [dependencies] //! # If you change this, also change the version of prost in Cargo.toml. //! prost-build = { version = "0.13" } //! ``` +// SPDX-License-Identifier: Apache-2.0 + use std::io::Result; fn add_serde_attrs(c: &mut prost_build::Config) { From 574c05a24d37fbec1ea3d909bb09e21091e522c4 Mon Sep 17 00:00:00 2001 From: Tomas Vrba Date: Wed, 4 Mar 2026 10:11:52 +0100 Subject: [PATCH 2/3] update generated protobuf license headers --- messages/antiklepto.proto | 14 +------------- messages/backup_commands.proto | 14 +------------- messages/bitbox02_system.proto | 21 ++++++--------------- messages/bluetooth.proto | 14 +------------- messages/btc.proto | 15 +-------------- messages/cardano.proto | 14 +------------- messages/common.proto | 14 +------------- messages/hww.proto | 14 +------------- messages/keystore.proto | 2 ++ messages/mnemonic.proto | 14 +------------- messages/perform_attestation.proto | 1 + messages/system.proto | 14 +------------- 12 files changed, 18 insertions(+), 133 deletions(-) diff --git a/messages/antiklepto.proto b/messages/antiklepto.proto index be51c41..b7829f3 100644 --- a/messages/antiklepto.proto +++ b/messages/antiklepto.proto @@ -1,16 +1,4 @@ -// Copyright 2020 Shift Crypto AG -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 syntax = "proto3"; package shiftcrypto.bitbox02; diff --git a/messages/backup_commands.proto b/messages/backup_commands.proto index 3fd10a9..2fbb73a 100644 --- a/messages/backup_commands.proto +++ b/messages/backup_commands.proto @@ -1,16 +1,4 @@ -// Copyright 2019 Shift Cryptosecurity AG -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 // This file is named backup_commands to avoid conflicting header files with top-most backup.proto diff --git a/messages/bitbox02_system.proto b/messages/bitbox02_system.proto index aef431f..463e689 100644 --- a/messages/bitbox02_system.proto +++ b/messages/bitbox02_system.proto @@ -1,16 +1,4 @@ -// Copyright 2019 Shift Cryptosecurity AG -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 syntax = "proto3"; package shiftcrypto.bitbox02; @@ -39,10 +27,13 @@ message DeviceInfoResponse { string version = 3; bool mnemonic_passphrase_enabled = 4; uint32 monotonic_increments_remaining = 5; - // From v9.6.0: "ATECC608A" or "ATECC608B". + // From v9.6.0: "ATECC608A" or "ATECC608B" or "OPTIGA_TRUST_M_V3". string securechip_model = 6; // Only present in Bluetooth-enabled devices. optional Bluetooth bluetooth = 7; + // From v9.25.0. This together with `securechip_model` determines the password stretching + // algorithm. + string password_stretching_algo = 8; } message InsertRemoveSDCardRequest { @@ -68,4 +59,4 @@ message SetPasswordRequest { } message ChangePasswordRequest{ -} \ No newline at end of file +} diff --git a/messages/bluetooth.proto b/messages/bluetooth.proto index db98275..016a479 100644 --- a/messages/bluetooth.proto +++ b/messages/bluetooth.proto @@ -1,16 +1,4 @@ -// Copyright 2025 Shift Crypto AG -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 syntax = "proto3"; package shiftcrypto.bitbox02; diff --git a/messages/btc.proto b/messages/btc.proto index 9793542..bee46ad 100644 --- a/messages/btc.proto +++ b/messages/btc.proto @@ -1,17 +1,4 @@ -// Copyright 2019 Shift Cryptosecurity AG -// Copyright 2020 Shift Crypto AG -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 syntax = "proto3"; package shiftcrypto.bitbox02; diff --git a/messages/cardano.proto b/messages/cardano.proto index 54b661d..56b6d9d 100644 --- a/messages/cardano.proto +++ b/messages/cardano.proto @@ -1,16 +1,4 @@ -// Copyright 2021 Shift Crypto AG -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 syntax = "proto3"; package shiftcrypto.bitbox02; diff --git a/messages/common.proto b/messages/common.proto index 0fed08c..92f4f02 100644 --- a/messages/common.proto +++ b/messages/common.proto @@ -1,16 +1,4 @@ -// Copyright 2019 Shift Cryptosecurity AG -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 syntax = "proto3"; package shiftcrypto.bitbox02; diff --git a/messages/hww.proto b/messages/hww.proto index ea7177e..07cb660 100644 --- a/messages/hww.proto +++ b/messages/hww.proto @@ -1,16 +1,4 @@ -// Copyright 2019 Shift Cryptosecurity AG -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 syntax = "proto3"; package shiftcrypto.bitbox02; diff --git a/messages/keystore.proto b/messages/keystore.proto index d6d5641..4f63cd3 100644 --- a/messages/keystore.proto +++ b/messages/keystore.proto @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + // This function can be used to get an identifying xpub at the keypath m/4541509'/1112098098'" // The keypath argument has to be m/4541509'/1112098098' diff --git a/messages/mnemonic.proto b/messages/mnemonic.proto index c13e02a..9137274 100644 --- a/messages/mnemonic.proto +++ b/messages/mnemonic.proto @@ -1,16 +1,4 @@ -// Copyright 2019 Shift Cryptosecurity AG -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 syntax = "proto3"; package shiftcrypto.bitbox02; diff --git a/messages/perform_attestation.proto b/messages/perform_attestation.proto index 50d0d48..db7f399 100644 --- a/messages/perform_attestation.proto +++ b/messages/perform_attestation.proto @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 syntax = "proto3"; package shiftcrypto.bitbox02; diff --git a/messages/system.proto b/messages/system.proto index c0fb8c0..5df5bc4 100644 --- a/messages/system.proto +++ b/messages/system.proto @@ -1,16 +1,4 @@ -// Copyright 2019 Shift Cryptosecurity AG -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 syntax = "proto3"; package shiftcrypto.bitbox02; From 3e73a6d6598e3a36d98d63f0e7d790a6fa0b36a2 Mon Sep 17 00:00:00 2001 From: Tomas Vrba Date: Wed, 4 Mar 2026 10:36:26 +0100 Subject: [PATCH 3/3] eth: add streaming support for large data for EIP-712 typed data --- CHANGELOG-npm.md | 2 +- CHANGELOG-rust.md | 2 +- messages/eth.proto | 3 ++ src/eth.rs | 41 ++++++++++++++++-------- src/shiftcrypto.bitbox02.rs | 4 +++ tests/test_eth.rs | 64 +++++++++++++++++++++++++++++++++++++ 6 files changed, 101 insertions(+), 15 deletions(-) diff --git a/CHANGELOG-npm.md b/CHANGELOG-npm.md index 8e214fa..2bd75aa 100644 --- a/CHANGELOG-npm.md +++ b/CHANGELOG-npm.md @@ -1,7 +1,7 @@ # Changelog ## [Unreleased] -- eth: add support for streaming transactions with large data +- eth: add support for streaming transactions and EIP-712 typed data with large data - eth: add optional `useAntiklepto` argument to `ethSignTypedMessage()` (set to `false` for deterministic typed-message signatures, firmware >=9.26.0) diff --git a/CHANGELOG-rust.md b/CHANGELOG-rust.md index 0b9a3d5..47ada9d 100644 --- a/CHANGELOG-rust.md +++ b/CHANGELOG-rust.md @@ -1,7 +1,7 @@ # Changelog ## [Unreleased] -- eth: add support for streaming transactions with large data +- eth: add support for streaming transactions and EIP-712 typed data with large data - eth: add `use_antiklepto` toggle to `eth_sign_typed_message()` (set `false` for deterministic typed-message signatures, firmware >=9.26.0) diff --git a/messages/eth.proto b/messages/eth.proto index ebff5c1..02c5c00 100644 --- a/messages/eth.proto +++ b/messages/eth.proto @@ -144,6 +144,9 @@ message ETHTypedMessageValueResponse { message ETHTypedMessageValueRequest { bytes value = 1; + // If non-zero, value should be empty and data will be streamed via + // DataRequestChunk/DataResponseChunk. + uint32 data_length = 2; } message ETHRequest { diff --git a/src/eth.rs b/src/eth.rs index bdb9d5f..f8c510b 100644 --- a/src/eth.rs +++ b/src/eth.rs @@ -383,7 +383,7 @@ fn encode_value(typ: &MemberType, value: &Value) -> Result, String> { fn get_value( what: &pb::EthTypedMessageValueResponse, msg: &Eip712Message, -) -> Result, String> { +) -> Result<(Vec, DataType), String> { enum Either<'a> { HashMap(&'a HashMap), JsonValue(Value), @@ -446,8 +446,10 @@ fn get_value( _ => return Err("path element does not point to struct or array".into()), } } + let data_type = + DataType::try_from(typ.r#type).map_err(|_| format!("invalid data type: {}", typ.r#type))?; if let Either::JsonValue(value) = &value { - encode_value(&typ, value) + encode_value(&typ, value).map(|v| (v, data_type)) } else { Err("path points to struct or array; value expected".to_string()) } @@ -700,12 +702,25 @@ impl PairedBitBox { )) .await?; while let pb::eth_response::Response::TypedMsgValue(typed_msg_value) = &response { - let value = get_value(typed_msg_value, &msg).map_err(Error::EthTypedMessage)?; + let (value, data_type) = + get_value(typed_msg_value, &msg).map_err(Error::EthTypedMessage)?; + if data_type == DataType::String && value.len() > STREAMING_THRESHOLD { + return Err(Error::EthTypedMessage( + "string value exceeds maximum size".into(), + )); + } + let use_streaming = value.len() > STREAMING_THRESHOLD; response = self .query_proto_eth(pb::eth_request::Request::TypedMsgValue( - pb::EthTypedMessageValueRequest { value }, + pb::EthTypedMessageValueRequest { + value: if use_streaming { vec![] } else { value.clone() }, + data_length: if use_streaming { value.len() as u32 } else { 0 }, + }, )) .await?; + if use_streaming { + response = self.handle_eth_data_streaming(&value, response).await?; + } } let mut signature = if use_antiklepto { self.handle_antiklepto(&response, host_nonce.unwrap()) @@ -1224,7 +1239,7 @@ mod tests { .is_err()); // domain.name - let value = get_value( + let (value, _) = get_value( &pb::EthTypedMessageValueResponse { root_object: RootObject::Domain as _, path: vec![0], @@ -1235,7 +1250,7 @@ mod tests { assert_eq!(value, b"Ether Mail".to_vec()); // domain.version - let value = get_value( + let (value, _) = get_value( &pb::EthTypedMessageValueResponse { root_object: RootObject::Domain as _, path: vec![1], @@ -1246,7 +1261,7 @@ mod tests { assert_eq!(value, b"1".to_vec()); // domain.chainId - let value = get_value( + let (value, _) = get_value( &pb::EthTypedMessageValueResponse { root_object: RootObject::Domain as _, path: vec![2], @@ -1257,7 +1272,7 @@ mod tests { assert_eq!(value, b"\x01".to_vec()); // domain.verifyingContract - let value = get_value( + let (value, _) = get_value( &pb::EthTypedMessageValueResponse { root_object: RootObject::Domain as _, path: vec![3], @@ -1282,7 +1297,7 @@ mod tests { // MESSAGE // message.from.name - let value = get_value( + let (value, _) = get_value( &pb::EthTypedMessageValueResponse { root_object: RootObject::Message as _, path: vec![0, 0], @@ -1293,7 +1308,7 @@ mod tests { assert_eq!(value, b"Cow".to_vec()); // message.from.wallet - let value = get_value( + let (value, _) = get_value( &pb::EthTypedMessageValueResponse { root_object: RootObject::Message as _, path: vec![0, 1], @@ -1307,7 +1322,7 @@ mod tests { ); // message.to.wallet - let value = get_value( + let (value, _) = get_value( &pb::EthTypedMessageValueResponse { root_object: RootObject::Message as _, path: vec![1, 1], @@ -1321,7 +1336,7 @@ mod tests { ); // message.attachments.0.contents - let value = get_value( + let (value, _) = get_value( &pb::EthTypedMessageValueResponse { root_object: RootObject::Message as _, path: vec![3, 0, 0], @@ -1332,7 +1347,7 @@ mod tests { assert_eq!(value, b"attachment1".to_vec()); // message.attachments.1.contents - let value = get_value( + let (value, _) = get_value( &pb::EthTypedMessageValueResponse { root_object: RootObject::Message as _, path: vec![3, 1, 0], diff --git a/src/shiftcrypto.bitbox02.rs b/src/shiftcrypto.bitbox02.rs index ce62944..2566677 100644 --- a/src/shiftcrypto.bitbox02.rs +++ b/src/shiftcrypto.bitbox02.rs @@ -1973,6 +1973,10 @@ pub mod eth_typed_message_value_response { pub struct EthTypedMessageValueRequest { #[prost(bytes = "vec", tag = "1")] pub value: ::prost::alloc::vec::Vec, + /// If non-zero, value should be empty and data will be streamed via + /// DataRequestChunk/DataResponseChunk. + #[prost(uint32, tag = "2")] + pub data_length: u32, } #[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))] diff --git a/tests/test_eth.rs b/tests/test_eth.rs index f819332..a52d059 100644 --- a/tests/test_eth.rs +++ b/tests/test_eth.rs @@ -103,6 +103,28 @@ fn eip1559_sighash(tx: &EIP1559Transaction) -> [u8; 32] { keccak256(&prefixed) } +fn eip712_sighash(primary_type: &str, data_type: &str, data: &[u8]) -> [u8; 32] { + let domain_type_hash = keccak256(b"EIP712Domain(string name)"); + let name_hash = keccak256(b"Test"); + let mut domain_input = Vec::new(); + domain_input.extend_from_slice(&domain_type_hash); + domain_input.extend_from_slice(&name_hash); + let domain_separator = keccak256(&domain_input); + + let type_hash = keccak256(format!("{primary_type}({data_type} data)").as_bytes()); + let data_hash = keccak256(data); + let mut struct_input = Vec::new(); + struct_input.extend_from_slice(&type_hash); + struct_input.extend_from_slice(&data_hash); + let struct_hash = keccak256(&struct_input); + + let mut sig_input = Vec::new(); + sig_input.extend_from_slice(b"\x19\x01"); + sig_input.extend_from_slice(&domain_separator); + sig_input.extend_from_slice(&struct_hash); + keccak256(&sig_input) +} + fn verify_eth_signature(sighash: &[u8; 32], signature: &[u8; 65]) { let secp = secp256k1::Secp256k1::new(); let path: bitcoin::bip32::DerivationPath = "m/44'/60'/0'/0/0".parse().unwrap(); @@ -312,3 +334,45 @@ async fn test_eth_sign_typed_message_antiklepto_disabled() { }) .await } + +#[tokio::test] +async fn test_eth_sign_typed_message_streaming_bytes() { + test_initialized_simulators(async |paired_bitbox| { + if !semver::VersionReq::parse(">=9.26.0") + .unwrap() + .matches(paired_bitbox.version()) + { + return; + } + + let large_bytes_hex = "aa".repeat(10000); + let msg = format!( + r#"{{ + "types": {{ + "EIP712Domain": [ + {{ "name": "name", "type": "string" }} + ], + "Msg": [ + {{ "name": "data", "type": "bytes" }} + ] + }}, + "primaryType": "Msg", + "domain": {{ + "name": "Test" + }}, + "message": {{ + "data": "0x{large_bytes_hex}" + }} +}}"# + ); + + let signature = paired_bitbox + .eth_sign_typed_message(1, &"m/44'/60'/0'/0/0".try_into().unwrap(), &msg, false) + .await + .unwrap(); + assert_eq!(signature.len(), 65); + let sighash = eip712_sighash("Msg", "bytes", &vec![0xaa; 10000]); + verify_eth_signature(&sighash, &signature); + }) + .await +}