Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Back Button Work #4

Merged
merged 16 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
314 changes: 149 additions & 165 deletions src/calcTxnHash.c

Large diffs are not rendered by default.

157 changes: 86 additions & 71 deletions src/calcTxnHash_nbgl.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,54 +15,58 @@
static calcTxnHashContext_t *ctx = &global.calcTxnHashContext;

static void fmtTxnElem(void);
static uint16_t display_index(void);
static bool nav_callback(uint8_t page, nbgl_pageContent_t *content);
static void confirm_callback(bool confirm);

// Gets the current index number to be displayed in the UI
static uint16_t display_index(void) {
txn_state_t *txn = &ctx->txn;
uint16_t first_index_of_type = 0;
const txnElemType_e current_type = txn->elements[ctx->elementIndex].elemType;
for (uint16_t i = 0; i < txn->elementIndex; i++) {
if (current_type == txn->elements[i].elemType) {
first_index_of_type = i;
break;
}
}
return ctx->elementIndex - first_index_of_type + 1;
}

// This is a helper function that prepares an element of the transaction for
// display. It stores the type of the element in labelStr, and a human-
// readable representation of the element in fullStr. As in previous screens,
// partialStr holds the visible portion of fullStr.
static void fmtTxnElem(void) {
txn_state_t *txn = &ctx->txn;

switch (txn->elemType) {
switch (txn->elements[ctx->elementIndex].elemType) {
case TXN_ELEM_SC_OUTPUT:
memmove(ctx->labelStr, "SC Output #", 11);
bin2dec(ctx->labelStr + 11, txn->displayIndex);
bin2dec(ctx->labelStr + 11, display_index());
// An element can have multiple screens. For each siacoin output, the
// user needs to see both the destination address and the amount.
// These are rendered in separate screens, and elemPart is used to
// identify which screen is being viewed.
if (ctx->elemPart == 0) {
memmove(ctx->fullStr, txn->outAddr, sizeof(txn->outAddr));
ctx->elemPart++;
} else {
memmove(ctx->fullStr, txn->outVal, sizeof(txn->outVal));
formatSC(ctx->fullStr, txn->valLen);
ctx->elemPart = 0;
}
memmove(ctx->fullStr[0], txn->elements[ctx->elementIndex].outAddr, sizeof(txn->elements[ctx->elementIndex].outAddr));
memmove(ctx->fullStr[1], txn->elements[ctx->elementIndex].outVal, sizeof(txn->elements[ctx->elementIndex].outVal));
formatSC(ctx->fullStr[1], txn->elements[ctx->elementIndex].valLen);
break;

case TXN_ELEM_SF_OUTPUT:
memmove(ctx->labelStr, "SF Output #", 11);
bin2dec(ctx->labelStr + 11, txn->displayIndex);
if (ctx->elemPart == 0) {
memmove(ctx->fullStr, txn->outAddr, sizeof(txn->outAddr));
ctx->elemPart++;
} else {
memmove(ctx->fullStr, txn->outVal, sizeof(txn->outVal));
memmove(ctx->fullStr + txn->valLen, " SF", 4);
ctx->elemPart = 0;
}
bin2dec(ctx->labelStr + 11, display_index());
memmove(ctx->fullStr[0], txn->elements[ctx->elementIndex].outAddr, sizeof(txn->elements[ctx->elementIndex].outAddr));
memmove(ctx->fullStr[1], txn->elements[ctx->elementIndex].outVal, sizeof(txn->elements[ctx->elementIndex].outVal));
memmove(ctx->fullStr[1] + txn->elements[ctx->elementIndex].valLen, " SF", 4);
break;

case TXN_ELEM_MINER_FEE:
// Miner fees only have one part.
memmove(ctx->labelStr, "Miner Fee #", 11);
bin2dec(ctx->labelStr + 11, txn->sliceIndex);
memmove(ctx->fullStr, txn->outVal, sizeof(txn->outVal));
formatSC(ctx->fullStr, txn->valLen);
ctx->elemPart = 0;
bin2dec(ctx->labelStr + 11, display_index());
memmove(ctx->fullStr[0], txn->elements[ctx->elementIndex].outVal, sizeof(txn->elements[ctx->elementIndex].outVal));
formatSC(ctx->fullStr[0], txn->elements[ctx->elementIndex].valLen);
break;

default:
Expand Down Expand Up @@ -96,61 +100,61 @@ static void confirm_callback(bool confirm) {
}
}

static nbgl_layoutTagValue_t pair;
static nbgl_layoutTagValue_t pairs[3];

static bool nav_callback(uint8_t page, nbgl_pageContent_t *content) {
UNUSED(page);
if (ctx->elemPart > 0) {
fmtTxnElem();
} else {
// Attempt to decode the next element of the transaction. Note that this
// code is essentially identical to ui_calcTxnHash_elem_button. Sadly,
// there doesn't seem to be a clean way to avoid this duplication.
switch (txn_next_elem(&ctx->txn)) {
case TXN_STATE_ERR:
io_exchange_with_code(SW_INVALID_PARAM, 0);
return false;
break;
case TXN_STATE_PARTIAL:
io_exchange_with_code(SW_OK, 0);
return false;
break;
case TXN_STATE_READY:
ctx->elemPart = 0;
fmtTxnElem();
break;
case TXN_STATE_FINISHED:
ctx->finished = true;

content->type = INFO_LONG_PRESS;
content->infoLongPress.icon = &C_stax_app_sia;
if (ctx->sign) {
memmove(ctx->fullStr, "with key #", 10);
bin2dec(ctx->fullStr + 10, ctx->keyIndex);
memmove(ctx->fullStr + 10 + (bin2dec(ctx->fullStr + 10, ctx->keyIndex)), "?", 2);

content->infoLongPress.text = "Sign Transaction";
content->infoLongPress.longPressText = ctx->fullStr;
} else {
memmove(G_io_apdu_buffer, ctx->txn.sigHash, 32);
io_exchange_with_code(SW_OK, 32);
bin2hex(ctx->fullStr, ctx->txn.sigHash, sizeof(ctx->txn.sigHash));

content->infoLongPress.text = ctx->fullStr;
content->infoLongPress.longPressText = "Confirm Hash";
}
return true;
break;
ctx->elementIndex = page;
if (ctx->elementIndex >= ctx->txn.elementIndex) {
const bool wasFinished = ctx->finished;
ctx->finished = true;

content->type = INFO_LONG_PRESS;
content->infoLongPress.icon = &C_stax_app_sia;
if (ctx->sign) {
memmove(ctx->fullStr[0], "with key #", 10);
bin2dec(ctx->fullStr[0] + 10, ctx->keyIndex);
memmove(ctx->fullStr[0] + 10 + (bin2dec(ctx->fullStr[0] + 10, ctx->keyIndex)), "?", 2);

content->infoLongPress.text = "Sign Transaction";
content->infoLongPress.longPressText = ctx->fullStr[0];
} else {
memmove(G_io_apdu_buffer, ctx->txn.sigHash, 32);
// prevent this from being sent twice and causing device to hang
if (!wasFinished) {
io_exchange_with_code(SW_OK, 32);
}
bin2hex(ctx->fullStr[0], ctx->txn.sigHash, sizeof(ctx->txn.sigHash));

content->infoLongPress.text = ctx->fullStr[0];
content->infoLongPress.longPressText = "Confirm Hash";
}
return true;
}

pair.item = ctx->labelStr;
pair.value = ctx->fullStr;
fmtTxnElem();

pairs[0].item = "Label";
pairs[0].value = ctx->labelStr;

if (ctx->txn.elements[ctx->elementIndex].elemType == TXN_ELEM_MINER_FEE) {
pairs[1].item = "Value";
pairs[1].value = ctx->fullStr[0];

content->tagValueList.nbPairs = 2;
content->tagValueList.pairs = &pairs;
} else {
pairs[1].item = "Address";
pairs[1].value = ctx->fullStr[0];
pairs[2].item = "Value";
pairs[2].value = ctx->fullStr[1];

content->tagValueList.nbPairs = 3;
content->tagValueList.pairs = &pairs;
}

content->type = TAG_VALUE_LIST;
content->title = NULL;
content->tagValueList.nbPairs = 1;
content->tagValueList.pairs = &pair;
content->type = TAG_VALUE_LIST;
content->tagValueList.callback = NULL;

content->tagValueList.startIndex = 0;
Expand Down Expand Up @@ -180,6 +184,7 @@ void handleCalcTxnHash(uint8_t p1, uint8_t p2, uint8_t *dataBuffer, uint16_t dat
if (prev_initialized) {
THROW(SW_IMPROPER_INIT);
}
ctx->elementIndex = 0;
ctx->finished = false;
ctx->initialized = true;

Expand Down Expand Up @@ -213,7 +218,17 @@ void handleCalcTxnHash(uint8_t p1, uint8_t p2, uint8_t *dataBuffer, uint16_t dat
txn_update(&ctx->txn, dataBuffer, dataLength);

*flags |= IO_ASYNCH_REPLY;
nbgl_useCaseRegularReview(0, 0, "Cancel", NULL, nav_callback, confirm_callback);
switch (txn_parse(&ctx->txn)) {
case TXN_STATE_ERR:
io_exchange_with_code(SW_INVALID_PARAM, 0);
break;
case TXN_STATE_PARTIAL:
io_exchange_with_code(SW_OK, 0);
break;
case TXN_STATE_FINISHED:
nbgl_useCaseRegularReview(0, 0, "Cancel", NULL, nav_callback, confirm_callback);
break;
}
}

#endif /* HAVE_BAGL */
32 changes: 16 additions & 16 deletions src/main.c
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
/*******************************************************************************
*
* (c) 2016 Ledger
* (c) 2018 Nebulous
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
*
* (c) 2016 Ledger
* (c) 2018 Nebulous
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/

// This code also serves as a walkthrough for writing your own Ledger Nano S
// app. Begin by reading this file top-to-bottom, and proceed to the next file
Expand Down
5 changes: 4 additions & 1 deletion src/sia_ux.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,13 @@ typedef struct {
uint32_t keyIndex;
bool sign;
uint8_t elemPart; // screen index of elements

uint16_t elementIndex;

txn_state_t txn;
// NULL-terminated strings for display
char labelStr[40]; // variable length
char fullStr[128]; // variable length
char fullStr[2][128]; // variable length
bool initialized; // protects against certain attacks
bool finished; // whether we have reached the end of the transaction
} calcTxnHashContext_t;
Expand Down
60 changes: 36 additions & 24 deletions src/txn.c
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ static void seek(txn_state_t *txn, uint64_t n) {

static void advance(txn_state_t *txn) {
// if elem is covered, add it to the hash
if (txn->elemType != TXN_ELEM_TXN_SIG) {
if (txn->elements[txn->elementIndex].elemType != TXN_ELEM_TXN_SIG) {
blake2b_update(&txn->blake, txn->buf, txn->pos);
} else if (txn->sliceIndex == txn->sigIndex && txn->pos >= 48) {
// add just the ParentID, Timelock, and PublicKeyIndex
Expand All @@ -137,7 +137,7 @@ static void readCurrency(txn_state_t *txn, uint8_t *outVal) {
uint64_t valLen = readInt(txn);
need_at_least(txn, valLen);
if (outVal) {
txn->valLen = cur2dec(outVal, txn->buf + txn->pos - 8);
txn->elements[txn->elementIndex].valLen = cur2dec(outVal, txn->buf + txn->pos - 8);
}
seek(txn, valLen);
}
Expand Down Expand Up @@ -193,56 +193,67 @@ static void addReplayProtection(cx_blake2b_t *S) {

// throws txnDecoderState_e
static void __txn_next_elem(txn_state_t *txn) {
// too many elements
if (txn->elementIndex == MAX_ELEMS) {
THROW(TXN_STATE_ERR);
}
// if we're on a slice boundary, read the next length prefix and bump the
// element type
while (txn->sliceIndex == txn->sliceLen) {
if (txn->elemType == TXN_ELEM_TXN_SIG) {
if (txn->elements[txn->elementIndex].elemType == TXN_ELEM_TXN_SIG) {
// store final hash
blake2b_final(&txn->blake, txn->sigHash, sizeof(txn->sigHash));
THROW(TXN_STATE_FINISHED);
}
// too many elements
txn->sliceLen = readInt(txn);
txn->sliceIndex = 0;
txn->displayIndex = 0;
txn->elemType++;
txn->elements[txn->elementIndex].elemType++;
advance(txn);

// if we've reached the TransactionSignatures, check that sigIndex is
// a valid index
if ((txn->elemType == TXN_ELEM_TXN_SIG) && (txn->sigIndex >= txn->sliceLen)) {
if ((txn->elements[txn->elementIndex].elemType == TXN_ELEM_TXN_SIG) && (txn->sigIndex >= txn->sliceLen)) {
THROW(TXN_STATE_ERR);
}
}

switch (txn->elemType) {
switch (txn->elements[txn->elementIndex].elemType) {
// these elements should be displayed
case TXN_ELEM_SC_OUTPUT:
readCurrency(txn, txn->outVal); // Value
readHash(txn, (char *)txn->outAddr); // UnlockHash
readCurrency(txn, txn->elements[txn->elementIndex].outVal); // Value
readHash(txn, (char *)txn->elements[txn->elementIndex].outAddr); // UnlockHash
advance(txn);
txn->sliceIndex++;
if (!memcmp(txn->outAddr, txn->changeAddr, sizeof(txn->outAddr))) {
if (!memcmp(txn->elements[txn->elementIndex].outAddr, txn->changeAddr, sizeof(txn->elements[txn->elementIndex].outAddr))) {
// do not display the change address or increment displayIndex
return;
}
txn->displayIndex++;
THROW(TXN_STATE_READY);

txn->sliceIndex++;
txn->elements[txn->elementIndex + 1].elemType = txn->elements[txn->elementIndex].elemType;
txn->elementIndex++;
return;

case TXN_ELEM_SF_OUTPUT:
readCurrency(txn, txn->outVal); // Value
readHash(txn, (char *)txn->outAddr); // UnlockHash
readCurrency(txn, NULL); // ClaimStart
readCurrency(txn, txn->elements[txn->elementIndex].outVal); // Value
readHash(txn, (char *)txn->elements[txn->elementIndex].outAddr); // UnlockHash
readCurrency(txn, NULL); // ClaimStart
advance(txn);

txn->sliceIndex++;
txn->displayIndex++;
THROW(TXN_STATE_READY);
txn->elements[txn->elementIndex + 1].elemType = txn->elements[txn->elementIndex].elemType;
txn->elementIndex++;
return;

case TXN_ELEM_MINER_FEE:
readCurrency(txn, txn->outVal); // Value
memmove(txn->outAddr, "[Miner Fee]", 12);
readCurrency(txn, txn->elements[txn->elementIndex].outVal); // Value
memmove(txn->elements[txn->elementIndex].outAddr, "[Miner Fee]", 12);
advance(txn);

txn->sliceIndex++;
THROW(TXN_STATE_READY);
txn->elements[txn->elementIndex + 1].elemType = txn->elements[txn->elementIndex].elemType;
txn->elementIndex++;
return;

// these elements should be decoded, but not displayed
case TXN_ELEM_SC_INPUT:
Expand Down Expand Up @@ -284,7 +295,7 @@ static void __txn_next_elem(txn_state_t *txn) {
}
}

txnDecoderState_e txn_next_elem(txn_state_t *txn) {
txnDecoderState_e txn_parse(txn_state_t *txn) {
// Like many transaction decoders, we use exceptions to jump out of deep
// call stacks when we encounter an error. There are two important rules
// for Ledger exceptions: declare modified variables as volatile, and do
Expand Down Expand Up @@ -317,10 +328,11 @@ txnDecoderState_e txn_next_elem(txn_state_t *txn) {

void txn_init(txn_state_t *txn, uint16_t sigIndex, uint32_t changeIndex) {
memset(txn, 0, sizeof(txn_state_t));
txn->buflen = txn->pos = txn->sliceIndex = txn->displayIndex = txn->sliceLen = txn->valLen = 0;
txn->elemType = -1; // first increment brings it to SC_INPUT
txn->sigIndex = sigIndex;

txn->elementIndex = 0;
txn->elements[txn->elementIndex].elemType = -1; // first increment brings it to SC_INPUT

cx_ecfp_public_key_t publicKey = {0};
deriveSiaKeypair(changeIndex, NULL, &publicKey);
pubkeyToSiaAddress((char *)&txn->changeAddr, &publicKey);
Expand Down
Loading
Loading