From 1619e8d6e91b7a888a483d539a8733184462192f Mon Sep 17 00:00:00 2001 From: Sergiy Yevtushenko Date: Wed, 13 Sep 2023 17:26:17 +0200 Subject: [PATCH] Feature/add in app settings (#42) * Add in-app settings --- Cargo.toml | 2 +- doc/api.md | 162 +++++------------ src/app_error.rs | 1 + src/command.rs | 12 +- src/crypto/address_verifier.rs | 2 +- src/handler.rs | 5 +- src/handler/dispatcher.rs | 5 +- src/handler/get_app_settings.rs | 7 + src/handler/sign_auth_ed25519.rs | 4 +- src/handler/sign_auth_secp256k1.rs | 4 +- src/handler/sign_tx_ed25519.rs | 4 +- src/handler/sign_tx_ed25519_summary.rs | 22 --- src/handler/sign_tx_secp256k1.rs | 4 +- src/handler/sign_tx_secp256k1_summary.rs | 22 --- src/main.rs | 124 +++++++++++-- src/settings.rs | 43 +++++ src/sign.rs | 2 +- src/sign/instruction_processor.rs | 30 ++-- src/sign/{sign_type.rs => sign_mode.rs} | 6 +- src/sign/signing_flow_state.rs | 46 ++--- src/sign/tx_state.rs | 165 +++++++++--------- src/ui/menu.rs | 65 +++++-- src/ui/single_message.rs | 44 ++++- src/ui/utils.rs | 25 ++- test/run-speculos-ns.sh | 7 + test/{run-speculos.sh => run-speculos-nsp.sh} | 0 test/test-all-debug.sh | 8 - test/test-all-interactive.sh | 9 - test/{test-all-release.sh => test-all.sh} | 2 +- test/test-get-application-version.py | 2 +- test/test-get-private-key-ed25519.py | 84 --------- test/test-get-private-key-secp256k1.py | 80 --------- test/test-sign-tx-ed25519-hash.py | 97 ---------- test/test-sign-tx-ed25519-summary.py | 97 ---------- test/test-sign-tx-secp256k1-hash.py | 102 ----------- test/test-sign-tx-secp256k1-summary.py | 102 ----------- test/test-verify-address-ed25519.py | 0 test/test-verify-address-secp256k1.py | 0 38 files changed, 465 insertions(+), 931 deletions(-) create mode 100644 src/handler/get_app_settings.rs delete mode 100644 src/handler/sign_tx_ed25519_summary.rs delete mode 100644 src/handler/sign_tx_secp256k1_summary.rs create mode 100644 src/settings.rs rename src/sign/{sign_type.rs => sign_mode.rs} (67%) create mode 100755 test/run-speculos-ns.sh rename test/{run-speculos.sh => run-speculos-nsp.sh} (100%) delete mode 100755 test/test-all-debug.sh delete mode 100755 test/test-all-interactive.sh rename test/{test-all-release.sh => test-all.sh} (78%) delete mode 100755 test/test-get-private-key-ed25519.py delete mode 100755 test/test-get-private-key-secp256k1.py delete mode 100755 test/test-sign-tx-ed25519-hash.py delete mode 100755 test/test-sign-tx-ed25519-summary.py delete mode 100755 test/test-sign-tx-secp256k1-hash.py delete mode 100755 test/test-sign-tx-secp256k1-summary.py mode change 100644 => 100755 test/test-verify-address-ed25519.py mode change 100644 => 100755 test/test-verify-address-secp256k1.py diff --git a/Cargo.toml b/Cargo.toml index f4380eac..46f15393 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "babylon-ledger-app" -version = "0.5.9-rcnet-v3.1" +version = "0.7.0" authors = ["siy"] edition = "2021" description = "Radix Babylon" diff --git a/doc/api.md b/doc/api.md index 68a447d3..e45fbf3b 100644 --- a/doc/api.md +++ b/doc/api.md @@ -4,23 +4,20 @@ All communication is performed using APDU protocol ([see APDU description](apdu. ## Overview -| API Name | Instruction Code | Description | -|---------------------------------------------------|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [GetAppVersion](#getappversion) | 0x10 | Get application version as 3 bytes, where each byte represents version component: __Major__, __Minor__ and __Patch Level__. | -| [GetDeviceModel](#getdevicemodel) | 0x11 | Get device model code byte. __0__ corresponds to Nano S, __1__ - Nano S Plus, __2__ - Nano X | -| [GetDeviceId](#getdeviceid) | 0x12 | Get device ID byte array (32 bytes) | -| [GetPubKeyEd25519](#getpubkeyed25519) | 0x21 | Get Ed25519 public key for provided derivation path. Derivation path must conform to CAP-26 SLIP 10 HD Derivation Path Scheme. | -| [GetPrivKeyEd25519](#getprivkeyed25519) | 0x22 | Get Ed25519 private key for provided derivation path. Derivation path must conform to CAP-26 SLIP 10 HD Derivation Path Scheme. Command available only in debug build. | -| [GetPubKeySecp256k1](#getpubkeysecp256k1) | 0x31 | Get Secp256k1 public key for provided derivation path. | -| [GetPrivKeySecp256k1](#getprivkeysecp256k1) | 0x32 | Get Secp256k1 private key for provided derivation path. Command available only in debug build. | -| [SignTxEd25519](#signtxed25519) | 0x41 | Sign transaction intent using Ed25519 curve and given derivation path. Derivation path must conform to CAP-26 SLIP 10 HD Derivation Path Scheme. Signing is done in "advanced mode", when every instruction from transaction intent is decoded and displayed to user. | -| [SignTxEd25519Summary](#signtxed25519summary) | 0x42 | Sign transaction intent using Ed25519 curve and given derivation path. Derivation path must conform to CAP-26 SLIP 10 HD Derivation Path Scheme. Signing is performed in "summary mode", when device tries to recognize known transaction format and provide summary for the transaction. | -| [SignTxSecp256k1](#signtxsecp256k1) | 0x51 | Sign transaction intent using Secp256k1 curve and given derivation path. Signing is done in "advanced mode", when every instruction from transaction intent is decoded and displayed to user. | -| [SignTxSecp256k1Smart](#signtxsecp256k1summary) | 0x52 | Sign transaction intent using Secp256k1 curve and given derivation path. Signing is performed in "summary mode", when device tries to recognize known transaction format and provide summary for the transaction. | -| [SignAuthEd25519](#signauthed25519) | 0x61 | Sign provided 32 bytes digest using Ed25519 curve and given derivation path. Derivation path must conform to CAP-26 SLIP 10 HD Derivation Path Scheme. | -| [SignAuthSecp256k1](#signauthsecp256k1) | 0x71 | Sign provided 32 bytes digest using Secp265k1 curve and given derivation path. | -| [VerifyAddressEd25519](#verifyaddressed25519) | 0x81 | Verify bech32m address for a given derivation path for Ed25519 curve. | -| [VerifyAddressSecp256k1](#verifyaddresssecp256k1) | 0x91 | Verify bech32m address for a given derivation path for Secp256k1 curve. | +| API Name | Instruction Code | Description | +|---------------------------------------------------|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [GetAppVersion](#getappversion) | 0x10 | Get application version as 3 bytes, where each byte represents version component: __Major__, __Minor__ and __Patch Level__. | +| [GetDeviceModel](#getdevicemodel) | 0x11 | Get device model code byte. __0__ corresponds to Nano S, __1__ - Nano S Plus, __2__ - Nano X | +| [GetDeviceId](#getdeviceid) | 0x12 | Get device ID byte array (32 bytes) | +| [GetAppSettingsId](#getappsettings) | 0x20 | Get application settings | +| [GetPubKeyEd25519](#getpubkeyed25519) | 0x21 | Get Ed25519 public key for provided derivation path. Derivation path must conform to CAP-26 SLIP 10 HD Derivation Path Scheme. | +| [GetPubKeySecp256k1](#getpubkeysecp256k1) | 0x31 | Get Secp256k1 public key for provided derivation path. | +| [SignTxEd25519](#signtxed25519) | 0x41 | Sign transaction intent using Ed25519 curve and given derivation path. Derivation path must conform to CAP-26 SLIP 10 HD Derivation Path Scheme. Signing is done in "advanced mode", when every instruction from transaction intent is decoded and displayed to user. | +| [SignTxSecp256k1](#signtxsecp256k1) | 0x51 | Sign transaction intent using Secp256k1 curve and given derivation path. Signing is done in "advanced mode", when every instruction from transaction intent is decoded and displayed to user. | +| [SignAuthEd25519](#signauthed25519) | 0x61 | Sign provided 32 bytes digest using Ed25519 curve and given derivation path. Derivation path must conform to CAP-26 SLIP 10 HD Derivation Path Scheme. | +| [SignAuthSecp256k1](#signauthsecp256k1) | 0x71 | Sign provided 32 bytes digest using Secp265k1 curve and given derivation path. | +| [VerifyAddressEd25519](#verifyaddressed25519) | 0x81 | Verify bech32m address for a given derivation path for Ed25519 curve. | +| [VerifyAddressSecp256k1](#verifyaddresssecp256k1) | 0x91 | Verify bech32m address for a given derivation path for Secp256k1 curve. | ## GetAppVersion @@ -72,37 +69,37 @@ Response (64 bytes): |-----------|----------------------| | byte 0-63 | Device ID byte array | -## GetPubKeyEd25519 -Get Ed25519 public key for provided derivation path. Derivation path should follow the format described in CAP-26 SLIP 10 HD Derivation Path Scheme. +## GetAppSettings + +Get application settings. APDU: -| CLA | INS | P1 | P2 | Data | -|------|------|------|------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 0xAA | 0x21 | 0x00 | 0x00 | Derivation path in the following format:
byte 0 - number of elements in derivation path
bytes 1-5 - first element of derivation path in big endian format
bytes 6-9 - second element of derivation path in big endian format
... - remaining elements of derivation path | +| CLA | INS | P1 | P2 | Data | +|------|------|------|------|------| +| 0xAA | 0x20 | 0x00 | 0x00 | None | -Response (32 bytes): +Response (2 bytes): -| Data | Description | -|-----------|--------------------| -| byte 0-31 | Ed25519 public key | - -## GetPrivKeyEd25519 -__WARNING!!!__ This command is available only in debug build and intended only for debugging purposes. Production build does not support this command and returns error code 0x6EFF (Not Implemented). +| Data | Description | +|--------|--------------------------------------------------------------------------------------| +| byte 0 | Verbose mode state
0 - Verbose mode disabled
1 - Verbose mode enabled | +| byte 1 | "Blind signing" state
0 - "Blind signing" disabled
1 - "Blind signing" enabled | -Get Ed25519 private key for provided derivation path. Derivation path should follow the format described in CAP-26 SLIP 10 HD Derivation Path Scheme. +## GetPubKeyEd25519 +Get Ed25519 public key for provided derivation path. Derivation path should follow the format described in CAP-26 SLIP 10 HD Derivation Path Scheme. APDU: | CLA | INS | P1 | P2 | Data | |------|------|------|------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 0xAA | 0x22 | 0x00 | 0x00 | Derivation path in the following format:
byte 0 - number of elements in derivation path
bytes 1-5 - first element of derivation path in big endian format
bytes 6-9 - second element of derivation path in big endian format
... - remaining elements of derivation path | +| 0xAA | 0x21 | 0x00 | 0x00 | Derivation path in the following format:
byte 0 - number of elements in derivation path
bytes 1-5 - first element of derivation path in big endian format
bytes 6-9 - second element of derivation path in big endian format
... - remaining elements of derivation path | Response (32 bytes): -| Data | Description | -|-----------|---------------------| -| byte 0-31 | Ed25519 private key | +| Data | Description | +|-----------|--------------------| +| byte 0-31 | Ed25519 public key | ## GetPubKeySecp256k1 @@ -123,70 +120,21 @@ Response (33 bytes): Returned key is a compressed key. First byte is always 0x02 or 0x03, depending on the parity of the y-coordinate. The remaining 32 bytes are the x-coordinate. -## GetPrivKeySecp256k1 -__WARNING!!!__ This command is available only in debug build and intended only for debugging purposes. Production build does not support this command and returns error code 0x6EFF (Not Implemented). - -Get Secp256k1 private key for provided derivation path. - -APDU: - -| CLA | INS | P1 | P2 | Data | -|------|------|------|------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 0xAA | 0x32 | 0x00 | 0x00 | Derivation path in the following format:
byte 0 - number of elements in derivation path
bytes 1-5 - first element of derivation path in big endian format
bytes 6-9 - second element of derivation path in big endian format
... - remaining elements of derivation path | - -Response (32 bytes): - -| Data | Description | -|-----------|-----------------------| -| byte 0-31 | Secp256k1 private key | - ## SignTxEd25519 Sign transaction intent using Ed25519 private key and derivation path. Derivation path should follow the format described in CAP-26 SLIP 10 HD Derivation Path Scheme. -Depending on the passed parameters the device may show transaction intent hash (digest) before asking user to sign transaction intent. - This command decodes transaction intent, retrieves instructions with their parameters and shows them to the user. Since decoded instruction and parameters may exceed available device resources, the information shown to user might be incomplete. This command is invoked in two steps: -- Send derivation path and "show digest" enable/disable parameter. -- Send transaction intent data (see below). This command can be sent one or more times, depending on the size of the transaction intent. The last chunk is accompanied with class byte set to `0xAC`. Other (intermediate) chunks are accompanied with class byte set to `0xAD`. - -APDU for derivation path: - -| CLA | INS | P1 | P2 | Data | -|------|------|-------------------------------------------------------------------------------|------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 0xAA | 0x41 | 0x00 - do not show digest before signing
0x01 - show digest before signing | 0x00 | Payload should contain derivation path in the following format:
byte 0 - number of elements in derivation path bytes 1-5 - first element of derivation path in big endian format bytes 6-9 - second element of derivation path in big endian format ... - remaining elements of derivation path | - -APDU for transaction intent data: - -| CLA | INS | P1 | P2 | Data | -|---------------------------------------------------------|------|------|------|-------------------------------| -| 0xAD - for intermediate chunks
0xAC - for last chunk | 0x41 | 0x00 | 0x00 | Transaction intent data chunk | - -Upon successful sign, the device returns the signature for the transaction intent. The signature is returned in the following format: - -| Data | Description | -|-------------|--------------------| -| byte 0-63 | Ed25519 signature | -| byte 64-95 | Ed25519 public key | -| byte 96-127 | Calculated digest | - -If user rejects the sign request, then the device returns error code 0x6e50 (User rejected the sign request). - -## SignTxEd25519Summary -Sign transaction intent using Ed25519 private key and derivation path. Derivation path should follow the format described in CAP-26 SLIP 10 HD Derivation Path Scheme. -This command receives expected transaction type from the host and tries to match incoming transaction intent against specified transaction type and show relevant elements of the transaction to the user. If transaction type does not match, this command warns user and shows calculated transaction intent hash (digest). - -This command is invoked in two steps: -- Send derivation path and expected transaction type. +- Send derivation path. - Send transaction intent data (see below). This command can be sent one or more times, depending on the size of the transaction intent. The last chunk is accompanied with class byte set to `0xAC`. Other (intermediate) chunks are accompanied with class byte set to `0xAD`. APDU for derivation path: -| CLA | INS | P1 | P2 | Data | -|------|------|-----------------|------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 0xAA | 0x41 | 0x00 - Transfer | 0x00 | Payload should contain derivation path in the following format:
byte 0 - number of elements in derivation path
bytes 1-5 - first element of derivation path in big endian format
bytes 6-9 - second element of derivation path in big endian format
... - remaining elements of derivation path | +| CLA | INS | P1 | P2 | Data | +|------|------|------|------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 0xAA | 0x41 | 0x00 | 0x00 | Payload should contain derivation path in the following format:
byte 0 - number of elements in derivation path bytes 1-5 - first element of derivation path in big endian format bytes 6-9 - second element of derivation path in big endian format ... - remaining elements of derivation path | APDU for transaction intent data: @@ -207,49 +155,17 @@ If user rejects the sign request, then the device returns error code 0x6e50 (Use ## SignTxSecp256k1 Sign transaction intent using Secp256k1 private key and derivation path. -Depending on the passed parameters the device may show transaction intent hash (digest) before asking user to sign transaction intent. - This command decodes transaction intent, retrieves instructions with their parameters and shows them to the user. Since decoded instruction and parameters may exceed available device resources, the information shown to user might be incomplete. This command is invoked in two steps: -- Send derivation path and "show digest" enable/disable parameter. +- Send derivation path. - Send transaction intent data (see below). This command can be sent one or more times, depending on the size of the transaction intent. The last chunk is accompanied with class byte set to `0xAC`. Other (intermediate) chunks are accompanied with class byte set to `0xAD`. APDU for derivation path: -| CLA | INS | P1 | P2 | Data | -|------|------|-------------------------------------------------------------------------------|------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 0xAA | 0x51 | 0x00 - do not show digest before signing
0x01 - show digest before signing | 0x00 | Derivation path in the following format:
byte 0 - number of elements in derivation path
bytes 1-5 - first element of derivation path in big endian format
bytes 6-9 - second element of derivation path in big endian format
... - remaining elements of derivation path | - -APDU for transaction intent data: - -| CLA | INS | P1 | P2 | Data | -|---------------------------------------------------------|------|------|------|-------------------------------| -| 0xAD - for intermediate chunks
0xAC - for last chunk | 0x41 | 0x00 | 0x00 | Transaction intent data chunk | - -Upon successful sign, the device returns the signature for the transaction intent. The signature is returned in the following format: - -| Data | Description | -|-------------|----------------------| -| byte 0-64 | Secp256k1 signature | -| byte 65-97 | Secp256k1 public key | -| byte 98-130 | Calculated digest | - -If user rejects the sign request, then the device returns error code 0x6e50 (User rejected the sign request). - -## SignTxSecp256k1Summary -Sign transaction intent using Secp256k1 private key and derivation path. This command -receives expected transaction type from the host and tries to match incoming transaction intent against specified transaction type and show relevant elements of the transaction to the user. If transaction type does not match, this command warns user and shows calculated transaction intent hash (digest). - -This command is invoked in two steps: -- Send derivation path and expected transaction type. -- Send transaction intent data (see below). This command can be sent one or more times, depending on the size of the transaction intent. The last chunk is accompanied with class byte set to `0xAC`. Other (intermediate) chunks are accompanied with class byte set to `0xAD`. - -- APDU for derivation path: - -| CLA | INS | P1 | P2 | Data | -|------|------|-----------------|------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 0xAA | 0x51 | 0x00 - Transfer | 0x00 | Derivation path in the following format:
byte 0 - number of elements in derivation path
bytes 1-5 - first element of derivation path in big endian format
bytes 6-9 - second element of derivation path in big endian format
... - remaining elements of derivation path | +| CLA | INS | P1 | P2 | Data | +|------|------|------|------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 0xAA | 0x51 | 0x00 | 0x00 | Derivation path in the following format:
byte 0 - number of elements in derivation path
bytes 1-5 - first element of derivation path in big endian format
bytes 6-9 - second element of derivation path in big endian format
... - remaining elements of derivation path | APDU for transaction intent data: diff --git a/src/app_error.rs b/src/app_error.rs index 05015ad0..507efbba 100644 --- a/src/app_error.rs +++ b/src/app_error.rs @@ -37,6 +37,7 @@ pub enum AppError { BadTxSignType = 0x6e35, BadTxSignDigestState = 0x6e36, BadTxSignRequestedState = 0x6e37, + BadTxSignHashSignState = 0x6e38, // Hash signing is disabled BadTxSignDecoderErrorInvalidInput = 0x6e41, BadTxSignDecoderErrorInvalidLen = 0x6e42, diff --git a/src/command.rs b/src/command.rs index 86a15c85..c328e836 100644 --- a/src/command.rs +++ b/src/command.rs @@ -3,17 +3,14 @@ use nanos_sdk::io::ApduHeader; #[repr(u8)] pub enum Command { - GetAppVersion, GetDeviceModel, GetDeviceId, + GetAppVersion, + GetAppSettings, GetPubKeyEd25519, - GetPrivKeyEd25519, GetPubKeySecp256k1, - GetPrivKeySecp256k1, SignTxEd25519, - SignTxEd25519Summary, SignTxSecp256k1, - SignTxSecp256k1Summary, SignAuthEd25519, SignAuthSecp256k1, VerifyAddressEd25519, @@ -29,14 +26,11 @@ impl TryFrom for Command { 0x10 => Ok(Command::GetAppVersion), 0x11 => Ok(Command::GetDeviceModel), 0x12 => Ok(Command::GetDeviceId), + 0x22 => Ok(Command::GetAppSettings), 0x21 => Ok(Command::GetPubKeyEd25519), - 0x22 => Ok(Command::GetPrivKeyEd25519), 0x31 => Ok(Command::GetPubKeySecp256k1), - 0x32 => Ok(Command::GetPrivKeySecp256k1), 0x41 => Ok(Command::SignTxEd25519), - 0x42 => Ok(Command::SignTxEd25519Summary), 0x51 => Ok(Command::SignTxSecp256k1), - 0x52 => Ok(Command::SignTxSecp256k1Summary), 0x61 => Ok(Command::SignAuthEd25519), 0x71 => Ok(Command::SignAuthSecp256k1), 0x81 => Ok(Command::VerifyAddressEd25519), diff --git a/src/crypto/address_verifier.rs b/src/crypto/address_verifier.rs index 1dcb3c81..5a82c615 100644 --- a/src/crypto/address_verifier.rs +++ b/src/crypto/address_verifier.rs @@ -1,5 +1,5 @@ -use crate::sign::tx_state::info_message; use crate::ui::multipage_validator::MultipageValidator; +use crate::ui::utils::info_message; use nanos_sdk::io::Comm; use sbor::bech32::address::Address; use sbor::bech32::encoder::Bech32; diff --git a/src/handler.rs b/src/handler.rs index bf55324b..34d31eb0 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,9 +1,8 @@ pub mod dispatcher; +mod get_app_settings; mod get_app_version; mod get_device_id; mod get_device_model; -mod get_private_key_ed25519; -mod get_private_key_secp256k1; mod get_public_key_ed25519; mod get_public_key_secp256k1; mod params_zero; @@ -11,8 +10,6 @@ mod process_sign_outcome; mod sign_auth_ed25519; mod sign_auth_secp256k1; mod sign_tx_ed25519; -mod sign_tx_ed25519_summary; mod sign_tx_secp256k1; -mod sign_tx_secp256k1_summary; mod verify_address_ed25519; mod verify_address_secp256k1; diff --git a/src/handler/dispatcher.rs b/src/handler/dispatcher.rs index 5d07bd90..c590a02a 100644 --- a/src/handler/dispatcher.rs +++ b/src/handler/dispatcher.rs @@ -15,16 +15,13 @@ pub fn dispatcher( match ins { Command::GetAppVersion => get_app_version::handle(comm), + Command::GetAppSettings => get_app_settings::handle(comm, state), Command::GetDeviceModel => get_device_model::handle(comm), Command::GetDeviceId => get_device_id::handle(comm), Command::GetPubKeyEd25519 => get_public_key_ed25519::handle(comm), - Command::GetPrivKeyEd25519 => get_private_key_ed25519::handle(comm), Command::GetPubKeySecp256k1 => get_public_key_secp256k1::handle(comm), - Command::GetPrivKeySecp256k1 => get_private_key_secp256k1::handle(comm), Command::SignTxEd25519 => sign_tx_ed25519::handle(comm, class, state), - Command::SignTxEd25519Summary => sign_tx_ed25519_summary::handle(comm, class, state), Command::SignTxSecp256k1 => sign_tx_secp256k1::handle(comm, class, state), - Command::SignTxSecp256k1Summary => sign_tx_secp256k1_summary::handle(comm, class, state), Command::SignAuthEd25519 => sign_auth_ed25519::handle(comm, class, state), Command::SignAuthSecp256k1 => sign_auth_secp256k1::handle(comm, class, state), Command::VerifyAddressEd25519 => verify_address_ed25519::handle(comm), diff --git a/src/handler/get_app_settings.rs b/src/handler/get_app_settings.rs new file mode 100644 index 00000000..ae2a5a42 --- /dev/null +++ b/src/handler/get_app_settings.rs @@ -0,0 +1,7 @@ +use crate::app_error::AppError; +use crate::sign::tx_state::TxState; +use nanos_sdk::io::Comm; + +pub fn handle(comm: &mut Comm, state: &mut TxState) -> Result<(), AppError> { + state.send_settings(comm) +} diff --git a/src/handler/sign_auth_ed25519.rs b/src/handler/sign_auth_ed25519.rs index 95b8ebc1..662779d5 100644 --- a/src/handler/sign_auth_ed25519.rs +++ b/src/handler/sign_auth_ed25519.rs @@ -2,8 +2,8 @@ use nanos_sdk::io::Comm; use crate::app_error::AppError; use crate::command_class::CommandClass; +use crate::crypto::curves::Curve; use crate::handler::process_sign_outcome::process_sign_outcome; -use crate::sign::sign_type::SignType; use crate::sign::tx_state::TxState; pub fn handle( @@ -12,6 +12,6 @@ pub fn handle( state: &mut TxState, ) -> Result<(), AppError> { state - .process_sign(comm, class, SignType::AuthEd25519) + .sign_auth(comm, class, Curve::Ed25519) .and_then(|outcome| process_sign_outcome(outcome)) } diff --git a/src/handler/sign_auth_secp256k1.rs b/src/handler/sign_auth_secp256k1.rs index 728118ba..fa227359 100644 --- a/src/handler/sign_auth_secp256k1.rs +++ b/src/handler/sign_auth_secp256k1.rs @@ -2,8 +2,8 @@ use nanos_sdk::io::Comm; use crate::app_error::AppError; use crate::command_class::CommandClass; +use crate::crypto::curves::Curve; use crate::handler::process_sign_outcome::process_sign_outcome; -use crate::sign::sign_type::SignType; use crate::sign::tx_state::TxState; pub fn handle( @@ -12,6 +12,6 @@ pub fn handle( state: &mut TxState, ) -> Result<(), AppError> { state - .process_sign(comm, class, SignType::AuthSecp256k1) + .sign_auth(comm, class, Curve::Secp256k1) .and_then(|outcome| process_sign_outcome(outcome)) } diff --git a/src/handler/sign_tx_ed25519.rs b/src/handler/sign_tx_ed25519.rs index a8bf2c83..4e44448f 100644 --- a/src/handler/sign_tx_ed25519.rs +++ b/src/handler/sign_tx_ed25519.rs @@ -2,8 +2,8 @@ use nanos_sdk::io::Comm; use crate::app_error::AppError; use crate::command_class::CommandClass; +use crate::crypto::curves::Curve; use crate::handler::process_sign_outcome::process_sign_outcome; -use crate::sign::sign_type::SignType; use crate::sign::tx_state::TxState; pub fn handle( @@ -12,6 +12,6 @@ pub fn handle( state: &mut TxState, ) -> Result<(), AppError> { state - .process_sign(comm, class, SignType::Ed25519) + .sign_tx(comm, class, Curve::Ed25519) .and_then(|outcome| process_sign_outcome(outcome)) } diff --git a/src/handler/sign_tx_ed25519_summary.rs b/src/handler/sign_tx_ed25519_summary.rs deleted file mode 100644 index bc38d866..00000000 --- a/src/handler/sign_tx_ed25519_summary.rs +++ /dev/null @@ -1,22 +0,0 @@ -use nanos_sdk::io::Comm; - -use crate::app_error::AppError; -use crate::command_class::CommandClass; -use crate::handler::process_sign_outcome::process_sign_outcome; -use crate::sign::sign_type::SignType; -use crate::sign::tx_state::TxState; - -pub fn handle( - comm: &mut Comm, - class: CommandClass, - state: &mut TxState, -) -> Result<(), AppError> { - state - .process_sign_summary( - comm, - class, - SignType::Ed25519Summary, - comm.get_apdu_metadata().p1.into(), - ) - .and_then(|outcome| process_sign_outcome(outcome)) -} diff --git a/src/handler/sign_tx_secp256k1.rs b/src/handler/sign_tx_secp256k1.rs index f439f9aa..0a420b97 100644 --- a/src/handler/sign_tx_secp256k1.rs +++ b/src/handler/sign_tx_secp256k1.rs @@ -2,8 +2,8 @@ use nanos_sdk::io::Comm; use crate::app_error::AppError; use crate::command_class::CommandClass; +use crate::crypto::curves::Curve; use crate::handler::process_sign_outcome::process_sign_outcome; -use crate::sign::sign_type::SignType; use crate::sign::tx_state::TxState; pub fn handle( @@ -12,6 +12,6 @@ pub fn handle( state: &mut TxState, ) -> Result<(), AppError> { state - .process_sign(comm, class, SignType::Secp256k1) + .sign_tx(comm, class, Curve::Secp256k1) .and_then(|outcome| process_sign_outcome(outcome)) } diff --git a/src/handler/sign_tx_secp256k1_summary.rs b/src/handler/sign_tx_secp256k1_summary.rs deleted file mode 100644 index 3dddebc0..00000000 --- a/src/handler/sign_tx_secp256k1_summary.rs +++ /dev/null @@ -1,22 +0,0 @@ -use nanos_sdk::io::Comm; - -use crate::app_error::AppError; -use crate::command_class::CommandClass; -use crate::handler::process_sign_outcome::process_sign_outcome; -use crate::sign::sign_type::SignType; -use crate::sign::tx_state::TxState; - -pub fn handle( - comm: &mut Comm, - class: CommandClass, - state: &mut TxState, -) -> Result<(), AppError> { - state - .process_sign_summary( - comm, - class, - SignType::Secp256k1Summary, - comm.get_apdu_metadata().p1.into(), - ) - .and_then(|outcome| process_sign_outcome(outcome)) -} diff --git a/src/main.rs b/src/main.rs index 406b1018..60513b5b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,17 +7,19 @@ #![feature(const_mut_refs)] use nanos_sdk::io::{Comm, Event}; -use nanos_ui::bagls::{CERTIFICATE_ICON, DASHBOARD_X_ICON, PROCESSING_ICON}; +use nanos_ui::bagls::{CERTIFICATE_ICON, COGGLE_ICON, DASHBOARD_X_ICON, PROCESSING_ICON}; use nanos_ui::ui::clear_screen; use handler::dispatcher; use crate::app_error::AppError; use crate::ledger_display_io::LedgerTTY; +use crate::settings::Settings; use crate::sign::tx_state::TxState; -use crate::ui::menu::{Menu, MenuItem}; +use crate::ui::menu::{Menu, MenuFeature, MenuItem}; +use crate::ui::multipage_validator::MultipageValidator; use crate::ui::single_message::SingleMessage; -use crate::ui::utils::RADIX_LOGO_ICON; +use crate::ui::utils::{BACK_ICON, RADIX_LOGO_ICON}; mod app_error; mod command; @@ -25,6 +27,7 @@ mod command_class; mod crypto; mod handler; mod ledger_display_io; +mod settings; mod sign; mod ui; mod utilities; @@ -38,19 +41,95 @@ const APPLICATION_ABOUT: &str = concat!( ); const APPLICATION_VERSION: &str = concat!("\n", env!("CARGO_PKG_VERSION"), "\n",); -fn app_menu_action() {} +fn app_menu_action() -> bool { + false +} -fn version_menu_action() { +fn version_menu_action() -> bool { clear_screen(); SingleMessage::new(APPLICATION_VERSION).show_and_wait(); + false +} + +fn get_verbose_mode_state() -> bool { + Settings::get().verbose_mode +} + +fn get_blind_signing_state() -> bool { + Settings::get().blind_signing +} + +fn settings_menu_action() -> bool { + clear_screen(); + + let menu = [ + MenuItem::new( + MenuFeature::OnOffState(get_verbose_mode_state), + "\nVerbose Mode", + verbose_mode_setting_action, + ), + MenuItem::new( + MenuFeature::OnOffState(get_blind_signing_state), + "\nBlind Signing", + blind_signing_setting_action, + ), + MenuItem::new( + MenuFeature::Icon(&BACK_ICON), + "\nBack", + back_from_setting_action, + ), + ]; + + Menu::new(&menu).event_loop(); + + false +} + +fn verbose_mode_setting_action() -> bool { + clear_screen(); + + Settings { + verbose_mode: MultipageValidator::new( + &[&"Set Verbose", &"Mode"], + &[&"Enable"], + &[&"Disable"], + ) + .ask(), + blind_signing: get_blind_signing_state(), + } + .update(); + + false +} + +fn blind_signing_setting_action() -> bool { + clear_screen(); + + Settings { + verbose_mode: get_verbose_mode_state(), + blind_signing: MultipageValidator::new( + &[&"Set Blind", &"Signing"], + &[&"Enable"], + &[&"Disable"], + ) + .ask(), + } + .update(); + + false +} + +fn back_from_setting_action() -> bool { + true } -fn about_menu_action() { +fn about_menu_action() -> bool { clear_screen(); SingleMessage::new(APPLICATION_ABOUT).show_and_wait(); + false } -fn quit_menu_action() { +fn quit_menu_action() -> bool { clear_screen(); nanos_sdk::exit_app(0); } @@ -58,10 +137,31 @@ fn quit_menu_action() { #[no_mangle] extern "C" fn sample_main() { let menu = [ - MenuItem::new(&RADIX_LOGO_ICON, "\nRadix Babylon", app_menu_action), - MenuItem::new(&PROCESSING_ICON, "\nVersion", version_menu_action), - MenuItem::new(&CERTIFICATE_ICON, "\nAbout", about_menu_action), - MenuItem::new(&DASHBOARD_X_ICON, "\nQuit", quit_menu_action), + MenuItem::new( + MenuFeature::Icon(&RADIX_LOGO_ICON), + "\nRadix Babylon", + app_menu_action, + ), + MenuItem::new( + MenuFeature::Icon(&PROCESSING_ICON), + "\nVersion", + version_menu_action, + ), + MenuItem::new( + MenuFeature::Icon(&COGGLE_ICON), + "\nSettings", + settings_menu_action, + ), + MenuItem::new( + MenuFeature::Icon(&CERTIFICATE_ICON), + "\nAbout", + about_menu_action, + ), + MenuItem::new( + MenuFeature::Icon(&DASHBOARD_X_ICON), + "\nQuit", + quit_menu_action, + ), ]; let mut comm = Comm::new(); let mut state = TxState::new(LedgerTTY::new_tty()); @@ -74,7 +174,7 @@ extern "C" fn sample_main() { let event = comm.next_event(); match event { - Event::Button(button_event) => main_menu.handle(button_event), + Event::Button(button_event) => _ = main_menu.handle(button_event), Event::Command(ins) => { match dispatcher::dispatcher(&mut comm, ins, &mut state) { Ok(()) => comm.reply_ok(), diff --git a/src/settings.rs b/src/settings.rs new file mode 100644 index 00000000..f6ce7a87 --- /dev/null +++ b/src/settings.rs @@ -0,0 +1,43 @@ +use nanos_sdk::nvm::{AtomicStorage, SingleStorage}; +use nanos_sdk::Pic; + +const BIT_VERBOSE_MODE: u32 = 0x01; +const BIT_HASH_SIGN: u32 = 0x02; + +// Note that bits are stored in inverse mode (0 = true, 1 = false) +#[link_section = ".nvm_data"] +static mut SETTINGS: Pic> = Pic::new(AtomicStorage::new(&3)); + +pub struct Settings { + pub verbose_mode: bool, + pub blind_signing: bool, +} + +impl Settings { + pub fn get() -> Self { + let settings = unsafe { SETTINGS.get_mut() }; + let value = *settings.get_ref(); + + Settings { + verbose_mode: (value & BIT_VERBOSE_MODE) == 0, + blind_signing: (value & BIT_HASH_SIGN) == 0, + } + } + + pub fn update(&self) { + let settings = unsafe { SETTINGS.get_mut() }; + let value = (!self.verbose_mode as u32 * BIT_VERBOSE_MODE) + | (!self.blind_signing as u32 * BIT_HASH_SIGN); + + if value != *settings.get_ref() { + settings.update(&value); + } + } + + pub fn as_bytes(&self) -> [u8; 2] { + [ + [0x00, 0x01][self.verbose_mode as usize], + [0x00, 0x01][self.blind_signing as usize], + ] + } +} diff --git a/src/sign.rs b/src/sign.rs index ea556317..b185e4da 100644 --- a/src/sign.rs +++ b/src/sign.rs @@ -1,5 +1,5 @@ pub mod instruction_processor; +pub mod sign_mode; pub mod sign_outcome; -pub mod sign_type; pub mod signing_flow_state; pub mod tx_state; diff --git a/src/sign/instruction_processor.rs b/src/sign/instruction_processor.rs index d9037c9e..43678128 100644 --- a/src/sign/instruction_processor.rs +++ b/src/sign/instruction_processor.rs @@ -15,8 +15,8 @@ use sbor::sbor_decoder::{SborEvent, SborEventHandler}; use crate::app_error::AppError; use crate::command_class::CommandClass; use crate::crypto::hash::Blake2bHasher; +use crate::sign::sign_mode::SignMode; use crate::sign::sign_outcome::SignOutcome; -use crate::sign::sign_type::SignType; use crate::sign::signing_flow_state::SigningFlowState; pub struct InstructionProcessor { @@ -53,10 +53,10 @@ impl InstructionProcessor { pub fn sign_tx( &self, comm: &mut Comm, - tx_type: SignType, + sign_mode: SignMode, digest: &Digest, ) -> Result { - self.state.sign_tx(comm, tx_type, digest) + self.state.sign_tx(comm, sign_mode, digest) } pub fn auth_digest( @@ -73,12 +73,12 @@ impl InstructionProcessor { } pub fn set_network(&mut self) -> Result<(), AppError> { - match self.state.sign_type() { - SignType::Ed25519 | SignType::Ed25519Summary | SignType::AuthEd25519 => { + match self.state.sign_mode() { + SignMode::Ed25519Verbose | SignMode::Ed25519Summary | SignMode::AuthEd25519 => { let network_id = self.state.network_id()?; self.printer.set_network(network_id); } - SignType::Secp256k1 | SignType::Secp256k1Summary | SignType::AuthSecp256k1 => { + SignMode::Secp256k1Verbose | SignMode::Secp256k1Summary | SignMode::AuthSecp256k1 => { self.printer.set_network(NetworkId::OlympiaMainNet); } }; @@ -86,14 +86,14 @@ impl InstructionProcessor { } pub fn set_show_instructions(&mut self) { - match self.state.sign_type() { - SignType::Secp256k1Summary - | SignType::Ed25519Summary - | SignType::AuthEd25519 - | SignType::AuthSecp256k1 => { + match self.state.sign_mode() { + SignMode::Secp256k1Summary + | SignMode::Ed25519Summary + | SignMode::AuthEd25519 + | SignMode::AuthSecp256k1 => { self.printer.set_show_instructions(false); } - SignType::Secp256k1 | SignType::Ed25519 => { + SignMode::Secp256k1Verbose | SignMode::Ed25519Verbose => { self.printer.set_show_instructions(true); } }; @@ -119,15 +119,15 @@ impl InstructionProcessor { &mut self, comm: &mut Comm, class: CommandClass, - tx_type: SignType, + sign_mode: SignMode, ) -> Result<(), AppError> { match class { CommandClass::Regular => { - self.state.init_sign(comm, tx_type)?; + self.state.init_sign(comm, sign_mode)?; self.calculator.start() } CommandClass::Continuation | CommandClass::LastData => { - self.state.continue_sign(comm, class, tx_type) + self.state.continue_sign(comm, class, sign_mode) } } } diff --git a/src/sign/sign_type.rs b/src/sign/sign_mode.rs similarity index 67% rename from src/sign/sign_type.rs rename to src/sign/sign_mode.rs index a6c7fac8..9d05a890 100644 --- a/src/sign/sign_type.rs +++ b/src/sign/sign_mode.rs @@ -1,9 +1,9 @@ #[repr(u8)] #[derive(PartialEq, Copy, Clone)] -pub enum SignType { - Ed25519, +pub enum SignMode { + Ed25519Verbose, Ed25519Summary, - Secp256k1, + Secp256k1Verbose, Secp256k1Summary, AuthEd25519, AuthSecp256k1, diff --git a/src/sign/signing_flow_state.rs b/src/sign/signing_flow_state.rs index 97d777c5..840e4509 100644 --- a/src/sign/signing_flow_state.rs +++ b/src/sign/signing_flow_state.rs @@ -7,12 +7,12 @@ use crate::command_class::CommandClass; use crate::crypto::bip32::Bip32Path; use crate::crypto::ed25519::KeyPair25519; use crate::crypto::secp256k1::KeyPairSecp256k1; +use crate::sign::sign_mode::SignMode; use crate::sign::sign_outcome::SignOutcome; -use crate::sign::sign_type::SignType; #[repr(align(4))] pub struct SigningFlowState { - sign_type: SignType, + sign_mode: SignMode, tx_packet_count: u32, tx_size: usize, path: Bip32Path, @@ -21,7 +21,7 @@ pub struct SigningFlowState { impl SigningFlowState { pub fn new() -> Self { Self { - sign_type: SignType::Ed25519, + sign_mode: SignMode::Ed25519Verbose, tx_packet_count: 0, tx_size: 0, path: Bip32Path::new(0), @@ -32,26 +32,26 @@ impl SigningFlowState { &mut self, comm: &mut Comm, class: CommandClass, - tx_type: SignType, + sign_mode: SignMode, ) -> Result<(), AppError> { - self.validate(class, tx_type)?; + self.validate(class, sign_mode)?; let data = comm.get_data()?; self.update_counters(data.len()); Ok(()) } - pub fn init_sign(&mut self, comm: &mut Comm, tx_type: SignType) -> Result<(), AppError> { - let path = match tx_type { - SignType::Ed25519 | SignType::Ed25519Summary | SignType::AuthEd25519 => { + pub fn init_sign(&mut self, comm: &mut Comm, sign_mode: SignMode) -> Result<(), AppError> { + let path = match sign_mode { + SignMode::Ed25519Verbose | SignMode::Ed25519Summary | SignMode::AuthEd25519 => { Bip32Path::read_cap26(comm) } - SignType::Secp256k1 | SignType::Secp256k1Summary | SignType::AuthSecp256k1 => { + SignMode::Secp256k1Verbose | SignMode::Secp256k1Summary | SignMode::AuthSecp256k1 => { Bip32Path::read_olympia(comm) } }?; - self.start(tx_type, path); + self.start(sign_mode, path); self.update_counters(0); // First packet contains no data Ok(()) } @@ -67,19 +67,19 @@ impl SigningFlowState { } #[inline(always)] - pub fn sign_type(&self) -> SignType { - self.sign_type + pub fn sign_mode(&self) -> SignMode { + self.sign_mode } pub fn reset(&mut self) { self.tx_packet_count = 0; self.tx_size = 0; - self.sign_type = SignType::Ed25519; + self.sign_mode = SignMode::Ed25519Summary; self.path = Bip32Path::new(0); } - fn start(&mut self, sign_type: SignType, path: Bip32Path) { - self.sign_type = sign_type; + fn start(&mut self, sign_mode: SignMode, path: Bip32Path) { + self.sign_mode = sign_mode; self.path = path; } @@ -88,9 +88,9 @@ impl SigningFlowState { self.tx_packet_count != 0 } - fn validate(&self, class: CommandClass, sign_type: SignType) -> Result<(), AppError> { + fn validate(&self, class: CommandClass, sign_mode: SignMode) -> Result<(), AppError> { if self.sign_started() { - self.validate_intermediate(class, sign_type) + self.validate_intermediate(class, sign_mode) } else { self.validate_initial(class) } @@ -99,9 +99,9 @@ impl SigningFlowState { fn validate_intermediate( &self, class: CommandClass, - sign_type: SignType, + sign_mode: SignMode, ) -> Result<(), AppError> { - if self.sign_type != sign_type { + if self.sign_mode != sign_mode { return Err(AppError::BadTxSignType); } @@ -122,16 +122,16 @@ impl SigningFlowState { pub fn sign_tx( &self, comm: &mut Comm, - tx_type: SignType, + sign_mode: SignMode, digest: &Digest, ) -> Result { - match tx_type { - SignType::Ed25519 | SignType::Ed25519Summary | SignType::AuthEd25519 => { + match sign_mode { + SignMode::Ed25519Verbose | SignMode::Ed25519Summary | SignMode::AuthEd25519 => { KeyPair25519::derive(&self.path) .and_then(|keypair| keypair.sign(comm, digest.as_bytes())) } - SignType::Secp256k1 | SignType::Secp256k1Summary | SignType::AuthSecp256k1 => { + SignMode::Secp256k1Verbose | SignMode::Secp256k1Summary | SignMode::AuthSecp256k1 => { KeyPairSecp256k1::derive(&self.path) .and_then(|keypair| keypair.sign(comm, digest.as_bytes())) } diff --git a/src/sign/tx_state.rs b/src/sign/tx_state.rs index 536b7d5a..1d853773 100644 --- a/src/sign/tx_state.rs +++ b/src/sign/tx_state.rs @@ -11,21 +11,15 @@ use sbor::utilities::conversion::{lower_as_hex, upper_as_hex}; use crate::app_error::AppError; use crate::command_class::CommandClass; +use crate::crypto::curves::Curve; +use crate::settings::Settings; use crate::sign::instruction_processor::InstructionProcessor; +use crate::sign::sign_mode::SignMode; use crate::sign::sign_outcome::SignOutcome; -use crate::sign::sign_type::SignType; use crate::ui::multiline_scroller::MultilineMessageScroller; use crate::ui::multipage_validator::MultipageValidator; use crate::ui::single_message::SingleMessage; - -pub fn info_message(title: &[u8], message: &[u8]) { - MultilineMessageScroller::with_title( - core::str::from_utf8(title).unwrap(), - core::str::from_utf8(message).unwrap(), - true, - ) - .event_loop(); -} +use crate::ui::utils; const CHALLENGE_LENGTH: usize = 32; const DAPP_ADDRESS_LENGTH: usize = 70; @@ -37,7 +31,6 @@ const MIN_VALID_LENGTH: usize = CHALLENGE_LENGTH + MIN_DAPP_ADDRESS_LENGTH + MIN pub struct TxState { decoder: SborDecoder, processor: InstructionProcessor, - show_digest: bool, } impl TxState { @@ -45,7 +38,6 @@ impl TxState { Self { decoder: SborDecoder::new(true), processor: InstructionProcessor::new(tty), - show_digest: false, } } @@ -54,23 +46,49 @@ impl TxState { self.decoder.reset(); } - pub fn process_sign( + pub fn send_settings(&self, comm: &mut Comm) -> Result<(), AppError> { + Ok(comm.append(&Settings::get().as_bytes())) + } + + pub fn sign_auth( &mut self, comm: &mut Comm, class: CommandClass, - tx_type: SignType, + curve: Curve, ) -> Result { - self.process_sign_summary(comm, class, tx_type, TxIntentType::General) + let sign_mode = match curve { + Curve::Ed25519 => SignMode::AuthEd25519, + Curve::Secp256k1 => SignMode::AuthSecp256k1, + }; + self.process_sign_with_mode(comm, class, sign_mode, TxIntentType::General) } - pub fn process_sign_summary( + pub fn sign_tx( &mut self, comm: &mut Comm, class: CommandClass, - tx_type: SignType, + curve: Curve, + ) -> Result { + let settings = Settings::get(); + + let sign_mode = match (curve, settings.verbose_mode) { + (Curve::Ed25519, true) => SignMode::Ed25519Verbose, + (Curve::Secp256k1, true) => SignMode::Secp256k1Verbose, + (Curve::Ed25519, false) => SignMode::Ed25519Summary, + (Curve::Secp256k1, false) => SignMode::Secp256k1Summary, + }; + + self.process_sign_with_mode(comm, class, sign_mode, TxIntentType::Transfer) + } + + pub fn process_sign_with_mode( + &mut self, + comm: &mut Comm, + class: CommandClass, + sign_mode: SignMode, intent_type: TxIntentType, ) -> Result { - let result = self.process_sign_internal(comm, class, tx_type, intent_type); + let result = self.process_sign_internal(comm, class, sign_mode, intent_type); match result { Ok(outcome) => match outcome { @@ -91,30 +109,25 @@ impl TxState { &mut self, comm: &mut Comm, class: CommandClass, - tx_type: SignType, + sign_mode: SignMode, intent_type: TxIntentType, ) -> Result { if class == CommandClass::Regular { self.reset(); self.processor.set_intent_type(intent_type); - self.processor.process_sign(comm, class, tx_type)?; + self.processor.process_sign(comm, class, sign_mode)?; self.processor.set_network()?; self.processor.set_show_instructions(); - self.show_digest = match tx_type { - SignType::Ed25519 | SignType::Secp256k1 => comm.get_apdu_metadata().p1 == 1, - SignType::Ed25519Summary | SignType::Secp256k1Summary => false, - SignType::AuthEd25519 | SignType::AuthSecp256k1 => false, - }; - self.show_introductory_screen(tx_type)?; + self.show_introductory_screen(sign_mode)?; } else { - self.processor.process_sign(comm, class, tx_type)?; + self.processor.process_sign(comm, class, sign_mode)?; - match tx_type { - SignType::AuthEd25519 | SignType::AuthSecp256k1 => { + match sign_mode { + SignMode::AuthEd25519 | SignMode::AuthSecp256k1 => { return if class != CommandClass::LastData { Err(AppError::BadAuthSignSequence) } else { - self.process_sign_auth(comm, tx_type) + self.process_sign_auth(comm, sign_mode) } } _ => self.decode_tx_intent(comm.get_data()?, class)?, @@ -122,19 +135,19 @@ impl TxState { } if class == CommandClass::LastData { - self.finalize_sign_tx(comm, tx_type) + self.finalize_sign_tx(comm, sign_mode) } else { Ok(SignOutcome::SendNextPacket) } } - fn show_introductory_screen(&mut self, tx_type: SignType) -> Result<(), AppError> { - let text = match tx_type { - SignType::Ed25519 - | SignType::Secp256k1 - | SignType::Ed25519Summary - | SignType::Secp256k1Summary => "Review\n\nTransaction", - SignType::AuthEd25519 | SignType::AuthSecp256k1 => "Review\nOwnership\nProof", + fn show_introductory_screen(&mut self, sign_mode: SignMode) -> Result<(), AppError> { + let text = match sign_mode { + SignMode::Ed25519Verbose + | SignMode::Secp256k1Verbose + | SignMode::Ed25519Summary + | SignMode::Secp256k1Summary => "Review\n\nTransaction", + SignMode::AuthEd25519 | SignMode::AuthSecp256k1 => "Review\nOwnership\nProof", }; SingleMessage::with_right_arrow(text).show_and_wait(); @@ -145,7 +158,7 @@ impl TxState { fn process_sign_auth( &mut self, comm: &mut Comm, - tx_type: SignType, + sign_mode: SignMode, ) -> Result { let value = comm.get_data()?; @@ -166,15 +179,15 @@ impl TxState { nonce_hex[i * 2 + 1] = lower_as_hex(byte); } - info_message(b"Origin:", origin); - info_message(b"dApp Address:", address); - info_message(b"Nonce:", &nonce_hex); + utils::info_message(b"Origin:", origin); + utils::info_message(b"dApp Address:", address); + utils::info_message(b"Nonce:", &nonce_hex); let rc = MultipageValidator::new(&[&"Sign Proof?"], &[&"Sign"], &[&"Reject"]).ask(); if rc { let digest = self.processor.auth_digest(challenge, address, origin)?; - self.processor.sign_tx(comm, tx_type, &digest) + self.processor.sign_tx(comm, sign_mode, &digest) } else { return Ok(SignOutcome::SigningRejected); } @@ -194,15 +207,15 @@ impl TxState { fn finalize_sign_tx( &mut self, comm: &mut Comm, - tx_type: SignType, + sign_mode: SignMode, ) -> Result { let digest = self.processor.finalize()?; - self.display_tx_info(tx_type, &digest); + self.display_tx_info(sign_mode, &digest)?; let rc = MultipageValidator::new(&[&"Sign TX?"], &[&"Sign"], &[&"Reject"]).ask(); if rc { - self.processor.sign_tx(comm, tx_type, &digest) + self.processor.sign_tx(comm, sign_mode, &digest) } else { return Ok(SignOutcome::SigningRejected); } @@ -225,52 +238,46 @@ impl TxState { DetectedTxType::Transfer { .. } => b"Transfer", DetectedTxType::Error(..) => b"Summary Failed", }; - info_message(b"TX Type:", text); + utils::info_message(b"TX Type:", text); } - fn show_digest(&mut self, digest: &Digest) { - if self.show_digest { - info_message(b"TX Hash:", &digest.as_hex()); - } - } - - fn display_tx_info(&mut self, tx_type: SignType, digest: &Digest) { + fn display_tx_info(&mut self, sign_mode: SignMode, digest: &Digest) -> Result<(), AppError> { let detected_type = self.processor.get_detected_tx_type(); - match tx_type { - SignType::Ed25519 | SignType::Secp256k1 => { + match sign_mode { + SignMode::Ed25519Verbose | SignMode::Secp256k1Verbose => { self.show_transaction_fee(&detected_type); - self.show_digest(digest); + Ok(()) } - SignType::Ed25519Summary | SignType::Secp256k1Summary => { - self.show_detected_tx_type(&detected_type); - - if let DetectedTxType::Transfer { + SignMode::Ed25519Summary | SignMode::Secp256k1Summary => match detected_type { + DetectedTxType::Transfer { fee: _, src_address, dst_address, res_address, amount, - } = detected_type - { + } => { + utils::info_message(b"TX Type:", b"Transfer"); + self.display_transfer_details( &src_address, &dst_address, &res_address, &amount, ); + Ok(self.show_transaction_fee(&detected_type)) } - - self.show_transaction_fee(&detected_type); - - self.show_digest = match detected_type { - DetectedTxType::Transfer { .. } => false, - DetectedTxType::Other(..) | DetectedTxType::Error(..) => true, - }; - - self.show_digest(digest); - } - SignType::AuthEd25519 | SignType::AuthSecp256k1 => {} + DetectedTxType::Other(_) | DetectedTxType::Error(_) => { + if Settings::get().blind_signing { + utils::info_message(b"TX Hash:", &digest.as_hex()); + Ok(self.show_transaction_fee(&detected_type)) + } else { + utils::error_message("\nBlind signing must\nbe enabled in Settings"); + Err(AppError::BadTxSignHashSignState) + } + } + }, + SignMode::AuthEd25519 | SignMode::AuthSecp256k1 => Ok(()), } } @@ -281,14 +288,14 @@ impl TxState { res_address: &Address, amount: &Decimal, ) { - info_message(b"From:", self.processor.format_address(src_address)); - info_message(b"To:", self.processor.format_address(dst_address)); + utils::info_message(b"From:", self.processor.format_address(src_address)); + utils::info_message(b"To:", self.processor.format_address(dst_address)); if res_address.is_xrd() { - info_message(b"Amount:", self.processor.format_decimal(amount, b" XRD")); + utils::info_message(b"Amount:", self.processor.format_decimal(amount, b" XRD")); } else { - info_message(b"Resource:", self.processor.format_address(res_address)); - info_message(b"Amount:", self.processor.format_decimal(amount, b"")); + utils::info_message(b"Resource:", self.processor.format_address(res_address)); + utils::info_message(b"Amount:", self.processor.format_decimal(amount, b"")); } } diff --git a/src/ui/menu.rs b/src/ui/menu.rs index 13b1ca0f..c5aaacf2 100644 --- a/src/ui/menu.rs +++ b/src/ui/menu.rs @@ -1,20 +1,30 @@ -use nanos_sdk::buttons::ButtonEvent; +use nanos_sdk::buttons::{ButtonEvent, ButtonsState}; use nanos_ui::bagls::{Icon, LEFT_ARROW, LEFT_S_ARROW, RIGHT_ARROW, RIGHT_S_ARROW}; use nanos_ui::layout::Draw; use nanos_ui::screen_util::screen_update; -use nanos_ui::ui::clear_screen; +use nanos_ui::ui::{clear_screen, get_event}; use crate::ui::utils::{CenteredText, LeftAlignedMiddle}; +pub enum MenuFeature<'a> { + Plain, + Icon(&'a Icon<'a>), + OnOffState(fn() -> bool), +} + pub struct MenuItem<'a> { text: &'a str, - icon: &'a Icon<'a>, - action: fn() -> (), + action: fn() -> bool, + feature: MenuFeature<'a>, } impl<'a> MenuItem<'a> { - pub fn new(icon: &'a Icon<'a>, text: &'a str, action: fn() -> ()) -> Self { - MenuItem { text, icon, action } + pub fn new(feature: MenuFeature<'a>, text: &'a str, action: fn() -> bool) -> Self { + MenuItem { + text, + action, + feature, + } } } @@ -25,6 +35,9 @@ pub struct Menu<'a> { const HALF_ICON_WIDTH: usize = 7; +const ON_TEXT: &str = "\n\nEnabled"; +const OFF_TEXT: &str = "\n\nDisabled"; + impl<'a> Menu<'a> { pub const fn new(items: &'a [MenuItem<'a>]) -> Self { Menu { items, current: 0 } @@ -33,8 +46,17 @@ impl<'a> Menu<'a> { pub fn display(&self) { clear_screen(); - self.items[self.current].icon.draw_left_aligned_middle(); - self.items[self.current].text.draw_centered(true); + let item = &self.items[self.current]; + + item.text.draw_centered(true); + + match item.feature { + MenuFeature::Plain => {} + MenuFeature::Icon(icon) => icon.draw_left_aligned_middle(), + MenuFeature::OnOffState(getter) => { + if (getter)() { ON_TEXT } else { OFF_TEXT }.draw_centered(false) + } + } LEFT_ARROW.display(); RIGHT_ARROW.display(); @@ -42,17 +64,37 @@ impl<'a> Menu<'a> { screen_update(); } - pub fn handle(&mut self, event: ButtonEvent) { + pub fn event_loop(&mut self) { + let mut buttons = ButtonsState::new(); + + self.display(); + + loop { + match get_event(&mut buttons) { + Some(event) => { + if self.handle(event) { + break; + } + } + _ => {} + } + } + } + + pub fn handle(&mut self, event: ButtonEvent) -> bool { match event { ButtonEvent::LeftButtonPress => { LEFT_S_ARROW.instant_display(); + false } ButtonEvent::RightButtonPress => { RIGHT_S_ARROW.instant_display(); + false } ButtonEvent::BothButtonsPress => { LEFT_S_ARROW.instant_display(); RIGHT_S_ARROW.instant_display(); + false } ButtonEvent::LeftButtonRelease => { LEFT_S_ARROW.erase(); @@ -62,6 +104,7 @@ impl<'a> Menu<'a> { self.items.len() - 1 }; self.display(); + false } ButtonEvent::RightButtonRelease => { RIGHT_S_ARROW.erase(); @@ -71,12 +114,14 @@ impl<'a> Menu<'a> { 0 }; self.display(); + false } ButtonEvent::BothButtonsRelease => { LEFT_S_ARROW.erase(); RIGHT_S_ARROW.erase(); - (self.items[self.current].action)(); + let result = (self.items[self.current].action)(); self.display(); + result } } } diff --git a/src/ui/single_message.rs b/src/ui/single_message.rs index 1ce905cf..1c25c9b2 100644 --- a/src/ui/single_message.rs +++ b/src/ui/single_message.rs @@ -1,13 +1,21 @@ -use crate::ui::utils::CenteredText; use nanos_sdk::buttons::{ButtonEvent, ButtonsState}; -use nanos_ui::bagls::{RIGHT_ARROW, RIGHT_S_ARROW}; +use nanos_ui::bagls::{Icon, RIGHT_ARROW, RIGHT_S_ARROW}; use nanos_ui::layout::Draw; use nanos_ui::ui::{clear_screen, get_event}; +use nanos_ui::SCREEN_WIDTH; + +use crate::ui::utils::CenteredText; + +pub enum MessageFeature<'a> { + Plain, + WithRightArrow, + WithIcon(Icon<'a>), +} pub struct SingleMessage<'a> { message: &'a str, bold: bool, - show_right_arrow: bool, + feature: MessageFeature<'a>, } impl<'a> SingleMessage<'a> { @@ -15,7 +23,7 @@ impl<'a> SingleMessage<'a> { SingleMessage { message, bold: false, - show_right_arrow: false, + feature: MessageFeature::Plain, } } @@ -23,16 +31,36 @@ impl<'a> SingleMessage<'a> { SingleMessage { message, bold: false, - show_right_arrow: true, + feature: MessageFeature::WithRightArrow, + } + } + + pub fn with_icon(message: &'a str, icon: Icon<'a>) -> Self { + SingleMessage { + message, + bold: false, + feature: MessageFeature::WithIcon(icon), } } pub fn show(&self) { clear_screen(); self.message.draw_centered(self.bold); - if self.show_right_arrow { - RIGHT_ARROW.display(); - RIGHT_S_ARROW.display(); + + match &self.feature { + MessageFeature::Plain => {} + MessageFeature::WithRightArrow => { + RIGHT_ARROW.display(); + RIGHT_S_ARROW.display(); + } + MessageFeature::WithIcon(icon) => { + let new_icon = Icon { + icon: icon.icon, + pos: ((SCREEN_WIDTH / 2) as i16, 0), + }; + + new_icon.display(); + } } } diff --git a/src/ui/utils.rs b/src/ui/utils.rs index 5a2369fe..9dbb8ea7 100644 --- a/src/ui/utils.rs +++ b/src/ui/utils.rs @@ -1,7 +1,11 @@ -use nanos_ui::bagls::Icon; -use nanos_ui::bitmaps::Glyph; +use include_gif::include_gif; +use nanos_ui::bagls::{Icon, CROSSMARK_ICON}; +use nanos_ui::bitmaps::{Glyph, BACK}; use nanos_ui::layout::{Draw, Layout, Location, StringPlace}; +use crate::ui::multiline_scroller::{MultilineMessageScroller, LINE1_Y, LINE2_Y, LINE3_Y}; +use crate::ui::single_message::SingleMessage; + pub trait CenteredText { fn draw_centered(&self, bold: bool); } @@ -47,8 +51,19 @@ impl LeftAlignedMiddle for Icon<'_> { } } -use crate::ui::multiline_scroller::{LINE1_Y, LINE2_Y, LINE3_Y}; -use include_gif::include_gif; - pub const RADIX_LOGO: Glyph = Glyph::from_include(include_gif!("icons/nanox_app_radix.gif")); pub const RADIX_LOGO_ICON: Icon = Icon::from(&RADIX_LOGO); +pub const BACK_ICON: Icon = Icon::from(&BACK); + +pub fn info_message(title: &[u8], message: &[u8]) { + MultilineMessageScroller::with_title( + core::str::from_utf8(title).unwrap(), + core::str::from_utf8(message).unwrap(), + true, + ) + .event_loop(); +} + +pub fn error_message(message: &str) { + SingleMessage::with_icon(message, CROSSMARK_ICON).show_and_wait(); +} diff --git a/test/run-speculos-ns.sh b/test/run-speculos-ns.sh new file mode 100755 index 00000000..f4c5d684 --- /dev/null +++ b/test/run-speculos-ns.sh @@ -0,0 +1,7 @@ +#!/bin/sh +cd .. + +# Run Nano S+ version +cp ./target/nanos/debug/babylon-ledger-app ./apps/babylon.elf + +docker run --rm -it -v "$(pwd)"/apps:/speculos/apps -p 1234:1234 -p 5000:5000 -p 9999:9999 speculos --model nanos ./apps/babylon.elf --seed "equip will roof matter pink blind book anxiety banner elbow sun young" --display headless --apdu-port 9999 \ No newline at end of file diff --git a/test/run-speculos.sh b/test/run-speculos-nsp.sh similarity index 100% rename from test/run-speculos.sh rename to test/run-speculos-nsp.sh diff --git a/test/test-all-debug.sh b/test/test-all-debug.sh deleted file mode 100755 index 9dfd3381..00000000 --- a/test/test-all-debug.sh +++ /dev/null @@ -1,8 +0,0 @@ -# Run all non-interactive tests (Debug binaries) -python3 -m test-get-application-version -python3 -m test-get-device-model -python3 -m test-get-device-id -python3 -m test-get-public-key-ed25519 -python3 -m test-get-public-key-secp256k1 -python3 -m test-get-private-key-ed25519 -python3 -m test-get-private-key-secp256k1 diff --git a/test/test-all-interactive.sh b/test/test-all-interactive.sh deleted file mode 100755 index ba163cab..00000000 --- a/test/test-all-interactive.sh +++ /dev/null @@ -1,9 +0,0 @@ -python3 -m test-sign-tx-ed25519 -python3 -m test-sign-tx-ed25519-summary -python3 -m test-sign-tx-secp265k1 -python3 -m test-sign-tx-secp265k1-summary -python3 -m test-sign-auth - - - - diff --git a/test/test-all-release.sh b/test/test-all.sh similarity index 78% rename from test/test-all-release.sh rename to test/test-all.sh index 8ee918ba..a07e76f4 100755 --- a/test/test-all-release.sh +++ b/test/test-all.sh @@ -1,4 +1,4 @@ -# Run all non-interactive tests (Release binaries) +# Run all non-interactive tests (Debug binaries) python3 -m test-get-application-version python3 -m test-get-device-model python3 -m test-get-device-id diff --git a/test/test-get-application-version.py b/test/test-get-application-version.py index b4a92a28..da02fdaa 100755 --- a/test/test-get-application-version.py +++ b/test/test-get-application-version.py @@ -23,5 +23,5 @@ print("Testing", "GetAppVersion", instructionCode, end=" ") response = dongle.exchange(bytes.fromhex(instructionClass + instructionCode + p1 + p2 + dataLength)) -assert response.hex() == '000509', "Invalid version\nReceived:" + response.hex() +assert response.hex() == '000700', "Invalid version\nReceived:" + response.hex() print("Success") diff --git a/test/test-get-private-key-ed25519.py b/test/test-get-private-key-ed25519.py deleted file mode 100755 index d2f05247..00000000 --- a/test/test-get-private-key-ed25519.py +++ /dev/null @@ -1,84 +0,0 @@ -# Test GetPrivKeyEd25519 instruction -# WARNING: Available only in debug builds -# WARNING: Requires device configured for development (see root README.md) - -import sys -import os - -from ledgerblue.comm import getDongle -from ledgerblue.commTCP import getDongle as getDongleTCP - - -# -------------------------------------------------------------------------------------------- -# Encode absolute BIP32 path into hex string representation -# -------------------------------------------------------------------------------------------- -def encodeBip32(path): - elements = path.replace('H', "'").replace('"', "'").split('/') - result = (len(elements) - 1).to_bytes(1, 'little').hex() - for i in range(1, len(elements)): - num = 0x80000000 if elements[i].endswith("'") else 0 - num += int(elements[i].replace("'", "")) - result += num.to_bytes(4, 'big').hex() - return result - - -# -------------------------------------------------------------------------------------------- -# -# -------------------------------------------------------------------------------------------- - -def call_and_check(path, expected_pub_key): - data = encodeBip32(path) - data_length = int(len(data) / 2).to_bytes(1, 'little').hex() - - response = dongle.exchange(bytes.fromhex(instructionClass + instructionCode + p1 + p2 + data_length + data)) - pk = response.hex() - assert pk == expected_pub_key, "Invalid public key\nExpected: " + expected_pub_key + "\nReceived: " + pk - - -# -------------------------------------------------------------------------------------------- -# disable printing stack trace -sys.tracebacklimit = 0 - -if os.environ.get('USE_SPECULOS') is not None: - dongle = getDongleTCP(debug=False) -else: - dongle = getDongle(False) - -instructionClass = "AA" -instructionCode = "22" -p1 = "00" -p2 = "00" - -test_vectors = [ - ("m/44H/1022H/12H/525H/1460H/0H", "13e971fb16cb2c816d6b9f12176e9b8ab9af1831d006114d344d119ab2715506"), - ("m/44H/1022H/12H/525H/1460H/1H", "ec7634aff9d698d9a5b4001d5eaa878eefc4fc05939dfedcef0112329fd9966a"), - ("m/44H/1022H/12H/525H/1460H/2H", "9e96517567abba3e5492db11bc016450abb4e60406038d6423a87a0ad860a9ce"), - ("m/44H/1022H/12H/525H/1460H/3H", "e92d352f6846e75fb760c40229de8c3c2b04210b2955129877286cf15893a21e"), - ("m/44H/1022H/12H/525H/1678H/0H", "4a39274fd5f320172329ec96e88b658ad9798ea47f292e30f80915f01f3acc48"), - ("m/44H/1022H/12H/525H/1678H/1H", "d02147e27720dba12acbddc3d6d4fc43a64cab1dbbdcc3e7e0268c766deeccce"), - ("m/44H/1022H/12H/525H/1678H/2H", "41519644a280fc18191765ee32fdcc7a37ac95012e18c5fb31679222925da1ce"), - ("m/44H/1022H/12H/525H/1678H/3H", "b843c520aca4d980f69dcf02a6d3deb50c10bdaa350ed262b49cbb0997fcbd28"), - ("m/44H/1022H/12H/525H/1391H/0H", "62e1255e91b6fafdf06d3d6e3cfa660a5dd39aaa6ab7c207e2535c86f597e47f"), - ("m/44H/1022H/12H/525H/1391H/1H", "30d9891cf7436d07f45a3358bbf4b0857388c08d1a7fe9973fd6044fa86a9ce0"), - ("m/44H/1022H/12H/525H/1391H/2H", "80b136f184159c7873321a73e6523be68d428440f95efb77fb67b43560cd5401"), - ("m/44H/1022H/12H/525H/1391H/3H", "215af43054ac6055f86c9986d482a8fe6b0bf70543f6ebe74f69e33424b11282"), - ("m/44H/1022H/12H/618H/1460H/0H", "9c683ba15644596f747bc749fed2657644c2873391f9c874efd32ccacc5adf08"), - ("m/44H/1022H/12H/618H/1460H/1H", "aa45993887e5fe45252db7b34ad26686a4ef165f65ba30206d87d900310ea360"), - ("m/44H/1022H/12H/618H/1460H/2H", "a5b3a586440f996d12ac9f21f61ed0758c13c012e42ed8c9d83e4bf4548e3dd3"), - ("m/44H/1022H/12H/618H/1460H/3H", "da699f61d6c2a4893d00b1f15158894974fb403a16a865583538f0542e883c54"), - ("m/44H/1022H/12H/618H/1678H/0H", "7997d39b74a390bc213c566ec016dd9023c4319af9da5194fb87c0d73f1d970f"), - ("m/44H/1022H/12H/618H/1678H/1H", "fa9c15acc1f46b790acdb060682c8d9fca307f02ba7a1deee4009c4f89cc3ddc"), - ("m/44H/1022H/12H/618H/1678H/2H", "4aea7c10102b93b173a72c62c6e5b3a19dacbc4e5dee6fc3f32e04a35d012059"), - ("m/44H/1022H/12H/618H/1678H/3H", "11e570fe1fc5c7a0deba1c672428b0793f45ca091580a50561ab46e50147ed07"), - ("m/44H/1022H/12H/618H/1391H/0H", "603ca94347db5edba67c73fa2c75d40f8534efbb6f043e279a62959b799fc55b"), - ("m/44H/1022H/12H/618H/1391H/1H", "8564e2302ef419354a47265b6e5e6bed276b34cb691ef5a73fd6a722052cacda"), - ("m/44H/1022H/12H/618H/1391H/2H", "7af1d0b3f6a634891fb502de1bc14d3a06402e96380dfe377d4fb5864922cdf6"), - ("m/44H/1022H/12H/618H/1391H/3H", "f5ec1d8379d2173975ea693afbd8940820f9d1b82b9f777f02c1ecd4197deab0"), -] - -print("Testing", "GetPrivKeyEd25519", instructionCode, end=" ") - -for vector in test_vectors: - call_and_check(vector[0], vector[1]) - -print("Success") diff --git a/test/test-get-private-key-secp256k1.py b/test/test-get-private-key-secp256k1.py deleted file mode 100755 index e58cbf5c..00000000 --- a/test/test-get-private-key-secp256k1.py +++ /dev/null @@ -1,80 +0,0 @@ -# Test GetPrivKeySecp256k1 instruction -# WARNING: Available only in debug builds -# WARNING: Requires device configured for development (see root README.md) - -import sys -import os - -from ledgerblue.comm import getDongle -from ledgerblue.commTCP import getDongle as getDongleTCP - - -# -------------------------------------------------------------------------------------------- -# Encode absolute BIP32 path into hex string representation -# -------------------------------------------------------------------------------------------- -def encodeBip32(path): - elements = path.replace('H', "'").replace('"', "'").split('/') - result = (len(elements) - 1).to_bytes(1, 'little').hex() - for i in range(1, len(elements)): - num = 0x80000000 if elements[i].endswith("'") else 0 - num += int(elements[i].replace("'", "")) - result += num.to_bytes(4, 'big').hex() - return result - - -# -------------------------------------------------------------------------------------------- -# -# -------------------------------------------------------------------------------------------- - -def call_and_check(path, expected_pub_key): - data = encodeBip32(path) - data_length = int(len(data) / 2).to_bytes(1, 'little').hex() - - response = dongle.exchange(bytes.fromhex(instructionClass + instructionCode + p1 + p2 + data_length + data)) - pk = response.hex() - assert pk == expected_pub_key, "Invalid public key\nExpected: " + expected_pub_key + "\nReceived: " + pk - - -# -------------------------------------------------------------------------------------------- -# disable printing stack trace -sys.tracebacklimit = 0 - -if os.environ.get('USE_SPECULOS') is not None: - dongle = getDongleTCP(debug=False) -else: - dongle = getDongle(False) - -instructionClass = "AA" -instructionCode = "32" -p1 = "00" -p2 = "00" - -test_vectors = [ - ("m/44H/1022H/0H/0/0H", "e6aec3c1b9c6b49f154c99708ce4bdb36a01de3f13a832111d7d64e368f939ce"), - ("m/44H/1022H/0H/0/1H", "7938cc222877aa0f9b4293478bf5733577ceb54cc4834e6e518cbc3847751fd6"), - ("m/44H/1022H/0H/0/2H", "4e71fa6a8c4612e79b2a9f0f4956f0a8eacf3f19ec56b6d40cc874ac4010f917"), - ("m/44H/1022H/0H/0/3H", "2ab7a40d7da148a98d200cf2f3a9b6f95c29e637b4d959928f6f315ad2bc0384"), - ("m/44H/1022H/0H/0/4H", "4c25b3cacca546e8682438e2915698d0ab388abd4e4247d7096814c6ac8f3015"), - ("m/44H/1022H/0H/0/5H", "318ffcfdf4c40964243f018363017c4749f2503fe948d189d83069684207e8c2"), - ("m/44H/1022H/0H/0/6H", "34418351c9f02e0b9b9392e1523c14c16cf4edb9df71dfb13b7a6d7ff73be18e"), - ("m/44H/1022H/0H/0/7H", "f0d67eaa2ff06afd435346ad5e1fd9ad8b892ac643de7bbe07570a152b6b0ffd"), - ("m/44H/1022H/0H/0/8H", "c5e76f69e4ecef5f590387f1f6d179f65736f77098eebe97e7c8d05c43d4cb0b"), - ("m/44H/1022H/0H/0/9H", "a019c18261515c004c170ea2d7da1bad2268a1751cd5233842c6ed4c91fa2e77"), - ("m/44H/1022H/0H/0/0", "623048f7bb88a4d162442b88cdd80c85e4d5933ad9e78523a97de769badb9ab2"), - ("m/44H/1022H/0H/0/1", "e94b6a64f99a1a143ed570bea9cf896ce82d14f861d0103066e835822037fe6b"), - ("m/44H/1022H/0H/0/2", "692824fba987bd09ddd42d8ceea38676ed48309d19dc159c4dd4ec83d2a666a1"), - ("m/44H/1022H/0H/0/3", "9343a365148bdbd0fd8adff8dc1a5f2630b61705141553c5d6d0526da8776f88"), - ("m/44H/1022H/0H/0/4", "5f288fad35651d1cd3c344d06512593e68ce0c4e6e7f96f1b1fbe337d20b7325"), - ("m/44H/1022H/0H/0/5", "1289c95c78bcbf21a5455654836b946561d3c477916a049601d4e19cfe7507c2"), - ("m/44H/1022H/0H/0/6", "c038f504e4fe4999dc9355e1c7547ececa2a8990ab63806e9593cbccfe28fa97"), - ("m/44H/1022H/0H/0/7", "24333e864e7143ec355bdc9db1405afc1188b2f8bb812075fec34d5459ef7df8"), - ("m/44H/1022H/0H/0/8", "53e8f579fc1a845357f1ad6194cce8c8e71ede05b3574177d3a31b92950dde5e"), - ("m/44H/1022H/0H/0/9", "d27012ba6fe8db796c6753790c381b77a86ce058bdbb0e59880d40efad5bdbc3"), -] - -print("Testing", "GetPrivKeySecp256k1", instructionCode, end=" ") - -for vector in test_vectors: - call_and_check(vector[0], vector[1]) - -print("Success") diff --git a/test/test-sign-tx-ed25519-hash.py b/test/test-sign-tx-ed25519-hash.py deleted file mode 100755 index 9653835e..00000000 --- a/test/test-sign-tx-ed25519-hash.py +++ /dev/null @@ -1,97 +0,0 @@ -# Test SignTxEd25519 instruction (mode: show digest) - -import sys -import os - -from ledgerblue.comm import getDongle -from ledgerblue.commTCP import getDongle as getDongleTCP -from cryptography.hazmat.primitives.asymmetric import ed25519 - -# disable printing stack trace -sys.tracebacklimit = 0 - -if os.environ.get('USE_SPECULOS') is not None: - dongle = getDongleTCP(debug=False) -else: - dongle = getDongle(False) - -instructionClass = "AA" -instructionCode = "41" -p1 = "01" -p2 = "00" -dataLength = "00" - -print("Testing", "SignTxEd25519 (show hash)", instructionCode) - - -def list_files(): - dir_path = "data" - res = [] - for path in os.listdir(dir_path): - if os.path.isfile(os.path.join(dir_path, path)): - res.append(os.path.join(dir_path, path)) - return res - - -def read_file(file): - print("Reading ", file) - with open(file, "rb") as f: - return f.read() - - -def encode_bip32(path): - elements = path.replace('H', "'").replace('"', "'").split('/') - result = (len(elements) - 1).to_bytes(1, 'little').hex() - for i in range(1, len(elements)): - num = 0x80000000 if elements[i].endswith("'") else 0 - num += int(elements[i].replace("'", "")) - result += num.to_bytes(4, 'big').hex() - return result - - -def send_tx_intent(txn): - num_chunks = len(txn) // 255 + 1 - # print("Sending txn (", len(txn), " bytes, ", num_chunks, " chunk(s))") - for i in range(num_chunks): - chunk = txn[i * 255:(i + 1) * 255] - cls = "AC" if i == num_chunks - 1 else "AB" - data_length = len(chunk).to_bytes(1, 'little').hex() - - # print("Chunk:", i, "data:", chunk.hex(), "len:", data_length, "cls:", cls) - - try: - rc = dongle.exchange(bytes.fromhex(cls + instructionCode + p1 + p2 + data_length + chunk.hex())) - except Exception as e: - print("Error sending txn chunk: ", e) - return None - return rc - - -def send_derivation_path(bip_path): - path_data = encode_bip32(bip_path) - data_length = int(len(path_data) / 2).to_bytes(1, 'little').hex() - # print("Sending derivation path: ", bip_path, ", data_len = ", data_length) - - try: - return dongle.exchange(bytes.fromhex(instructionClass + instructionCode + p1 + p2 + data_length + path_data)) - except Exception as e: - print("Error sending derivation path: ", e) - return None - - -for file_name in list_files(): - if not file_name.endswith(".txn"): - continue - data = read_file(file_name) - send_derivation_path("m/44H/1022H/12H/525H/1460H/0H") - rc = send_tx_intent(data) - - if rc is None: - print("Failed") - else: - pubkey = ed25519.Ed25519PublicKey.from_public_bytes(bytes(rc[64:96])) - try: - pubkey.verify(bytes(rc[0:64]), bytes(rc[96:128])) - print("Success") - except Exception as e: - print("Invalid signature ", e) diff --git a/test/test-sign-tx-ed25519-summary.py b/test/test-sign-tx-ed25519-summary.py deleted file mode 100755 index cb13649b..00000000 --- a/test/test-sign-tx-ed25519-summary.py +++ /dev/null @@ -1,97 +0,0 @@ -# Test SignTxEd25519Summary instruction - -import sys -import os - -from ledgerblue.comm import getDongle -from ledgerblue.commTCP import getDongle as getDongleTCP -from cryptography.hazmat.primitives.asymmetric import ed25519 - -# disable printing stack trace -sys.tracebacklimit = 0 - -if os.environ.get('USE_SPECULOS') is not None: - dongle = getDongleTCP(debug=False) -else: - dongle = getDongle(False) - -instructionClass = "AA" -instructionCode = "42" -p1 = "00" -p2 = "00" -dataLength = "00" - -print("Testing", "SignTxEd25519Summary", instructionCode) - - -def list_files(): - dir_path = "data" - res = [] - for path in os.listdir(dir_path): - if os.path.isfile(os.path.join(dir_path, path)): - res.append(os.path.join(dir_path, path)) - return res - - -def read_file(file): - print("Reading ", file) - with open(file, "rb") as f: - return f.read() - - -def encode_bip32(path): - elements = path.replace('H', "'").replace('"', "'").split('/') - result = (len(elements) - 1).to_bytes(1, 'little').hex() - for i in range(1, len(elements)): - num = 0x80000000 if elements[i].endswith("'") else 0 - num += int(elements[i].replace("'", "")) - result += num.to_bytes(4, 'big').hex() - return result - - -def send_tx_intent(txn): - num_chunks = len(txn) // 255 + 1 - # print("Sending txn (", len(txn), " bytes, ", num_chunks, " chunk(s))") - for i in range(num_chunks): - chunk = txn[i * 255:(i + 1) * 255] - cls = "AC" if i == num_chunks - 1 else "AB" - data_length = len(chunk).to_bytes(1, 'little').hex() - - # print("Chunk:", i, "data:", chunk.hex(), "len:", data_length, "cls:", cls) - - try: - rc = dongle.exchange(bytes.fromhex(cls + instructionCode + p1 + p2 + data_length + chunk.hex())) - except Exception as e: - print("Error sending txn chunk: ", e) - return None - return rc - - -def send_derivation_path(bip_path): - path_data = encode_bip32(bip_path) - data_length = int(len(path_data) / 2).to_bytes(1, 'little').hex() - # print("Sending derivation path: ", bip_path, ", data_len = ", data_length) - - try: - return dongle.exchange(bytes.fromhex(instructionClass + instructionCode + p1 + p2 + data_length + path_data)) - except Exception as e: - print("Error sending derivation path: ", e) - return None - - -for file_name in list_files(): - if not file_name.endswith(".txn"): - continue - data = read_file(file_name) - send_derivation_path("m/44H/1022H/12H/525H/1460H/0H") - rc = send_tx_intent(data) - - if rc is None: - print("Failed") - else: - pubkey = ed25519.Ed25519PublicKey.from_public_bytes(bytes(rc[64:96])) - try: - pubkey.verify(bytes(rc[0:64]), bytes(rc[96:128])) - print("Success") - except Exception as e: - print("Invalid signature ", e) diff --git a/test/test-sign-tx-secp256k1-hash.py b/test/test-sign-tx-secp256k1-hash.py deleted file mode 100755 index c4c641d5..00000000 --- a/test/test-sign-tx-secp256k1-hash.py +++ /dev/null @@ -1,102 +0,0 @@ -# Test SignTxSecp256k1 instruction (mode: show digest) - -import sys -import os - -from ledgerblue.comm import getDongle -from ledgerblue.commTCP import getDongle as getDongleTCP -from cryptography.hazmat.primitives.asymmetric import ec, utils -from cryptography.hazmat.primitives import hashes - -# disable printing stack trace -sys.tracebacklimit = 0 - -if os.environ.get('USE_SPECULOS') is not None: - dongle = getDongleTCP(debug=False) -else: - dongle = getDongle(False) - -instructionClass = "AA" -instructionCode = "51" -p1 = "01" -p2 = "00" -dataLength = "00" - -print("Testing", "SignTxSecp256k1 (show hash)", instructionCode) - - -def list_files(): - dir_path = "data" - res = [] - for path in os.listdir(dir_path): - if os.path.isfile(os.path.join(dir_path, path)): - res.append(os.path.join(dir_path, path)) - return res - - -def read_file(file): - print("Reading ", file) - with open(file, "rb") as f: - return f.read() - - -def encode_bip32(path): - elements = path.replace('H', "'").replace('"', "'").split('/') - result = (len(elements) - 1).to_bytes(1, 'little').hex() - for i in range(1, len(elements)): - num = 0x80000000 if elements[i].endswith("'") else 0 - num += int(elements[i].replace("'", "")) - result += num.to_bytes(4, 'big').hex() - return result - - -def send_tx_intent(txn): - num_chunks = len(txn) // 255 + 1 - # print("Sending txn (", len(txn), " bytes, ", num_chunks, " chunk(s))") - for i in range(num_chunks): - chunk = txn[i * 255:(i + 1) * 255] - cls = "AC" if i == num_chunks - 1 else "AB" - data_length = len(chunk).to_bytes(1, 'little').hex() - - # print("Chunk:", i, "data:", chunk.hex(), "len:", data_length, "cls:", cls) - - try: - rc = dongle.exchange(bytes.fromhex(cls + instructionCode + p1 + p2 + data_length + chunk.hex())) - except Exception as e: - print("Error sending txn chunk: ", e) - return None - return rc - - -def send_derivation_path(bip_path): - path_data = encode_bip32(bip_path) - data_length = int(len(path_data) / 2).to_bytes(1, 'little').hex() - # print("Sending derivation path: ", bip_path, ", data_len = ", data_length) - - try: - return dongle.exchange(bytes.fromhex(instructionClass + instructionCode + p1 + p2 + data_length + path_data)) - except Exception as e: - print("Error sending derivation path: ", e) - return None - - -for file_name in list_files(): - if not file_name.endswith(".txn"): - continue - data = read_file(file_name) - send_derivation_path("m/44H/1022H/10H/525H/1238H") - rc = send_tx_intent(data) - - if rc is None: - print("Failed") - else: - r = int.from_bytes(rc[1:33], byteorder='big', signed=False) - s = int.from_bytes(rc[33:65], byteorder='big', signed=False) - signature = utils.encode_dss_signature(int(r), int(s)) - pubkey = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256K1(), bytes(rc[65:98])) - try: - # Note that Prehashed parameter is irrelevant here, we just need to pass something known to the library - pubkey.verify(signature, bytes(rc[98:130]), ec.ECDSA(utils.Prehashed(hashes.SHA256()))) - print("Success") - except Exception as e: - print("Invalid signature ", e) diff --git a/test/test-sign-tx-secp256k1-summary.py b/test/test-sign-tx-secp256k1-summary.py deleted file mode 100755 index 019fc9df..00000000 --- a/test/test-sign-tx-secp256k1-summary.py +++ /dev/null @@ -1,102 +0,0 @@ -# Test SignTxSecp256k1Summary instruction - -import sys -import os - -from ledgerblue.comm import getDongle -from ledgerblue.commTCP import getDongle as getDongleTCP -from cryptography.hazmat.primitives.asymmetric import ec, utils -from cryptography.hazmat.primitives import hashes - -# disable printing stack trace -sys.tracebacklimit = 0 - -if os.environ.get('USE_SPECULOS') is not None: - dongle = getDongleTCP(debug=False) -else: - dongle = getDongle(False) - -instructionClass = "AA" -instructionCode = "52" -p1 = "00" -p2 = "00" -dataLength = "00" - -print("Testing", "SignTxSecp256k1Summary", instructionCode) - - -def list_files(): - dir_path = "data" - res = [] - for path in os.listdir(dir_path): - if os.path.isfile(os.path.join(dir_path, path)): - res.append(os.path.join(dir_path, path)) - return res - - -def read_file(file): - print("Reading ", file) - with open(file, "rb") as f: - return f.read() - - -def encode_bip32(path): - elements = path.replace('H', "'").replace('"', "'").split('/') - result = (len(elements) - 1).to_bytes(1, 'little').hex() - for i in range(1, len(elements)): - num = 0x80000000 if elements[i].endswith("'") else 0 - num += int(elements[i].replace("'", "")) - result += num.to_bytes(4, 'big').hex() - return result - - -def send_tx_intent(txn): - num_chunks = len(txn) // 255 + 1 - # print("Sending txn (", len(txn), " bytes, ", num_chunks, " chunk(s))") - for i in range(num_chunks): - chunk = txn[i * 255:(i + 1) * 255] - cls = "AC" if i == num_chunks - 1 else "AB" - data_length = len(chunk).to_bytes(1, 'little').hex() - - # print("Chunk:", i, "data:", chunk.hex(), "len:", data_length, "cls:", cls) - - try: - rc = dongle.exchange(bytes.fromhex(cls + instructionCode + p1 + p2 + data_length + chunk.hex())) - except Exception as e: - print("Error sending txn chunk: ", e) - return None - return rc - - -def send_derivation_path(bip_path): - path_data = encode_bip32(bip_path) - data_length = int(len(path_data) / 2).to_bytes(1, 'little').hex() - # print("Sending derivation path: ", bip_path, ", data_len = ", data_length) - - try: - return dongle.exchange(bytes.fromhex(instructionClass + instructionCode + p1 + p2 + data_length + path_data)) - except Exception as e: - print("Error sending derivation path: ", e) - return None - - -for file_name in list_files(): - if not file_name.endswith(".txn"): - continue - data = read_file(file_name) - send_derivation_path("m/44H/1022H/10H/525H/1238H") - rc = send_tx_intent(data) - - if rc is None: - print("Failed") - else: - r = int.from_bytes(rc[1:33], byteorder='big', signed=False) - s = int.from_bytes(rc[33:65], byteorder='big', signed=False) - signature = utils.encode_dss_signature(int(r), int(s)) - pubkey = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256K1(), bytes(rc[65:98])) - try: - # Note that Prehashed parameter is irrelevant here, we just need to pass something known to the library - pubkey.verify(signature, bytes(rc[98:130]), ec.ECDSA(utils.Prehashed(hashes.SHA256()))) - print("Success") - except Exception as e: - print("Invalid signature ", e) diff --git a/test/test-verify-address-ed25519.py b/test/test-verify-address-ed25519.py old mode 100644 new mode 100755 diff --git a/test/test-verify-address-secp256k1.py b/test/test-verify-address-secp256k1.py old mode 100644 new mode 100755