diff --git a/firmware/Makefile b/firmware/Makefile index cb61ea05c..9cd23f6ef 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -28,7 +28,7 @@ OBJS += ethereum.o OBJS += ethereum_tokens.o OBJS += nem2.o OBJS += nem_mosaics.o -# OBJS += stellar.o +OBJS += stellar.o OBJS += debug.o diff --git a/firmware/fsm.c b/firmware/fsm.c index aca5fe0a4..5997db6a3 100644 --- a/firmware/fsm.c +++ b/firmware/fsm.c @@ -247,5 +247,5 @@ static bool fsm_layoutAddress(const char *address, const char *desc, bool ignore #include "fsm_msg_ethereum.h" #include "fsm_msg_crypto.h" #include "fsm_msg_nem.h" -// #include "fsm_msg_stellar.h" +#include "fsm_msg_stellar.h" #include "fsm_msg_debug.h" diff --git a/firmware/fsm_msg_stellar.h b/firmware/fsm_msg_stellar.h index 38b176586..f12ee8e01 100644 --- a/firmware/fsm_msg_stellar.h +++ b/firmware/fsm_msg_stellar.h @@ -96,7 +96,7 @@ void fsm_msgStellarSignTx(StellarSignTx *msg) void fsm_msgStellarCreateAccountOp(StellarCreateAccountOp *msg) { - stellar_confirmCreateAccountOp(msg); + if (!stellar_confirmCreateAccountOp(msg)) return; if (stellar_allOperationsConfirmed()) { RESP_INIT(StellarSignedTx); @@ -116,7 +116,7 @@ void fsm_msgStellarCreateAccountOp(StellarCreateAccountOp *msg) void fsm_msgStellarPaymentOp(StellarPaymentOp *msg) { // This will display additional dialogs to the user - stellar_confirmPaymentOp(msg); + if (!stellar_confirmPaymentOp(msg)) return; // Last operation was confirmed, send a StellarSignedTx if (stellar_allOperationsConfirmed()) { @@ -136,7 +136,7 @@ void fsm_msgStellarPaymentOp(StellarPaymentOp *msg) void fsm_msgStellarPathPaymentOp(StellarPathPaymentOp *msg) { - stellar_confirmPathPaymentOp(msg); + if (!stellar_confirmPathPaymentOp(msg)) return; if (stellar_allOperationsConfirmed()) { RESP_INIT(StellarSignedTx); @@ -155,7 +155,7 @@ void fsm_msgStellarPathPaymentOp(StellarPathPaymentOp *msg) void fsm_msgStellarManageOfferOp(StellarManageOfferOp *msg) { - stellar_confirmManageOfferOp(msg); + if (!stellar_confirmManageOfferOp(msg)) return; if (stellar_allOperationsConfirmed()) { RESP_INIT(StellarSignedTx); @@ -174,7 +174,7 @@ void fsm_msgStellarManageOfferOp(StellarManageOfferOp *msg) void fsm_msgStellarCreatePassiveOfferOp(StellarCreatePassiveOfferOp *msg) { - stellar_confirmCreatePassiveOfferOp(msg); + if (!stellar_confirmCreatePassiveOfferOp(msg)) return; if (stellar_allOperationsConfirmed()) { RESP_INIT(StellarSignedTx); @@ -193,7 +193,7 @@ void fsm_msgStellarCreatePassiveOfferOp(StellarCreatePassiveOfferOp *msg) void fsm_msgStellarSetOptionsOp(StellarSetOptionsOp *msg) { - stellar_confirmSetOptionsOp(msg); + if (!stellar_confirmSetOptionsOp(msg)) return; if (stellar_allOperationsConfirmed()) { RESP_INIT(StellarSignedTx); @@ -212,7 +212,7 @@ void fsm_msgStellarSetOptionsOp(StellarSetOptionsOp *msg) void fsm_msgStellarChangeTrustOp(StellarChangeTrustOp *msg) { - stellar_confirmChangeTrustOp(msg); + if (!stellar_confirmChangeTrustOp(msg)) return; if (stellar_allOperationsConfirmed()) { RESP_INIT(StellarSignedTx); @@ -231,7 +231,7 @@ void fsm_msgStellarChangeTrustOp(StellarChangeTrustOp *msg) void fsm_msgStellarAllowTrustOp(StellarAllowTrustOp *msg) { - stellar_confirmAllowTrustOp(msg); + if (!stellar_confirmAllowTrustOp(msg)) return; if (stellar_allOperationsConfirmed()) { RESP_INIT(StellarSignedTx); @@ -250,7 +250,7 @@ void fsm_msgStellarAllowTrustOp(StellarAllowTrustOp *msg) void fsm_msgStellarAccountMergeOp(StellarAccountMergeOp *msg) { - stellar_confirmAccountMergeOp(msg); + if (!stellar_confirmAccountMergeOp(msg)) return; if (stellar_allOperationsConfirmed()) { RESP_INIT(StellarSignedTx); @@ -269,7 +269,7 @@ void fsm_msgStellarAccountMergeOp(StellarAccountMergeOp *msg) void fsm_msgStellarManageDataOp(StellarManageDataOp *msg) { - stellar_confirmManageDataOp(msg); + if (!stellar_confirmManageDataOp(msg)) return; if (stellar_allOperationsConfirmed()) { RESP_INIT(StellarSignedTx); @@ -288,7 +288,7 @@ void fsm_msgStellarManageDataOp(StellarManageDataOp *msg) void fsm_msgStellarBumpSequenceOp(StellarBumpSequenceOp *msg) { - stellar_confirmBumpSequenceOp(msg); + if (!stellar_confirmBumpSequenceOp(msg)) return; if (stellar_allOperationsConfirmed()) { RESP_INIT(StellarSignedTx); diff --git a/firmware/protob/Makefile b/firmware/protob/Makefile index a741bc4c5..74392afcb 100644 --- a/firmware/protob/Makefile +++ b/firmware/protob/Makefile @@ -12,7 +12,7 @@ PYTHON ?= python protoc -I/usr/include -I. $< --python_out=. messages_map.h: messages_map.py messages_pb2.py types_pb2.py - $(PYTHON) $< | grep -v -e MessageType_Lisk -e MessageType_Stellar > $@ + $(PYTHON) $< | grep -v -e MessageType_Lisk > $@ clean: rm -f *.pb *.o *.d *.pb.c *.pb.h *_pb2.py messages_map.h diff --git a/firmware/protob/types.options b/firmware/protob/types.options index 07d1db77e..be97685eb 100644 --- a/firmware/protob/types.options +++ b/firmware/protob/types.options @@ -70,7 +70,7 @@ NEMCosignatoryModification.public_key max_size:32 NEMImportanceTransfer.public_key max_size:32 StellarAssetType.code max_size:13 -StellarAssetType.issuer max_size:32 +StellarAssetType.issuer max_size:57 # Lisk will be supported later diff --git a/firmware/stellar.c b/firmware/stellar.c index 195c82eb1..d33d0f11b 100644 --- a/firmware/stellar.c +++ b/firmware/stellar.c @@ -14,13 +14,13 @@ * 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 + * Stellar signing workflow: + * 1. Client sends a StellarSignTx method to the device with transaction header information + * 2. Device confirms transaction details with the user and requests first operation + * 3. Client sends protobuf message with details about the operation to sign + * 4. Device confirms operation with user + * 5a. If there are more operations in the transaction, device responds with StellarTxOpRequest. Go to 3 + * 5b. If the operation is the last one, device responds with StellarSignedTx */ #include @@ -41,6 +41,7 @@ #include "util.h" #include "layout2.h" #include "fonts.h" +#include "memzero.h" static bool stellar_signing = false; static StellarTransaction stellar_activeTx; @@ -151,11 +152,17 @@ void stellar_signingInit(StellarSignTx *msg) } } -void stellar_confirmSourceAccount(bool has_source_account, uint8_t *bytes) +bool stellar_confirmSourceAccount(bool has_source_account, char *str_account) { if (!has_source_account) { stellar_hashupdate_bool(false); - return; + return true; + } + + // Convert account string to public key bytes + uint8_t bytes[32]; + if (!stellar_getAddressBytes(str_account, bytes)) { + return false; } const char **str_addr_rows = stellar_lineBreakAddress(bytes); @@ -168,21 +175,36 @@ void stellar_confirmSourceAccount(bool has_source_account, uint8_t *bytes) str_addr_rows[2] ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } // Hash: source account stellar_hashupdate_address(bytes); + + return true; } -void stellar_confirmCreateAccountOp(StellarCreateAccountOp *msg) +bool stellar_confirmCreateAccountOp(StellarCreateAccountOp *msg) { - stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + // Hash: operation type stellar_hashupdate_uint32(0); - const char **str_addr_rows = stellar_lineBreakAddress(msg->new_account.bytes); + // Validate new account and convert to bytes + uint8_t new_account_bytes[STELLAR_KEY_SIZE]; + if (!stellar_getAddressBytes(msg->new_account, new_account_bytes)) { + stellar_signingAbort(_("Invalid destination account")); + return false; + } + + const char **str_addr_rows = stellar_lineBreakAddress(new_account_bytes); // Amount being funded char str_amount_line[32]; @@ -201,25 +223,39 @@ void stellar_confirmCreateAccountOp(StellarCreateAccountOp *msg) str_amount_line ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } // Hash: address - stellar_hashupdate_address(msg->new_account.bytes); + stellar_hashupdate_address(new_account_bytes); // Hash: starting amount stellar_hashupdate_uint64(msg->starting_balance); stellar_activeTx.confirmed_operations++; + return true; } -void stellar_confirmPaymentOp(StellarPaymentOp *msg) +bool stellar_confirmPaymentOp(StellarPaymentOp *msg) { - stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + // Hash: operation type stellar_hashupdate_uint32(1); - const char **str_addr_rows = stellar_lineBreakAddress(msg->destination_account.bytes); + // Validate destination account and convert to bytes + uint8_t destination_account_bytes[STELLAR_KEY_SIZE]; + if (!stellar_getAddressBytes(msg->destination_account, destination_account_bytes)) { + stellar_signingAbort(_("Invalid destination account")); + return false; + } + + const char **str_addr_rows = stellar_lineBreakAddress(destination_account_bytes); // To: G... char str_to[32]; @@ -245,12 +281,12 @@ void stellar_confirmPaymentOp(StellarPaymentOp *msg) str_addr_rows[2] ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } // Hash destination - stellar_hashupdate_address(msg->destination_account.bytes); + stellar_hashupdate_address(destination_account_bytes); // asset stellar_hashupdate_asset(&(msg->asset)); // amount (even though amount is signed it doesn't matter for hashing) @@ -258,15 +294,28 @@ void stellar_confirmPaymentOp(StellarPaymentOp *msg) // At this point, the operation is confirmed stellar_activeTx.confirmed_operations++; + return true; } -void stellar_confirmPathPaymentOp(StellarPathPaymentOp *msg) +bool stellar_confirmPathPaymentOp(StellarPathPaymentOp *msg) { - stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + // Hash: operation type stellar_hashupdate_uint32(2); - const char **str_dest_rows = stellar_lineBreakAddress(msg->destination_account.bytes); + // Validate destination account and convert to bytes + uint8_t destination_account_bytes[STELLAR_KEY_SIZE]; + if (!stellar_getAddressBytes(msg->destination_account, destination_account_bytes)) { + stellar_signingAbort(_("Invalid destination account")); + return false; + } + const char **str_dest_rows = stellar_lineBreakAddress(destination_account_bytes); // To: G... char str_to[32]; @@ -301,8 +350,8 @@ void stellar_confirmPathPaymentOp(StellarPathPaymentOp *msg) str_dest_rows[2] ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } // Confirm what the sender is using to pay @@ -321,8 +370,8 @@ void stellar_confirmPathPaymentOp(StellarPathPaymentOp *msg) _("from your account.") ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } // Note: no confirmation for intermediate steps since they don't impact the user @@ -331,7 +380,7 @@ void stellar_confirmPathPaymentOp(StellarPathPaymentOp *msg) // 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); + stellar_hashupdate_address(destination_account_bytes); // destination asset stellar_hashupdate_asset(&(msg->destination_asset)); // destination amount @@ -345,11 +394,18 @@ void stellar_confirmPathPaymentOp(StellarPathPaymentOp *msg) // At this point, the operation is confirmed stellar_activeTx.confirmed_operations++; + return true; } -void stellar_confirmManageOfferOp(StellarManageOfferOp *msg) +bool stellar_confirmManageOfferOp(StellarManageOfferOp *msg) { - stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + // Hash: operation type stellar_hashupdate_uint32(3); @@ -409,8 +465,8 @@ void stellar_confirmManageOfferOp(StellarManageOfferOp *msg) str_buying_asset ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } // Hash selling asset @@ -428,11 +484,18 @@ void stellar_confirmManageOfferOp(StellarManageOfferOp *msg) // At this point, the operation is confirmed stellar_activeTx.confirmed_operations++; + return true; } -void stellar_confirmCreatePassiveOfferOp(StellarCreatePassiveOfferOp *msg) +bool stellar_confirmCreatePassiveOfferOp(StellarCreatePassiveOfferOp *msg) { - stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + // Hash: operation type stellar_hashupdate_uint32(4); @@ -482,8 +545,8 @@ void stellar_confirmCreatePassiveOfferOp(StellarCreatePassiveOfferOp *msg) str_buying_asset ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } // Hash selling asset @@ -499,11 +562,18 @@ void stellar_confirmCreatePassiveOfferOp(StellarCreatePassiveOfferOp *msg) // At this point, the operation is confirmed stellar_activeTx.confirmed_operations++; + return true; } -void stellar_confirmSetOptionsOp(StellarSetOptionsOp *msg) +bool stellar_confirmSetOptionsOp(StellarSetOptionsOp *msg) { - stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + // Hash: operation type stellar_hashupdate_uint32(5); @@ -517,7 +587,14 @@ void stellar_confirmSetOptionsOp(StellarSetOptionsOp *msg) 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); + + // Validate account and convert to bytes + uint8_t inflation_destination_account_bytes[STELLAR_KEY_SIZE]; + if (!stellar_getAddressBytes(msg->inflation_destination_account, inflation_destination_account_bytes)) { + stellar_signingAbort(_("Invalid inflation destination account")); + return false; + } + const char **str_addr_rows = stellar_lineBreakAddress(inflation_destination_account_bytes); stellar_layoutTransactionDialog( str_title, @@ -527,12 +604,12 @@ void stellar_confirmSetOptionsOp(StellarSetOptionsOp *msg) str_addr_rows[2] ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } // address - stellar_hashupdate_address(msg->inflation_destination_account.bytes); + stellar_hashupdate_address(inflation_destination_account_bytes); } // Clear flags @@ -559,8 +636,8 @@ void stellar_confirmSetOptionsOp(StellarSetOptionsOp *msg) rows[3] ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } memset(rows, 0, sizeof(rows)); row_idx = 0; @@ -593,8 +670,8 @@ void stellar_confirmSetOptionsOp(StellarSetOptionsOp *msg) rows[3] ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } memset(rows, 0, sizeof(rows)); row_idx = 0; @@ -666,8 +743,8 @@ void stellar_confirmSetOptionsOp(StellarSetOptionsOp *msg) rows[3] ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } memset(rows, 0, sizeof(rows)); row_idx = 0; @@ -696,8 +773,8 @@ void stellar_confirmSetOptionsOp(StellarSetOptionsOp *msg) NULL ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } memset(rows, 0, sizeof(rows)); row_idx = 0; @@ -738,8 +815,8 @@ void stellar_confirmSetOptionsOp(StellarSetOptionsOp *msg) str_addr_rows[2] ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } } if (msg->signer_type == 1) { @@ -755,8 +832,8 @@ void stellar_confirmSetOptionsOp(StellarSetOptionsOp *msg) _("screen)") ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } } if (msg->signer_type == 2) { @@ -772,8 +849,8 @@ void stellar_confirmSetOptionsOp(StellarSetOptionsOp *msg) _("screen)") ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } } @@ -792,8 +869,8 @@ void stellar_confirmSetOptionsOp(StellarSetOptionsOp *msg) rows[3] ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } memset(rows, 0, sizeof(rows)); row_idx = 0; @@ -809,11 +886,18 @@ void stellar_confirmSetOptionsOp(StellarSetOptionsOp *msg) // At this point, the operation is confirmed stellar_activeTx.confirmed_operations++; + return true; } -void stellar_confirmChangeTrustOp(StellarChangeTrustOp *msg) +bool stellar_confirmChangeTrustOp(StellarChangeTrustOp *msg) { - stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + // Hash: operation type stellar_hashupdate_uint32(6); @@ -840,8 +924,16 @@ void stellar_confirmChangeTrustOp(StellarChangeTrustOp *msg) strlcat(str_amount_row, str_amount, sizeof(str_amount_row)); } + // Validate destination account and convert to bytes + uint8_t asset_issuer_bytes[STELLAR_KEY_SIZE]; + if (!stellar_getAddressBytes(msg->asset.issuer, asset_issuer_bytes)) { + stellar_signingAbort(_("User canceled")); + fsm_sendFailure(FailureType_Failure_ProcessError, _("Invalid asset issuer")); + return false; + } + // Display full issuer address - const char **str_addr_rows = stellar_lineBreakAddress(msg->asset.issuer.bytes); + const char **str_addr_rows = stellar_lineBreakAddress(asset_issuer_bytes); stellar_layoutTransactionDialog( str_title, @@ -851,8 +943,8 @@ void stellar_confirmChangeTrustOp(StellarChangeTrustOp *msg) str_addr_rows[2] ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } // Hash: asset @@ -862,11 +954,18 @@ void stellar_confirmChangeTrustOp(StellarChangeTrustOp *msg) // At this point, the operation is confirmed stellar_activeTx.confirmed_operations++; + return true; } -void stellar_confirmAllowTrustOp(StellarAllowTrustOp *msg) +bool stellar_confirmAllowTrustOp(StellarAllowTrustOp *msg) { - stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + // Hash: operation type stellar_hashupdate_uint32(7); @@ -883,7 +982,14 @@ void stellar_confirmAllowTrustOp(StellarAllowTrustOp *msg) 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); + // Validate account and convert to bytes + uint8_t trusted_account_bytes[STELLAR_KEY_SIZE]; + if (!stellar_getAddressBytes(msg->trusted_account, trusted_account_bytes)) { + stellar_signingAbort(_("Invalid trusted account")); + return false; + } + + const char **str_trustor_rows = stellar_lineBreakAddress(trusted_account_bytes); // By: G... char str_by[32]; @@ -898,12 +1004,12 @@ void stellar_confirmAllowTrustOp(StellarAllowTrustOp *msg) str_trustor_rows[2] ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } // Hash: trustor account (the account being allowed to access the asset) - stellar_hashupdate_address(msg->trusted_account.bytes); + stellar_hashupdate_address(trusted_account_bytes); // asset type stellar_hashupdate_uint32(msg->asset_type); // asset code @@ -924,15 +1030,29 @@ void stellar_confirmAllowTrustOp(StellarAllowTrustOp *msg) // At this point, the operation is confirmed stellar_activeTx.confirmed_operations++; + return true; } -void stellar_confirmAccountMergeOp(StellarAccountMergeOp *msg) +bool stellar_confirmAccountMergeOp(StellarAccountMergeOp *msg) { - stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + // Hash: operation type stellar_hashupdate_uint32(8); - const char **str_destination_rows = stellar_lineBreakAddress(msg->destination_account.bytes); + // Validate account and convert to bytes + uint8_t destination_account_bytes[STELLAR_KEY_SIZE]; + if (!stellar_getAddressBytes(msg->destination_account, destination_account_bytes)) { + stellar_signingAbort(_("Invalid destination account")); + return false; + } + + const char **str_destination_rows = stellar_lineBreakAddress(destination_account_bytes); stellar_layoutTransactionDialog( _("Merge Account"), @@ -942,20 +1062,27 @@ void stellar_confirmAccountMergeOp(StellarAccountMergeOp *msg) str_destination_rows[2] ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } // Hash: destination account - stellar_hashupdate_address(msg->destination_account.bytes); + stellar_hashupdate_address(destination_account_bytes); // At this point, the operation is confirmed stellar_activeTx.confirmed_operations++; + return true; } -void stellar_confirmManageDataOp(StellarManageDataOp *msg) +bool stellar_confirmManageDataOp(StellarManageDataOp *msg) { - stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + // Hash: operation type stellar_hashupdate_uint32(10); @@ -978,8 +1105,8 @@ void stellar_confirmManageDataOp(StellarManageDataOp *msg) str_key_lines[3] ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } // Confirm value by displaying sha256 hash since this can contain non-printable characters @@ -998,8 +1125,8 @@ void stellar_confirmManageDataOp(StellarManageDataOp *msg) str_hash_lines[3] ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } } @@ -1018,11 +1145,18 @@ void stellar_confirmManageDataOp(StellarManageDataOp *msg) // At this point, the operation is confirmed stellar_activeTx.confirmed_operations++; + return true; } -void stellar_confirmBumpSequenceOp(StellarBumpSequenceOp *msg) +bool stellar_confirmBumpSequenceOp(StellarBumpSequenceOp *msg) { - stellar_confirmSourceAccount(msg->has_source_account, msg->source_account.bytes); + if (!stellar_signing) return false; + + if (!stellar_confirmSourceAccount(msg->has_source_account, msg->source_account)) { + stellar_signingAbort(_("Source account error")); + return false; + } + // Hash: operation type stellar_hashupdate_uint32(11); @@ -1037,8 +1171,8 @@ void stellar_confirmBumpSequenceOp(StellarBumpSequenceOp *msg) NULL ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); - return; + stellar_signingAbort(_("User canceled")); + return false; } // Hash: bump to @@ -1046,12 +1180,17 @@ void stellar_confirmBumpSequenceOp(StellarBumpSequenceOp *msg) // At this point, the operation is confirmed stellar_activeTx.confirmed_operations++; + return true; } -void stellar_signingAbort() +void stellar_signingAbort(const char *reason) { + if (!reason) { + reason = _("Unknown error"); + } + stellar_signing = false; - fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL); + fsm_sendFailure(FailureType_Failure_ProcessError, reason); layoutHome(); } @@ -1207,8 +1346,6 @@ const char **stellar_lineBreakAddress(uint8_t *addrbytes) 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]; @@ -1216,8 +1353,11 @@ void stellar_format_asset(StellarAssetType *asset, char *str_formatted, size_t l 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)); + // Validate issuer account for non-native assets + if (asset->type != 0 && !stellar_validateAddress(asset->issuer)) { + stellar_signingAbort(_("Invalid asset issuer")); + return; + } // Native asset if (asset->type == 0) { @@ -1229,7 +1369,7 @@ void stellar_format_asset(StellarAssetType *asset, char *str_formatted, size_t l strlcpy(str_formatted, str_asset_code, len); // Truncate issuer to 13 chars - memcpy(str_asset_issuer_trunc, str_asset_issuer, 13); + memcpy(str_asset_issuer_trunc, asset->issuer, 13); } // 12-character custom if (asset->type == 2) { @@ -1237,7 +1377,7 @@ void stellar_format_asset(StellarAssetType *asset, char *str_formatted, size_t l strlcpy(str_formatted, str_asset_code, len); // Truncate issuer to 5 characters - memcpy(str_asset_issuer_trunc, str_asset_issuer, 5); + memcpy(str_asset_issuer_trunc, asset->issuer, 5); } // Issuer is read the same way for both types of custom assets if (asset->type == 1 || asset->type == 2) { @@ -1267,6 +1407,65 @@ size_t stellar_publicAddressAsStr(uint8_t *bytes, char *out, size_t outlen) return 56; } +/** + * Stellar account string is a base32-encoded string that starts with "G" + * + * It decodes to the following format: + * Byte 0 - always 0x30 ("G" when base32 encoded), version byte indicating a public key + * Bytes 1-33 - 32-byte public key bytes + * Bytes 34-35 - 2-byte CRC16 checksum of the version byte + public key bytes (first 33 bytes) + * + * Note that the stellar "seed" (private key) also uses this format except the version byte + * is 0xC0 which encodes to "S" in base32 + */ +bool stellar_validateAddress(const char *str_address) +{ + bool valid = false; + uint8_t decoded[STELLAR_ADDRESS_SIZE_RAW]; + + if (strlen(str_address) != STELLAR_ADDRESS_SIZE) { + return false; + } + + // Check that it decodes correctly + uint8_t *ret = base32_decode(str_address, STELLAR_ADDRESS_SIZE, decoded, sizeof(decoded), BASE32_ALPHABET_RFC4648); + valid = (ret != NULL); + + // ... and that version byte is 0x30 + if (valid && decoded[0] != 0x30) { + valid = false; + } + + // ... and that checksums match + uint16_t checksum_expected = stellar_crc16(decoded, 33); + uint16_t checksum_actual = (decoded[34] << 8) | decoded[33]; // unsigned short (little endian) + if (valid && checksum_expected != checksum_actual) { + valid = false; + } + + memzero(decoded, sizeof(decoded)); + return valid; +} + +/** + * Converts a string address (G...) to the 32-byte raw address + */ +bool stellar_getAddressBytes(char* str_address, uint8_t *out_bytes) +{ + uint8_t decoded[STELLAR_ADDRESS_SIZE_RAW]; + + // Ensure address is valid + if (!stellar_validateAddress(str_address)) return false; + + base32_decode(str_address, STELLAR_ADDRESS_SIZE, decoded, sizeof(decoded), BASE32_ALPHABET_RFC4648); + + // The 32 bytes with offset 1-33 represent the public key + memcpy(out_bytes, &decoded[1], 32); + + memzero(decoded, sizeof(decoded)); + return true; +} + /* * CRC16 implementation compatible with the Stellar version * Ported from this implementation: http://introcs.cs.princeton.edu/java/61data/CRC16CCITT.java.html @@ -1406,6 +1605,13 @@ void stellar_hashupdate_asset(StellarAssetType *asset) { stellar_hashupdate_uint32(asset->type); + // For non-native assets, validate issuer account and convert to bytes + uint8_t issuer_bytes[STELLAR_KEY_SIZE]; + if (asset->type != 0 && !stellar_getAddressBytes(asset->issuer, issuer_bytes)) { + stellar_signingAbort(_("Invalid asset issuer")); + return; + } + // 4-character asset code if (asset->type == 1) { char code4[4+1]; @@ -1413,7 +1619,7 @@ void stellar_hashupdate_asset(StellarAssetType *asset) strlcpy(code4, asset->code, sizeof(code4)); stellar_hashupdate_bytes((uint8_t *)code4, 4); - stellar_hashupdate_address(asset->issuer.bytes); + stellar_hashupdate_address(issuer_bytes); } // 12-character asset code @@ -1423,7 +1629,7 @@ void stellar_hashupdate_asset(StellarAssetType *asset) strlcpy(code12, asset->code, sizeof(code12)); stellar_hashupdate_bytes((uint8_t *)code12, 12); - stellar_hashupdate_address(asset->issuer.bytes); + stellar_hashupdate_address(issuer_bytes); } } @@ -1475,7 +1681,7 @@ void stellar_layoutTransactionSummary(StellarSignTx *msg) str_addr_rows[2] ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); + stellar_signingAbort(_("User canceled")); return; } @@ -1529,7 +1735,7 @@ void stellar_layoutTransactionSummary(StellarSignTx *msg) str_lines[4] ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); + stellar_signingAbort(_("User canceled")); return; } @@ -1577,7 +1783,7 @@ void stellar_layoutTransactionSummary(StellarSignTx *msg) str_lines[3] ); if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) { - stellar_signingAbort(); + stellar_signingAbort(_("User canceled")); return; } } diff --git a/firmware/stellar.h b/firmware/stellar.h index 81b287a35..c7ab4fd0b 100644 --- a/firmware/stellar.h +++ b/firmware/stellar.h @@ -23,6 +23,14 @@ #include "crypto.h" #include "messages.pb.h" #include "fsm.h" +#include "base32.h" + +// 56 character base-32 encoded string +#define STELLAR_ADDRESS_SIZE 56 +// Decodes to 35 bytes +#define STELLAR_ADDRESS_SIZE_RAW 35 +// Raw key size is 32 bytes +#define STELLAR_KEY_SIZE 32 typedef struct { // BIP32 path to the address being used for signing @@ -44,18 +52,19 @@ typedef struct { // 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_signingAbort(const char *reason); +bool stellar_confirmSourceAccount(bool has_source_account, char *str_account); +bool stellar_confirmCreateAccountOp(StellarCreateAccountOp *msg); +bool stellar_confirmPaymentOp(StellarPaymentOp *msg); +bool stellar_confirmPathPaymentOp(StellarPathPaymentOp *msg); +bool stellar_confirmManageOfferOp(StellarManageOfferOp *msg); +bool stellar_confirmCreatePassiveOfferOp(StellarCreatePassiveOfferOp *msg); +bool stellar_confirmSetOptionsOp(StellarSetOptionsOp *msg); +bool stellar_confirmChangeTrustOp(StellarChangeTrustOp *msg); +bool stellar_confirmAllowTrustOp(StellarAllowTrustOp *msg); +bool stellar_confirmAccountMergeOp(StellarAccountMergeOp *msg); +bool stellar_confirmManageDataOp(StellarManageDataOp *msg); +bool stellar_confirmBumpSequenceOp(StellarBumpSequenceOp *msg); // Layout void stellar_layoutTransactionDialog(const char *line1, const char *line2, const char *line3, const char *line4, const char *line5); @@ -87,6 +96,8 @@ 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); +bool stellar_validateAddress(const char *str_address); +bool stellar_getAddressBytes(char* str_address, uint8_t *out_bytes); uint16_t stellar_crc16(uint8_t *bytes, uint32_t length); #endif \ No newline at end of file