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