Skip to content

Commit

Permalink
update comments
Browse files Browse the repository at this point in the history
  • Loading branch information
chris124567 committed Feb 9, 2024
1 parent 6e2012b commit a5a0b15
Show file tree
Hide file tree
Showing 4 changed files with 2 additions and 240 deletions.
114 changes: 0 additions & 114 deletions src/app_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,92 +16,6 @@
* 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
// when directed. It is recommended that you install this app on your Nano S
// so that you can see how each section of code maps to real-world behavior.
// This also allows you to experiment by modifying the code and observing the
// effect on the app.
//
// I'll begin by describing the high-level architecture of the app. The entry
// point is this file, main.c, which initializes the app and runs the APDU
// request/response loop. The loop reads APDU packets from the computer, which
// instructs it to run various commands. The Sia app supports three commands,
// each defined in a separate file: getPublicKey, signHash, and calcTxnHash.
// These each make use of Sia-specific functions, which are defined in sia.c.
// Finally, some global variables and helper functions are declared in ux.h.
//
// Each command consists of a command handler and a set of screens. Each
// screen has an associated set of elements that can be rendered, a
// preprocessor that controls which elements are rendered, and a button
// handler that processes user input. The command handler is called whenever
// sia_main receives an APDU requesting that command, and is responsible for
// displaying the first screen of the command. Control flow then moves to the
// button handler for that screen, which selects the next screen to display
// based on which button was pressed. Button handlers are also responsible for
// sending APDU replies back to the computer.
//
// The control flow can be a little confusing to understand, because the
// button handler isn't really on the "main execution path" -- it's only
// called via interrupt, typically while execution is blocked on an
// io_exchange call. (In general, it is instructive to think of io_exchange as
// the *only* call that can block.) io_exchange exchanges APDU packets with
// the computer: first it sends a response packet, then it receives a request
// packet. This ordering may seem strange, but it makes sense when you
// consider that the Nano S has to do work in between receiving a command and
// replying to it. Thus, the packet sent by io_exchange is a *response* to the
// previous request, and the packet received is the next request.
//
// But there's a problem with this flow: in most cases, we can't respond to
// the command request until we've received some user input, e.g. approving a
// signature. If io_exchange is the only call that blocks, how can we tell it
// to wait for user input? The answer is a special flag, IO_ASYNC_REPLY. When
// io_exchange is called with this flag, it blocks, but it doesn't send a
// response; instead, it just waits for a new request. Later on, we make a
// separate call to io_exchange, this time with the IO_RETURN_AFTER_TX flag.
// This call sends the response, and then returns immediately without waiting
// for the next request. Visually, it is clear that these flags have opposite
// effects on io_exchange:
//
// ----Time--->
// io_exchange: [---Send Response---|---Wait for Request---]
// IO_ASYNC_REPLY: ^Only do this part^
// IO_RETURN_AFTER_TX: ^Only do this part^
//
// So a typical command flow looks something like this. We start in sia_main,
// which is an infinite loop that starts by calling io_exchange. It receives
// an APDU request from the computer and calls the associated command handler.
// The handler displays a screen, e.g. "Generate address?", and sets the
// IO_ASYNC_REPLY flag before returning. Control returns to sia_main, which
// loops around and calls io_exchange again; due to the flag, it now blocks.
// Everything is frozen until the user decides which button to press. When
// they eventually press the "Approve" button, the button handler jumps into
// action. It generates the address, constructs a response APDU containing
// that address, calls io_exchange with IO_RETURN_AFTER_TX, and redisplays the
// main menu. When a new command arrives, it will be received by the blocked
// io_exchange in sia_main.
//
// More complex commands may require multiple requests and responses. There
// are two approaches to handling this. One approach is to treat each command
// handler as a self-contained unit: that is, the main loop should only handle
// the *first* request for a given command. Subsequent requests are handled by
// additional io_exchange calls within the command handler. The other approach
// is to let the main loop handle all requests, and design the handlers so
// that they can "pick up where they left off." Both designs have tradeoffs.
// In the Sia app, the only handler that requires multiple requests is
// calcTxnHash, and it takes the latter approach.
//
// On the other end of the spectrum, there are simple commands that do not
// require any user input. Many Nano S apps have a "getVersion" command that
// replies to the computer with the app's version. In this case, it is
// sufficient for the command handler to prepare the response APDU and allow
// the main loop to send it immediately, without setting IO_ASYNC_REPLY.
//
// The important things to remember are:
// - io_exchange is the only blocking call
// - the main loop invokes command handlers, which display screens and set button handlers
// - button handlers switch between screens and reply to the computer

#include <glyphs.h>
#include <os.h>
#include <os_io_seproxyhal.h>
Expand All @@ -115,23 +29,11 @@
#include "sia.h"
#include "sia_ux.h"

// You may notice that this file includes blake2b.h despite doing no hashing.
// This is because the Sia app uses the Plan 9 convention for header files:
// header files may not #include other header files. This file needs ux.h, but
// ux.h depends on sia.h, which depends on blake2b.h; so all three must be
// included before we can include ux.h. Feel free to use the more conventional
// #ifndef guards in your own app.

