Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 16 additions & 13 deletions messages/eth.proto
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -63,6 +51,8 @@ message ETHSignRequest {
// If non-zero, `coin` is ignored and `chain_id` is used to identify the network.
uint64 chain_id = 10;
ETHAddressCase address_case = 11;
// For streaming: if non-zero, data field should be empty and data will be requested in chunks
uint32 data_length = 12;
}

// TX payload for an EIP-1559 (type 2) transaction: https://eips.ethereum.org/EIPS/eip-1559
Expand All @@ -78,6 +68,17 @@ message ETHSignEIP1559Request {
bytes data = 9;
AntiKleptoHostNonceCommitment host_nonce_commitment = 10;
ETHAddressCase address_case = 11;
// For streaming: if non-zero, data field should be empty and data will be requested in chunks
uint32 data_length = 12;
}

message ETHSignDataRequestChunkResponse {
uint32 offset = 1;
uint32 length = 2;
}

message ETHSignDataResponseChunkRequest {
bytes chunk = 1;
}

message ETHSignMessageRequest {
Expand Down Expand Up @@ -154,6 +155,7 @@ message ETHRequest {
ETHSignTypedMessageRequest sign_typed_msg = 5;
ETHTypedMessageValueRequest typed_msg_value = 6;
ETHSignEIP1559Request sign_eip1559 = 7;
ETHSignDataResponseChunkRequest data_chunk = 8;
}
}

Expand All @@ -163,5 +165,6 @@ message ETHResponse {
ETHSignResponse sign = 2;
AntiKleptoSignerCommitment antiklepto_signer_commitment = 3;
ETHTypedMessageValueResponse typed_msg_value = 4;
ETHSignDataRequestChunkResponse data_chunk_request = 5;
}
}
57 changes: 53 additions & 4 deletions src/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ use num_bigint::{BigInt, BigUint};
//use num_traits::ToPrimitive;
use serde_json::Value;

/// Threshold above which transaction data is streamed in chunks.
/// Transactions with data larger than this use streaming mode.
const STREAMING_THRESHOLD: usize = 6144;

