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
44 changes: 38 additions & 6 deletions src/rust/bitbox02-rust/src/hal/testing/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ use crate::hal::ui::{

use alloc::boxed::Box;
use alloc::collections::VecDeque;
use alloc::rc::Rc;
use alloc::string::String;
use alloc::vec::Vec;
use core::cell::RefCell;
use core::time::Duration;

#[derive(Debug, Eq, PartialEq, Clone)]
Expand Down Expand Up @@ -53,6 +55,12 @@ pub enum Screen {
More,
}

#[derive(Debug, PartialEq, Clone)]
pub struct ProgressScreen {
pub title: String,
pub values: Vec<f32>,
}

type EnterStringCb<'a> = Box<dyn FnMut(&EnterStringParams<'_>) -> Result<String, UserAbort> + 'a>;
type MenuCb<'a> = Box<dyn FnMut(&[&str], Option<&str>) -> Result<u8, UserAbort> + 'a>;
type TrinaryChoiceCb<'a> =
Expand All @@ -64,16 +72,24 @@ pub struct TestingUi<'a> {
_abort_nth: Option<usize>,
pub screens: Vec<Screen>,
pub confirm_display_sizes: Vec<usize>,
progress_screens: Rc<RefCell<Vec<ProgressScreen>>>,
_enter_string: Option<EnterStringCb<'a>>,
_menu: Option<MenuCb<'a>>,
_trinary_choice: Option<TrinaryChoiceCb<'a>>,
_quiz_choices: VecDeque<u8>,
}

pub struct NoopProgress;
pub struct TestingProgress {
progress_screens: Rc<RefCell<Vec<ProgressScreen>>>,
index: usize,
}

impl Progress for NoopProgress {
fn set(&mut self, _progress: f32) {}
impl Progress for TestingProgress {
fn set(&mut self, progress: f32) {
self.progress_screens.borrow_mut()[self.index]
.values
.push(progress);
}
}

pub struct NoopEmpty;
Expand All @@ -83,12 +99,23 @@ impl Empty for NoopEmpty {}
pub struct NoopUnlockAnimation;

impl Ui for TestingUi<'_> {
type Progress = NoopProgress;
type Progress = TestingProgress;
type Empty = NoopEmpty;
type UnlockAnimation = NoopUnlockAnimation;

fn progress_create(&mut self, _title: &str) -> Self::Progress {
NoopProgress
fn progress_create(&mut self, title: &str) -> Self::Progress {
let index = {
let mut progress_screens = self.progress_screens.borrow_mut();
progress_screens.push(ProgressScreen {
title: title.into(),
values: vec![],
});
progress_screens.len() - 1
};
TestingProgress {
progress_screens: self.progress_screens.clone(),
index,
}
}

fn empty_create(&mut self) -> Self::Empty {
Expand Down Expand Up @@ -274,6 +301,7 @@ impl<'a> TestingUi<'a> {
Self {
screens: vec![],
confirm_display_sizes: vec![],
progress_screens: Rc::new(RefCell::new(vec![])),
_abort_nth: None,
_enter_string: None,
_menu: None,
Expand All @@ -282,6 +310,10 @@ impl<'a> TestingUi<'a> {
}
}

pub fn progress_screens(&self) -> Vec<ProgressScreen> {
self.progress_screens.borrow().clone()
}

/// Make the `n`-th workflow (0-indexed) fail with a user abort. If that workflow cannot be
/// aborted, there will be panic.
pub fn abort_nth(&mut self, n: usize) {
Expand Down
44 changes: 44 additions & 0 deletions src/rust/bitbox02-rust/src/hww/api/ethereum/sighash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use core::pin::Pin;
use alloc::boxed::Box;
use alloc::vec::Vec;

use crate::hal::ui::Progress;

use super::Error;

/// An async producer/generator of a bytes array. This is used to be able to accumulate the RLP hash
Expand All @@ -26,6 +28,48 @@ pub trait DataProducer {
-> Pin<Box<dyn Future<Output = Result<Option<Vec<u8>>, Error>> + 'a>>;
}

pub struct ProgressProducer<'a, P: Progress> {
producer: &'a mut dyn DataProducer,
progress: &'a mut P,
consumed: u32,
}

impl<'a, P: Progress> ProgressProducer<'a, P> {
pub fn new(producer: &'a mut dyn DataProducer, progress: &'a mut P) -> Self {
Self {
producer,
progress,
consumed: 0,
}
}
}

impl<P: Progress> DataProducer for ProgressProducer<'_, P> {
fn len(&self) -> u32 {
self.producer.len()
}

fn first_byte<'a>(&'a mut self) -> Pin<Box<dyn Future<Output = Result<u8, Error>> + 'a>> {
self.producer.first_byte()
}

fn next<'a>(
&'a mut self,
) -> Pin<Box<dyn Future<Output = Result<Option<Vec<u8>>, Error>> + 'a>> {
Box::pin(async move {
let chunk = self.producer.next().await?;
if let Some(chunk) = &chunk {
let total = self.producer.len();
self.consumed = self.consumed.saturating_add(chunk.len() as u32).min(total);
if total > 0 {
self.progress.set(self.consumed as f32 / total as f32);
}
}
Ok(chunk)
})
}
}

