Skip to content

Commit 15f6243

Browse files
committed
Merge branch 'eth-stream'
2 parents acbb4b7 + fdfd4c7 commit 15f6243

File tree

15 files changed

+354
-29
lines changed

15 files changed

+354
-29
lines changed

CHANGELOG-npm.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## [Unreleased]
4+
- eth: add support for streaming transactions with large data
5+
36
## 0.12.0
47
- btc: add support for OP_RETURN outputs
58
- add `changePassword()` to change the device password (firmware >=9.25.0)

CHANGELOG-rust.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## [Unreleased]
4+
- eth: add support for streaming transactions with large data
5+
36
## 0.11.0
47
- btc: add support for OP_RETURN outputs
58
- add `change_password()` to change the device password (firmware >=9.25.0)

Cargo.lock

Lines changed: 17 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "bitbox-api"
33
authors = ["Marko Bencun <benma@bitbox.swiss>"]
4-
version = "0.11.0"
4+
version = "0.12.0"
55
homepage = "https://bitbox.swiss/"
66
repository = "https://github.com/BitBoxSwiss/bitbox-api-rs/"
77
readme = "README-rust.md"
@@ -50,6 +50,8 @@ wasm-bindgen-test = "0.3.42"
5050
tokio = { version = "1", features = ["time", "macros", "rt", "fs"] }
5151
reqwest = "0.12"
5252
url = "2.5"
53+
tiny-keccak = { version = "2.0", features = ["keccak"] }
54+
rlp = "0.5"
5355
# Enable this to be able to get coverage using `cargo tarpaulin --features=simulator,tokio --out=Html` without compilation error.
5456
# See https://github.com/rust-bitcoin/rust-bitcoinconsensus/pull/94
5557
# bitcoinconsensus = { git = "https://github.com/rust-bitcoin/rust-bitcoinconsensus.git", rev = "788ce4d210f7fe6fae4414f5be80968216ba0fd8", default-features = false }

NPM_VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.12.0
1+
0.13.0

messages/eth.proto

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,4 @@
1-
// Copyright 2019 Shift Cryptosecurity AG
2-
//
3-
// Licensed under the Apache License, Version 2.0 (the "License");
4-
// you may not use this file except in compliance with the License.
5-
// You may obtain a copy of the License at
6-
//
7-
// http://www.apache.org/licenses/LICENSE-2.0
8-
//
9-
// Unless required by applicable law or agreed to in writing, software
10-
// distributed under the License is distributed on an "AS IS" BASIS,
11-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12-
// See the License for the specific language governing permissions and
13-
// limitations under the License.
1+
// SPDX-License-Identifier: Apache-2.0
142

153
syntax = "proto3";
164
package shiftcrypto.bitbox02;
@@ -63,6 +51,8 @@ message ETHSignRequest {
6351
// If non-zero, `coin` is ignored and `chain_id` is used to identify the network.
6452
uint64 chain_id = 10;
6553
ETHAddressCase address_case = 11;
54+
// For streaming: if non-zero, data field should be empty and data will be requested in chunks
55+
uint32 data_length = 12;
6656
}
6757

6858
// TX payload for an EIP-1559 (type 2) transaction: https://eips.ethereum.org/EIPS/eip-1559
@@ -78,6 +68,17 @@ message ETHSignEIP1559Request {
7868
bytes data = 9;
7969
AntiKleptoHostNonceCommitment host_nonce_commitment = 10;
8070
ETHAddressCase address_case = 11;
71+
// For streaming: if non-zero, data field should be empty and data will be requested in chunks
72+
uint32 data_length = 12;
73+
}
74+
75+
message ETHSignDataRequestChunkResponse {
76+
uint32 offset = 1;
77+
uint32 length = 2;
78+
}
79+
80+
message ETHSignDataResponseChunkRequest {
81+
bytes chunk = 1;
8182
}
8283

