diff --git a/src/apdu_sign.c b/src/apdu_sign.c index 1ce1d5db..02871e5f 100644 --- a/src/apdu_sign.c +++ b/src/apdu_sign.c @@ -4,23 +4,22 @@ #include "baking_auth.h" #include "globals.h" #include "keys.h" -#include "memory.h" -#include "protocol.h" -#include "to_string.h" -#include "ui.h" - -#include "cx.h" - -#include +#include "ui_delegation.h" +#include "ui_empty.h" #define G global.apdu.u.sign #define PARSE_ERROR() THROW(EXC_PARSE_ERROR) -#define B2B_BLOCKBYTES 128 +#define B2B_BLOCKBYTES 128 /// blake2b hash size -int perform_signature(bool const send_hash); +size_t perform_signature(bool const send_hash); +/** + * @brief Initializes the blake2b state if it is not + * + * @param state: blake2b state + */ static inline void conditional_init_hash_state(blake2b_hash_state_t *const state) { check_null(state); if (!state->initialized) { @@ -30,37 +29,62 @@ static inline void conditional_init_hash_state(blake2b_hash_state_t *const state } } -static void blake2b_incremental_hash( - /*in/out*/ uint8_t *const out, - size_t const out_size, - /*in/out*/ size_t *const out_length, - /*in/out*/ blake2b_hash_state_t *const state) { - check_null(out); - check_null(out_length); +/** + * @brief Hashes incrementally a buffer using a blake2b state + * + * The hash remains in the buffer, the buffer content length is + * updated and the blake2b state is also updated + * + * @param buff: buffer + * @param buff_size: buffer size + * @param buff_length: buffer content length + * @param state: blake2b state + */ +static void blake2b_incremental_hash(uint8_t *const buff, + size_t const buff_size, + size_t *const buff_length, + blake2b_hash_state_t *const state) { + check_null(buff); + check_null(buff_length); check_null(state); - uint8_t *current = out; - while (*out_length > B2B_BLOCKBYTES) { - if (current - out > (int) out_size) { + uint8_t *current = buff; + while (*buff_length > B2B_BLOCKBYTES) { + if (current - buff > (int) buff_size) { THROW(EXC_MEMORY_ERROR); } conditional_init_hash_state(state); CX_THROW( cx_hash_no_throw((cx_hash_t *) &state->state, 0, current, B2B_BLOCKBYTES, NULL, 0)); - *out_length -= B2B_BLOCKBYTES; + *buff_length -= B2B_BLOCKBYTES; current += B2B_BLOCKBYTES; } // TODO use circular buffer at some point - memmove(out, current, *out_length); + memmove(buff, current, *buff_length); } -static void blake2b_finish_hash( - /*out*/ uint8_t *const out, - size_t const out_size, - /*in/out*/ uint8_t *const buff, - size_t const buff_size, - /*in/out*/ size_t *const buff_length, - /*in/out*/ blake2b_hash_state_t *const state) { +/** + * @brief Finalizes the hashes of a buffer using a blake2b state + * + * The buffer is modified and the buffer content length is + * updated + * + * The final hash is stored in the ouput, its size is also + * stored and the blake2b state is updated + * + * @param out: output buffer + * @param out_size: output size + * @param buff: buffer + * @param buff_size: buffer size + * @param buff_length: buffer content length + * @param state: blake2b state + */ +static void blake2b_finish_hash(uint8_t *const out, + size_t const out_size, + uint8_t *const buff, + size_t const buff_size, + size_t *const buff_length, + blake2b_hash_state_t *const state) { check_null(out); check_null(buff); check_null(buff_length); @@ -72,26 +96,55 @@ static void blake2b_finish_hash( cx_hash_no_throw((cx_hash_t *) &state->state, CX_LAST, buff, *buff_length, out, out_size)); } +/** + * @brief Allows to clear all data related to signature + * + */ static inline void clear_data(void) { memset(&G, 0, sizeof(G)); } +/** + * @brief Sends asynchronously the signature of the read message + * + * @return true + */ static bool sign_without_hash_ok(void) { delayed_send(perform_signature(false)); return true; } +/** + * @brief Sends asynchronously the signature of the read message + * preceded by its hash + * + * @return true + */ static bool sign_with_hash_ok(void) { delayed_send(perform_signature(true)); return true; } +/** + * @brief Rejects the signature + * + * Makes sure to keep no trace of the signature + * + * @return true: to return to idle without showing a reject screen + */ static bool sign_reject(void) { clear_data(); delay_reject(); - return true; // Return to idle + return true; } +/** + * @brief Carries out final checks before signing + * + * @param send_hash: if the message hash is requested + * @param flags: request flags + * @return size_t: offset of the apdu response + */ size_t baking_sign_complete(bool const send_hash, volatile uint32_t *flags) { size_t result = 0; switch (G.magic_byte) { @@ -118,7 +171,7 @@ size_t baking_sign_complete(bool const send_hash, volatile uint32_t *flags) { 0) { ui_callback_t const ok_c = send_hash ? sign_with_hash_ok : sign_without_hash_ok; - prompt_register_delegate(ok_c, sign_reject); + prompt_delegation(ok_c, sign_reject); *flags = IO_ASYNCH_REPLY; result = 0; } else { @@ -147,18 +200,30 @@ size_t baking_sign_complete(bool const send_hash, volatile uint32_t *flags) { return result; } -#define P1_FIRST 0x00 -#define P1_NEXT 0x01 -#define P1_HASH_ONLY_NEXT 0x03 // You only need it once -#define P1_LAST_MARKER 0x80 - +/** + * @brief Packet indexes + * + */ +#define P1_FIRST 0x00 /// First packet +#define P1_NEXT 0x01 /// Other packet +#define P1_LAST_MARKER 0x80 /// Last packet + +/** + * @brief Get the magic byte of a buffer + * + * Throws an error if the magic byte does not correspond to an expected byte + * + * @param buff: buffer + * @param buff_size: buffer size + * @return uint8_t: magic_byte + */ static uint8_t get_magic_byte_or_throw(uint8_t const *const buff, size_t const buff_size) { uint8_t const magic_byte = get_magic_byte(buff, buff_size); switch (magic_byte) { case MAGIC_BYTE_BLOCK: case MAGIC_BYTE_PREENDORSEMENT: case MAGIC_BYTE_ENDORSEMENT: - case MAGIC_BYTE_UNSAFE_OP: // Only for self-delegations + case MAGIC_BYTE_UNSAFE_OP: // Only for self-delegations and reveals return magic_byte; default: @@ -213,8 +278,11 @@ size_t handle_apdu_sign(uint8_t instruction, volatile uint32_t *flags) { buff_size, global.path_with_curve.derivation_type, &global.path_with_curve.bip32_path); - } else if (!parse_baking_data(&G.parsed_baking_data, buff, buff_size)) { - PARSE_ERROR(); + } else { + // This should be a baking operation so parse it. + if (!parse_baking_data(&G.parsed_baking_data, buff, buff_size)) { + PARSE_ERROR(); + } } // Hash contents of *previous* message (which may be empty). @@ -232,10 +300,6 @@ size_t handle_apdu_sign(uint8_t instruction, volatile uint32_t *flags) { if (last) { // Hash contents of *this* message and then get the final hash value. - blake2b_incremental_hash(G.message_data, - sizeof(G.message_data), - &G.message_data_length, - &G.hash_state); blake2b_finish_hash(G.final_hash, sizeof(G.final_hash), G.message_data, @@ -251,7 +315,17 @@ size_t handle_apdu_sign(uint8_t instruction, volatile uint32_t *flags) { } } -int perform_signature(bool const send_hash) { +/** + * @brief Perfoms the signature of the read message + * + * Fills apdu response with the signature + * + * Precedes the signature with the message hash if requested + * + * @param send_hash: if the message hash is requested + * @return size_t: offset of the apdu response + */ +size_t perform_signature(bool const send_hash) { if (os_global_pin_is_validated() != BOLOS_UX_OK) { THROW(EXC_SECURITY); } diff --git a/src/apdu_sign.h b/src/apdu_sign.h index dc3490e3..9752210e 100644 --- a/src/apdu_sign.h +++ b/src/apdu_sign.h @@ -1,7 +1,13 @@ #pragma once -#include "apdu.h" +#include +#include +/** + * @brief Handles SIGN and SIGN_WITH_HASH instruction + * + * @param instruction: apdu instruction + * @param flags: io flags + * @return size_t: offset of the apdu response + */ size_t handle_apdu_sign(uint8_t instruction, volatile uint32_t* flags); - -void prompt_register_delegate(ui_callback_t const ok_cb, ui_callback_t const cxl_cb); diff --git a/src/ui.h b/src/ui.h index 05f30e02..98202ac5 100644 --- a/src/ui.h +++ b/src/ui.h @@ -17,7 +17,6 @@ void ux_confirm_screen(ui_callback_t ok_c, ui_callback_t cxl_c); void ux_idle_screen(ui_callback_t ok_c, ui_callback_t cxl_c); -void ux_empty_screen(void); /* Initializes the formatter stack. Should be called once before calling `push_ui_callback()`. */ void init_screen_stack(); /* User MUST call `init_screen_stack()` before calling this function for the first time. */ diff --git a/src/ui_bagl.c b/src/ui_bagl.c index ee8289bf..1a9b5883 100644 --- a/src/ui_bagl.c +++ b/src/ui_bagl.c @@ -11,6 +11,7 @@ #include "memory.h" #include "os_cx.h" // ui-menu #include "to_string.h" +#include "ui_empty.h" #include #include diff --git a/src/ui_delegation.h b/src/ui_delegation.h new file mode 100644 index 00000000..93aa161c --- /dev/null +++ b/src/ui_delegation.h @@ -0,0 +1,17 @@ +#pragma once + +#include "types.h" + +/** + * @brief Draws delegation confirmation pages flow + * + * - Initial screen + * - Values: + * - Address + * - Fee + * - Confirmation screens + * + * @param ok_cb: accept callback + * @param cxl_cb: reject callback + */ +void prompt_delegation(ui_callback_t const ok_cb, ui_callback_t const cxl_cb); diff --git a/src/ui_delegation_bagl.c b/src/ui_delegation_bagl.c new file mode 100644 index 00000000..1e1f4366 --- /dev/null +++ b/src/ui_delegation_bagl.c @@ -0,0 +1,20 @@ +#ifdef HAVE_BAGL + +#include "ui_delegation.h" + +#include "globals.h" +#include "to_string.h" +#include "ui.h" + +#define G global.apdu.u.sign + +void prompt_delegation(ui_callback_t const ok_cb, ui_callback_t const cxl_cb) { + init_screen_stack(); + push_ui_callback("Register", copy_string, "as delegate?"); + push_ui_callback("Address", bip32_path_with_curve_to_pkh_string, &global.path_with_curve); + push_ui_callback("Fee", microtez_to_string_indirect, &G.maybe_ops.v.total_fee); + + ux_confirm_screen(ok_cb, cxl_cb); +} + +#endif // HAVE_BAGL diff --git a/src/ui_delegation_nbgl.c b/src/ui_delegation_nbgl.c new file mode 100644 index 00000000..cb2f9a40 --- /dev/null +++ b/src/ui_delegation_nbgl.c @@ -0,0 +1,120 @@ +#ifdef HAVE_NBGL + +#include "ui_delegation.h" + +#include "globals.h" +#include "to_string.h" +#include "ui.h" + +#include "nbgl_use_case.h" + +#define G global.apdu.u.sign + +#define BUFFER_SIZE 100 + +#define NUMBER_VALUES 2u /// Number of values displayed + +/** + * @brief This structure represents a context needed for delegation screens navigation + * + */ +typedef struct { + ui_callback_t ok_cb; /// accept callback + ui_callback_t cxl_cb; /// cancel callback + nbgl_layoutTagValue_t tagValues[NUMBER_VALUES]; + nbgl_layoutTagValueList_t tagValueList; + nbgl_pageInfoLongPress_t infoLongPress; + char buffer[NUMBER_VALUES][BUFFER_SIZE]; /// values buffers; +} DelegationContext_t; + +/** + * @brief Current delegation context + * + */ +static DelegationContext_t delegation_context; + +/** + * @brief Callback called when delegation is rejected + * + */ +static void cancel_callback(void) { + nbgl_useCaseStatus("Delegate registration\ncancelled", false, ui_initial_screen); + delegation_context.cxl_cb(); +} + +/** + * @brief Callback called when delegation is approved + * + */ +static void approve_callback(void) { + nbgl_useCaseStatus("DELEGATE\nCONFIRMED", true, ui_initial_screen); + delegation_context.ok_cb(); +} + +/** + * @brief Callback called when delegation is accepted or cancelled + * + * @param confirm: true if accepted, false if cancelled + */ +static void confirmation_callback(bool confirm) { + if (confirm) { + approve_callback(); + } else { + cancel_callback(); + } +} + +/** + * @brief Draws a confirmation page + * + */ +static void confirm_delegation_page(void) { + delegation_context.infoLongPress.icon = &C_tezos; + delegation_context.infoLongPress.longPressText = "Approve"; + delegation_context.infoLongPress.tuneId = TUNE_TAP_CASUAL; + delegation_context.infoLongPress.text = "Confirm delegate\nregistration"; + + nbgl_useCaseStaticReviewLight(&delegation_context.tagValueList, + &delegation_context.infoLongPress, + "Cancel", + confirmation_callback); +} + +void prompt_delegation(ui_callback_t const ok_cb, ui_callback_t const cxl_cb) { + uint8_t value_index = 0; + + delegation_context.ok_cb = ok_cb; + delegation_context.cxl_cb = cxl_cb; + + bip32_path_with_curve_to_pkh_string(delegation_context.buffer[value_index], + BUFFER_SIZE, + &global.path_with_curve); + + delegation_context.tagValues[value_index].item = "Address"; + delegation_context.tagValues[value_index].value = delegation_context.buffer[value_index]; + value_index++; + + microtez_to_string_indirect(delegation_context.buffer[value_index], + BUFFER_SIZE, + &G.maybe_ops.v.total_fee); + + delegation_context.tagValues[value_index].item = "Fee"; + delegation_context.tagValues[value_index].value = delegation_context.buffer[value_index]; + value_index++; + + if (value_index != NUMBER_VALUES) { + THROW(EXC_MEMORY_ERROR); + } + + delegation_context.tagValueList.nbPairs = NUMBER_VALUES; + delegation_context.tagValueList.pairs = delegation_context.tagValues; + + nbgl_useCaseReviewStart(&C_tezos, + "Register delegate", + NULL, + "Cancel", + confirm_delegation_page, + cancel_callback); +} + +#endif // HAVE_NBGL diff --git a/src/ui_empty.h b/src/ui_empty.h new file mode 100644 index 00000000..a90ea618 --- /dev/null +++ b/src/ui_empty.h @@ -0,0 +1,11 @@ +#pragma once + +/** + * @brief Empties the screen + * + * Waits a click to return to home screen + * + * Applies only for Nano devices + * + */ +void ux_empty_screen(void); diff --git a/src/ui_sign_bagl.c b/src/ui_sign_bagl.c deleted file mode 100644 index 94ec7336..00000000 --- a/src/ui_sign_bagl.c +++ /dev/null @@ -1,37 +0,0 @@ -#ifdef HAVE_BAGL -#include "apdu_sign.h" - -#include "apdu.h" -#include "baking_auth.h" -#include "globals.h" -#include "keys.h" -#include "memory.h" -#include "protocol.h" -#include "to_string.h" -#include "ui.h" -#include "cx.h" - -#include - -#define G global.apdu.u.sign - -#define PARSE_ERROR() THROW(EXC_PARSE_ERROR) - -#define B2B_BLOCKBYTES 128 - -__attribute__((noreturn)) void prompt_register_delegate(ui_callback_t const ok_cb, - ui_callback_t const cxl_cb) { - if (!G.maybe_ops.is_valid) { - THROW(EXC_MEMORY_ERROR); - } - - init_screen_stack(); - push_ui_callback("Register", copy_string, "as delegate?"); - push_ui_callback("Address", bip32_path_with_curve_to_pkh_string, &global.path_with_curve); - push_ui_callback("Fee", microtez_to_string_indirect, &G.maybe_ops.v.total_fee); - - ux_confirm_screen(ok_cb, cxl_cb); - __builtin_unreachable(); -} - -#endif // HAVE_BAGL diff --git a/src/ui_sign_nbgl.c b/src/ui_sign_nbgl.c deleted file mode 100644 index af3a96ad..00000000 --- a/src/ui_sign_nbgl.c +++ /dev/null @@ -1,102 +0,0 @@ -#ifdef HAVE_NBGL -#include "nbgl_use_case.h" -#include "apdu_sign.h" - -#include "apdu.h" -#include "baking_auth.h" -#include "globals.h" -#include "keys.h" -#include "memory.h" -#include "protocol.h" -#include "to_string.h" -#include "ui.h" -#include "cx.h" - -#include - -#define G global.apdu.u.sign - -#define MAX_LENGTH 100 - -#define PARSE_ERROR() THROW(EXC_PARSE_ERROR) - -typedef struct { - ui_callback_t ok_cb; - ui_callback_t cxl_cb; - nbgl_layoutTagValue_t tagValuePair[6]; - nbgl_layoutTagValueList_t tagValueList; - nbgl_pageInfoLongPress_t infoLongPress; - const char* confirmed_status; - const char* cancelled_status; - char buffer[6][MAX_LENGTH]; -} TransactionContext_t; - -static TransactionContext_t transactionContext; - -static void cancel_callback(void) { - nbgl_useCaseStatus(transactionContext.cancelled_status, false, ui_initial_screen); - transactionContext.cxl_cb(); -} - -static void approve_callback(void) { - nbgl_useCaseStatus(transactionContext.confirmed_status, true, ui_initial_screen); - transactionContext.ok_cb(); -} - -static void confirmation_callback(bool confirm) { - if (confirm) { - approve_callback(); - } else { - cancel_callback(); - } -} - -static void continue_light_callback(void) { - transactionContext.infoLongPress.icon = &C_tezos; - transactionContext.infoLongPress.longPressText = "Approve"; - transactionContext.infoLongPress.tuneId = TUNE_TAP_CASUAL; - - nbgl_useCaseStaticReviewLight(&transactionContext.tagValueList, - &transactionContext.infoLongPress, - "Cancel", - confirmation_callback); -} - -void prompt_register_delegate(ui_callback_t const ok_cb, ui_callback_t const cxl_cb) { - if (!G.maybe_ops.is_valid) { - THROW(EXC_MEMORY_ERROR); - } - - transactionContext.ok_cb = ok_cb; - transactionContext.cxl_cb = cxl_cb; - - bip32_path_with_curve_to_pkh_string(transactionContext.buffer[0], - sizeof(transactionContext.buffer[0]), - &global.path_with_curve); - microtez_to_string_indirect(transactionContext.buffer[1], - sizeof(transactionContext.buffer[1]), - &G.maybe_ops.v.total_fee); - - transactionContext.tagValuePair[0].item = "Address"; - transactionContext.tagValuePair[0].value = transactionContext.buffer[0]; - - transactionContext.tagValuePair[1].item = "Fee"; - transactionContext.tagValuePair[1].value = transactionContext.buffer[1]; - - transactionContext.tagValueList.nbPairs = 2; - transactionContext.tagValueList.pairs = transactionContext.tagValuePair; - - transactionContext.infoLongPress.text = "Confirm delegate\nregistration"; - - transactionContext.confirmed_status = "DELEGATE\nCONFIRMED"; - transactionContext.cancelled_status = "Delegate registration\ncancelled"; - - nbgl_useCaseReviewStart(&C_tezos, - "Register delegate", - NULL, - "Cancel", - continue_light_callback, - cancel_callback); -} - -#endif // HAVE_NBGL