diff --git a/firmware/Makefile b/firmware/Makefile index f5341021e2..e760d57f38 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -28,6 +28,7 @@ OBJS += ethereum.o OBJS += ethereum_tokens.o OBJS += nem2.o OBJS += nem_mosaics.o +OBJS += stellar.o OBJS += debug.o diff --git a/firmware/fsm.c b/firmware/fsm.c index 72093a0df6..9e150eebd1 100644 --- a/firmware/fsm.c +++ b/firmware/fsm.c @@ -56,6 +56,7 @@ #include "rfc6979.h" #include "gettext.h" #include "supervise.h" +#include "stellar.h" // message methods @@ -1637,6 +1638,280 @@ void fsm_msgCosiSign(CosiSign *msg) layoutHome(); } +void fsm_msgStellarGetPublicKey(StellarGetPublicKey *msg) +{ + RESP_INIT(StellarPublicKey); + + CHECK_INITIALIZED + + CHECK_PIN + + // Will exit if the user does not confirm + stellar_layoutGetPublicKey(msg->address_n, msg->address_n_count); + + // Read public key and write it to the response + resp->has_public_key = true; + resp->public_key.size = 32; + stellar_getPubkeyAtAddress(msg->address_n, msg->address_n_count, resp->public_key.bytes, sizeof(resp->public_key.bytes)); + + msg_write(MessageType_MessageType_StellarPublicKey, resp); + + layoutHome(); +} + +void fsm_msgStellarSignMessage(StellarSignMessage *msg) +{ + CHECK_INITIALIZED + CHECK_PIN + + RESP_INIT(StellarMessageSignature); + + // Will exit if the user does not confirm + stellar_confirmSignString(msg, resp); + + msg_write(MessageType_MessageType_StellarMessageSignature, resp); + + layoutHome(); +} + +void fsm_msgStellarVerifyMessage(StellarVerifyMessage *msg) +{ + if (!stellar_verifySignature(msg)) { + fsm_sendFailure(FailureType_Failure_DataError, _("Invalid signature")); + return; + } + + fsm_sendSuccess(_("Message verified")); + layoutHome(); +} + +void fsm_msgStellarSignTx(StellarSignTx *msg) +{ + CHECK_INITIALIZED + CHECK_PIN + + stellar_signingInit(msg); + + // Confirm transaction basics + stellar_layoutTransactionSummary(msg); + + // Respond with a request for the first operation + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); +} + +void fsm_msgStellarCreateAccountOp(StellarCreateAccountOp *msg) +{ + stellar_confirmCreateAccountOp(msg); + + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + +void fsm_msgStellarPaymentOp(StellarPaymentOp *msg) +{ + // This will display additional dialogs to the user + stellar_confirmPaymentOp(msg); + + // Last operation was confirmed, send a StellarSignedTx + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + +void fsm_msgStellarPathPaymentOp(StellarPathPaymentOp *msg) +{ + stellar_confirmPathPaymentOp(msg); + + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + +void fsm_msgStellarManageOfferOp(StellarManageOfferOp *msg) +{ + stellar_confirmManageOfferOp(msg); + + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + +void fsm_msgStellarCreatePassiveOfferOp(StellarCreatePassiveOfferOp *msg) +{ + stellar_confirmCreatePassiveOfferOp(msg); + + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + +void fsm_msgStellarSetOptionsOp(StellarSetOptionsOp *msg) +{ + stellar_confirmSetOptionsOp(msg); + + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + +void fsm_msgStellarChangeTrustOp(StellarChangeTrustOp *msg) +{ + stellar_confirmChangeTrustOp(msg); + + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + +void fsm_msgStellarAllowTrustOp(StellarAllowTrustOp *msg) +{ + stellar_confirmAllowTrustOp(msg); + + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + +void fsm_msgStellarAccountMergeOp(StellarAccountMergeOp *msg) +{ + stellar_confirmAccountMergeOp(msg); + + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + +void fsm_msgStellarManageDataOp(StellarManageDataOp *msg) +{ + stellar_confirmManageDataOp(msg); + + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + +void fsm_msgStellarBumpSequenceOp(StellarBumpSequenceOp *msg) +{ + stellar_confirmBumpSequenceOp(msg); + + if (stellar_allOperationsConfirmed()) { + RESP_INIT(StellarSignedTx); + + stellar_fillSignedTx(resp); + msg_write(MessageType_MessageType_StellarSignedTx, resp); + layoutHome(); + } + // Request the next operation to sign + else { + RESP_INIT(StellarTxOpRequest); + + msg_write(MessageType_MessageType_StellarTxOpRequest, resp); + } +} + #if DEBUG_LINK void fsm_msgDebugLinkGetState(DebugLinkGetState *msg) diff --git a/firmware/fsm.h b/firmware/fsm.h index 24861a2f07..6bdb22c490 100644 --- a/firmware/fsm.h +++ b/firmware/fsm.h @@ -80,6 +80,23 @@ void fsm_msgNEMDecryptMessage(NEMDecryptMessage *msg); void fsm_msgCosiCommit(CosiCommit *msg); void fsm_msgCosiSign(CosiSign *msg); +// Stellar +void fsm_msgStellarGetPublicKey(StellarGetPublicKey *msg); +void fsm_msgStellarSignTx(StellarSignTx *msg); +void fsm_msgStellarPaymentOp(StellarPaymentOp *msg); +void fsm_msgStellarCreateAccountOp(StellarCreateAccountOp *msg); +void fsm_msgStellarPathPaymentOp(StellarPathPaymentOp *msg); +void fsm_msgStellarManageOfferOp(StellarManageOfferOp *msg); +void fsm_msgStellarCreatePassiveOfferOp(StellarCreatePassiveOfferOp *msg); +void fsm_msgStellarSetOptionsOp(StellarSetOptionsOp *msg); +void fsm_msgStellarChangeTrustOp(StellarChangeTrustOp *msg); +void fsm_msgStellarAllowTrustOp(StellarAllowTrustOp *msg); +void fsm_msgStellarAccountMergeOp(StellarAccountMergeOp *msg); +void fsm_msgStellarManageDataOp(StellarManageDataOp *msg); +void fsm_msgStellarSignMessage(StellarSignMessage *msg); +void fsm_msgStellarVerifyMessage(StellarVerifyMessage *msg); +void fsm_msgStellarBumpSequenceOp(StellarBumpSequenceOp *msg); + // debug message functions #if DEBUG_LINK //void fsm_msgDebugLinkDecision(DebugLinkDecision *msg); diff --git a/firmware/protob/messages.options b/firmware/protob/messages.options index 4541c68ed4..5e3028ed04 100644 --- a/firmware/protob/messages.options +++ b/firmware/protob/messages.options @@ -176,6 +176,68 @@ CosiSign.global_pubkey max_size:32 CosiSignature.signature max_size:32 + +# Stellar +StellarGetPublicKey.address_n max_count:10 + +StellarPublicKey.public_key max_size:32 + +StellarSignMessage.address_n max_count:10 +StellarSignMessage.message max_size:1024 + +StellarMessageSignature.public_key max_size:32 +StellarMessageSignature.signature max_size:64 + +StellarVerifyMessage.public_key max_size:32 +StellarVerifyMessage.message max_size:1024 +StellarVerifyMessage.signature max_size:64 + +StellarMessageVerification.public_key max_size: 32 + +StellarSignTx.address_n max_count:10 +StellarSignTx.network_passphrase max_size:1024 +StellarSignTx.source_account max_size:32 +StellarSignTx.memo_text max_size:29 +StellarSignTx.memo_hash max_size:32 + +StellarPaymentOp.source_account max_size:32 +StellarPaymentOp.destination_account max_size:32 + +StellarCreateAccountOp.source_account max_size:32 +StellarCreateAccountOp.new_account max_size:32 + +StellarPathPaymentOp.source_account max_size:32 +StellarPathPaymentOp.destination_account max_size:32 +StellarPathPaymentOp.paths max_count:5 + +StellarManageOfferOp.source_account max_size:32 + +StellarCreatePassiveOfferOp.source_account max_size:32 + +StellarSetOptionsOp.source_account max_size:32 +StellarSetOptionsOp.inflation_destination_account max_size:32 +StellarSetOptionsOp.home_domain max_size:33 +StellarSetOptionsOp.signer_key max_size:32 + +StellarChangeTrustOp.source_account max_size:32 + +StellarAllowTrustOp.source_account max_size:32 +StellarAllowTrustOp.trusted_account max_size:32 +StellarAllowTrustOp.asset_code max_size:13 + +StellarAccountMergeOp.source_account max_size:32 +StellarAccountMergeOp.destination_account max_size:32 + +StellarManageDataOp.source_account max_size:32 +StellarManageDataOp.key max_size:65 +StellarManageDataOp.value max_size:65 + +StellarBumpSequenceOp.source_account max_size:32 + +StellarSignedTx.public_key max_size:32 +StellarSignedTx.signature max_size:64 # ed25519 signatures are 64 bytes, this does not include the hint + + # deprecated SimpleSignTx skip_message:true diff --git a/firmware/protob/types.options b/firmware/protob/types.options index 527e4fac73..ea53f085c7 100644 --- a/firmware/protob/types.options +++ b/firmware/protob/types.options @@ -72,3 +72,6 @@ NEMAggregateModification.modifications max_count:16 NEMCosignatoryModification.public_key max_size:32 NEMImportanceTransfer.public_key max_size:32 + +StellarAssetType.code max_size:13 +StellarAssetType.issuer max_size:32 \ No newline at end of file diff --git a/firmware/stellar.c b/firmware/stellar.c new file mode 100644 index 0000000000..e242dee2c9 --- /dev/null +++ b/firmware/stellar.c @@ -0,0 +1,1850 @@ +/* + * This file is part of the TREZOR project. + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + * + * Stellar signing has the following workflow: + * 1. Client sends first 1024 bytes of the transaction + * 2. Trezor parses the transaction header and confirms the details with the user + * 3. Trezor responds to the client with an offset for where to send the next chunk of bytes + * 4. Client sends next 1024 bytes starting at + * 5. Trezor parses and confirms the next operation + * 6. Trezor responds with either an offset for the next operation or a signature + */ + +#include +#include +#include "messages.h" +#include "messages.pb.h" +#include "stellar.h" +#include "bip32.h" +#include "crypto.h" +#include "layout2.h" +#include "gettext.h" +#include "bignum.h" +#include "oled.h" +#include "base32.h" +#include "storage.h" +#include "fsm.h" +#include "protect.h" +#include "util.h" +#include "layout2.h" +#include "fonts.h" + +static bool stellar_signing = false; +static StellarTransaction stellar_activeTx; + +static const char **split_message(const uint8_t *msg, uint32_t len, uint32_t rowlen) +{ + static char str[4][32 + 1]; + if (rowlen > 32) { + rowlen = 32; + } + memset(str, 0, sizeof(str)); + strlcpy(str[0], (char *)msg, rowlen + 1); + if (len > rowlen) { + strlcpy(str[1], (char *)msg + rowlen, rowlen + 1); + } + if (len > rowlen * 2) { + strlcpy(str[2], (char *)msg + rowlen * 2, rowlen + 1); + } + if (len > rowlen * 3) { + strlcpy(str[3], (char *)msg + rowlen * 3, rowlen + 1); + } + if (len > rowlen * 4) { + str[3][rowlen - 1] = '.'; + str[3][rowlen - 2] = '.'; + str[3][rowlen - 3] = '.'; + } + static const char *ret[4] = { str[0], str[1], str[2], str[3] }; + return ret; +} + +void stellar_confirmSignString(StellarSignMessage *msg, StellarMessageSignature *resp) +{ + // Max protobuf length is 1024, so string is 1023 + null + int message_len = strnlen(msg->message, 1023); + + // Verify that message only includes printable ascii characters + bool is_valid = true; + for (int i=0; i < message_len; i++) { + if (msg->message[i] < 32) { + is_valid = false; + break; + } + if (msg->message[i] >126) { + is_valid = false; + break; + } + } + if (!is_valid) { + stellar_layoutSigningDialog( + _("Cannot sign message"), + NULL, + _("Message contains"), + _("non-printable ascii"), + _("characters."), + msg->address_n, + msg->address_n_count, + NULL, + false + ); + protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false); + layoutHome(); + return; + } + + // Message can be signed, display as much of it as possible to the user + const char **str_message_lines = split_message((const uint8_t*)(msg->message), message_len, 24); + + stellar_layoutSigningDialog( + _("Sign message?"), + str_message_lines[0], + str_message_lines[1], + str_message_lines[2], + str_message_lines[3], + msg->address_n, + msg->address_n_count, + NULL, + true + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + return; + } + + // Populate response message + stellar_signString((const unsigned char*)(msg->message), msg->address_n, msg->address_n_count, resp->signature.bytes); + resp->has_signature = true; + resp->signature.size = 64; + + stellar_getPubkeyAtAddress(msg->address_n, msg->address_n_count, resp->public_key.bytes, sizeof(resp->public_key.bytes)); + resp->has_public_key = true; + resp->public_key.size = 32; +} + +/* + * Starts the signing process and parses the transaction header + */ +void stellar_signingInit(StellarSignTx *msg) +{ + memset(&stellar_activeTx, 0, sizeof(StellarTransaction)); + stellar_signing = true; + // Initialize signing context + sha256_Init(&(stellar_activeTx.sha256_ctx)); + + // Calculate sha256 for network passphrase + // max length defined in messages.options + uint8_t network_hash[32]; + sha256_Raw((uint8_t *)msg->network_passphrase, strnlen(msg->network_passphrase, 1024), network_hash); + + uint8_t tx_type_bytes[4] = { 0x00, 0x00, 0x00, 0x02 }; + + // Copy some data into the active tx + stellar_activeTx.num_operations = msg->num_operations; + + // Start building what will be signed: + // sha256 of: + // sha256(network passphrase) + // 4-byte unsigned big-endian int type constant (2 for tx) + // remaining bytes are operations added in subsequent messages + stellar_hashupdate_bytes(network_hash, sizeof(network_hash)); + stellar_hashupdate_bytes(tx_type_bytes, sizeof(tx_type_bytes)); + + // Public key comes from deriving the specified account path (we ignore the one sent by the client) + uint8_t bytes_pubkey[32]; + stellar_getPubkeyAtAddress(msg->address_n, msg->address_n_count, bytes_pubkey, sizeof(bytes_pubkey)); + memcpy(&(stellar_activeTx.signing_pubkey), bytes_pubkey, sizeof(stellar_activeTx.signing_pubkey)); + + stellar_activeTx.address_n_count = msg->address_n_count; + // todo: fix sizeof check + memcpy(&(stellar_activeTx.address_n), &(msg->address_n), sizeof(stellar_activeTx.address_n)); + + // Hash: public key + stellar_hashupdate_address(bytes_pubkey); + + // Hash: fee + stellar_hashupdate_uint32(msg->fee); + + // Hash: sequence number + stellar_hashupdate_uint64(msg->sequence_number); + + // Timebounds are only present if timebounds_start or timebounds_end is non-zero + uint8_t has_timebounds = (msg->timebounds_start > 0 || msg->timebounds_end > 0); + if (has_timebounds) { + // Hash: the "has timebounds?" boolean + stellar_hashupdate_bool(true); + + // Timebounds are sent as uint32s since that's all we can display, but they must be hashed as + // 64-bit values + stellar_hashupdate_uint32(0); + stellar_hashupdate_uint32(msg->timebounds_start); + + stellar_hashupdate_uint32(0); + stellar_hashupdate_uint32(msg->timebounds_end); + } + // No timebounds, hash a false boolean + else { + stellar_hashupdate_bool(false); + } + + // Hash: memo + stellar_hashupdate_uint32(msg->memo_type); + switch (msg->memo_type) { + // None, nothing else to do + case 0: + break; + // Text: 4 bytes (size) + up to 28 bytes + case 1: + stellar_hashupdate_string((unsigned char*)&(msg->memo_text), strnlen(msg->memo_text, 28)); + break; + // ID (8 bytes, uint64) + case 2: + stellar_hashupdate_uint64(msg->memo_id); + break; + // Hash and return are the same data structure (32 byte tx hash) + case 3: + case 4: + stellar_hashupdate_bytes(msg->memo_hash.bytes, msg->memo_hash.size); + break; + default: + break; + } + + // Hash: number of operations + stellar_hashupdate_uint32(msg->num_operations); + + // Determine what type of network this transaction is for + if (strncmp("Public Global Stellar Network ; September 2015", msg->network_passphrase, 1024) == 0) { + stellar_activeTx.network_type = 1; + } + else if (strncmp("Test SDF Network ; September 2015", msg->network_passphrase, 1024) == 0) { + stellar_activeTx.network_type = 2; + } + else { + stellar_activeTx.network_type = 3; + } +} + +void stellar_confirmSourceAccount(bool has_source_account, uint8_t *bytes) +{ + if (!has_source_account) { + stellar_hashupdate_bool(false); + return; + } + + const char **str_addr_rows = stellar_lineBreakAddress(bytes); + + stellar_layoutTransactionDialog( + _("Op src account OK?"), + NULL, + str_addr_rows[0], + str_addr_rows[1], + str_addr_rows[2] + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + + // Hash: source account + stellar_hashupdate_address(bytes); +} + +void stellar_confirmCreateAccountOp(StellarCreateAccountOp *msg) +{ + stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + // Hash: operation type + stellar_hashupdate_uint32(0); + + const char **str_addr_rows = stellar_lineBreakAddress(msg->new_account.bytes); + + // Amount being funded + char str_amount_line[32]; + char str_amount[32]; + stellar_format_stroops(msg->starting_balance, str_amount, sizeof(str_amount)); + + strlcpy(str_amount_line, _("With "), sizeof(str_amount_line)); + strlcat(str_amount_line, str_amount, sizeof(str_amount_line)); + strlcat(str_amount_line, _(" XLM"), sizeof(str_amount_line)); + + stellar_layoutTransactionDialog( + _("Create account: "), + str_addr_rows[0], + str_addr_rows[1], + str_addr_rows[2], + str_amount_line + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + + // Hash: address + stellar_hashupdate_address(msg->new_account.bytes); + // Hash: starting amount + stellar_hashupdate_uint64(msg->starting_balance); + + stellar_activeTx.confirmed_operations++; +} + +void stellar_confirmPaymentOp(StellarPaymentOp *msg) +{ + stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + // Hash: operation type + stellar_hashupdate_uint32(1); + + const char **str_addr_rows = stellar_lineBreakAddress(msg->destination_account.bytes); + + // To: G... + char str_to[32]; + strlcpy(str_to, _("To: "), sizeof(str_to)); + strlcat(str_to, str_addr_rows[0], sizeof(str_to)); + + char str_asset_row[32]; + memset(str_asset_row, 0, sizeof(str_asset_row)); + stellar_format_asset(&(msg->asset), str_asset_row, sizeof(str_asset_row)); + + char str_pay_amount[32]; + char str_amount[32]; + stellar_format_stroops(msg->amount, str_amount, sizeof(str_amount)); + + strlcpy(str_pay_amount, _("Pay "), sizeof(str_pay_amount)); + strlcat(str_pay_amount, str_amount, sizeof(str_pay_amount)); + + stellar_layoutTransactionDialog( + str_pay_amount, + str_asset_row, + str_to, + str_addr_rows[1], + str_addr_rows[2] + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + + // Hash destination + stellar_hashupdate_address(msg->destination_account.bytes); + // asset + stellar_hashupdate_asset(&(msg->asset)); + // amount (even though amount is signed it doesn't matter for hashing) + stellar_hashupdate_uint64(msg->amount); + + // At this point, the operation is confirmed + stellar_activeTx.confirmed_operations++; +} + +void stellar_confirmPathPaymentOp(StellarPathPaymentOp *msg) +{ + stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + // Hash: operation type + stellar_hashupdate_uint32(2); + + const char **str_dest_rows = stellar_lineBreakAddress(msg->destination_account.bytes); + + // To: G... + char str_to[32]; + strlcpy(str_to, _("To: "), sizeof(str_to)); + strlcat(str_to, str_dest_rows[0], sizeof(str_to)); + + char str_send_asset[32]; + char str_dest_asset[32]; + stellar_format_asset(&(msg->send_asset), str_send_asset, sizeof(str_send_asset)); + stellar_format_asset(&(msg->destination_asset), str_dest_asset, sizeof(str_dest_asset)); + + char str_pay_amount[32]; + char str_amount[32]; + stellar_format_stroops(msg->destination_amount, str_amount, sizeof(str_amount)); + + strlcpy(str_pay_amount, _("Path Pay "), sizeof(str_pay_amount)); + strlcat(str_pay_amount, str_amount, sizeof(str_pay_amount)); + + // Confirm what the receiver will get + /* + Path Pay 100 + JPY (G1234ABCDEF) + To: G.... + .... + .... + */ + stellar_layoutTransactionDialog( + str_pay_amount, + str_dest_asset, + str_to, + str_dest_rows[1], + str_dest_rows[2] + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + + // Confirm what the sender is using to pay + char str_source_amount[32]; + char str_source_number[32]; + stellar_format_stroops(msg->send_max, str_source_number, sizeof(str_source_number)); + + strlcpy(str_source_amount, _("Pay Using "), sizeof(str_source_amount)); + strlcat(str_source_amount, str_source_number, sizeof(str_source_amount)); + + stellar_layoutTransactionDialog( + str_source_amount, + str_send_asset, + NULL, + _("This is the amount debited"), + _("from your account.") + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + // Note: no confirmation for intermediate steps since they don't impact the user + + // Hash send asset + stellar_hashupdate_asset(&(msg->send_asset)); + // send max (signed vs. unsigned doesn't matter wrt hashing) + stellar_hashupdate_uint64(msg->send_max); + // destination account + stellar_hashupdate_address(msg->destination_account.bytes); + // destination asset + stellar_hashupdate_asset(&(msg->destination_asset)); + // destination amount + stellar_hashupdate_uint64(msg->destination_amount); + + // paths are stored as an array so hash the number of elements as a uint32 + stellar_hashupdate_uint32(msg->paths_count); + for (uint8_t i=0; i < msg->paths_count; i++) { + stellar_hashupdate_asset(&(msg->paths[i])); + } + + // At this point, the operation is confirmed + stellar_activeTx.confirmed_operations++; +} + +void stellar_confirmManageOfferOp(StellarManageOfferOp *msg) +{ + stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + // Hash: operation type + stellar_hashupdate_uint32(3); + + // New Offer / Delete #123 / Update #123 + char str_offer[32]; + if (msg->offer_id == 0) { + strlcpy(str_offer, _("New Offer"), sizeof(str_offer)); + } + else { + char str_offer_id[20]; + stellar_format_uint64(msg->offer_id, str_offer_id, sizeof(str_offer_id)); + + if (msg->amount == 0) { + strlcpy(str_offer, _("Delete #"), sizeof(str_offer)); + } + else { + strlcpy(str_offer, _("Update #"), sizeof(str_offer)); + } + + strlcat(str_offer, str_offer_id, sizeof(str_offer)); + } + + char str_selling[32]; + char str_sell_amount[32]; + char str_selling_asset[32]; + + stellar_format_asset(&(msg->selling_asset), str_selling_asset, sizeof(str_selling_asset)); + stellar_format_stroops(msg->amount, str_sell_amount, sizeof(str_sell_amount)); + + /* + Sell 200 + XLM (Native Asset) + */ + strlcpy(str_selling, _("Sell "), sizeof(str_selling)); + strlcat(str_selling, str_sell_amount, sizeof(str_selling)); + + char str_buying[32]; + char str_buying_asset[32]; + char str_price[17]; + + stellar_format_asset(&(msg->buying_asset), str_buying_asset, sizeof(str_buying_asset)); + stellar_format_price(msg->price_n, msg->price_d, str_price, sizeof(str_price)); + + /* + For 0.675952 Per + USD (G12345678) + */ + strlcpy(str_buying, _("For "), sizeof(str_buying)); + strlcat(str_buying, str_price, sizeof(str_buying)); + strlcat(str_buying, _(" Per"), sizeof(str_buying)); + + stellar_layoutTransactionDialog( + str_offer, + str_selling, + str_selling_asset, + str_buying, + str_buying_asset + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + + // Hash selling asset + stellar_hashupdate_asset(&(msg->selling_asset)); + // buying asset + stellar_hashupdate_asset(&(msg->buying_asset)); + // amount to sell (signed vs. unsigned doesn't matter wrt hashing) + stellar_hashupdate_uint64(msg->amount); + // numerator + stellar_hashupdate_uint32(msg->price_n); + // denominator + stellar_hashupdate_uint32(msg->price_d); + // offer ID + stellar_hashupdate_uint64(msg->offer_id); + + // At this point, the operation is confirmed + stellar_activeTx.confirmed_operations++; +} + +void stellar_confirmCreatePassiveOfferOp(StellarCreatePassiveOfferOp *msg) +{ + stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + // Hash: operation type + stellar_hashupdate_uint32(4); + + // New Offer / Delete #123 / Update #123 + char str_offer[32]; + if (msg->amount == 0) { + strlcpy(str_offer, _("Delete Passive Offer"), sizeof(str_offer)); + } + else { + strlcpy(str_offer, _("New Passive Offer"), sizeof(str_offer)); + } + + char str_selling[32]; + char str_sell_amount[32]; + char str_selling_asset[32]; + + stellar_format_asset(&(msg->selling_asset), str_selling_asset, sizeof(str_selling_asset)); + stellar_format_stroops(msg->amount, str_sell_amount, sizeof(str_sell_amount)); + + /* + Sell 200 + XLM (Native Asset) + */ + strlcpy(str_selling, _("Sell "), sizeof(str_selling)); + strlcat(str_selling, str_sell_amount, sizeof(str_selling)); + + char str_buying[32]; + char str_buying_asset[32]; + char str_price[17]; + + stellar_format_asset(&(msg->buying_asset), str_buying_asset, sizeof(str_buying_asset)); + stellar_format_price(msg->price_n, msg->price_d, str_price, sizeof(str_price)); + + /* + For 0.675952 Per + USD (G12345678) + */ + strlcpy(str_buying, _("For "), sizeof(str_buying)); + strlcat(str_buying, str_price, sizeof(str_buying)); + strlcat(str_buying, _(" Per"), sizeof(str_buying)); + + stellar_layoutTransactionDialog( + str_offer, + str_selling, + str_selling_asset, + str_buying, + str_buying_asset + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + + // Hash selling asset + stellar_hashupdate_asset(&(msg->selling_asset)); + // buying asset + stellar_hashupdate_asset(&(msg->buying_asset)); + // amount to sell (signed vs. unsigned doesn't matter wrt hashing) + stellar_hashupdate_uint64(msg->amount); + // numerator + stellar_hashupdate_uint32(msg->price_n); + // denominator + stellar_hashupdate_uint32(msg->price_d); + + // At this point, the operation is confirmed + stellar_activeTx.confirmed_operations++; +} + +void stellar_confirmSetOptionsOp(StellarSetOptionsOp *msg) +{ + stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + // Hash: operation type + stellar_hashupdate_uint32(5); + + // Something like Set Inflation Destination + char str_title[32]; + char rows[4][32]; + int row_idx = 0; + memset(rows, 0, sizeof(rows)); + + // Inflation destination + stellar_hashupdate_bool(msg->has_inflation_destination_account); + if (msg->has_inflation_destination_account) { + strlcpy(str_title, _("Set Inflation Destination"), sizeof(str_title)); + const char **str_addr_rows = stellar_lineBreakAddress(msg->inflation_destination_account.bytes); + + stellar_layoutTransactionDialog( + str_title, + NULL, + str_addr_rows[0], + str_addr_rows[1], + str_addr_rows[2] + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + + // address + stellar_hashupdate_address(msg->inflation_destination_account.bytes); + } + + // Clear flags + stellar_hashupdate_bool(msg->has_clear_flags); + if (msg->has_clear_flags) { + strlcpy(str_title, _("Clear Flag(s)"), sizeof(str_title)); + + // Auth required + if (msg->clear_flags & 0x01) { + strlcpy(rows[row_idx], _("AUTH_REQUIRED"), sizeof(rows[row_idx])); + row_idx++; + } + // Auth revocable + if (msg->clear_flags & 0x02) { + strlcpy(rows[row_idx], _("AUTH_REVOCABLE"), sizeof(rows[row_idx])); + row_idx++; + } + + stellar_layoutTransactionDialog( + str_title, + rows[0], + rows[1], + rows[2], + rows[3] + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + memset(rows, 0, sizeof(rows)); + row_idx = 0; + + // Hash flags + stellar_hashupdate_uint32(msg->clear_flags); + } + + // Set flags + stellar_hashupdate_bool(msg->has_set_flags); + if (msg->has_set_flags) { + strlcpy(str_title, _("Set Flag(s)"), sizeof(str_title)); + + // Auth required + if (msg->set_flags & 0x01) { + strlcpy(rows[row_idx], _("AUTH_REQUIRED"), sizeof(rows[row_idx])); + row_idx++; + } + // Auth revocable + if (msg->set_flags & 0x02) { + strlcpy(rows[row_idx], _("AUTH_REVOCABLE"), sizeof(rows[row_idx])); + row_idx++; + } + + stellar_layoutTransactionDialog( + str_title, + rows[0], + rows[1], + rows[2], + rows[3] + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + memset(rows, 0, sizeof(rows)); + row_idx = 0; + + // Hash flags + stellar_hashupdate_uint32(msg->set_flags); + } + + // Account thresholds + bool show_thresholds_confirm = false; + row_idx = 0; + stellar_hashupdate_bool(msg->has_master_weight); + if (msg->has_master_weight) { + char str_master_weight[10+1]; + show_thresholds_confirm = true; + stellar_format_uint32(msg->master_weight, str_master_weight, sizeof(str_master_weight)); + strlcpy(rows[row_idx], _("Master Weight: "), sizeof(rows[row_idx])); + strlcat(rows[row_idx], str_master_weight, sizeof(rows[row_idx])); + row_idx++; + + // Hash master weight + stellar_hashupdate_uint32(msg->master_weight); + } + + stellar_hashupdate_bool(msg->has_low_threshold); + if (msg->has_low_threshold) { + char str_low_threshold[10+1]; + show_thresholds_confirm = true; + stellar_format_uint32(msg->low_threshold, str_low_threshold, sizeof(str_low_threshold)); + strlcpy(rows[row_idx], _("Low: "), sizeof(rows[row_idx])); + strlcat(rows[row_idx], str_low_threshold, sizeof(rows[row_idx])); + row_idx++; + + // Hash low threshold + stellar_hashupdate_uint32(msg->low_threshold); + } + stellar_hashupdate_bool(msg->has_medium_threshold); + if (msg->has_medium_threshold) { + char str_med_threshold[10+1]; + show_thresholds_confirm = true; + stellar_format_uint32(msg->medium_threshold, str_med_threshold, sizeof(str_med_threshold)); + strlcpy(rows[row_idx], _("Medium: "), sizeof(rows[row_idx])); + strlcat(rows[row_idx], str_med_threshold, sizeof(rows[row_idx])); + row_idx++; + + // Hash medium threshold + stellar_hashupdate_uint32(msg->medium_threshold); + } + stellar_hashupdate_bool(msg->has_high_threshold); + if (msg->has_high_threshold) { + char str_high_threshold[10+1]; + show_thresholds_confirm = true; + stellar_format_uint32(msg->high_threshold, str_high_threshold, sizeof(str_high_threshold)); + strlcpy(rows[row_idx], _("High: "), sizeof(rows[row_idx])); + strlcat(rows[row_idx], str_high_threshold, sizeof(rows[row_idx])); + row_idx++; + + // Hash high threshold + stellar_hashupdate_uint32(msg->high_threshold); + } + + if (show_thresholds_confirm) { + strlcpy(str_title, _("Account Thresholds"), sizeof(str_title)); + stellar_layoutTransactionDialog( + str_title, + rows[0], + rows[1], + rows[2], + rows[3] + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + memset(rows, 0, sizeof(rows)); + row_idx = 0; + } + + // Home domain + stellar_hashupdate_bool(msg->has_home_domain); + if (msg->has_home_domain) { + strlcpy(str_title, _("Home Domain"), sizeof(str_title)); + + // Split home domain if longer than 22 characters + int home_domain_len = strnlen(msg->home_domain, 32); + if (home_domain_len > 22) { + strlcpy(rows[0], msg->home_domain, 22); + strlcpy(rows[1], msg->home_domain + 21, sizeof(rows[1])); + } + else { + strlcpy(rows[0], msg->home_domain, sizeof(rows[0])); + } + + stellar_layoutTransactionDialog( + str_title, + rows[0], + rows[1], + NULL, + NULL + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + memset(rows, 0, sizeof(rows)); + row_idx = 0; + + stellar_hashupdate_string((unsigned char*)&(msg->home_domain), strnlen(msg->home_domain, 32)); + } + + // Signer + stellar_hashupdate_bool(msg->has_signer_type); + if (msg->has_signer_type) { + if (msg->signer_weight > 0) { + strlcpy(str_title, _("Add Signer: "), sizeof(str_title)); + } + else { + strlcpy(str_title, _("REMOVE Signer: "), sizeof(str_title)); + } + + // Format weight as a string + char str_weight[16]; + stellar_format_uint32(msg->signer_weight, str_weight, sizeof(str_weight)); + char str_weight_row[32]; + strlcpy(str_weight_row, _("Weight: "), sizeof(str_weight_row)); + strlcat(str_weight_row, str_weight, sizeof(str_weight_row)); + + // 0 = account, 1 = pre-auth, 2 = hash(x) + char str_signer_type[16]; + bool needs_hash_confirm = false; + if (msg->signer_type == 0) { + strlcpy(str_signer_type, _("account"), sizeof(str_signer_type)); + strlcat(str_title, str_signer_type, sizeof(str_title)); + + const char **str_addr_rows = stellar_lineBreakAddress(msg->signer_key.bytes); + stellar_layoutTransactionDialog( + str_title, + str_weight_row, + str_addr_rows[0], + str_addr_rows[1], + str_addr_rows[2] + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + } + if (msg->signer_type == 1) { + needs_hash_confirm = true; + strlcpy(str_signer_type, _("pre-auth hash"), sizeof(str_signer_type)); + strlcat(str_title, str_signer_type, sizeof(str_title)); + + stellar_layoutTransactionDialog( + str_title, + str_weight_row, + NULL, + _("(confirm hash on next"), + _("screen)") + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + } + if (msg->signer_type == 2) { + needs_hash_confirm = true; + strlcpy(str_signer_type, _("hash(x)"), sizeof(str_signer_type)); + strlcat(str_title, str_signer_type, sizeof(str_title)); + + stellar_layoutTransactionDialog( + str_title, + str_weight_row, + NULL, + _("(confirm hash on next"), + _("screen)") + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + } + + // Extra confirmation step for hash signers + if (needs_hash_confirm) { + data2hex(msg->signer_key.bytes + 0, 8, rows[row_idx++]); + data2hex(msg->signer_key.bytes + 8, 8, rows[row_idx++]); + data2hex(msg->signer_key.bytes + 16, 8, rows[row_idx++]); + data2hex(msg->signer_key.bytes + 24, 8, rows[row_idx++]); + + stellar_layoutTransactionDialog( + _("Confirm Hash"), + rows[0], + rows[1], + rows[2], + rows[3] + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + memset(rows, 0, sizeof(rows)); + row_idx = 0; + } + + // Hash: signer type + stellar_hashupdate_uint32(msg->signer_type); + // key + stellar_hashupdate_bytes(msg->signer_key.bytes, msg->signer_key.size); + // weight + stellar_hashupdate_uint32(msg->signer_weight); + } + + // At this point, the operation is confirmed + stellar_activeTx.confirmed_operations++; +} + +void stellar_confirmChangeTrustOp(StellarChangeTrustOp *msg) +{ + stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + // Hash: operation type + stellar_hashupdate_uint32(6); + + // Add Trust: USD + char str_title[32]; + if (msg->limit == 0) { + strlcpy(str_title, _("DELETE Trust: "), sizeof(str_title)); + } + else { + strlcpy(str_title, _("Add Trust: "), sizeof(str_title)); + } + strlcat(str_title, msg->asset.code, sizeof(str_title)); + + // Amount: MAX (or a number) + char str_amount_row[32]; + strlcpy(str_amount_row, _("Amount: "), sizeof(str_amount_row)); + + if (msg->limit == 9223372036854775807) { + strlcat(str_amount_row, _("[Maximum]"), sizeof(str_amount_row)); + } + else { + char str_amount[32]; + stellar_format_stroops(msg->limit, str_amount, sizeof(str_amount)); + strlcat(str_amount_row, str_amount, sizeof(str_amount_row)); + } + + // Display full issuer address + const char **str_addr_rows = stellar_lineBreakAddress(msg->asset.issuer.bytes); + + stellar_layoutTransactionDialog( + str_title, + str_amount_row, + str_addr_rows[0], + str_addr_rows[1], + str_addr_rows[2] + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + + // Hash: asset + stellar_hashupdate_asset(&(msg->asset)); + // limit + stellar_hashupdate_uint64(msg->limit); + + // At this point, the operation is confirmed + stellar_activeTx.confirmed_operations++; +} + +void stellar_confirmAllowTrustOp(StellarAllowTrustOp *msg) +{ + stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + // Hash: operation type + stellar_hashupdate_uint32(7); + + // Add Trust: USD + char str_title[32]; + if (msg->is_authorized) { + strlcpy(str_title, _("Allow Trust of"), sizeof(str_title)); + } + else { + strlcpy(str_title, _("REVOKE Trust of"), sizeof(str_title)); + } + + // Asset code + char str_asset_row[32]; + strlcpy(str_asset_row, msg->asset_code, sizeof(str_asset_row)); + + const char **str_trustor_rows = stellar_lineBreakAddress(msg->trusted_account.bytes); + + // By: G... + char str_by[32]; + strlcpy(str_by, _("By: "), sizeof(str_by)); + strlcat(str_by, str_trustor_rows[0], sizeof(str_by)); + + stellar_layoutTransactionDialog( + str_title, + str_asset_row, + str_by, + str_trustor_rows[1], + str_trustor_rows[2] + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + + // Hash: trustor account (the account being allowed to access the asset) + stellar_hashupdate_address(msg->trusted_account.bytes); + // asset type + stellar_hashupdate_uint32(msg->asset_type); + // asset code + if (msg->asset_type == 1) { + char code4[4+1]; + memset(code4, 0, sizeof(code4)); + strlcpy(code4, msg->asset_code, sizeof(code4)); + stellar_hashupdate_bytes((uint8_t *)code4, 4); + } + if (msg->asset_type == 2) { + char code12[12+1]; + memset(code12, 0, sizeof(code12)); + strlcpy(code12, msg->asset_code, sizeof(code12)); + stellar_hashupdate_bytes((uint8_t *)code12, 12); + } + // is authorized + stellar_hashupdate_bool(msg->is_authorized); + + // At this point, the operation is confirmed + stellar_activeTx.confirmed_operations++; +} + +void stellar_confirmAccountMergeOp(StellarAccountMergeOp *msg) +{ + stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + // Hash: operation type + stellar_hashupdate_uint32(8); + + const char **str_destination_rows = stellar_lineBreakAddress(msg->destination_account.bytes); + + stellar_layoutTransactionDialog( + _("Merge Account"), + _("All XLM will be sent to:"), + str_destination_rows[0], + str_destination_rows[1], + str_destination_rows[2] + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + + // Hash: destination account + stellar_hashupdate_address(msg->destination_account.bytes); + + // At this point, the operation is confirmed + stellar_activeTx.confirmed_operations++; +} + +void stellar_confirmManageDataOp(StellarManageDataOp *msg) +{ + stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + // Hash: operation type + stellar_hashupdate_uint32(10); + + char str_title[32]; + if (msg->has_value) { + strlcpy(str_title, _("Set data value key:"), sizeof(str_title)); + } + else { + strlcpy(str_title, _("CLEAR data value key:"), sizeof(str_title)); + } + + // Confirm key + const char **str_key_lines = split_message((const uint8_t*)(msg->key), strnlen(msg->key, 64), 16); + + stellar_layoutTransactionDialog( + str_title, + str_key_lines[0], + str_key_lines[1], + str_key_lines[2], + str_key_lines[3] + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + + // Confirm value by displaying sha256 hash since this can contain non-printable characters + if (msg->has_value) { + strlcpy(str_title, _("Confirm sha256 of value:"), sizeof(str_title)); + + char str_hash_digest[SHA256_DIGEST_STRING_LENGTH]; + sha256_Data(msg->value.bytes, msg->value.size, str_hash_digest); + const char **str_hash_lines = split_message((const uint8_t*)str_hash_digest, sizeof(str_hash_digest), 16); + + stellar_layoutTransactionDialog( + str_title, + str_hash_lines[0], + str_hash_lines[1], + str_hash_lines[2], + str_hash_lines[3] + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + } + + // Hash: key + stellar_hashupdate_string((unsigned char*)&(msg->key), strnlen(msg->key, 64)); + // value + if (msg->has_value) { + stellar_hashupdate_bool(true); + // Variable opaque field is length + raw bytes + stellar_hashupdate_uint32(msg->value.size); + stellar_hashupdate_bytes(msg->value.bytes, msg->value.size); + } + else { + stellar_hashupdate_bool(false); + } + + // At this point, the operation is confirmed + stellar_activeTx.confirmed_operations++; +} + +void stellar_confirmBumpSequenceOp(StellarBumpSequenceOp *msg) +{ + stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + // Hash: operation type + stellar_hashupdate_uint32(11); + + char str_bump_to[20]; + stellar_format_uint64(msg->bump_to, str_bump_to, sizeof(str_bump_to)); + + stellar_layoutTransactionDialog( + _("Bump Sequence"), + _("Set sequence to:"), + str_bump_to, + NULL, + NULL + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + + // Hash: bump to + stellar_hashupdate_uint64(msg->bump_to); + + // At this point, the operation is confirmed + stellar_activeTx.confirmed_operations++; +} + +void stellar_signingAbort() +{ + stellar_signing = false; + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); +} + +/** + * Populates the fields of resp with the signature of the active transaction + */ +void stellar_fillSignedTx(StellarSignedTx *resp) +{ + StellarTransaction *activeTx = stellar_getActiveTx(); + + // Finalize the transaction by hashing 4 null bytes representing a (currently unused) empty union + stellar_hashupdate_uint32(0); + + // Add the public key for verification that the right account was used for signing + memcpy(resp->public_key.bytes, &(activeTx->signing_pubkey), 32); + resp->public_key.size = 32; + resp->has_public_key = true; + + // Add the signature (note that this does not include the 4-byte hint since it can be calculated from the public key) + uint8_t signature[64]; + // Note: this calls sha256_Final on the hash context + stellar_getSignatureForActiveTx(signature); + memcpy(resp->signature.bytes, signature, sizeof(signature)); + resp->signature.size = sizeof(signature); + resp->has_signature = true; +} + +uint8_t stellar_allOperationsConfirmed() +{ + return stellar_activeTx.confirmed_operations == stellar_activeTx.num_operations; +} + +StellarTransaction *stellar_getActiveTx() +{ + return &stellar_activeTx; +} + +/* + * Calculates and sets the signature for the active transaction + */ +void stellar_getSignatureForActiveTx(uint8_t *out_signature) +{ + HDNode *node = stellar_deriveNode(stellar_activeTx.address_n, stellar_activeTx.address_n_count); + + // Signature is the ed25519 detached signature of the sha256 of all the bytes + // that have been read so far + uint8_t to_sign[32]; + sha256_Final(&(stellar_activeTx.sha256_ctx), to_sign); + + uint8_t signature[64]; + ed25519_sign(to_sign, sizeof(to_sign), node->private_key, node->public_key + 1, signature); + + memcpy(out_signature, signature, sizeof(signature)); +} + +void stellar_signString(const uint8_t *str_to_sign, uint32_t *address_n, size_t address_n_count, uint8_t *out_signature) +{ + HDNode *node = stellar_deriveNode(address_n, address_n_count); + + uint8_t signature[64]; + // Maximum field size in protobuf message is 1024, so strlen of 1023 + null + ed25519_sign(str_to_sign, strnlen((const char *)str_to_sign, 1023), node->private_key, node->public_key + 1, signature); + + memcpy(out_signature, signature, sizeof(signature)); +} + +bool stellar_verifySignature(StellarVerifyMessage *msg) +{ + // returns 0 if signature is valid + return ed25519_sign_open( + msg->message.bytes, + msg->message.size, + msg->public_key.bytes, + msg->signature.bytes + ) == 0; +} + +/* + * Returns number (representing stroops) formatted as XLM + * For example, if number has value 1000000000 then it will be returned as "100.0" + */ +void stellar_format_stroops(uint64_t number, char *out, size_t outlen) +{ + bn_format_uint64(number, NULL, NULL, 7, 0, false, out, outlen); +} + +/* + * Formats a price represented as a uint32 numerator and uint32 denominator + * + * Note that there may be a loss of precision between the real price value and what + * is shown to the user + * + * Smallest possible price is 1 / 4294967296 which is: + * 0.00000000023283064365386962890625 + * + * largest possible price is: + * 4294967296 + */ +void stellar_format_price(uint32_t numerator, uint32_t denominator, char *out, size_t outlen) +{ + memset(out, 0, outlen); + + // early exist for invalid denominator + if (denominator == 0) { + strlcpy(out, _("[Invalid Price]"), outlen); + return; + } + + int scale = 0; + double dbl_value = (double)numerator / (double)denominator; + + // Multiply by 10 until the value is larger than the largest possible offer size + // Largest possible offer size is UINT32_MAX (4294967296) + while (dbl_value < UINT32_MAX) { + dbl_value *= (double)10; + scale++; + } + + // Cast back to an integer + uint64_t scaled_value = (uint64_t) dbl_value; + + // Format with bn_format_uint64 + bn_format_uint64(scaled_value, NULL, NULL, scale, 0, false, out, outlen); +} + +/* + * Returns a uint32 formatted as a string + */ +void stellar_format_uint32(uint32_t number, char *out, size_t outlen) +{ + bignum256 bn_number; + bn_read_uint32(number, &bn_number); + bn_format(&bn_number, NULL, NULL, 0, 0, false, out, outlen); +} + +/* + * Returns a uint64 formatted as a string + */ +void stellar_format_uint64(uint64_t number, char *out, size_t outlen) +{ + bn_format_uint64(number, NULL, NULL, 0, 0, false, out, outlen); +} + +/* + * Breaks a 56 character address into 3 lines of lengths 16, 20, 20 + * This is to allow a small label to be prepended to the first line + */ +const char **stellar_lineBreakAddress(uint8_t *addrbytes) +{ + char str_fulladdr[56+1]; + static char rows[3][20+1]; + + memset(rows, 0, sizeof(rows)); + + // get full address string + stellar_publicAddressAsStr(addrbytes, str_fulladdr, sizeof(str_fulladdr)); + + // Break it into 3 lines + strlcpy(rows[0], str_fulladdr + 0, 17); + strlcpy(rows[1], str_fulladdr + 16, 21); + strlcpy(rows[2], str_fulladdr + 16 + 20, 21); + + static const char *ret[3] = { rows[0], rows[1], rows[2] }; + return ret; +} + +/* + * Returns the asset formatted to fit in a single row + * + * Examples: + * XLM (Native Asset) + * MOBI (G1234) + * ALPHA12EXAMP (G0987) + */ +void stellar_format_asset(StellarAssetType *asset, char *str_formatted, size_t len) +{ + char str_asset_code[12 + 1]; + // Full asset issuer string + char str_asset_issuer[56+1]; + // truncated asset issuer, final length depends on length of asset code + char str_asset_issuer_trunc[13 + 1]; + + memset(str_formatted, 0, len); + memset(str_asset_code, 0, sizeof(str_asset_code)); + memset(str_asset_issuer_trunc, 0, sizeof(str_asset_issuer_trunc)); + + // Get string representation of address + stellar_publicAddressAsStr(asset->issuer.bytes, str_asset_issuer, sizeof(str_asset_issuer)); + + // Native asset + if (asset->type == 0) { + strlcpy(str_formatted, _("XLM (native asset)"), len); + } + // 4-character custom + if (asset->type == 1) { + memcpy(str_asset_code, asset->code, 4); + strlcpy(str_formatted, str_asset_code, len); + + // Truncate issuer to 13 chars + memcpy(str_asset_issuer_trunc, str_asset_issuer, 13); + } + // 12-character custom + if (asset->type == 2) { + memcpy(str_asset_code, asset->code, 12); + strlcpy(str_formatted, str_asset_code, len); + + // Truncate issuer to 5 characters + memcpy(str_asset_issuer_trunc, str_asset_issuer, 5); + } + // Issuer is read the same way for both types of custom assets + if (asset->type == 1 || asset->type == 2) { + strlcat(str_formatted, _(" ("), len); + strlcat(str_formatted, str_asset_issuer_trunc, len); + strlcat(str_formatted, _(")"), len); + } +} + +size_t stellar_publicAddressAsStr(uint8_t *bytes, char *out, size_t outlen) +{ + // version + key bytes + checksum + uint8_t keylen = 1 + 32 + 2; + uint8_t bytes_full[keylen]; + bytes_full[0] = 6 << 3; // 'G' + + memcpy(bytes_full + 1, bytes, 32); + + // Last two bytes are the checksum + uint16_t checksum = stellar_crc16(bytes_full, 33); + bytes_full[keylen-2] = checksum & 0x00ff; + bytes_full[keylen-1] = (checksum>>8) & 0x00ff; + + base32_encode(bytes_full, keylen, out, outlen, BASE32_ALPHABET_RFC4648); + + // Public key will always be 56 characters + return 56; +} + +/* + * CRC16 implementation compatible with the Stellar version + * Ported from this implementation: http://introcs.cs.princeton.edu/java/61data/CRC16CCITT.java.html + * Initial value changed to 0x0000 to match Stellar + */ +uint16_t stellar_crc16(uint8_t *bytes, uint32_t length) +{ + // Calculate checksum for existing bytes + uint16_t crc = 0x0000; + uint16_t polynomial = 0x1021; + uint32_t i; + uint8_t bit; + uint8_t byte; + uint8_t bitidx; + uint8_t c15; + + for (i=0; i < length; i++) { + byte = bytes[i]; + for (bitidx=0; bitidx < 8; bitidx++) { + bit = ((byte >> (7 - bitidx) & 1) == 1); + c15 = ((crc >> 15 & 1) == 1); + crc <<= 1; + if (c15 ^ bit) crc ^= polynomial; + } + } + + return crc & 0xffff; +} + +/* + * Writes 32-byte public key to out + */ +void stellar_getPubkeyAtAddress(uint32_t *address_n, size_t address_n_count, uint8_t *out, size_t outlen) +{ + if (outlen < 32) return; + + HDNode *node = stellar_deriveNode(address_n, address_n_count); + + if (node == 0) { + stellar_signingAbort(); + return; + } + + memcpy(out, node->public_key + 1, outlen); +} + +/* + * Derives the HDNode at the given index + * Standard Stellar prefix is m/44'/148'/ and the default account is m/44'/148'/0' + * + * All paths must be hardened + */ +HDNode *stellar_deriveNode(uint32_t *address_n, size_t address_n_count) +{ + static CONFIDENTIAL HDNode node; + const char *curve = "ed25519"; + + // Device not initialized, passphrase request cancelled, or unsupported curve + if (!storage_getRootNode(&node, curve, true)) { + return 0; + } + // Failed to derive private key + if (hdnode_private_ckd_cached(&node, address_n, address_n_count, NULL) == 0) { + return 0; + } + + hdnode_fill_public_key(&node); + + return &node; +} + +void stellar_hashupdate_uint32(uint32_t value) +{ + // Ensure uint32 is big endian +#if BYTE_ORDER == LITTLE_ENDIAN + REVERSE32(value, value); +#endif + + // Byte values must be hashed as big endian + uint8_t data[4]; + data[3] = (value >> 24) & 0xFF; + data[2] = (value >> 16) & 0xFF; + data[1] = (value >> 8) & 0xFF; + data[0] = value & 0xFF; + + stellar_hashupdate_bytes(data, sizeof(data)); +} + +void stellar_hashupdate_uint64(uint64_t value) +{ + // Ensure uint64 is big endian +#if BYTE_ORDER == LITTLE_ENDIAN + REVERSE64(value, value); +#endif + + // Byte values must be hashed as big endian + uint8_t data[8]; + data[7] = (value >> 56) & 0xFF; + data[6] = (value >> 48) & 0xFF; + data[5] = (value >> 40) & 0xFF; + data[4] = (value >> 32) & 0xFF; + data[3] = (value >> 24) & 0xFF; + data[2] = (value >> 16) & 0xFF; + data[1] = (value >> 8) & 0xFF; + data[0] = value & 0xFF; + + stellar_hashupdate_bytes(data, sizeof(data)); +} + +void stellar_hashupdate_bool(bool value) +{ + if (value) { + stellar_hashupdate_uint32(1); + } + else { + stellar_hashupdate_uint32(0); + } +} + +void stellar_hashupdate_string(uint8_t *data, size_t len) +{ + // Hash the length of the string + stellar_hashupdate_uint32((uint32_t)len); + + // Hash the raw bytes of the string + stellar_hashupdate_bytes(data, len); + + // If len isn't a multiple of 4, add padding bytes + int remainder = len % 4; + uint8_t null_byte[1] = { 0x00 }; + if (remainder) { + while (remainder < 4) { + stellar_hashupdate_bytes(null_byte, 1); + remainder++; + } + } +} + +void stellar_hashupdate_address(uint8_t *address_bytes) +{ + // First 4 bytes of an address are the type. There's only one type (0) + stellar_hashupdate_uint32(0); + + // Remaining part of the address is 32 bytes + stellar_hashupdate_bytes(address_bytes, 32); +} + +/* + * Note about string handling below: this field is an XDR "opaque" field and not a typical string, + * so if "TEST" is the asset code then the hashed value needs to be 4 bytes and not include the null + * at the end of the string + */ +void stellar_hashupdate_asset(StellarAssetType *asset) +{ + stellar_hashupdate_uint32(asset->type); + + // 4-character asset code + if (asset->type == 1) { + char code4[4+1]; + memset(code4, 0, sizeof(code4)); + strlcpy(code4, asset->code, sizeof(code4)); + + stellar_hashupdate_bytes((uint8_t *)code4, 4); + stellar_hashupdate_address(asset->issuer.bytes); + } + + // 12-character asset code + if (asset->type == 2) { + char code12[12+1]; + memset(code12, 0, sizeof(code12)); + strlcpy(code12, asset->code, sizeof(code12)); + + stellar_hashupdate_bytes((uint8_t *)code12, 12); + stellar_hashupdate_address(asset->issuer.bytes); + } +} + +void stellar_hashupdate_bytes(uint8_t *data, size_t len) +{ + sha256_Update(&(stellar_activeTx.sha256_ctx), data, len); +} + +/* + * Displays a summary of the overall transaction + */ +void stellar_layoutTransactionSummary(StellarSignTx *msg) +{ + char str_lines[5][32]; + memset(str_lines, 0, sizeof(str_lines)); + + char str_fee[12]; + char str_num_ops[12]; + + // Will be set to true for some large hashes that don't fit on one screen + uint8_t needs_memo_hash_confirm = 0; + + // Format the fee + stellar_format_stroops(msg->fee, str_fee, sizeof(str_fee)); + + strlcpy(str_lines[0], _("Fee: "), sizeof(str_lines[0])); + strlcat(str_lines[0], str_fee, sizeof(str_lines[0])); + strlcat(str_lines[0], _(" XLM"), sizeof(str_lines[0])); + + // add in numOperations + stellar_format_uint32(msg->num_operations, str_num_ops, sizeof(str_num_ops)); + + strlcat(str_lines[0], _(" ("), sizeof(str_lines[0])); + strlcat(str_lines[0], str_num_ops, sizeof(str_lines[0])); + if (msg->num_operations == 1) { + strlcat(str_lines[0], _(" op)"), sizeof(str_lines[0])); + } else { + strlcat(str_lines[0], _(" ops)"), sizeof(str_lines[0])); + } + + // Display full address being used to sign transaction + const char **str_addr_rows = stellar_lineBreakAddress(stellar_activeTx.signing_pubkey); + + stellar_layoutTransactionDialog( + str_lines[0], + _("Signing with:"), + str_addr_rows[0], + str_addr_rows[1], + str_addr_rows[2] + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + + // Reset lines for displaying memo + memset(str_lines, 0, sizeof(str_lines)); + + // Memo: none + if (msg->memo_type == 0) { + strlcpy(str_lines[0], _("[No Memo Set]"), sizeof(str_lines[0])); + strlcpy(str_lines[1], _("Important:"), sizeof(str_lines[0])); + strlcpy(str_lines[2], _("Many exchanges require"), sizeof(str_lines[0])); + strlcpy(str_lines[3], _("a memo when depositing."), sizeof(str_lines[0])); + } + // Memo: text + if (msg->memo_type == 1) { + strlcpy(str_lines[0], _("Memo (TEXT)"), sizeof(str_lines[0])); + + // Split 28-character string into two lines of 19 / 9 + // todo: word wrap method? + strlcpy(str_lines[1], (const char*)msg->memo_text, 19 + 1); + strlcpy(str_lines[2], (const char*)(msg->memo_text + 19), 9 + 1); + } + // Memo: ID + if (msg->memo_type == 2) { + strlcpy(str_lines[0], _("Memo (ID)"), sizeof(str_lines[0])); + stellar_format_uint64(msg->memo_id, str_lines[1], sizeof(str_lines[1])); + } + // Memo: hash + if (msg->memo_type == 3) { + needs_memo_hash_confirm = 1; + strlcpy(str_lines[0], _("Memo (HASH)"), sizeof(str_lines[0])); + } + // Memo: return + if (msg->memo_type == 4) { + needs_memo_hash_confirm = 1; + strlcpy(str_lines[0], _("Memo (RETURN)"), sizeof(str_lines[0])); + } + + if (needs_memo_hash_confirm) { + data2hex(msg->memo_hash.bytes + 0, 8, str_lines[1]); + data2hex(msg->memo_hash.bytes + 8, 8, str_lines[2]); + data2hex(msg->memo_hash.bytes + 16, 8, str_lines[3]); + data2hex(msg->memo_hash.bytes + 24, 8, str_lines[4]); + } + + stellar_layoutTransactionDialog( + str_lines[0], + str_lines[1], + str_lines[2], + str_lines[3], + str_lines[4] + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + + // Verify timebounds, if present + memset(str_lines, 0, sizeof(str_lines)); + + // Timebound: lower + if (msg->timebounds_start || msg->timebounds_end) { + time_t timebound; + char str_timebound[32]; + const struct tm *tm; + + timebound = (time_t)msg->timebounds_start; + strlcpy(str_lines[0], _("Valid from:"), sizeof(str_lines[0])); + if (timebound) { + tm = gmtime(&timebound); + strftime(str_timebound, sizeof(str_timebound), "%F %T (UTC)", tm); + strlcpy(str_lines[1], str_timebound, sizeof(str_lines[1])); + } + else { + strlcpy(str_lines[1], _("[no restriction]"), sizeof(str_lines[1])); + } + + // Reset for timebound_max + memset(str_timebound, 0, sizeof(str_timebound)); + + timebound = (time_t)msg->timebounds_end; + strlcpy(str_lines[0], _("Valid from:"), sizeof(str_lines[0])); + if (timebound) { + tm = gmtime(&timebound); + strftime(str_timebound, sizeof(str_timebound), "%F %T (UTC)", tm); + strlcpy(str_lines[1], str_timebound, sizeof(str_lines[1])); + } + else { + strlcpy(str_lines[1], _("[no restriction]"), sizeof(str_lines[1])); + } + } + + if (msg->timebounds_start || msg->timebounds_end) { + stellar_layoutTransactionDialog( + _("Confirm Time Bounds"), + str_lines[0], + str_lines[1], + str_lines[2], + str_lines[3] + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + stellar_signingAbort(); + return; + } + } +} + +/* + * Most basic dialog used for signing + * - Header indicating which key is being used for signing + * - 5 rows for content + * - Cancel / Next buttons + * - Warning message can appear between cancel/next buttons + */ +void stellar_layoutSigningDialog(const char *line1, const char *line2, const char *line3, const char *line4, const char *line5, uint32_t *address_n, size_t address_n_count, const char *warning, bool is_final_step) +{ + // Start with some initial padding and use these to track position as rendering moves down the screen + int offset_x = 1; + int offset_y = 1; + int line_height = 9; + + uint8_t public_key[32]; + stellar_getPubkeyAtAddress(address_n, address_n_count, public_key, sizeof(public_key)); + + char str_pubaddr_truncated[12]; // G???? + null + memset(str_pubaddr_truncated, 0, sizeof(str_pubaddr_truncated)); + + layoutLast = layoutDialogSwipe; + layoutSwipe(); + oledClear(); + + // Load up public address + char str_pubaddr[56+1]; + memset(str_pubaddr, 0, sizeof(str_pubaddr)); + stellar_publicAddressAsStr(public_key, str_pubaddr, sizeof(str_pubaddr)); + memcpy(str_pubaddr_truncated, str_pubaddr, sizeof(str_pubaddr_truncated) - 1); + + // Header + // Ends up as: Signing with GABCDEFGHIJKL + char str_header[32]; + memset(str_header, 0, sizeof(str_header)); + strlcpy(str_header, _("Signing with "), sizeof(str_header)); + strlcat(str_header, str_pubaddr_truncated, sizeof(str_header)); + + oledDrawString(offset_x, offset_y, str_header, FONT_STANDARD); + offset_y += line_height; + // Invert color on header + oledInvert(0, 0, OLED_WIDTH, offset_y - 2); + + // Dialog contents begin + if (line1) { + oledDrawString(offset_x, offset_y, line1, FONT_STANDARD); + } + offset_y += line_height; + if (line2) { + oledDrawString(offset_x, offset_y, line2, FONT_STANDARD); + } + offset_y += line_height; + if (line3) { + oledDrawString(offset_x, offset_y, line3, FONT_STANDARD); + } + offset_y += line_height; + if (line4) { + oledDrawString(offset_x, offset_y, line4, FONT_STANDARD); + } + offset_y += line_height; + if (line5) { + oledDrawString(offset_x, offset_y, line5, FONT_STANDARD); + } + offset_y += line_height; + + // Cancel button + oledDrawString(1, OLED_HEIGHT - 8, "\x15", FONT_STANDARD); + oledDrawString(fontCharWidth(FONT_STANDARD, '\x15') + 3, OLED_HEIGHT - 8, "Cancel", FONT_STANDARD); + oledInvert(0, OLED_HEIGHT - 9, fontCharWidth(FONT_STANDARD, '\x15') + oledStringWidth("Cancel", FONT_STANDARD) + 2, OLED_HEIGHT - 1); + + // Warnings (drawn centered between the buttons + if (warning) { + oledDrawStringCenter(OLED_HEIGHT - 8, warning, FONT_STANDARD); + } + + // Next / sign button + char str_next_label[8]; + if (is_final_step) { + strlcpy(str_next_label, _("SIGN"), sizeof(str_next_label)); + } + else { + strlcpy(str_next_label, _("Next"), sizeof(str_next_label)); + } + + oledDrawString(OLED_WIDTH - fontCharWidth(FONT_STANDARD, '\x06') - 1, OLED_HEIGHT - 8, "\x06", FONT_STANDARD); + oledDrawString(OLED_WIDTH - oledStringWidth(str_next_label, FONT_STANDARD) - fontCharWidth(FONT_STANDARD, '\x06') - 3, OLED_HEIGHT - 8, str_next_label, FONT_STANDARD); + oledInvert(OLED_WIDTH - oledStringWidth(str_next_label, FONT_STANDARD) - fontCharWidth(FONT_STANDARD, '\x06') - 4, OLED_HEIGHT - 9, OLED_WIDTH - 1, OLED_HEIGHT - 1); + + oledRefresh(); +} + +/* + * Main dialog helper method. Allows displaying 5 lines. + * A title showing the account being used to sign is always displayed. + */ +void stellar_layoutTransactionDialog(const char *line1, const char *line2, const char *line3, const char *line4, const char *line5) +{ + char str_warning[16]; + memset(str_warning, 0, sizeof(str_warning)); + + if (stellar_activeTx.network_type == 2) { + // Warning: testnet + strlcpy(str_warning, _("WRN:TN"), sizeof(str_warning)); + } + if (stellar_activeTx.network_type == 3) { + // Warning: private network + strlcpy(str_warning, _("WRN:PN"), sizeof(str_warning)); + } + + stellar_layoutSigningDialog( + line1, + line2, + line3, + line4, + line5, + stellar_activeTx.address_n, + stellar_activeTx.address_n_count, + str_warning, + false + ); +} + +void stellar_layoutGetPublicKey(uint32_t *address_n, size_t address_n_count) +{ + // Derive node and calculate address + uint8_t pubkey_bytes[32]; + stellar_getPubkeyAtAddress(address_n, address_n_count, pubkey_bytes, sizeof(pubkey_bytes)); + const char **str_addr_rows = stellar_lineBreakAddress(pubkey_bytes); + + layoutDialogSwipe(&bmp_icon_question, _("Cancel"), _("Confirm"), _("Share public account ID?"), + str_addr_rows[0], + str_addr_rows[1], + str_addr_rows[2], + NULL, + NULL, NULL + ); + if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { + fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + layoutHome(); + return; + } +} \ No newline at end of file diff --git a/firmware/stellar.h b/firmware/stellar.h new file mode 100644 index 0000000000..4e01ae9df9 --- /dev/null +++ b/firmware/stellar.h @@ -0,0 +1,99 @@ +/* + * This file is part of the TREZOR project. + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __STELLAR_H__ +#define __STELLAR_H__ + +#include +#include "bip32.h" +#include "crypto.h" +#include "messages.pb.h" +#include "fsm.h" + +typedef struct { + // BIP32 path to the address being used for signing + uint32_t address_n[10]; + size_t address_n_count; + uint8_t signing_pubkey[32]; + + // 1 - public network, 2 - official testnet, 3 - other private network + uint8_t network_type; + + // Total number of operations expected + uint8_t num_operations; + // Number that have been confirmed by the user + uint8_t confirmed_operations; + + // sha256 context that will eventually be signed + SHA256_CTX sha256_ctx; +} StellarTransaction; + +// Signing process +void stellar_signingInit(StellarSignTx *tx); +void stellar_signingAbort(void); +void stellar_confirmCreateAccountOp(StellarCreateAccountOp *msg); +void stellar_confirmPaymentOp(StellarPaymentOp *msg); +void stellar_confirmPathPaymentOp(StellarPathPaymentOp *msg); +void stellar_confirmManageOfferOp(StellarManageOfferOp *msg); +void stellar_confirmCreatePassiveOfferOp(StellarCreatePassiveOfferOp *msg); +void stellar_confirmSetOptionsOp(StellarSetOptionsOp *msg); +void stellar_confirmChangeTrustOp(StellarChangeTrustOp *msg); +void stellar_confirmAllowTrustOp(StellarAllowTrustOp *msg); +void stellar_confirmAccountMergeOp(StellarAccountMergeOp *msg); +void stellar_confirmManageDataOp(StellarManageDataOp *msg); +void stellar_confirmBumpSequenceOp(StellarBumpSequenceOp *msg); + +void stellar_confirmSignString(StellarSignMessage *msg, StellarMessageSignature *resp); + +void stellar_signString(const uint8_t *str_to_sign, uint32_t *address_n, size_t address_n_count, uint8_t *out_signature); +bool stellar_verifySignature(StellarVerifyMessage *msg); + +// Layout +void stellar_layoutGetPublicKey(uint32_t *address_n, size_t address_n_count); +void stellar_layoutTransactionDialog(const char *line1, const char *line2, const char *line3, const char *line4, const char *line5); +void stellar_layoutTransactionSummary(StellarSignTx *msg); +void stellar_layoutSigningDialog(const char *line1, const char *line2, const char *line3, const char *line4, const char *line5, uint32_t *address_n, size_t address_n_count, const char *warning, bool is_final_step); + +// Helpers +HDNode *stellar_deriveNode(uint32_t *address_n, size_t address_n_count); + +size_t stellar_publicAddressAsStr(uint8_t *bytes, char *out, size_t outlen); +const char **stellar_lineBreakAddress(uint8_t *addrbytes); +void stellar_getPubkeyAtAddress(uint32_t *address_n, size_t address_n_count, uint8_t *out, size_t outlen); + +void stellar_hashupdate_uint32(uint32_t value); +void stellar_hashupdate_uint64(uint64_t value); +void stellar_hashupdate_bool(bool value); +void stellar_hashupdate_string(uint8_t *data, size_t len); +void stellar_hashupdate_address(uint8_t *address_bytes); +void stellar_hashupdate_asset(StellarAssetType *asset); +void stellar_hashupdate_bytes(uint8_t *data, size_t len); + +StellarTransaction *stellar_getActiveTx(void); +void stellar_fillSignedTx(StellarSignedTx *resp); +uint8_t stellar_allOperationsConfirmed(void); +void stellar_getSignatureForActiveTx(uint8_t *out_signature); + +void stellar_format_uint32(uint32_t number, char *out, size_t outlen); +void stellar_format_uint64(uint64_t number, char *out, size_t outlen); +void stellar_format_stroops(uint64_t number, char *out, size_t outlen); +void stellar_format_asset(StellarAssetType *asset, char *str_formatted, size_t len); +void stellar_format_price(uint32_t numerator, uint32_t denominator, char *out, size_t outlen); + +uint16_t stellar_crc16(uint8_t *bytes, uint32_t length); + +#endif \ No newline at end of file