8384
message ETHSignMessageRequest {
@@ -154,6 +155,7 @@ message ETHRequest {
154155
ETHSignTypedMessageRequest sign_typed_msg = 5;
155156
ETHTypedMessageValueRequest typed_msg_value = 6;
156157
ETHSignEIP1559Request sign_eip1559 = 7;
158+
ETHSignDataResponseChunkRequest data_response_chunk = 8;
157159
}
158160
}
159161

@@ -163,5 +165,6 @@ message ETHResponse {
163165
ETHSignResponse sign = 2;
164166
AntiKleptoSignerCommitment antiklepto_signer_commitment = 3;
165167
ETHTypedMessageValueResponse typed_msg_value = 4;
168+
ETHSignDataRequestChunkResponse data_request_chunk = 5;
166169
}
167170
}

src/eth.rs

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ use num_bigint::{BigInt, BigUint};
2020
//use num_traits::ToPrimitive;
2121
use serde_json::Value;
2222

23+
/// Threshold above which transaction data is streamed in chunks.
24+
/// Transactions with data larger than this use streaming mode.
25+
const STREAMING_THRESHOLD: usize = 6144;
26+
2327
impl<R: Runtime> PairedBitBox<R> {
2428
async fn query_proto_eth(
2529
&self,
@@ -478,6 +482,31 @@ impl<R: Runtime> PairedBitBox<R> {
478482
}
479483
}
480484

485+
/// Handles streaming of transaction data when in streaming mode.
486+
/// The device requests data chunks, and this method responds with the requested chunks.
487+
async fn handle_eth_data_streaming(
488+
&self,
489+
data: &[u8],
490+
mut response: pb::eth_response::Response,
491+
) -> Result<pb::eth_response::Response, Error> {
492+
while let pb::eth_response::Response::DataRequestChunk(chunk_req) = &response {
493+
let offset = chunk_req.offset as usize;
494+
let length = chunk_req.length as usize;
495+
496+
if offset + length > data.len() {
497+
return Err(Error::UnexpectedResponse);
498+
}
499+
500+
let chunk = data[offset..offset + length].to_vec();
501+
response = self
502+
.query_proto_eth(pb::eth_request::Request::DataResponseChunk(
503+
pb::EthSignDataResponseChunkRequest { chunk },
504+
))
505+
.await?;
506+
}
507+
Ok(response)
508+
}
509+
481510
/// Signs an Ethereum transaction. It returns a 65 byte signature (R, S, and 1 byte recID). The
482511
/// `tx` param can be constructed manually or parsed from a raw transaction using
483512
/// `raw_tx_slice.try_into()` (`rlp` feature required).
@@ -491,6 +520,11 @@ impl<R: Runtime> PairedBitBox<R> {
491520
// passing chainID instead of coin only since v9.10.0
492521
self.validate_version(">=9.10.0")?;
493522

523+
let use_streaming = tx.data.len() > STREAMING_THRESHOLD;
524+
if use_streaming {
525+
self.validate_version(">=9.26.0")?;
526+
}
527+
494528
let host_nonce = crate::antiklepto::gen_host_nonce()?;
495529
let request = pb::eth_request::Request::Sign(pb::EthSignRequest {
496530
coin: 0,
@@ -500,14 +534,27 @@ impl<R: Runtime> PairedBitBox<R> {
500534
gas_limit: crate::util::remove_leading_zeroes(&tx.gas_limit),
501535
recipient: tx.recipient.to_vec(),
502536
value: crate::util::remove_leading_zeroes(&tx.value),
503-
data: tx.data.clone(),
537+
data: if use_streaming {
538+
vec![]
539+
} else {
540+
tx.data.clone()
541+
},
504542
host_nonce_commitment: Some(pb::AntiKleptoHostNonceCommitment {
505543
commitment: crate::antiklepto::host_commit(&host_nonce).to_vec(),
506544
}),
507545
chain_id,
508546
address_case: address_case.unwrap_or(pb::EthAddressCase::Mixed).into(),
547+
data_length: if use_streaming {
548+
tx.data.len() as u32
549+
} else {
550+
0
551+
},
509552
});
510-
let response = self.query_proto_eth(request).await?;
553+
554+
let mut response = self.query_proto_eth(request).await?;
555+
if use_streaming {
556+
response = self.handle_eth_data_streaming(&tx.data, response).await?;
557+
}
511558
self.handle_antiklepto(&response, host_nonce).await
512559
}
513560

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

573+
let use_streaming = tx.data.len() > STREAMING_THRESHOLD;
574+
if use_streaming {
575+
self.validate_version(">=9.26.0")?;
576+
}
577+
526578
let host_nonce = crate::antiklepto::gen_host_nonce()?;
527579
let request = pb::eth_request::Request::SignEip1559(pb::EthSignEip1559Request {
528580
chain_id: tx.chain_id,
@@ -535,13 +587,26 @@ impl<R: Runtime> PairedBitBox<R> {
535587
gas_limit: crate::util::remove_leading_zeroes(&tx.gas_limit),
536588
recipient: tx.recipient.to_vec(),
537589
value: crate::util::remove_leading_zeroes(&tx.value),
538-
data: tx.data.clone(),
590+
data: if use_streaming {
591+
vec![]
592+
} else {
593+
tx.data.clone()
594+
},
539595
host_nonce_commitment: Some(pb::AntiKleptoHostNonceCommitment {
540596
commitment: crate::antiklepto::host_commit(&host_nonce).to_vec(),
541597
}),
542598
address_case: address_case.unwrap_or(pb::EthAddressCase::Mixed).into(),
599+
data_length: if use_streaming {
600+
tx.data.len() as u32
601+
} else {
602+
0
603+
},
543604
});
544-
let response = self.query_proto_eth(request).await?;
605+
606+
let mut response = self.query_proto_eth(request).await?;
607+
if use_streaming {
608+
response = self.handle_eth_data_streaming(&tx.data, response).await?;
609+
}
545610
self.handle_antiklepto(&response, host_nonce).await
546611
}
547612

src/shiftcrypto.bitbox02.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,16 @@ pub struct DeviceInfoResponse {
157157
pub mnemonic_passphrase_enabled: bool,
158158
#[prost(uint32, tag = "5")]
159159
pub monotonic_increments_remaining: u32,
160-
/// From v9.6.0: "ATECC608A" or "ATECC608B".
160+
/// From v9.6.0: "ATECC608A" or "ATECC608B" or "OPTIGA_TRUST_M_V3".
161161
#[prost(string, tag = "6")]
162162
pub securechip_model: ::prost::alloc::string::String,
163163
/// Only present in Bluetooth-enabled devices.
164164
#[prost(message, optional, tag = "7")]
165165
pub bluetooth: ::core::option::Option<device_info_response::Bluetooth>,
166+
/// From v9.25.0. This together with `securechip_model` determines the password stretching
167+
/// algorithm.
168+
#[prost(string, tag = "8")]
169+
pub password_stretching_algo: ::prost::alloc::string::String,
166170
}
167171
/// Nested message and enum types in `DeviceInfoResponse`.
168172
pub mod device_info_response {
@@ -1706,6 +1710,9 @@ pub struct EthSignRequest {
17061710
pub chain_id: u64,
17071711
#[prost(enumeration = "EthAddressCase", tag = "11")]
17081712
pub address_case: i32,
1713+
/// For streaming: if non-zero, data field should be empty and data will be requested in chunks
1714+
#[prost(uint32, tag = "12")]
1715+
pub data_length: u32,
17091716
}
17101717
/// TX payload for an EIP-1559 (type 2) transaction: <https://eips.ethereum.org/EIPS/eip-1559>
17111718
#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))]
@@ -1744,6 +1751,25 @@ pub struct EthSignEip1559Request {
17441751
pub host_nonce_commitment: ::core::option::Option<AntiKleptoHostNonceCommitment>,
17451752
#[prost(enumeration = "EthAddressCase", tag = "11")]
17461753
pub address_case: i32,
1754+
/// For streaming: if non-zero, data field should be empty and data will be requested in chunks
1755+
#[prost(uint32, tag = "12")]
1756+
pub data_length: u32,
1757+
}
1758+
#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))]
1759+
#[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))]
1760+
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
1761+
pub struct EthSignDataRequestChunkResponse {
1762+
#[prost(uint32, tag = "1")]
1763+
pub offset: u32,
1764+
#[prost(uint32, tag = "2")]
1765+
pub length: u32,
1766+
}
1767+
#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))]
1768+
#[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))]
1769+
#[derive(Clone, PartialEq, ::prost::Message)]
1770+
pub struct EthSignDataResponseChunkRequest {
1771+
#[prost(bytes = "vec", tag = "1")]
1772+
pub chunk: ::prost::alloc::vec::Vec<u8>,
17471773
}
17481774
#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))]
17491775
#[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))]
@@ -1952,7 +1978,7 @@ pub struct EthTypedMessageValueRequest {
19521978
#[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))]
19531979
#[derive(Clone, PartialEq, ::prost::Message)]
19541980
pub struct EthRequest {
1955-
#[prost(oneof = "eth_request::Request", tags = "1, 2, 3, 4, 5, 6, 7")]
1981+
#[prost(oneof = "eth_request::Request", tags = "1, 2, 3, 4, 5, 6, 7, 8")]
19561982
pub request: ::core::option::Option<eth_request::Request>,
19571983
}
19581984
/// Nested message and enum types in `ETHRequest`.
@@ -1975,13 +2001,15 @@ pub mod eth_request {
19752001
TypedMsgValue(super::EthTypedMessageValueRequest),
19762002
#[prost(message, tag = "7")]
19772003
SignEip1559(super::EthSignEip1559Request),
2004+
#[prost(message, tag = "8")]
2005+
DataResponseChunk(super::EthSignDataResponseChunkRequest),
19782006
}
19792007
}
19802008
#[cfg_attr(feature = "wasm", derive(serde::Serialize, serde::Deserialize))]
19812009
#[cfg_attr(feature = "wasm", serde(rename_all = "camelCase"))]
19822010
#[derive(Clone, PartialEq, ::prost::Message)]
19832011
pub struct EthResponse {
1984-
#[prost(oneof = "eth_response::Response", tags = "1, 2, 3, 4")]
2012+
#[prost(oneof = "eth_response::Response", tags = "1, 2, 3, 4, 5")]
19852013
pub response: ::core::option::Option<eth_response::Response>,
19862014
}
19872015
/// Nested message and enum types in `ETHResponse`.
@@ -1998,6 +2026,8 @@ pub mod eth_response {
19982026
AntikleptoSignerCommitment(super::AntiKleptoSignerCommitment),
19992027
#[prost(message, tag = "4")]
20002028
TypedMsgValue(super::EthTypedMessageValueResponse),
2029+
#[prost(message, tag = "5")]
2030+
DataRequestChunk(super::EthSignDataRequestChunkResponse),
20012031
}
20022032
}
20032033
/// Kept for backwards compatibility. Use chain_id instead, introduced in v9.10.0.

src/wasm/connect.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ struct JsReadWrite {
1111

1212
impl crate::util::Threading for JsReadWrite {}
1313

14-
#[wasm_bindgen(raw_module = "./webhid")]
14+
#[wasm_bindgen(raw_module = "./webhid.js")]
1515
extern "C" {
1616
#[wasm_bindgen(catch)]
1717
async fn getWebHIDDevice(

src/wasm/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ pub fn eth_identify_case(recipient_address: &str) -> types::TsEthAddressCase {
107107
crate::eth::eth_identify_case(recipient_address).into()
108108
}
109109

110-
#[wasm_bindgen(raw_module = "./webhid")]
110+
#[wasm_bindgen(raw_module = "./webhid.js")]
111111
extern "C" {
112112
async fn jsSleep(millis: f64);
113113
}

0 commit comments

Comments
 (0)