pub struct Preview {
cap: usize,
bytes: Vec<u8>,
Expand Down
31 changes: 26 additions & 5 deletions src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,18 +281,25 @@ struct PreparedStreamingStandardData {
}

async fn prepare_streaming_standard_data(
hal: &mut impl crate::hal::Hal,
chain_id: u64,
request: &Transaction<'_>,
) -> Result<PreparedStreamingStandardData, Error> {
let display_size = request.data_length() as usize;
let display_cap = truncating_hex_preview_byte_cap(0, display_size);
let mut producer = super::sighash::ChunkingProducer::from_host(request.data_length())
.with_preview(display_cap);
let hash = match request {
Transaction::Legacy(legacy) => {
hash_legacy_with_producer(chain_id, legacy, &mut producer).await?
let hash = {
let mut progress = hal.ui().progress_create("Loading data...");
let mut producer = super::sighash::ProgressProducer::new(&mut producer, &mut progress);
match request {
Transaction::Legacy(legacy) => {
hash_legacy_with_producer(chain_id, legacy, &mut producer).await?
}
Transaction::Eip1559(eip1559) => {
hash_eip1559_with_producer(eip1559, &mut producer).await?
}
}
Transaction::Eip1559(eip1559) => hash_eip1559_with_producer(eip1559, &mut producer).await?,
};
Ok(PreparedStreamingStandardData {
body: hex::encode(producer.preview()),
Expand Down Expand Up @@ -439,7 +446,7 @@ async fn verify_standard_transaction(
.await?;

let (display_size, body) = if data_length > 0 {
let prepared = prepare_streaming_standard_data(params.chain_id, request).await?;
let prepared = prepare_streaming_standard_data(hal, params.chain_id, request).await?;
let display_size = prepared.display_size;
let body = prepared.body.clone();
prepared_streaming_data = Some(prepared);
Expand Down Expand Up @@ -663,6 +670,16 @@ mod tests {
clear_chunk_responder, setup_chunk_responder, setup_counting_chunk_responder,
};

fn assert_progress_screen(mock_hal: &TestingHal<'_>, expected_values: &[f32]) {
let progress_screens = mock_hal.ui.progress_screens();
assert_eq!(progress_screens.len(), 1);
assert_eq!(progress_screens[0].title, "Loading data...");
assert_eq!(progress_screens[0].values.len(), expected_values.len());
for (actual, expected) in progress_screens[0].values.iter().zip(expected_values) {
assert!((actual - expected).abs() < 1e-6);
}
}

// Base payment request fixture for ETH-side swap tests.
fn make_eth_swap_payment_request() -> pb::BtcPaymentRequestRequest {
pb::BtcPaymentRequestRequest {
Expand Down Expand Up @@ -2034,6 +2051,7 @@ mod tests {
}
_ => panic!("unexpected screen"),
}
assert!(mock_hal.ui.progress_screens().is_empty());
}

#[async_test::test]
Expand Down Expand Up @@ -2070,6 +2088,7 @@ mod tests {
}))
);
clear_chunk_responder();
assert_progress_screen(&mock_hal, &[4096.0 / 10_000.0, 8192.0 / 10_000.0, 1.0]);
assert_eq!(mock_hal.ui.confirm_display_sizes, vec![0, 0, 0, 0, 10_000]);
assert_eq!(
mock_hal.ui.screens[0],
Expand Down Expand Up @@ -2167,6 +2186,7 @@ mod tests {
}
other => panic!("expected Ok(Sign), got {:?}", other),
}
assert_progress_screen(&mock_hal, &[1.0]);
}

#[async_test::test]
Expand Down Expand Up @@ -2203,6 +2223,7 @@ mod tests {
}))
);
clear_chunk_responder();
assert_progress_screen(&mock_hal, &[4096.0 / 12_000.0, 8192.0 / 12_000.0, 1.0]);
assert_eq!(mock_hal.ui.confirm_display_sizes, vec![0, 0, 0, 0, 12_000]);
assert_eq!(
mock_hal.ui.screens,
Expand Down
30 changes: 28 additions & 2 deletions src/rust/bitbox02-rust/src/hww/api/ethereum/sign_typed_msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,13 @@ async fn encode_member<U: sha3::digest::Update>(
let mut producer = super::sighash::ChunkingProducer::from_host(req.data_length)
.with_preview(display_cap);
let mut keccak = sha3::Keccak256::new();
while let Some(chunk) = producer.next().await? {
keccak.update(&chunk);
{
let mut progress = hal.ui().progress_create("Loading data...");
let mut producer =
super::sighash::ProgressProducer::new(&mut producer, &mut progress);
while let Some(chunk) = producer.next().await? {
keccak.update(&chunk);
}
}
hasher.update(&keccak.finalize());

Expand Down Expand Up @@ -672,6 +677,16 @@ mod tests {

use pb::eth_sign_typed_message_request::Member;

fn assert_progress_screen(mock_hal: &TestingHal<'_>, expected_values: &[f32]) {
let progress_screens = mock_hal.ui.progress_screens();
assert_eq!(progress_screens.len(), 1);
assert_eq!(progress_screens[0].title, "Loading data...");
assert_eq!(progress_screens[0].values.len(), expected_values.len());
for (actual, expected) in progress_screens[0].values.iter().zip(expected_values) {
assert!((actual - expected).abs() < 1e-6);
}
}

fn mk_type(data_type: DataType) -> MemberType {
MemberType {
r#type: data_type as _,
Expand Down Expand Up @@ -1183,6 +1198,7 @@ mod tests {
let line2 = "b".repeat(MAX_CONFIRM_BODY_SIZE);
let mock_hal = run_single_string_message(format!("ok\n{line2}")).await;

assert!(mock_hal.ui.progress_screens().is_empty());
assert_eq!(
mock_hal.ui.screens,
vec![
Expand Down Expand Up @@ -1215,6 +1231,7 @@ mod tests {
let data: Vec<u8> = (0u8..=255).cycle().take(10_000).collect();
let mock_hal = run_single_streaming_bytes_message(data).await;

assert_progress_screen(&mock_hal, &[4096.0 / 10_000.0, 8192.0 / 10_000.0, 1.0]);
assert_eq!(mock_hal.ui.confirm_display_sizes, vec![0, 0, 10_000]);
assert_eq!(
mock_hal.ui.screens[1],
Expand All @@ -1233,6 +1250,15 @@ mod tests {
}
}

#[async_test::test]
async fn test_inline_bytes_no_progress() {
let data: &'static [u8] = Box::leak(vec![0x01, 0x02, 0x03].into_boxed_slice());
let mock_hal =
run_single_message_typed_msg(mk_type(DataType::Bytes), Object::Bytes(data)).await;

assert!(mock_hal.ui.progress_screens().is_empty());
}

#[test]
fn test_encode_type() {
assert_eq!(
Expand Down
Loading