// These are global variables declared in ux.h. They can't be defined there
// because multiple files include ux.h; they need to be defined in exactly one
// place. See ux.h for their descriptions.
commandContext global;

// Here we define the main menu, using the Ledger-provided menu API. This menu
// turns out to be fairly unimportant for Nano S apps, since commands are sent
// by the computer instead of being initiated by the user. It typically just
// contains an idle screen and a version screen.

void ui_idle(void);
void ui_menu_about(void);

Expand Down Expand Up @@ -248,14 +150,6 @@ static handler_fn_t *lookupHandler(uint8_t ins) {
#define OFFSET_LC 0x04
#define OFFSET_CDATA 0x05

// Everything below this point is Ledger magic. And the magic isn't well-
// documented, so if you want to understand it, you'll need to read the
// source, which you can find in the nanos-secure-sdk repo. Fortunately, you
// don't need to understand any of this in order to write an app.
//
// Next, we'll look at how the various commands are implemented. We'll start
// with the simplest command, signHash.c.

void send_error_code(uint16_t e) {
// Convert the exception to a response code. All error codes
// start with 6, except for 0x9000, which is a special
Expand Down Expand Up @@ -316,14 +210,6 @@ void app_main() {
continue;
}

// without calling this, pagination will always begin on the last page if a paginated menu has been
// scrolled through before during the session
#ifdef TARGET_NANOX
ux_layout_bnnn_paging_reset();
#elif defined(HAVE_BAGL)
ux_layout_paging_reset();
#endif

const uint16_t e = handlerFn(cmd.p1, cmd.p2, cmd.data, cmd.lc);
if (e != 0) {
send_error_code(e);
Expand Down
89 changes: 1 addition & 88 deletions src/calcTxnHash.c
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,7 @@ static void zero_ctx(void) {

// handleCalcTxnHash reads a signature index and a transaction, calculates the
// SigHash of the transaction, and optionally signs the hash using a specified
// key. The transaction is processed in a streaming fashion and displayed
// piece-wise to the user.
// key. The transaction is displayed piece-wise to the user.
uint16_t handleCalcTxnHash(uint8_t p1, uint8_t p2, uint8_t *dataBuffer, uint16_t dataLength) {
if ((p1 != P1_FIRST && p1 != P1_MORE) || (p2 != P2_DISPLAY_HASH && p2 != P2_SIGN_HASH)) {
return SW_INVALID_PARAM;
Expand Down Expand Up @@ -267,92 +266,6 @@ uint16_t handleCalcTxnHash(uint8_t p1, uint8_t p2, uint8_t *dataBuffer, uint16_t
}

return 0;
// The above code does something strange: it calls io_exchange
// directly from the command handler. You might wonder: why not
// just prepare the APDU buffer and let sia_main call io_exchange?
// The answer, surprisingly, is that we also need to call
// UX_DISPLAY, and UX_DISPLAY affects io_exchange in subtle ways.
// To understand why, we'll need to dive deep into the Nano S
// firmware. I recommend that you don't skip this section, even
// though it's lengthy, because it will save you a lot of
// frustration when you go "off the beaten path" in your own app.
//
// Recall that the Nano S has two chips. Your app (and the Ledger
// OS, BOLOS) runs on the Secure Element. The SE is completely
// self-contained; it doesn't talk to the outside world at all. It
// only talks to the other chip, the MCU. The MCU is what
// processes button presses, renders things on screen, and
// exchanges APDU packets with the computer. The communication
// layer between the SE and the MCU is called SEPROXYHAL. There
// are some nice diagrams in the "Hardware Architecture" section
// of Ledger's docs that will help you visualize all this.
//
// The SEPROXYHAL protocol, like any communication protocol,
// specifies exactly when each party is allowed to talk.
// Communication happens in a loop: first the MCU sends an Event,
// then the SE replies with zero or more Commands, and finally the
// SE sends a Status to indicate that it has finished processing
// the Event, completing one iteration:
//
// Event -> Commands -> Status -> Event -> Commands -> ...
//
// For our purposes, an "Event" is a request APDU, and a "Command"
// is a response APDU. (There are other types of Events and
// Commands, such as button presses, but they aren't relevant
// here.) As for the Status, there is a "General" Status and a
// "Display" Status. A General Status tells the MCU to send the
// response APDU, and a Display Status tells it to render an
// element on the screen. Remember, it's "zero or more Commands,"
// so it's legal to send just a Status without any Commands.
//
// You may have some picture of the problem now. Imagine we
// prepare the APDU buffer, then call UX_DISPLAY, and then let
// sia_main send the APDU with io_exchange. What happens at the
// SEPROXYHAL layer? First, UX_DISPLAY will send a Display Status.
// Then, io_exchange will send a Command and a General Status. But
// no Event was processed between the two Statuses! This causes
// SEPROXYHAL to freak out and crash, forcing you to reboot your
// Nano S.
//
// So why does calling io_exchange before UX_DISPLAY fix the
// problem? Won't we just end up sending two Statuses again? The
// secret is that io_exchange_with_code uses the
// IO_RETURN_AFTER_TX flag. Previously, the only thing we needed
// to know about IO_RETURN_AFTER_TX is that it sends a response
// APDU without waiting for the next request APDU. But it has one
// other important property: it tells io_exchange not to send a
// Status! So the only Status we send comes from UX_DISPLAY. This
// preserves the ordering required by SEPROXYHAL.
//
// Lastly: what if we prepare the APDU buffer in the handler, but
// with the IO_RETURN_AFTER_TX flag set? Will that work?
// Unfortunately not. io_exchange won't send a status, but it
// *will* send a Command containing the APDU, so we still end up
// breaking the correct SEPROXYHAL ordering.
//
// Here's a list of rules that will help you debug similar issues:
//
// - Always preserve the order: Event -> Commands -> Status
// - UX_DISPLAY sends a Status
// - io_exchange sends a Command and a Status
// - IO_RETURN_AFTER_TX makes io_exchange not send a Status
// - IO_ASYNCH_REPLY (or tx=0) makes io_exchange not send a Command
//
// Okay, that second rule isn't 100% accurate. UX_DISPLAY doesn't
// necessarily send a single Status: it sends a separate Status
// for each element you render! The reason this works is that the
// MCU replies to each Display Status with a Display Processed
// Event. That means you can call UX_DISPLAY many times in a row
// without disrupting SEPROXYHAL. Anyway, as far as we're
// concerned, it's simpler to think of UX_DISPLAY as sending just
// a single Status.
}

// It is not necessary to completely understand this handler to write your own
// Nano S app; much of it is Sia-specific and will not generalize to other
// apps. The important part is knowing how to structure handlers that involve
// multiple APDU exchanges. If you would like to dive deeper into how the
// handler buffers transaction data and parses elements, proceed to txn.c.
// Otherwise, this concludes the walkthrough. Feel free to fork this app and
// modify it to suit your own needs.
#endif /* HAVE_BAGL */
3 changes: 1 addition & 2 deletions src/calcTxnHash_nbgl.c
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,7 @@ static void zero_ctx(void) {

// handleCalcTxnHash reads a signature index and a transaction, calculates the
// SigHash of the transaction, and optionally signs the hash using a specified
// key. The transaction is processed in a streaming fashion and displayed
// piece-wise to the user.
// key. The transaction is displayed piece-wise to the user.
uint16_t handleCalcTxnHash(uint8_t p1, uint8_t p2, uint8_t *dataBuffer, uint16_t dataLength) {
if ((p1 != P1_FIRST && p1 != P1_MORE) || (p2 != P2_DISPLAY_HASH && p2 != P2_SIGN_HASH)) {
return SW_INVALID_PARAM;
Expand Down
36 changes: 0 additions & 36 deletions src/signHash.c
Original file line number Diff line number Diff line change
Expand Up @@ -138,39 +138,3 @@ uint16_t handleSignHash(uint8_t p1 __attribute__((unused)),

return 0;
}

// Now that we've seen the individual pieces, we can construct a full picture
// of what the signHash command looks like.
//
// The command begins when sia_main reads an APDU packet from the computer
// with INS == INS_SIGN_HASH. sia_main looks up the appropriate handler,
// handleSignHash, and calls it. handleSignHash reads the command data,
// prepares and displays the comparison screen, and sets the IO_ASYNC_REPLY
// flag. Control returns to sia_main, which blocks when it reaches the
// io_exchange call.
//
// UX_DISPLAY was called with the ui_prepro_signHash_compare preprocessor, so
// that preprocessor is now called each time the compare screen is rendered.
// Since we are initially displaying the beginning of the hash, the
// preprocessor hides the left arrow. The user presses and holds the right
// button, which triggers the button handler to advance the displayIndex every
// 100ms. Each advance requires redisplaying the screen via UX_REDISPLAY(),
// and thus rerunning the preprocessor. As soon as the right button is
// pressed, the preprocessor detects that text has scrolled off the left side
// of the screen, so it unhides the left arrow; when the end of the hash is
// reached, it hides the right arrow.
//
// When the user has finished comparing the hashes, they press both buttons
// together, triggering ui_signHash_compare_button to prepare the approval
// screen and call UX_DISPLAY on ui_signHash_approve. A NULL preprocessor is
// specified for this screen, since we don't need to filter out any of its
// elements. We'll assume that the user presses the 'approve' button, causing
// the button handler to place the hash in G_io_apdu_buffer and call
// io_exchange_with_code, which sends the response APDU to the computer with
// the IO_RETURN_AFTER_TX flag set. The button handler then calls ui_idle,
// thus returning to the main menu.
//
// This completes the signHash command. Back in sia_main, io_exchange is still
// blocked, waiting for the computer to send a new request APDU. For the next
// section of this walkthrough, we will assume that the next APDU requests the
// getPublicKey command, so proceed to getPublicKey.c.

0 comments on commit a5a0b15

Please sign in to comment.