From a5a0b1519d085fc0e64a2bba3ed2b19b07a1de35 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Fri, 9 Feb 2024 11:19:48 -0500 Subject: [PATCH] update comments --- src/app_main.c | 114 ----------------------------------------- src/calcTxnHash.c | 89 +------------------------------- src/calcTxnHash_nbgl.c | 3 +- src/signHash.c | 36 ------------- 4 files changed, 2 insertions(+), 240 deletions(-) diff --git a/src/app_main.c b/src/app_main.c index 9df2eff..918f08c 100644 --- a/src/app_main.c +++ b/src/app_main.c @@ -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 #include #include @@ -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); @@ -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 @@ -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); diff --git a/src/calcTxnHash.c b/src/calcTxnHash.c index f0880b2..c4079bc 100644 --- a/src/calcTxnHash.c +++ b/src/calcTxnHash.c @@ -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; @@ -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 */ \ No newline at end of file diff --git a/src/calcTxnHash_nbgl.c b/src/calcTxnHash_nbgl.c index 9192f87..86a8fc2 100644 --- a/src/calcTxnHash_nbgl.c +++ b/src/calcTxnHash_nbgl.c @@ -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; diff --git a/src/signHash.c b/src/signHash.c index 3d915a8..3e7f2a2 100644 --- a/src/signHash.c +++ b/src/signHash.c @@ -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.