diff --git a/README.md b/README.md index 9aef687b..0c676232 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,9 @@ For the Ledger Nano S: - Two bars along the top (just below the buttons) indicates that there is a double press function available (generally confirm/toggle or back). We will be working to ensure this function is always intuitive. +For the Ledger Nano X: +- Behavior is the same as the Nano S. Graphically there are no confirmation bars along the top (but double pressing the buttons still confirms/toggles). + For the Ledger Blue: - The Ledger Blue uses a touchscreen, thus all you need to do is tap the buttons on the screen. @@ -138,7 +141,7 @@ All warnings on the Ledger are there for a reason, **MAKE SURE TO READ THEM** an When generating a transaction for the Ledger to sign, you will scroll through each transaction before signing off on the bundle. The transaction information will come up in order (while scrolling from left to right). - On the Ledger Nano S, the first screen will display the tx type (output, input, or change), as well as the amount. The next screen will display the corresponding address for said transaction. This will repeat until all transactions have been displayed, then you can select "Approve" or "Deny". + On the Ledger Nano S/X, the first screen will display the tx type (output, input, or change), as well as the amount. The next screen will display the corresponding address for said transaction. This will repeat until all transactions have been displayed, then you can select "Approve" or "Deny". On the Ledger Blue each transaction entry in a bundle will fit on the screen, use the next button until you've confirmed all transactions and then select approve if everything is correct. @@ -164,9 +167,9 @@ All warnings on the Ledger are there for a reason, **MAKE SURE TO READ THEM** an ### Limitations of Ledger Hardware Wallets -Due to the memory limitations of both the Ledger Nano S and the Ledger Blue, the transaction bundles have certain restrictions. The Ledger Nano S can only accept a transaction with a maximum bundle size of 8 and the Ledger Blue is limited to a maximum bundle size of 20. +Due to the memory limitations of the Ledger Nano S/X and the Ledger Blue, the transaction bundles have certain restrictions. The Ledger Nano S/X can only accept a transaction with a maximum bundle size of 10 and the Ledger Blue is limited to a maximum bundle size of 20. -An output and a change transaction each only require 1 bundle entry, however every input transaction requires the same number of bundle entries as the security level being used on the seed. Thus if using a Ledger Nano S you could have 1 output + 3 inputs (security level 2) + 1 change transaction and this would take up all 8 bundle entries. For security level 3 you could only have 1 output + 2 inputs + 1 change transaction. +An output and a change transaction each only require 1 bundle entry, however every input transaction requires the same number of bundle entries as the security level being used on the seed. Thus if using a Ledger Nano S or X you could have 1 output + 4 inputs (security level 2) + 1 change transaction and this would take up all 10 bundle entries. For security level 3 you could only have 1 output + 2 inputs + 1 change transaction, or 1 output + 3 inputs without a change transaction. *Security level 2 is the default security level.* @@ -231,13 +234,16 @@ See: [APDU API Specification](/docs/specification.md) ## Contributing ### Donations -Would you like to donate to help the development team? Send some IOTA to the following address: +This is a community project, done in our spare time for the betterment of the IOTA ecosystem and community. +Donating is not required, but is greatly appreciated. If you would like to donate please send some IOTA to the following address: ``` -ADLJXS9SKYQKMVQFXR9JDUUJHJWGDNWHQZMDGJFGZOX9BZEKDSXBSPZTTWEYPTNM9OZMYDQWZXFHRTXRCOITXAGCJZ +TLCZOGKIARUQRBSJYSUTXVQYKPTOYOMQAUWUGBOCJJFQSXELFPEDF9LKDCUVKYDVGCJTCRANLOZJJKKNBEKVDHCJ9B ``` ![IOTA Ledger Donation](resources/ledger_donation.png) -Please know that the donations made to this address will be shared with everyone who contributes (the contributions has to be worth something, of course) +Please know that the donations made to this address will be shared with everyone who meaningfully contributes to the project. + +Thanks! ### As a developer Would you like to contribute as a dev? Please check out our [Discord channel](https://discord.gg/U3qRjZj) to contact us! diff --git a/resources/ledger_donation.png b/resources/ledger_donation.png index 718e172a..057d575d 100644 Binary files a/resources/ledger_donation.png and b/resources/ledger_donation.png differ diff --git a/src/api.c b/src/api.c index 6edc2fed..efe9afa2 100644 --- a/src/api.c +++ b/src/api.c @@ -11,6 +11,10 @@ #include "iota/seed.h" #include "iota/signing.h" +// ms until timeout; must not be > 1min to avoid locking before timeout +#define TIMEOUT_MS 3000 // 3 sec +#define TIMEOUT_USER_BUNDLE_MS 100000 // 100 sec + #define CHECK_STATE(state, INS) \ ((((state)&INS##_REQUIRED_STATE) != INS##_REQUIRED_STATE) || \ ((state)&INS##_FORBIDDEN_STATE)) @@ -27,7 +31,7 @@ /// global variable storing all data needed across multiple api calls API_CTX api; -void api_initialize(void) +void api_initialize() { MEMCLEAR(api); } @@ -38,7 +42,6 @@ static void reset_bundle(void) MEMCLEAR(api.ctx.bundle); MEMCLEAR(api.ctx.signing); api.state_flags = 0; - ui_timeout_stop(); } /** @brief Checks whether the given path differes from the stored path. */ @@ -283,8 +286,6 @@ unsigned int api_tx(uint8_t p1, const unsigned char *input_data, } ui_display_recv(); - // reset next transaction timer - ui_timeout_start(false); if (first) { if (!IN_RANGE(input->last_index, 1, MAX_BUNDLE_SIZE - 1)) { @@ -328,13 +329,16 @@ unsigned int api_tx(uint8_t p1, const unsigned char *input_data, // perfectly valid bundle if (!bundle_has_open_txs(&api.ctx.bundle)) { // start interactive timeout - ui_timeout_start(true); + io_timeout_set(TIMEOUT_USER_BUNDLE_MS); ui_sign_tx(); return IO_ASYNCH_REPLY; } + // set timeout for the next tx to receive + io_timeout_set(TIMEOUT_MS); // as the bundle is not yet complete, we cannot compute the hash yet io_send_unfinished_bundle(); + return 0; } @@ -395,18 +399,21 @@ unsigned int api_sign(uint8_t p1, const unsigned char *input_data, // temporary screen during signing process ui_display_signing(); - ui_timeout_start(false); SIGN_OUTPUT output; output.fragments_remaining = next_signature_fragment(&api.ctx.signing, output.signature_fragment); + // set timeout for the next fragment to be querried + if (output.fragments_remaining) { + io_timeout_set(TIMEOUT_MS); + } io_send(&output, sizeof(output), SW_OK); + // signing is finished if (!output.fragments_remaining) { - - // signing is finished api.state_flags &= ~SIGNING_STARTED; + io_timeout_reset(); ui_display_main_menu(); } @@ -425,6 +432,7 @@ static void io_send_bundle_hash(const BUNDLE_CTX *ctx) void user_sign_tx() { + io_timeout_reset(); ui_display_validating(); const int retcode = bundle_validating_finalize( @@ -475,6 +483,7 @@ unsigned int api_reset(uint8_t p1, const unsigned char *input_data, reset_bundle(); ui_reset(); + io_timeout_reset(); io_send(NULL, 0, SW_OK); return 0; diff --git a/src/iota_io.c b/src/iota_io.c index 7862a46a..a47c00f4 100644 --- a/src/iota_io.c +++ b/src/iota_io.c @@ -1,5 +1,6 @@ #include "iota_io.h" #include "common.h" +#include "os_io_seproxyhal.h" #include "api.h" extern unsigned char G_io_apdu_buffer[IO_APDU_BUFFER_SIZE]; @@ -8,6 +9,7 @@ void io_initialize() { os_memset(G_io_apdu_buffer, 0, IO_APDU_BUFFER_SIZE); api_initialize(); + io_timeout_reset(); } void io_send(const void *ptr, unsigned int length, unsigned short sw) @@ -51,3 +53,28 @@ unsigned int iota_dispatch(const uint8_t ins, const uint8_t p1, return 0; } } + +void io_timeout_reset() +{ + UX_CALLBACK_SET_INTERVAL(0); +} + +void io_timeout_set(const unsigned int ms) +{ + if (ms == 0) { + THROW(INVALID_PARAMETER); + } + UX_CALLBACK_SET_INTERVAL(ms); +} + +void io_timeout_callback(const bool ux_allowed) +{ +#ifdef TARGET_NANOS + UNUSED(ux_allowed); +#else + if (!ux_allowed) { + THROW(EXCEPTION_IO_RESET); + } +#endif + THROW(SW_COMMAND_TIMEOUT); +} diff --git a/src/iota_io.h b/src/iota_io.h index a80307ec..d6e10a63 100644 --- a/src/iota_io.h +++ b/src/iota_io.h @@ -13,6 +13,13 @@ void io_send(const void *ptr, unsigned int length, unsigned short sw); unsigned int iota_dispatch(uint8_t ins, uint8_t p1, uint8_t p2, uint8_t len, const unsigned char *input_data); +/// Sets the IO timeout to the given ms. +void io_timeout_set(unsigned int ms); +/// Resets and stops the IO timeout. +void io_timeout_reset(void); +// Callback to be called on timeout. +void io_timeout_callback(bool ux_allowed); + /* --- CLA --- */ #define CLA 0x7A diff --git a/src/main.c b/src/main.c index 594fccc8..a6807368 100644 --- a/src/main.c +++ b/src/main.c @@ -28,8 +28,11 @@ APDU_HEADER; /// Returns true, if the device is not locked static bool device_is_unlocked(void) { - // this is the precise usage suggested by the SDK +#if CX_APILEVEL >= 9 return os_global_pin_is_validated() == BOLOS_UX_OK; +#else + return os_global_pin_is_validated(); +#endif } static void IOTA_main() @@ -99,6 +102,7 @@ static void IOTA_main() // reset states and UI api_initialize(); ui_reset(); + io_timeout_reset(); } // send the error code @@ -168,12 +172,9 @@ unsigned char io_event(unsigned char channel) break; case SEPROXYHAL_TAG_TICKER_EVENT: - ui_timeout_tick(); - // do not forward the ticker_event when the transaction is shown - if (ui_lock_forbidden()) { - break; - } - // fallthrough + UX_TICKER_EVENT(G_io_seproxyhal_spi_buffer, + { io_timeout_callback(UX_ALLOWED); }); + break; default: UX_DEFAULT_EVENT(); diff --git a/src/ui/blue/blue_core.c b/src/ui/blue/blue_core.c index 25d21a47..5f91326e 100644 --- a/src/ui/blue/blue_core.c +++ b/src/ui/blue/blue_core.c @@ -19,7 +19,6 @@ static void blue_ctx_initialize() void ui_init() { blue_ctx_initialize(); - ui_timeout_stop(); UX_SET_STATUS_BAR_COLOR(COLOUR_WHITE, COLOUR_GREEN); ui_display_main_menu(); @@ -104,12 +103,4 @@ void ui_restore() ui_force_draw(); } -bool ui_lock_forbidden() -{ - if (blue_ui_state.state == STATE_TX) - return true; - else - return false; -} - #endif // TARGET_BLUE diff --git a/src/ui/nano/nano_core.c b/src/ui/nano/nano_core.c index c8295fce..99a8a173 100644 --- a/src/ui/nano/nano_core.c +++ b/src/ui/nano/nano_core.c @@ -115,7 +115,6 @@ void ui_init() { MEMCLEAR(ui_text); MEMCLEAR(ui_state); - ui_timeout_stop(); ui_display_main_menu(); } @@ -197,23 +196,6 @@ void ui_restore() ui_force_draw(); } -bool ui_lock_forbidden(void) -{ - // forbid app from locking during transaction (rely on tx timeout) - switch (ui_state.state) { - // BIP Path could be in tx or disp_addr - // (backup state will tell us which) - case STATE_BIP_PATH: - if (ui_state.nano_state_backup != STATE_BUNDLE) - return false; - case STATE_BUNDLE: - case STATE_BUNDLE_ADDR: - return true; - default: - return false; - } -} - static UI_BUTTON_PRESS translate_button_mask(const unsigned int button_mask) { switch (button_mask) { diff --git a/src/ui/ui.c b/src/ui/ui.c index 0d2a4af8..5c0fbd38 100644 --- a/src/ui/ui.c +++ b/src/ui/ui.c @@ -4,18 +4,10 @@ #include "api.h" #include "iota/addresses.h" -#define TICKS_PER_SECOND 10 - -// Seconds until UI timeout if expected inputs are not received -#define UI_TIMEOUT_SECONDS 3 -#define UI_TIMEOUT_INTERACTIVE_SECONDS 100 - #define WAIT_EVENT() \ io_seproxyhal_spi_recv(G_io_seproxyhal_spi_buffer, \ sizeof(G_io_seproxyhal_spi_buffer), 0) -static uint16_t timer; - void ui_force_draw() { bool display_event_occurred = false; @@ -33,32 +25,3 @@ void ui_force_draw() WAIT_EVENT(); } } - -void ui_timeout_tick() -{ - // timer not started - if (timer <= 0) { - return; - } - - timer--; - if (timer == 0) { - // throw an exception so that a result is always returned - THROW(SW_COMMAND_TIMEOUT); - } -} - -void ui_timeout_start(bool interactive) -{ - if (interactive) { - timer = UI_TIMEOUT_INTERACTIVE_SECONDS * TICKS_PER_SECOND; - } - else { - timer = UI_TIMEOUT_SECONDS * TICKS_PER_SECOND; - } -} - -void ui_timeout_stop() -{ - timer = 0; -} diff --git a/src/ui/ui.h b/src/ui/ui.h index da6774d8..2ec733a1 100644 --- a/src/ui/ui.h +++ b/src/ui/ui.h @@ -1,14 +1,9 @@ #ifndef UI_H #define UI_H -#include - +/// Displays the current screen without sending an APDU message. void ui_force_draw(void); -void ui_timeout_tick(void); -void ui_timeout_start(bool interactive); -void ui_timeout_stop(void); - // the following implementation are different for Blue and Nano void ui_init(void); void ui_display_main_menu(void); @@ -21,6 +16,4 @@ void ui_sign_tx(void); void ui_reset(void); void ui_restore(void); -bool ui_lock_forbidden(void); - #endif // UI_H diff --git a/tests/test_mocks.c b/tests/test_mocks.c index 4bd021a8..f804d0a7 100644 --- a/tests/test_mocks.c +++ b/tests/test_mocks.c @@ -32,15 +32,6 @@ void ui_display_signing() { } -void ui_timeout_start(bool interactive) -{ - UNUSED(interactive); -} - -void ui_timeout_stop() -{ -} - void ui_display_address(const unsigned char *addr_bytes) { UNUSED(addr_bytes); @@ -88,3 +79,12 @@ __attribute__((weak)) void io_send(const void *ptr, unsigned int length, snprintf(msg, 100, "%s should not be called", __FUNCTION__); mock_assert(false, msg, __FILE__, __LINE__); } + +void io_timeout_set(const unsigned int ms) +{ + mock_assert(ms > 0, "invalid ms", __FILE__, __LINE__); +} + +void io_timeout_reset() +{ +}