impl<R: Runtime> PairedBitBox<R> {
async fn query_proto_eth(
&self,
Expand Down Expand Up @@ -478,6 +482,31 @@ impl<R: Runtime> PairedBitBox<R> {
}
}

/// Handles streaming of transaction data when in streaming mode.
/// The device requests data chunks, and this method responds with the requested chunks.
async fn handle_eth_data_streaming(
&self,
data: &[u8],
mut response: pb::eth_response::Response,
) -> Result<pb::eth_response::Response, Error> {
while let pb::eth_response::Response::DataChunkRequest(chunk_req) = &response {
let offset = chunk_req.offset as usize;
let length = chunk_req.length as usize;

if offset + length > data.len() {
return Err(Error::UnexpectedResponse);
}

let chunk = data[offset..offset + length].to_vec();
response = self
.query_proto_eth(pb::eth_request::Request::DataChunk(
pb::EthSignDataResponseChunkRequest { chunk },
))
.await?;
}
Ok(response)
}

/// Signs an Ethereum transaction. It returns a 65 byte signature (R, S, and 1 byte recID). The
/// `tx` param can be constructed manually or parsed from a raw transaction using
/// `raw_tx_slice.try_into()` (`rlp` feature required).
Expand All @@ -491,6 +520,11 @@ impl<R: Runtime> PairedBitBox<R> {
// passing chainID instead of coin only since v9.10.0
self.validate_version(">=9.10.0")?;

let use_streaming = tx.data.len() > STREAMING_THRESHOLD;
if use_streaming {
self.validate_version(">=9.26.0")?;
}

let host_nonce = crate::antiklepto::gen_host_nonce()?;
let request = pb::eth_request::Request::Sign(pb::EthSignRequest {
coin: 0,
Expand All @@ -500,14 +534,19 @@ impl<R: Runtime> PairedBitBox<R> {
gas_limit: crate::util::remove_leading_zeroes(&tx.gas_limit),
recipient: tx.recipient.to_vec(),
value: crate::util::remove_leading_zeroes(&tx.value),
data: tx.data.clone(),
data: if use_streaming { vec![] } else { tx.data.clone() },
host_nonce_commitment: Some(pb::AntiKleptoHostNonceCommitment {
commitment: crate::antiklepto::host_commit(&host_nonce).to_vec(),
}),
chain_id,
address_case: address_case.unwrap_or(pb::EthAddressCase::Mixed).into(),
data_length: if use_streaming { tx.data.len() as u32 } else { 0 },
});
let response = self.query_proto_eth(request).await?;

let mut response = self.query_proto_eth(request).await?;
if use_streaming {
response = self.handle_eth_data_streaming(&tx.data, response).await?;
}
self.handle_antiklepto(&response, host_nonce).await
}

Expand All @@ -523,6 +562,11 @@ impl<R: Runtime> PairedBitBox<R> {
// EIP1559 is suported from v9.16.0
self.validate_version(">=9.16.0")?;

let use_streaming = tx.data.len() > STREAMING_THRESHOLD;
if use_streaming {
self.validate_version(">=9.26.0")?;
}

let host_nonce = crate::antiklepto::gen_host_nonce()?;
let request = pb::eth_request::Request::SignEip1559(pb::EthSignEip1559Request {
chain_id: tx.chain_id,
Expand All @@ -535,13 +579,18 @@ impl<R: Runtime> PairedBitBox<R> {
gas_limit: crate::util::remove_leading_zeroes(&tx.gas_limit),
recipient: tx.recipient.to_vec(),
value: crate::util::remove_leading_zeroes(&tx.value),
data: tx.data.clone(),
data: if use_streaming { vec![] } else { tx.data.clone() },
host_nonce_commitment: Some(pb::AntiKleptoHostNonceCommitment {
commitment: crate::antiklepto::host_commit(&host_nonce).to_vec(),
}),
address_case: address_case.unwrap_or(pb::EthAddressCase::Mixed).into(),
data_length: if use_streaming { tx.data.len() as u32 } else { 0 },
});
let response = self.query_proto_eth(request).await?;

let mut response = self.query_proto_eth(request).await?;
if use_streaming {
response = self.handle_eth_data_streaming(&tx.data, response).await?;
}
self.handle_antiklepto(&response, host_nonce).await
}

Expand Down
36 changes: 33 additions & 3 deletions src/shiftcrypto.bitbox02.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,16 @@ pub struct DeviceInfoResponse {
pub mnemonic_passphrase_enabled: bool,
#[prost(uint32, tag = "5")]
pub monotonic_increments_remaining: u32,
/// From v9.6.0: "ATECC608A" or "ATECC608B".
/// From v9.6.0: "ATECC608A" or "ATECC608B" or "OPTIGA_TRUST_M_V3".
#[prost(string, tag = "6")]
pub securechip_model: ::prost::alloc::string::String,
/// Only present in Bluetooth-enabled devices.
#[prost(message, optional, tag = "7")]
pub bluetooth: ::core::option::Option<device_info_response::Bluetooth>,
/// From v9.25.0. This together with `securechip_model` determines the password stretching
/// algorithm.
#[prost(string, tag = "8")]
pub password_stretching_algo: ::prost::alloc::string::String,
}
/// Nested message and enum types in `DeviceInfoResponse`.
pub mod device_info_response {
Expand Down Expand Up @@ -1706,6 +1710,9 @@ pub struct EthSignRequest {
pub chain_id: u64,
#[prost(enumeration = "EthAddressCase", tag = "11")]
pub address_case: i32,
/// For streaming: if non-zero, data field should be empty and data will be requested in chunks
#[prost(uint32, tag = "12")]
pub data_length: u32,
}
/// TX payload for an EIP-1559 (type 2) transaction: <https://eips.ethereum.org/EIPS/eip-1559>
#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))]
Expand Down Expand Up @@ -1744,6 +1751,25 @@ pub struct EthSignEip1559Request {
pub host_nonce_commitment: ::core::option::Option<AntiKleptoHostNonceCommitment>,
#[prost(enumeration = "EthAddressCase", tag = "11")]
pub address_case: i32,
/// For streaming: if non-zero, data field should be empty and data will be requested in chunks
#[prost(uint32, tag = "12")]
pub data_length: u32,
}
#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))]
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
pub struct EthSignDataRequestChunkResponse {
#[prost(uint32, tag = "1")]
pub offset: u32,
#[prost(uint32, tag = "2")]
pub length: u32,
}
#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct EthSignDataResponseChunkRequest {
#[prost(bytes = "vec", tag = "1")]
pub chunk: ::prost::alloc::vec::Vec<u8>,
}
#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))]
Expand Down Expand Up @@ -1952,7 +1978,7 @@ pub struct EthTypedMessageValueRequest {
#[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct EthRequest {
#[prost(oneof = "eth_request::Request", tags = "1, 2, 3, 4, 5, 6, 7")]
#[prost(oneof = "eth_request::Request", tags = "1, 2, 3, 4, 5, 6, 7, 8")]
pub request: ::core::option::Option<eth_request::Request>,
}
/// Nested message and enum types in `ETHRequest`.
Expand All @@ -1975,13 +2001,15 @@ pub mod eth_request {
TypedMsgValue(super::EthTypedMessageValueRequest),
#[prost(message, tag = "7")]
SignEip1559(super::EthSignEip1559Request),
#[prost(message, tag = "8")]
DataChunk(super::EthSignDataResponseChunkRequest),
}
}
#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct EthResponse {
#[prost(oneof = "eth_response::Response", tags = "1, 2, 3, 4")]
#[prost(oneof = "eth_response::Response", tags = "1, 2, 3, 4, 5")]
pub response: ::core::option::Option<eth_response::Response>,
}
/// Nested message and enum types in `ETHResponse`.
Expand All @@ -1998,6 +2026,8 @@ pub mod eth_response {
AntikleptoSignerCommitment(super::AntiKleptoSignerCommitment),
#[prost(message, tag = "4")]
TypedMsgValue(super::EthTypedMessageValueResponse),
#[prost(message, tag = "5")]
DataChunkRequest(super::EthSignDataRequestChunkResponse),
}
}
/// Kept for backwards compatibility. Use chain_id instead, introduced in v9.10.0.
Expand Down
Loading
Loading