From 2722a6f23852a5a6ce7908c0019ea0fb86f5ef7c Mon Sep 17 00:00:00 2001 From: BTChip Date: Tue, 17 Sep 2019 15:08:03 +0200 Subject: [PATCH 1/9] v2.2.0 --- Makefile | 4 +- README.md | 50 ++++++++++++--- WELCOME.md | 91 ++++++++++++++++++++++++++ default.nix | 39 +++++++---- nix/install.sh | 8 ++- nix/setup-vscode.sh | 6 +- release-installer.sh | 4 +- src/apdu.c | 4 ++ src/apdu_baking.c | 7 +- src/apdu_hmac.c | 15 +++-- src/apdu_pubkey.c | 9 ++- src/apdu_setup.c | 7 +- src/apdu_sign.c | 149 ++++++++++++++++++++++++++----------------- src/baking_auth.c | 17 ++--- src/baking_auth.h | 4 +- src/exception.h | 2 +- src/globals.c | 41 ++++++++++-- src/globals.h | 105 ++++++++++++++++-------------- src/key_macros.h | 34 ++++++++++ src/keys.c | 67 ++++++++++--------- src/keys.h | 89 ++++++++++++++++---------- src/operations.c | 131 ++++++++++++++++++++++++------------- src/operations.h | 18 +----- src/to_string.c | 50 +++++++++------ src/to_string.h | 14 ++-- src/types.h | 86 +++++++++++++++++++------ src/ui.h | 3 +- src/ui_common.c | 15 ++++- src/ui_nano_s.c | 17 +++-- src/ui_nano_x.c | 48 +++++++++++++- test/baking_test.sh | 69 +++++++++++++++++++- test/transaction.sh | 85 ++++++++++++++++++++++++ 32 files changed, 921 insertions(+), 367 deletions(-) create mode 100644 WELCOME.md create mode 100644 src/key_macros.h create mode 100755 test/transaction.sh diff --git a/Makefile b/Makefile index 3416c1d8..34b9da28 100644 --- a/Makefile +++ b/Makefile @@ -18,8 +18,8 @@ GIT_DESCRIBE ?= $(shell git describe --tags --abbrev=8 --always --long --dirty 2 VERSION_TAG ?= $(shell echo "$(GIT_DESCRIBE)" | cut -f1 -d-) APPVERSION_M=2 -APPVERSION_N=0 -APPVERSION_P=1 +APPVERSION_N=2 +APPVERSION_P=0 APPVERSION=$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P) # Only warn about version tags if specified/inferred diff --git a/README.md b/README.md index 92d5141e..708313b3 100644 --- a/README.md +++ b/README.md @@ -509,12 +509,14 @@ The output of this command includes three Tezos addresses derived from the secre stored on the device, via different signing curves and BIP32 paths. ``` -Found a Tezos Wallet 1.4.0 (commit 764625b1) application running on Ledger Nano S at [0001:002a:00]. +Found a Tezos Baking 2.0.1 (git-description: "") application running on +Ledger Nano S at [0001:0003:00]. -To use keys at BIP32 path m/44'/1729'/0'/0' (default Tezos key path), use one of - tezos-client import secret key ledger_jhartzell "ledger://major-squirrel-thick-hedgehog/ed25519/0'/0'" - tezos-client import secret key ledger_jhartzell "ledger://major-squirrel-thick-hedgehog/secp256k1/0'/0'" - tezos-client import secret key ledger_jhartzell "ledger://major-squirrel-thick-hedgehog/P-256/0'/0'" +To use keys at BIP32 path m/44'/1729'/0'/0' (default Tezos key path), use one +of: + tezos-client import secret key ledger_kiln "ledger://major-squirrel-thick-hedgehog/ed25519/0h/0h" + tezos-client import secret key ledger_kiln "ledger://major-squirrel-thick-hedgehog/secp256k1/0h/0h" + tezos-client import secret key ledger_kiln "ledger://major-squirrel-thick-hedgehog/P-256/0h/0h" ``` These show you how to import keys with a specific signing curve (e.g. `ed25519`) and derivation path (e.g. `/0'/0'`). The @@ -579,11 +581,11 @@ eventually display more transaction details along with this. When block headers and endorsements are sent to the Ledger Nano S, they are rejected silently as if the user rejected them. -### Faucet (zeronet only) +### Faucet (alphanet and zeronet only) -On zeronet, you will need to use the [Tezos Faucet](https://faucet.tzalpha.net/) +On alphanet and zeronet, you will need to use the [Tezos Faucet](https://faucet.tzalpha.net/) to obtain some tez. Tell them you're not a robot, then click "Get alphanet tz." -It works on zeronet (even though the URL says alphanet). +It works on zeronet (even though the URL says "alpha"). Run the following command, where `` is some alias you want to use for this wallet, and `tz1<...>.json` is the name of the file you just downloaded @@ -702,7 +704,7 @@ $ tezos-client submit proposals for The Wallet app will then ask you to confirm the various details of the proposal submission. -**Note:** While `tezos-client` will let you submit multiple proposals at once with this command, submitting more than one will cause the Wallet app to show "Sign Unverified?" instead of showing each field of each proposal for your confirmation. Signing an operation that you can't confirm is not safe and it is highly recommended that you simply submit each proposal one at a time so you can properly confirm the fields on the ledger device. +**Note:** While `tezos-client` will let you submit multiple proposals at once with this command, submitting more than one will cause the Wallet app to show "Sign Hash" instead of showing each field of each proposal for your confirmation. Signing an operation that you can't confirm is not safe and it is highly recommended that you simply submit each proposal one at a time so you can properly confirm the fields on the ledger device. To manually confirm the hash, refer to [Manually Confirming Operation Hashes](#Manually-Confirming-Operation-Hashes). Voting for a proposal during the Exploration or Promotion Vote Period also requires that you have the Wallet app open. You can then run @@ -714,6 +716,36 @@ The Wallet app will ask you to confirm the details of your vote. Keep in mind that only registered delegate accounts can submit proposals and vote. Each account can submit up to 20 proposals per proposal period and vote only once per voting period. For a more detailed post on participating during each phase of the amendment process, see this [Medium post](https://medium.com/@obsidian.systems/voting-on-tezos-with-your-ledger-nano-s-8d75f8c1f076). For a full description of how voting works, refer to the [Tezos documentation](https://gitlab.com/tezos/tezos/blob/master/docs/whitedoc/voting.rst). +### Manually Confirming Operation Hashes + +Many operations are too large or complex for Tezos Wallet to show you enough detail on the device that you could safely confirm it. For example, it is possible to create an operation that includes hundreds of transactions. It is not feasible to confirm all of them on a tiny device screen. For any operation that Tezos Wallet can't easily confirm via screen prompts, it will instead show you the "Sign Hash" prompt. This shows you a *hash* of the entire operation that you should cross-check with another source. `tezos-client` will show you this hash if you ask it to run the operation with `--verbose-signing`. This will include additional output like the following: + +``` +Pre-signature information (verbose signing): + * Branch: BMRELbkCkHvCAr2vZfavjYUKXLbKrGvX6oN3qNEDKPjp8aJHqRm + * Watermark: `Generic-operation` (0x03) + * Operation bytes: + e0ac9e16f0005865f71bcf039d10ec2bb8d604210c9139968949f64ea5c9d1320500aed01 + 1841ffbb0bcc3b51c80f2b6c333a1be3df00000000000000040ab22e46e7872aa13e366e4 + 55bb4f5dbede856ab0864e1da7e122554579ee71f876cd995a324193bbe09ac2d5c53f69f + 93778f8d608f1fea885f9b53e0abdb6e4 + * Blake 2B Hash (raw): Hnw7wQsfv8fvMUejXNJ31NauapEtzLZg859JwqNUEDEE + * Blake 2B Hash (ledger-style, with operation watermark): + C5Qkk9tTwaUbhnrN29JpXSmsYCEi1uhM8rSsentBwmbN + * JSON encoding: + { "branch": "BMRELbkCkHvCAr2vZfavjYUKXLbKrGvX6oN3qNEDKPjp8aJHqRm", + "contents": + [ { "kind": "proposals", + "source": "tz1baMXLyDZ7nx7v96P2mEwM9U5Rhj5xJUnJ", "period": 0, + "proposals": + [ "Pt24m4xiPbLDhVgVfABUjirbmda3yohdN82Sp9FeuAXJ4eV9otd", + "Psd1ynUBhMZAeajwcZJAeq5NrxorM6UCU4GJqxZ7Bx2e9vUWB6z" ] } ] } +``` + +Here, the hash under `Blake 2B Hash (ledger-style, with operation watermark)` is `C5Qkk9tTwaUbhnrN29JpXSmsYCEi1uhM8rSsentBwmbN` and should match the hash on the Ledger screen. + +To be truly confident in the correctness of this operation, run the same operation multiple times from different places. `tezos-client` has two options to help with this: `--dry-run` which skips the last step of injecting the operation into the chain, and `--block ` to pin an operation to a specific block. + ## Using the Tezos Baking Application The Tezos Baking Application supports the following operations: diff --git a/WELCOME.md b/WELCOME.md new file mode 100644 index 00000000..6fb1d288 --- /dev/null +++ b/WELCOME.md @@ -0,0 +1,91 @@ +The Exciting World of Ledger C +------------------------------ + +Knowing C will help you in this adventure. But not as much as it should. There are some fun twists when it comes to Ledger C. Explore them below. Memorize them. There *will* be a quiz... + +### Exceptions + +C doesn't have them. So you don't have to think about bracketing, exception safety, RAII, try/catch, all that. + +Well not on the Ledger. You have exceptions! Which means you also have out-of-band code paths, and you now have to worry about exception safety. + +You can `THROW` a `uint16_t` like this `THROW(0x9000)`. + +Handling exceptions looks like this. + +```c +volatile int something = 0; +BEGIN_TRY { + TRY { + //do something; + } + CATCH(EXC_PARSE_ERROR) { + //do something on parse error + } + CATCH_OTHER(e) { + THROW(e); + } + FINALLY { } +} +END_TRY; +``` + +Exceptions that make it all the way to the top of the application are caught and returned as status codes from the APDU. + +#### Gotchas + + 1. If a variable will be accessed both outside and inside the `BEGIN_TRY`/`END_TRY` block it must be `volatile`. The compiler doesn't expect these shenanigans and will optimize incorrectly if you don't. + 2. Do not `return` in the `TRY` block. It will cause the Ledger to crash. Instead use a `volatile` variable to capture the result you want to `return` at the end. + 3. Don't try to leave out blocks like `CATCH_OTHER(e)` and `FINALLY`. I don't know if that will work right and it's not worth the risk. + +#### Implications + + 1. If you have some global state and an exception is thrown then, unless you do something about it, that global state will remain. That might be a *very bad thing*. As long as you use globals our way (see Globals Our Way) you should be safe. + + +### Globals Our Way + +`static const` globals are fine. `static` non-const are not fine for two reasons: + + 1. If you try to initialize them (which you would want to do!) then the app will crash. For example `static int my_bool = 3;` crashes whenever you try to read or write `my_bool`... + 2. Instead of getting initialized to 0 like the C standard says, they are initialized to `0xA5`. Yes this can cause the compiler to incorrectly optimize your code. + +So just don't use `static` non-const globals. Instead we have `globals.h` which defines a large `struct` wher you can put your globals. At the beginning of the application we `memset(&global, 0, sizeof(global))` to clear it all to zeros. + +Anything inside of `global.apdu` will get cleared when an exception gets to the top of the app (see Exceptions). To benefit from this behavior you should never return an error code via the in-band way of sending bytes back. All errors should be sent via `THROW`. + +### Relocation + +When we said `static const` globals were fine, we meant that they were possible. There is +a major gotcha, however: if you initialize a `static const` value with a pointer to another +`static` or `static const` value, the pointers might be incorrect and require relocation. + +For example: + +``` +static const char important_string[] = "Important!"; +static const char **important_string_ptrs = { important_string, NULL }; +const char *str1 = important_string_ptrs[0]; +const char *str2 = important_string; +``` + +`str` will now have the wrong value. `str2` will not. The reason `str1` +has the wrong value is that the linker gets confused with a reference +from one `static const` variable to another `static` variable on this +platform. To resolve, you can use the `PIC` macro, which will fix broken +pointers but never break a good pointer. Because of this, you can use +it liberally and not have to worry about breaking anything: + +``` +static const char important_string[] = "Important!"; +static const char **important_string_ptrs = { important_string, NULL }; +const char *str1 = PIC(important_string_ptrs[0]); // necessary use of PIC +const char *str2 = PIC(important_string); // unnecessary but harmless use of PIC +``` + +Many of the UI functions call `PIC` for you, so just because a UI function +accepts a data structure, doesn't mean that data structure is valid. + +### Dynamic Allocation + +Nope. Don't even try. No `malloc`/`calloc`/`free`. Use globals (see Globals). diff --git a/default.nix b/default.nix index ac8c249e..79c8b554 100644 --- a/default.nix +++ b/default.nix @@ -1,6 +1,5 @@ -{ pkgs ? import nix/nixpkgs.nix {}, gitDescribe ? null, nanoXSdk ? throw "No NanoX SDK", ... }: +{ pkgs ? import nix/nixpkgs.nix {}, gitDescribe ? null, nanoXSdk ? null, ... }: let - fetchThunk = p: if builtins.pathExists (p + /git.json) then pkgs.fetchgit { inherit (builtins.fromJSON (builtins.readFile (p + /git.json))) url rev sha256; } @@ -19,14 +18,32 @@ let sdk = fetchThunk nix/dep/nanos-secure-sdk; env = pkgs.callPackage nix/bolos-env.nix { clangVersion = 4; }; target = "TARGET_NANOS"; + targetId = "0x31100004"; fhsWith = fhs (_: [env.clang]); + iconHex = pkgs.runCommand "nano-s-icon-hex" {} '' + '${fhsWith "python"}' ${sdk + /icon.py} '${icons/nano-s-tezos.gif}' hexbitmaponly > "$out" + ''; + nvramDataSize = appDir: pkgs.runCommand "${name}-nvram-data-size" {} '' + grep _nvram_data_size '${appDir + /debug/app.map}' | tr -s ' ' | cut -f2 -d' ' > "$out" + ''; }; x = rec { name = "x"; - sdk = nanoXSdk; + sdk = if nanoXSdk == null + then throw "No NanoX SDK" + else assert builtins.typeOf nanoXSdk == "path"; nanoXSdk; env = pkgs.callPackage nix/bolos-env.nix { clangVersion = 7; }; target = "TARGET_NANOX"; + targetId = "0x33000004"; fhsWith = fhs (_: [env.clang]); + iconHex = pkgs.runCommand "${name}-icon-hex" {} '' + '${fhsWith "python3"}' '${sdk + /icon3.py}' --hexbitmaponly '${icons/nano-x-tezos.gif}' > "$out" + ''; + nvramDataSize = appDir: pkgs.runCommand "${name}-nvram-data-size" {} '' + envram_data="0x$(cat | grep _envram_data '${appDir + /debug/app.map}' | cut -f1 -d' ')" + nvram_data="0x$(grep _nvram_data '${appDir + /debug/app.map}' | cut -f1 -d' ')" + echo "$(($envram_data - $nvram_data))" > "$out" + ''; }; }; @@ -46,6 +63,7 @@ let export BOLOS_ENV='${bolos.env}' export APP='${if bakingApp then "tezos_baking" else "tezos_wallet"}' export GIT_DESCRIBE='${gitDescribe}' + export TARGET='${bolos.target}' make clean make all EOF @@ -66,25 +84,24 @@ let cat > "$out/app.manifest" < size_t provide_pubkey(uint8_t *const io_buffer, cx_ecfp_public_key_t const *const pubkey) { + check_null(io_buffer); + check_null(pubkey); size_t tx = 0; io_buffer[tx++] = pubkey->W_len; memmove(io_buffer + tx, pubkey->W, pubkey->W_len); @@ -74,6 +76,8 @@ void main_loop(apdu_handler const *const handlers, size_t const handlers_size) { THROW(EXCEPTION_IO_RESET); } CATCH_OTHER(e) { + clear_apdu_globals(); // IMPORTANT: Application state must not persist through errors + uint16_t sw = e; switch (sw) { default: diff --git a/src/apdu_baking.c b/src/apdu_baking.c index 827235e2..dfe5ae7d 100644 --- a/src/apdu_baking.c +++ b/src/apdu_baking.c @@ -12,7 +12,7 @@ #include -#define G global.u.baking +#define G global.apdu.u.baking static bool reset_ok(void); @@ -92,13 +92,10 @@ size_t handle_apdu_query_auth_key(__attribute__((unused)) uint8_t instruction) { } size_t handle_apdu_query_auth_key_with_curve(__attribute__((unused)) uint8_t instruction) { - uint8_t const curve_code = curve_to_curve_code(N_data.baking_key.curve); - if (curve_code == TEZOS_NO_CURVE) THROW(EXC_REFERENCED_DATA_NOT_FOUND); - uint8_t const length = N_data.baking_key.bip32_path.length; size_t tx = 0; - G_io_apdu_buffer[tx++] = curve_code; + G_io_apdu_buffer[tx++] = unparse_derivation_type(N_data.baking_key.derivation_type); G_io_apdu_buffer[tx++] = length; for (uint8_t i = 0; i < length; ++i) { tx = send_word_big_endian(tx, N_data.baking_key.bip32_path.components[i]); diff --git a/src/apdu_hmac.c b/src/apdu_hmac.c index 3b0c725d..0935a5a4 100644 --- a/src/apdu_hmac.c +++ b/src/apdu_hmac.c @@ -3,11 +3,12 @@ #include "apdu_hmac.h" #include "globals.h" +#include "key_macros.h" #include "keys.h" #include "protocol.h" -#define G global.u.hmac +#define G global.apdu.u.hmac static inline size_t hmac( uint8_t *const out, size_t const out_size, @@ -27,10 +28,12 @@ static inline size_t hmac( 0x5a, 0x90, 0x47, 0x5e, 0xc0, 0xdb, 0xdb, 0x9f }; // Deterministically sign the SHA256 value to get something directly tied to the secret key. - size_t const signed_hmac_key_size = sign( - state->signed_hmac_key, sizeof(state->signed_hmac_key), - &state->key, - key_sha256, sizeof(key_sha256)); + size_t const signed_hmac_key_size = WITH_KEY_PAIR(state->key, key_pair, size_t, ({ + sign( + state->signed_hmac_key, sizeof(state->signed_hmac_key), + state->key.derivation_type, key_pair, + key_sha256, sizeof(key_sha256)); + })); // Hash the signed value with SHA512 to get a 64-byte key for HMAC. cx_hash_sha512( @@ -52,7 +55,7 @@ size_t handle_apdu_hmac(__attribute__((unused)) uint8_t instruction) { memset(&G, 0, sizeof(G)); - G.key.curve = curve_code_to_curve( + G.key.derivation_type = parse_derivation_type( READ_UNALIGNED_BIG_ENDIAN(uint8_t, &G_io_apdu_buffer[OFFSET_CURVE])); size_t consumed = 0; diff --git a/src/apdu_pubkey.c b/src/apdu_pubkey.c index 9e522f76..28b8b59e 100644 --- a/src/apdu_pubkey.c +++ b/src/apdu_pubkey.c @@ -10,7 +10,7 @@ #include -#define G global.u.pubkey +#define G global.apdu.u.pubkey static bool pubkey_ok(void) { delayed_send(provide_pubkey(G_io_apdu_buffer, &G.public_key)); @@ -19,7 +19,7 @@ static bool pubkey_ok(void) { #ifdef BAKING_APP static bool baking_ok(void) { - authorize_baking(G.key.curve, &G.key.bip32_path); + authorize_baking(G.key.derivation_type, &G.key.bip32_path); pubkey_ok(); return true; } @@ -76,8 +76,7 @@ size_t handle_apdu_get_public_key(uint8_t instruction) { // do not expose pks without prompt over U2F (browser support) if (instruction == INS_GET_PUBLIC_KEY) require_hid(); - uint8_t const curve_code = READ_UNALIGNED_BIG_ENDIAN(uint8_t, &G_io_apdu_buffer[OFFSET_CURVE]); - G.key.curve = curve_code_to_curve(curve_code); + G.key.derivation_type = parse_derivation_type(READ_UNALIGNED_BIG_ENDIAN(uint8_t, &G_io_apdu_buffer[OFFSET_CURVE])); size_t const cdata_size = READ_UNALIGNED_BIG_ENDIAN(uint8_t, &G_io_apdu_buffer[OFFSET_LC]); @@ -91,7 +90,7 @@ size_t handle_apdu_get_public_key(uint8_t instruction) { if (G.key.bip32_path.length == 0) THROW(EXC_WRONG_LENGTH_FOR_INS); } #endif - generate_public_key(&G.public_key, G.key.curve, &G.key.bip32_path); + generate_public_key(&G.public_key, G.key.derivation_type, &G.key.bip32_path); if (instruction == INS_GET_PUBLIC_KEY) { return provide_pubkey(G_io_apdu_buffer, &G.public_key); diff --git a/src/apdu_setup.c b/src/apdu_setup.c index 1fde2594..279761e1 100644 --- a/src/apdu_setup.c +++ b/src/apdu_setup.c @@ -11,7 +11,7 @@ #include -#define G global.u.setup +#define G global.apdu.u.setup struct setup_wire { uint32_t main_chain_id; @@ -33,7 +33,7 @@ static bool ok(void) { }); cx_ecfp_public_key_t const *const pubkey = generate_public_key_return_global( - G.key.curve, &G.key.bip32_path); + G.key.derivation_type, &G.key.bip32_path); delayed_send(provide_pubkey(G_io_apdu_buffer, pubkey)); return true; } @@ -72,8 +72,7 @@ __attribute__((noreturn)) size_t handle_apdu_setup(__attribute__((unused)) uint8 uint32_t const buff_size = READ_UNALIGNED_BIG_ENDIAN(uint8_t, &G_io_apdu_buffer[OFFSET_LC]); if (buff_size < sizeof(struct setup_wire)) THROW(EXC_WRONG_LENGTH_FOR_INS); - uint8_t const curve_code = READ_UNALIGNED_BIG_ENDIAN(uint8_t, &G_io_apdu_buffer[OFFSET_CURVE]); - G.key.curve = curve_code_to_curve(curve_code); + G.key.derivation_type = parse_derivation_type(READ_UNALIGNED_BIG_ENDIAN(uint8_t, &G_io_apdu_buffer[OFFSET_CURVE])); { struct setup_wire const *const buff_as_setup = (struct setup_wire const *)&G_io_apdu_buffer[OFFSET_CDATA]; diff --git a/src/apdu_sign.c b/src/apdu_sign.c index b4859199..ae96a374 100644 --- a/src/apdu_sign.c +++ b/src/apdu_sign.c @@ -5,6 +5,7 @@ #include "base58.h" #include "blake2.h" #include "globals.h" +#include "key_macros.h" #include "keys.h" #include "memory.h" #include "protocol.h" @@ -15,7 +16,9 @@ #include -#define G global.u.sign +#define G global.apdu.u.sign + +#define PARSE_ERROR() THROW(EXC_PARSE_ERROR) #define B2B_BLOCKBYTES 128 @@ -87,27 +90,31 @@ static bool sign_reject(void) { return true; // Return to idle } +static bool is_operation_allowed(enum operation_tag tag) { + switch (tag) { + case OPERATION_TAG_ATHENS_DELEGATION: return true; + case OPERATION_TAG_ATHENS_REVEAL: return true; + case OPERATION_TAG_BABYLON_DELEGATION: return true; + case OPERATION_TAG_BABYLON_REVEAL: return true; +# ifndef BAKING_APP + case OPERATION_TAG_PROPOSAL: return true; + case OPERATION_TAG_BALLOT: return true; + case OPERATION_TAG_ATHENS_ORIGINATION: return true; + case OPERATION_TAG_ATHENS_TRANSACTION: return true; + case OPERATION_TAG_BABYLON_ORIGINATION: return true; + case OPERATION_TAG_BABYLON_TRANSACTION: return true; +# endif + default: return false; + } +} + static bool parse_allowed_operations( struct parsed_operation_group *const out, uint8_t const *const in, size_t const in_size, bip32_path_with_curve_t const *const key ) { - // TODO: Simplify this to just switch on what we got. - allowed_operation_set allowed; - clear_operation_set(&allowed); - - allow_operation(&allowed, OPERATION_TAG_DELEGATION); - allow_operation(&allowed, OPERATION_TAG_REVEAL); -# ifndef BAKING_APP - allow_operation(&allowed, OPERATION_TAG_PROPOSAL); - allow_operation(&allowed, OPERATION_TAG_BALLOT); - allow_operation(&allowed, OPERATION_TAG_ORIGINATION); - allow_operation(&allowed, OPERATION_TAG_TRANSACTION); - // TODO: Add still other operations -# endif - - return parse_operations(out, in, in_size, key->curve, &key->bip32_path, allowed); + return parse_operations(out, in, in_size, key->derivation_type, &key->bip32_path, &is_operation_allowed); } #ifdef BAKING_APP // ---------------------------------------------------------- @@ -137,22 +144,19 @@ __attribute__((noreturn)) static void prompt_register_delegate( } size_t baking_sign_complete(bool const send_hash) { - switch (G.magic_number) { + switch (G.magic_byte) { case MAGIC_BYTE_BLOCK: case MAGIC_BYTE_BAKING_OP: - if (G.maybe_parsed_baking_data.is_valid) { - guard_baking_authorized(&G.maybe_parsed_baking_data.v, &G.key); - return perform_signature(true, send_hash); - } else { - THROW(EXC_PARSE_ERROR); - } + guard_baking_authorized(&G.parsed_baking_data, &G.key); + return perform_signature(true, send_hash); break; case MAGIC_BYTE_UNSAFE_OP: { + if (!G.maybe_ops.is_valid) PARSE_ERROR(); + // Must be self-delegation signed by the *authorized* baking key - if (G.maybe_ops.is_valid && - bip32_path_with_curve_eq(&G.key, &N_data.baking_key) && + if (bip32_path_with_curve_eq(&G.key, &N_data.baking_key) && // ops->signing is generated from G.bip32_path and G.curve COMPARE(&G.maybe_ops.v.operation.source, &G.maybe_ops.v.signing) == 0 && @@ -162,11 +166,12 @@ size_t baking_sign_complete(bool const send_hash) { prompt_register_delegate(ok_c, sign_reject); } THROW(EXC_SECURITY); + break; } case MAGIC_BYTE_UNSAFE_OP2: case MAGIC_BYTE_UNSAFE_OP3: default: - THROW(EXC_PARSE_ERROR); + PARSE_ERROR(); } } @@ -189,7 +194,7 @@ bool prompt_transaction( switch (ops->operation.tag) { default: - THROW(EXC_PARSE_ERROR); + PARSE_ERROR(); case OPERATION_TAG_PROPOSAL: { @@ -249,7 +254,8 @@ bool prompt_transaction( ui_prompt(ballot_prompts, ok, cxl); } - case OPERATION_TAG_ORIGINATION: + case OPERATION_TAG_ATHENS_ORIGINATION: + case OPERATION_TAG_BABYLON_ORIGINATION: { static const uint32_t TYPE_INDEX = 0; static const uint32_t AMOUNT_INDEX = 1; @@ -304,7 +310,7 @@ bool prompt_transaction( char const *const *prompts; bool const delegatable = ops->operation.flags & ORIGINATION_FLAG_DELEGATABLE; - bool const has_delegate = ops->operation.delegate.curve_code != TEZOS_NO_CURVE; + bool const has_delegate = ops->operation.delegate.signature_type != SIGNATURE_TYPE_UNSET; if (delegatable && has_delegate) { prompts = origination_prompts_delegatable; register_ui_callback(DELEGATE_INDEX, parsed_contract_to_string, @@ -323,7 +329,8 @@ bool prompt_transaction( ui_prompt(prompts, ok, cxl); } - case OPERATION_TAG_DELEGATION: + case OPERATION_TAG_ATHENS_DELEGATION: + case OPERATION_TAG_BABYLON_DELEGATION: { static const uint32_t TYPE_INDEX = 0; static const uint32_t FEE_INDEX = 1; @@ -358,13 +365,14 @@ bool prompt_transaction( REGISTER_STATIC_UI_VALUE(TYPE_INDEX, "Delegation"); - bool withdrawal = ops->operation.destination.originated == 0 && - ops->operation.destination.curve_code == TEZOS_NO_CURVE; + bool const withdrawal = ops->operation.destination.originated == 0 && + ops->operation.destination.signature_type == SIGNATURE_TYPE_UNSET; ui_prompt(withdrawal ? withdrawal_prompts : delegation_prompts, ok, cxl); } - case OPERATION_TAG_TRANSACTION: + case OPERATION_TAG_ATHENS_TRANSACTION: + case OPERATION_TAG_BABYLON_TRANSACTION: { static const uint32_t TYPE_INDEX = 0; static const uint32_t AMOUNT_INDEX = 1; @@ -452,11 +460,11 @@ static size_t wallet_sign_complete(uint8_t instruction) { } else { ui_callback_t const ok_c = instruction == INS_SIGN_WITH_HASH ? sign_with_hash_ok : sign_without_hash_ok; - switch (G.magic_number) { + switch (G.magic_byte) { case MAGIC_BYTE_BLOCK: case MAGIC_BYTE_BAKING_OP: default: - THROW(EXC_PARSE_ERROR); + PARSE_ERROR(); case MAGIC_BYTE_UNSAFE_OP: if (!G.maybe_ops.is_valid || !prompt_transaction(&G.maybe_ops.v, &G.key, ok_c, sign_reject)) { goto unsafe; @@ -483,6 +491,24 @@ static size_t wallet_sign_complete(uint8_t instruction) { #define P1_HASH_ONLY_NEXT 0x03 // You only need it once #define P1_LAST_MARKER 0x80 +static uint8_t get_magic_byte_or_throw(uint8_t const *const buff, size_t const buff_size) { + uint8_t const magic_byte = get_magic_byte(buff, buff_size); + switch (magic_byte) { +# ifdef BAKING_APP + case MAGIC_BYTE_BLOCK: + case MAGIC_BYTE_BAKING_OP: + case MAGIC_BYTE_UNSAFE_OP: // Only for self-delegations +# else + case MAGIC_BYTE_UNSAFE_OP: +# endif + return magic_byte; + + case MAGIC_BYTE_UNSAFE_OP2: + case MAGIC_BYTE_UNSAFE_OP3: + default: PARSE_ERROR(); + } +} + static size_t handle_apdu(bool const enable_hashing, bool const enable_parsing, uint8_t const instruction) { uint8_t *const buff = &G_io_apdu_buffer[OFFSET_CDATA]; uint8_t const p1 = READ_UNALIGNED_BIG_ENDIAN(uint8_t, &G_io_apdu_buffer[OFFSET_P1]); @@ -494,7 +520,7 @@ static size_t handle_apdu(bool const enable_hashing, bool const enable_parsing, case P1_FIRST: clear_data(); read_bip32_path(&G.key.bip32_path, buff, buff_size); - G.key.curve = curve_code_to_curve(READ_UNALIGNED_BIG_ENDIAN(uint8_t, &G_io_apdu_buffer[OFFSET_CURVE])); + G.key.derivation_type = parse_derivation_type(READ_UNALIGNED_BIG_ENDIAN(uint8_t, &G_io_apdu_buffer[OFFSET_CURVE])); return finalize_successful_send(0); #ifndef BAKING_APP case P1_HASH_ONLY_NEXT: @@ -503,30 +529,37 @@ static size_t handle_apdu(bool const enable_hashing, bool const enable_parsing, // FALL THROUGH #endif case P1_NEXT: - if (G.key.bip32_path.length == 0) { - THROW(EXC_WRONG_LENGTH_FOR_INS); - } + if (G.key.bip32_path.length == 0) THROW(EXC_WRONG_LENGTH_FOR_INS); + + // Guard against overflow + if (G.packet_index >= 0xFF) PARSE_ERROR(); + G.packet_index++; + break; default: THROW(EXC_WRONG_PARAM); } if (enable_parsing) { - if (G.magic_number == MAGIC_BYTE_INVALID) { - G.magic_number = get_magic_byte(buff, buff_size); - } - # ifdef BAKING_APP - // Keep trying to parse baking data unless we already did so successfully. - G.maybe_parsed_baking_data.is_valid = - G.maybe_parsed_baking_data.is_valid - || parse_baking_data(&G.maybe_parsed_baking_data.v, buff, buff_size); -# endif + if (G.packet_index != 1) PARSE_ERROR(); // Only parse a single packet when baking - // Keep trying to parse operations. - G.maybe_ops.is_valid = - G.maybe_ops.is_valid - || parse_allowed_operations(&G.maybe_ops.v, buff, buff_size, &G.key); + G.magic_byte = get_magic_byte_or_throw(buff, buff_size); + if (G.magic_byte == MAGIC_BYTE_UNSAFE_OP) { + // Parse the operation. It will be verified in `baking_sign_complete`. + G.maybe_ops.is_valid = parse_allowed_operations(&G.maybe_ops.v, buff, buff_size, &G.key); + } else { + // This should be a baking operation so parse it. + if (!parse_baking_data(&G.parsed_baking_data, buff, buff_size)) PARSE_ERROR(); + } +# else + if (G.packet_index == 1) { + G.magic_byte = get_magic_byte_or_throw(buff, buff_size); + G.maybe_ops.is_valid = parse_allowed_operations(&G.maybe_ops.v, buff, buff_size, &G.key); + } else { + G.maybe_ops.is_valid = false; // Force multiple packets to be treated as unparsed + } +# endif } if (enable_hashing) { @@ -537,9 +570,7 @@ static size_t handle_apdu(bool const enable_hashing, bool const enable_parsing, &G.hash_state); } - if (G.message_data_length + buff_size > sizeof(G.message_data)) { - THROW(EXC_PARSE_ERROR); - } + if (G.message_data_length + buff_size > sizeof(G.message_data)) PARSE_ERROR(); memmove(G.message_data + G.message_data_length, buff, buff_size); G.message_data_length += buff_size; @@ -583,9 +614,7 @@ size_t handle_apdu_sign_with_hash(uint8_t instruction) { static int perform_signature(bool const on_hash, bool const send_hash) { # ifdef BAKING_APP - if (G.maybe_parsed_baking_data.is_valid) { - write_high_water_mark(&G.maybe_parsed_baking_data.v); - } + write_high_water_mark(&G.parsed_baking_data); # else if (on_hash && G.hash_only) { memcpy(G_io_apdu_buffer, G.final_hash, sizeof(G.final_hash)); @@ -602,7 +631,9 @@ static int perform_signature(bool const on_hash, bool const send_hash) { uint8_t const *const data = on_hash ? G.final_hash : G.message_data; size_t const data_length = on_hash ? sizeof(G.final_hash) : G.message_data_length; - tx += sign(&G_io_apdu_buffer[tx], MAX_SIGNATURE_SIZE, &G.key, data, data_length); + tx += WITH_KEY_PAIR(G.key, key_pair, size_t, ({ + sign(&G_io_apdu_buffer[tx], MAX_SIGNATURE_SIZE, G.key.derivation_type, key_pair, data, data_length); + })); clear_data(); return finalize_successful_send(tx); diff --git a/src/baking_auth.c b/src/baking_auth.c index de1d9e59..1350e511 100644 --- a/src/baking_auth.c +++ b/src/baking_auth.c @@ -23,18 +23,18 @@ void write_high_water_mark(parsed_baking_data_t const *const in) { if (!is_valid_level(in->level)) THROW(EXC_WRONG_VALUES); UPDATE_NVRAM(ram, { // If the chain matches the main chain *or* the main chain is not set, then use 'main' HWM. - high_watermark_t *const dest = select_hwm_by_chain(in->chain_id, ram); + high_watermark_t volatile *const dest = select_hwm_by_chain(in->chain_id, ram); dest->highest_level = MAX(in->level, dest->highest_level); dest->had_endorsement = in->is_endorsement; }); } -void authorize_baking(cx_curve_t const curve, bip32_path_t const *const bip32_path) { +void authorize_baking(derivation_type_t const derivation_type, bip32_path_t const *const bip32_path) { check_null(bip32_path); if (bip32_path->length > NUM_ELEMENTS(N_data.baking_key.bip32_path.components) || bip32_path->length == 0) return; UPDATE_NVRAM(ram, { - ram->baking_key.curve = curve; + ram->baking_key.derivation_type = derivation_type; copy_bip32_path(&ram->baking_key.bip32_path, bip32_path); }); } @@ -42,7 +42,7 @@ void authorize_baking(cx_curve_t const curve, bip32_path_t const *const bip32_pa static bool is_level_authorized(parsed_baking_data_t const *const baking_info) { check_null(baking_info); if (!is_valid_level(baking_info->level)) return false; - high_watermark_t const *const hwm = select_hwm_by_chain(baking_info->chain_id, &N_data); + high_watermark_t volatile const *const hwm = select_hwm_by_chain(baking_info->chain_id, &N_data); return baking_info->level > hwm->highest_level // Levels are tied. In order for this to be OK, this must be an endorsement, and we must not @@ -51,18 +51,19 @@ static bool is_level_authorized(parsed_baking_data_t const *const baking_info) { && baking_info->is_endorsement && !hwm->had_endorsement); } -bool is_path_authorized(cx_curve_t curve, bip32_path_t const *const bip32_path) { +bool is_path_authorized(derivation_type_t const derivation_type, bip32_path_t const *const bip32_path) { check_null(bip32_path); return - curve == N_data.baking_key.curve && + derivation_type != 0 && + derivation_type == N_data.baking_key.derivation_type && bip32_path->length > 0 && - bip32_paths_eq(bip32_path, &N_data.baking_key.bip32_path); + bip32_paths_eq(bip32_path, (const bip32_path_t *)&N_data.baking_key.bip32_path); } void guard_baking_authorized(parsed_baking_data_t const *const baking_info, bip32_path_with_curve_t const *const key) { check_null(baking_info); check_null(key); - if (!is_path_authorized(key->curve, &key->bip32_path)) THROW(EXC_SECURITY); + if (!is_path_authorized(key->derivation_type, &key->bip32_path)) THROW(EXC_SECURITY); if (!is_level_authorized(baking_info)) THROW(EXC_WRONG_VALUES); } diff --git a/src/baking_auth.h b/src/baking_auth.h index 84bb63e3..e44fc04b 100644 --- a/src/baking_auth.h +++ b/src/baking_auth.h @@ -10,9 +10,9 @@ #include #include -void authorize_baking(cx_curve_t const curve, bip32_path_t const *const bip32_path); +void authorize_baking(derivation_type_t const derivation_type, bip32_path_t const *const bip32_path); void guard_baking_authorized(parsed_baking_data_t const *const baking_data, bip32_path_with_curve_t const *const key); -bool is_path_authorized(cx_curve_t curve, bip32_path_t const *const bip32_path); +bool is_path_authorized(derivation_type_t const derivation_type, bip32_path_t const *const bip32_path); bool is_valid_level(level_t level); void write_high_water_mark(parsed_baking_data_t const *const in); diff --git a/src/exception.h b/src/exception.h index cb2cd9e5..6617e3bb 100644 --- a/src/exception.h +++ b/src/exception.h @@ -22,7 +22,7 @@ #define EXC_MEMORY_ERROR 0x9200 // Crashes can be harder to debug than exceptions and latency isn't a big concern -static inline void check_null(const void *ptr) { +static inline void check_null(void volatile const *const ptr) { if (ptr == NULL) { THROW(EXC_MEMORY_ERROR); } diff --git a/src/globals.c b/src/globals.c index 3d216206..8115caef 100644 --- a/src/globals.c +++ b/src/globals.c @@ -30,6 +30,10 @@ globals_t global; unsigned char G_io_seproxyhal_spi_buffer[IO_SEPROXYHAL_BUFFER_SIZE_B]; +void clear_apdu_globals(void) { + memset(&global.apdu, 0, sizeof(global.apdu)); +} + void init_globals(void) { memset(&global, 0, sizeof(global)); @@ -53,27 +57,52 @@ void init_globals(void) { nvram_data N_data_real; # endif -high_watermark_t *select_hwm_by_chain(chain_id_t const chain_id, nvram_data *const ram) { +high_watermark_t volatile *select_hwm_by_chain(chain_id_t const chain_id, nvram_data volatile *const ram) { check_null(ram); return chain_id.v == ram->main_chain_id.v || ram->main_chain_id.v == 0 ? &ram->hwm.main : &ram->hwm.test; } -void update_baking_idle_screens(void) { - number_to_string(global.ui.baking_idle_screens.hwm, N_data.hwm.main.highest_level); +void calculate_baking_idle_screens_data(void) { +# ifdef TARGET_NANOX + memset(global.ui.baking_idle_screens.hwm, 0, sizeof(global.ui.baking_idle_screens.hwm)); + static char const HWM_PREFIX[] = "HWM: "; + strcpy(global.ui.baking_idle_screens.hwm, HWM_PREFIX); + number_to_string(&global.ui.baking_idle_screens.hwm[sizeof(HWM_PREFIX) - 1], (level_t const)N_data.hwm.main.highest_level); +# else + number_to_string(global.ui.baking_idle_screens.hwm, N_data.hwm.main.highest_level); +# endif if (N_data.baking_key.bip32_path.length == 0) { STRCPY(global.ui.baking_idle_screens.pkh, "No Key Authorized"); } else { cx_ecfp_public_key_t const *const pubkey = generate_public_key_return_global( - N_data.baking_key.curve, &N_data.baking_key.bip32_path); + (derivation_type_t const)N_data.baking_key.derivation_type, + (bip32_path_t const *const)&N_data.baking_key.bip32_path); pubkey_to_pkh_string( global.ui.baking_idle_screens.pkh, sizeof(global.ui.baking_idle_screens.pkh), - N_data.baking_key.curve, pubkey); + (derivation_type_t const)N_data.baking_key.derivation_type, pubkey); } - chain_id_to_string_with_aliases(global.ui.baking_idle_screens.chain, sizeof(global.ui.baking_idle_screens.chain), &N_data.main_chain_id); +# ifdef TARGET_NANOX + if (N_data.main_chain_id.v == 0) { + strcpy(global.ui.baking_idle_screens.chain, "Chain: any"); + } else { +# endif + + chain_id_to_string_with_aliases( + global.ui.baking_idle_screens.chain, sizeof(global.ui.baking_idle_screens.chain), + (chain_id_t const *const)&N_data.main_chain_id); + +# ifdef TARGET_NANOX + } +# endif +} + +void update_baking_idle_screens(void) { + calculate_baking_idle_screens_data(); + ui_refresh(); } #endif // #ifdef BAKING_APP diff --git a/src/globals.h b/src/globals.h index e0c2f3c1..bcf8e7bc 100644 --- a/src/globals.h +++ b/src/globals.h @@ -5,6 +5,11 @@ #include "bolos_target.h" +// Zeros out all globals that can keep track of APDU instruction state. +// Notably this does *not* include UI state. +void clear_apdu_globals(void); + +// Zeros out all application-specific globals and SDK-specific UI/exchange buffers. void init_globals(void); #define MAX_APDU_SIZE 230 // Maximum number of bytes in a single APDU @@ -17,8 +22,8 @@ void init_globals(void); #define MAX_SIGNATURE_SIZE 100 struct priv_generate_key_pair { - uint8_t privateKeyData[PRIVATE_KEY_DATA_SIZE]; - struct key_pair res; + uint8_t private_key_data[PRIVATE_KEY_DATA_SIZE]; + key_pair_t res; }; #ifdef BAKING_APP @@ -38,11 +43,10 @@ typedef struct { typedef struct { bip32_path_with_curve_t key; + uint8_t packet_index; // 0-index is the initial setup packet, 1 is first packet to hash, etc. + # ifdef BAKING_APP - struct { - bool is_valid; - parsed_baking_data_t v; - } maybe_parsed_baking_data; + parsed_baking_data_t parsed_baking_data; # endif struct { @@ -57,7 +61,7 @@ typedef struct { blake2b_hash_state_t hash_state; uint8_t final_hash[SIGN_HASH_SIZE]; - uint8_t magic_number; + uint8_t magic_byte; bool hash_only; } apdu_sign_state_t; @@ -65,40 +69,16 @@ typedef struct { void *stack_root; apdu_handler handlers[INS_MAX + 1]; - union { - struct { - bip32_path_with_curve_t key; - cx_ecfp_public_key_t public_key; - } pubkey; - - apdu_sign_state_t sign; - -# ifdef BAKING_APP - struct { - level_t reset_level; - } baking; - - struct { - bip32_path_with_curve_t key; - chain_id_t main_chain_id; - struct { - level_t main; - level_t test; - } hwm; - } setup; - - apdu_hmac_state_t hmac; -# endif - } u; - struct { ui_callback_t ok_callback; ui_callback_t cxl_callback; +# ifndef TARGET_NANOX uint32_t ux_step; uint32_t ux_step_count; uint32_t timeout_cycle_count; +# endif # ifdef BAKING_APP struct { @@ -127,21 +107,49 @@ typedef struct { } prompt; } ui; -#ifdef BAKING_APP struct { - nvram_data new_data; // Staging area for setting N_data + union { + struct { + bip32_path_with_curve_t key; + cx_ecfp_public_key_t public_key; + } pubkey; + + apdu_sign_state_t sign; + +# ifdef BAKING_APP + struct { + level_t reset_level; + } baking; + + struct { + bip32_path_with_curve_t key; + chain_id_t main_chain_id; + struct { + level_t main; + level_t test; + } hwm; + } setup; + + apdu_hmac_state_t hmac; +# endif + } u; + +# ifdef BAKING_APP + struct { + nvram_data new_data; // Staging area for setting N_data - char address_display_data[VALUE_WIDTH + 1]; - } baking_auth; -#endif + char address_display_data[VALUE_WIDTH + 1]; + } baking_auth; +# endif - struct { - struct priv_generate_key_pair generate_key_pair; + struct { + struct priv_generate_key_pair generate_key_pair; - struct { - cx_ecfp_public_key_t compressed; - } public_key_hash; - } priv; + struct { + cx_ecfp_public_key_t compressed; + } public_key_hash; + } priv; + } apdu; } globals_t; extern globals_t global; @@ -173,17 +181,18 @@ static inline void throw_stack_size() { # define N_data (*(nvram_data*)PIC(&N_data_real)) # endif +void calculate_baking_idle_screens_data(void); void update_baking_idle_screens(void); -high_watermark_t *select_hwm_by_chain(chain_id_t const chain_id, nvram_data *const ram); +high_watermark_t volatile *select_hwm_by_chain(chain_id_t const chain_id, nvram_data volatile *const ram); // Properly updates NVRAM data to prevent any clobbering of data. // 'out_param' defines the name of a pointer to the nvram_data struct // that 'body' can change to apply updates. #define UPDATE_NVRAM(out_name, body) ({ \ - nvram_data *const out_name = &global.baking_auth.new_data; \ - memcpy(&global.baking_auth.new_data, &N_data, sizeof(global.baking_auth.new_data)); \ + nvram_data *const out_name = &global.apdu.baking_auth.new_data; \ + memcpy(&global.apdu.baking_auth.new_data, (nvram_data const *const)&N_data, sizeof(global.apdu.baking_auth.new_data)); \ body; \ - nvm_write((void*)&N_data, &global.baking_auth.new_data, sizeof(N_data)); \ + nvm_write((void*)&N_data, &global.apdu.baking_auth.new_data, sizeof(N_data)); \ update_baking_idle_screens(); \ }) #endif diff --git a/src/key_macros.h b/src/key_macros.h new file mode 100644 index 00000000..93040041 --- /dev/null +++ b/src/key_macros.h @@ -0,0 +1,34 @@ +#pragma once + +#include "globals.h" +#include "keys.h" + +// Yes you need this oddness if you want to use __LINE__ +#define CONCAT_(a, b) a##b +#define CONCAT(a, b) CONCAT_(a, b) +#define MACROVAR(a, b) CONCAT(____ ## _ ## a ## _ ## b, __LINE__) + +#define WITH_KEY_PAIR(bip32_path_with_curve, vname, type, body) ({ \ + bip32_path_with_curve_t volatile const *const MACROVAR(vname, key) = &(bip32_path_with_curve); \ + key_pair_t *const MACROVAR(vname, generated_pair) = \ + generate_key_pair_return_global( \ + (derivation_type_t const /* cast away volatile! */)MACROVAR(vname, key)->derivation_type, \ + (bip32_path_t const *const /* cast away volatile! */)&MACROVAR(vname, key)->bip32_path); \ + volatile type MACROVAR(vname, retval); \ + BEGIN_TRY { \ + TRY { \ + key_pair_t *const vname = MACROVAR(vname, generated_pair); \ + MACROVAR(vname, retval) = body; \ + } \ + CATCH_OTHER(e) { \ + THROW(e); \ + } \ + FINALLY { \ + if (MACROVAR(vname, generated_pair) != NULL) { \ + memset(MACROVAR(vname, generated_pair), 0, sizeof(*MACROVAR(vname, generated_pair))); \ + } \ + } \ + } \ + END_TRY; \ + MACROVAR(vname, retval); \ +}) diff --git a/src/keys.c b/src/keys.c index 9b3eceeb..db4fd997 100644 --- a/src/keys.c +++ b/src/keys.c @@ -45,67 +45,69 @@ size_t read_bip32_path(bip32_path_t *const out, uint8_t const *const in, size_t return ix; } -struct key_pair *generate_key_pair_return_global( - cx_curve_t const curve, +key_pair_t *generate_key_pair_return_global( + derivation_type_t const derivation_type, bip32_path_t const *const bip32_path ) { check_null(bip32_path); - struct priv_generate_key_pair *const priv = &global.priv.generate_key_pair; + struct priv_generate_key_pair *const priv = &global.apdu.priv.generate_key_pair; -#if CX_APILEVEL > 8 - if (curve == CX_CURVE_Ed25519) { + cx_curve_t const cx_curve = signature_type_to_cx_curve(derivation_type_to_signature_type(derivation_type)); + + if (derivation_type == DERIVATION_TYPE_ED25519) { + // Old, non BIP32_Ed25519 way... os_perso_derive_node_bip32_seed_key( - HDW_ED25519_SLIP10, curve, bip32_path->components, bip32_path->length, - priv->privateKeyData, NULL, NULL, 0); + HDW_ED25519_SLIP10, CX_CURVE_Ed25519, bip32_path->components, bip32_path->length, + priv->private_key_data, NULL, NULL, 0); } else { -#endif - os_perso_derive_node_bip32(curve, bip32_path->components, bip32_path->length, priv->privateKeyData, NULL); -#if CX_APILEVEL > 8 + os_perso_derive_node_bip32( + cx_curve, bip32_path->components, bip32_path->length, + priv->private_key_data, NULL); } -#endif - cx_ecfp_init_private_key(curve, priv->privateKeyData, sizeof(priv->privateKeyData), &priv->res.private_key); - cx_ecfp_generate_pair(curve, &priv->res.public_key, &priv->res.private_key, 1); - if (curve == CX_CURVE_Ed25519) { + cx_ecfp_init_private_key(cx_curve, priv->private_key_data, sizeof(priv->private_key_data), &priv->res.private_key); + cx_ecfp_generate_pair(cx_curve, &priv->res.public_key, &priv->res.private_key, 1); + + if (cx_curve == CX_CURVE_Ed25519) { cx_edward_compress_point( - curve, + CX_CURVE_Ed25519, priv->res.public_key.W, priv->res.public_key.W_len); priv->res.public_key.W_len = 33; } - memset(priv->privateKeyData, 0, sizeof(priv->privateKeyData)); + memset(priv->private_key_data, 0, sizeof(priv->private_key_data)); return &priv->res; } cx_ecfp_public_key_t const *generate_public_key_return_global( - cx_curve_t const curve, + derivation_type_t const curve, bip32_path_t const *const bip32_path ) { check_null(bip32_path); - struct key_pair *const pair = generate_key_pair_return_global(curve, bip32_path); + key_pair_t *const pair = generate_key_pair_return_global(curve, bip32_path); memset(&pair->private_key, 0, sizeof(pair->private_key)); return &pair->public_key; } cx_ecfp_public_key_t const *public_key_hash_return_global( uint8_t *const out, size_t const out_size, - cx_curve_t const curve, + derivation_type_t const curve, cx_ecfp_public_key_t const *const restrict public_key) { check_null(out); check_null(public_key); if (out_size < HASH_SIZE) THROW(EXC_WRONG_LENGTH); - cx_ecfp_public_key_t *const compressed = &global.priv.public_key_hash.compressed; - switch (curve) { - case CX_CURVE_Ed25519: + cx_ecfp_public_key_t *const compressed = &global.apdu.priv.public_key_hash.compressed; + switch (derivation_type_to_signature_type(curve)) { + case SIGNATURE_TYPE_ED25519: { compressed->W_len = public_key->W_len - 1; memcpy(compressed->W, public_key->W + 1, compressed->W_len); break; } - case CX_CURVE_SECP256K1: - case CX_CURVE_SECP256R1: + case SIGNATURE_TYPE_SECP256K1: + case SIGNATURE_TYPE_SECP256R1: { memcpy(compressed->W, public_key->W, public_key->W_len); compressed->W[0] = 0x02 + (public_key->W[64] & 0x01); @@ -125,18 +127,17 @@ cx_ecfp_public_key_t const *public_key_hash_return_global( size_t sign( uint8_t *const out, size_t const out_size, - bip32_path_with_curve_t const *const key, + derivation_type_t const derivation_type, + key_pair_t const *const pair, uint8_t const *const in, size_t const in_size ) { check_null(out); - check_null(key); + check_null(pair); check_null(in); - struct key_pair *const pair = generate_key_pair_return_global(key->curve, &key->bip32_path); - size_t tx = 0; - switch (key->curve) { - case CX_CURVE_Ed25519: { + switch (derivation_type_to_signature_type(derivation_type)) { + case SIGNATURE_TYPE_ED25519: { static size_t const SIG_SIZE = 64; if (out_size < SIG_SIZE) THROW(EXC_WRONG_LENGTH); tx += cx_eddsa_sign( @@ -152,8 +153,8 @@ size_t sign( NULL); } break; - case CX_CURVE_SECP256K1: - case CX_CURVE_SECP256R1: + case SIGNATURE_TYPE_SECP256K1: + case SIGNATURE_TYPE_SECP256R1: { static size_t const SIG_SIZE = 100; if (out_size < SIG_SIZE) THROW(EXC_WRONG_LENGTH); @@ -176,7 +177,5 @@ size_t sign( THROW(EXC_WRONG_PARAM); // This should not be able to happen. } - memset(&pair->private_key, 0, sizeof(pair->private_key)); - return tx; } diff --git a/src/keys.h b/src/keys.h index 1a7e8abf..ee21471a 100644 --- a/src/keys.h +++ b/src/keys.h @@ -5,9 +5,14 @@ #include #include "exception.h" +#include "memory.h" #include "os_cx.h" #include "types.h" +#if CX_APILEVEL <= 8 +# error "CX_APILEVEL 8 and below is not supported" +#endif + struct bip32_path_wire { uint8_t length; uint32_t components[0]; @@ -17,52 +22,54 @@ struct bip32_path_wire { size_t read_bip32_path(bip32_path_t *const out, uint8_t const *const in, size_t const in_size); // Non-reentrant -struct key_pair *generate_key_pair_return_global( - cx_curve_t const curve, +key_pair_t *generate_key_pair_return_global( + derivation_type_t const derivation_type, bip32_path_t const *const bip32_path); // Non-reentrant static inline void generate_key_pair( - struct key_pair *const out, - cx_curve_t const curve, + key_pair_t *const out, + derivation_type_t const derivation_type, bip32_path_t const *const bip32_path ) { check_null(out); - struct key_pair *const result = generate_key_pair_return_global(curve, bip32_path); + key_pair_t *const result = generate_key_pair_return_global(derivation_type, bip32_path); memcpy(out, result, sizeof(*out)); + memset(result, 0, sizeof(*result)); } // Non-reentrant cx_ecfp_public_key_t const *generate_public_key_return_global( - cx_curve_t const curve, + derivation_type_t const derivation_type, bip32_path_t const *const bip32_path); // Non-reentrant static inline void generate_public_key( cx_ecfp_public_key_t *const out, - cx_curve_t const curve, + derivation_type_t const derivation_type, bip32_path_t const *const bip32_path ) { check_null(out); - cx_ecfp_public_key_t const *const result = generate_public_key_return_global(curve, bip32_path); + cx_ecfp_public_key_t const *const result = generate_public_key_return_global( + derivation_type, bip32_path); memcpy(out, result, sizeof(*out)); } // Non-reentrant cx_ecfp_public_key_t const *public_key_hash_return_global( uint8_t *const out, size_t const out_size, - cx_curve_t const curve, + derivation_type_t const derivation_type, cx_ecfp_public_key_t const *const restrict public_key); // Non-reentrant static inline void public_key_hash( uint8_t *const hash_out, size_t const hash_out_size, cx_ecfp_public_key_t *const pubkey_out, // pass NULL if this value is not desired - cx_curve_t const curve, + derivation_type_t const derivation_type, cx_ecfp_public_key_t const *const restrict public_key ) { cx_ecfp_public_key_t const *const pubkey = public_key_hash_return_global( - hash_out, hash_out_size, curve, public_key); + hash_out, hash_out_size, derivation_type, public_key); if (pubkey_out != NULL) { memcpy(pubkey_out, pubkey, sizeof(*pubkey_out)); } @@ -70,33 +77,47 @@ static inline void public_key_hash( size_t sign( uint8_t *const out, size_t const out_size, - bip32_path_with_curve_t const *const key, + derivation_type_t const derivation_type, + key_pair_t const *const key, uint8_t const *const in, size_t const in_size); -enum curve_code { - TEZOS_ED, - TEZOS_SECP256K1, - TEZOS_SECP256R1, - TEZOS_NO_CURVE = 255, -}; - -static inline uint8_t curve_to_curve_code(cx_curve_t curve) { - switch(curve) { - case CX_CURVE_Ed25519: - return TEZOS_ED; - case CX_CURVE_SECP256K1: - return TEZOS_SECP256K1; - case CX_CURVE_SECP256R1: - return TEZOS_SECP256R1; - default: - return TEZOS_NO_CURVE; +// Read a curve code from wire-format and parse into `deviration_type`. +static inline derivation_type_t parse_derivation_type(uint8_t const curve_code) { + switch (curve_code) { + case 0: return DERIVATION_TYPE_ED25519; + case 1: return DERIVATION_TYPE_SECP256K1; + case 2: return DERIVATION_TYPE_SECP256R1; + case 3: return DERIVATION_TYPE_BIP32_ED25519; + default: THROW(EXC_WRONG_PARAM); + } +} + +// Convert `derivation_type` to wire-format. +static inline uint8_t unparse_derivation_type(derivation_type_t const derivation_type) { + switch (derivation_type) { + case DERIVATION_TYPE_ED25519: return 0; + case DERIVATION_TYPE_SECP256K1: return 1; + case DERIVATION_TYPE_SECP256R1: return 2; + case DERIVATION_TYPE_BIP32_ED25519: return 3; + default: THROW(EXC_REFERENCED_DATA_NOT_FOUND); + } +} + +static inline signature_type_t derivation_type_to_signature_type(derivation_type_t const derivation_type) { + switch (derivation_type) { + case DERIVATION_TYPE_SECP256K1: return SIGNATURE_TYPE_SECP256K1; + case DERIVATION_TYPE_SECP256R1: return SIGNATURE_TYPE_SECP256R1; + case DERIVATION_TYPE_ED25519: return SIGNATURE_TYPE_ED25519; + case DERIVATION_TYPE_BIP32_ED25519: return SIGNATURE_TYPE_ED25519; + default: return SIGNATURE_TYPE_UNSET; } } -static inline cx_curve_t curve_code_to_curve(uint8_t curve_code) { - static const cx_curve_t curves[] = { CX_CURVE_Ed25519, CX_CURVE_SECP256K1, CX_CURVE_SECP256R1 }; - if (curve_code > sizeof(curves) / sizeof(*curves)) { - THROW(EXC_WRONG_PARAM); +static inline cx_curve_t signature_type_to_cx_curve(signature_type_t const signature_type) { + switch (signature_type) { + case SIGNATURE_TYPE_SECP256K1: return CX_CURVE_SECP256K1; + case SIGNATURE_TYPE_SECP256R1: return CX_CURVE_SECP256R1; + case SIGNATURE_TYPE_ED25519: return CX_CURVE_Ed25519; + default: return CX_CURVE_NONE; } - return curves[curve_code]; } diff --git a/src/operations.c b/src/operations.c index 28ff2631..250d1e39 100644 --- a/src/operations.c +++ b/src/operations.c @@ -9,13 +9,18 @@ #include #include +// Wire format that gets parsed into `signature_type`. +typedef struct { + uint8_t v; +} __attribute__((packed)) raw_tezos_header_signature_type_t; + struct operation_group_header { uint8_t magic_byte; uint8_t hash[32]; } __attribute__((packed)); struct implicit_contract { - uint8_t curve_code; + raw_tezos_header_signature_type_t signature_type; uint8_t pkh[HASH_SIZE]; } __attribute__((packed)); @@ -31,7 +36,7 @@ struct contract { } __attribute__((packed)); struct delegation_contents { - uint8_t curve_code; + raw_tezos_header_signature_type_t signature_type; uint8_t hash[HASH_SIZE]; } __attribute__((packed)); @@ -105,41 +110,55 @@ static inline uint64_t parse_z(const void *data, size_t *ix, size_t length, uint val; \ }) +static inline signature_type_t parse_raw_tezos_header_signature_type( + raw_tezos_header_signature_type_t const *const raw_signature_type +) { + check_null(raw_signature_type); + switch (READ_UNALIGNED_BIG_ENDIAN(uint8_t, &raw_signature_type->v)) { + case 0: return SIGNATURE_TYPE_ED25519; + case 1: return SIGNATURE_TYPE_SECP256K1; + case 2: return SIGNATURE_TYPE_SECP256R1; + default: PARSE_ERROR(); + } +} + static inline void compute_pkh( cx_ecfp_public_key_t *const compressed_pubkey_out, - struct parsed_contract *const contract_out, - cx_curve_t const curve, + parsed_contract_t *const contract_out, + derivation_type_t const derivation_type, bip32_path_t const *const bip32_path ) { check_null(bip32_path); check_null(compressed_pubkey_out); check_null(contract_out); - cx_ecfp_public_key_t const *const pubkey = generate_public_key_return_global(curve, bip32_path); + cx_ecfp_public_key_t const *const pubkey = generate_public_key_return_global(derivation_type, bip32_path); public_key_hash( contract_out->hash, sizeof(contract_out->hash), compressed_pubkey_out, - curve, pubkey); - contract_out->curve_code = curve_to_curve_code(curve); - if (contract_out->curve_code == TEZOS_NO_CURVE) THROW(EXC_MEMORY_ERROR); + derivation_type, pubkey); + contract_out->signature_type = derivation_type_to_signature_type(derivation_type); + if (contract_out->signature_type == SIGNATURE_TYPE_UNSET) THROW(EXC_MEMORY_ERROR); contract_out->originated = 0; } static inline void parse_implicit( - struct parsed_contract *out, uint8_t curve_code, - const uint8_t hash[HASH_SIZE] + parsed_contract_t *const out, + raw_tezos_header_signature_type_t const *const raw_signature_type, + uint8_t const hash[HASH_SIZE] ) { + check_null(raw_signature_type); out->originated = 0; - out->curve_code = curve_code; + out->signature_type = parse_raw_tezos_header_signature_type(raw_signature_type); memcpy(out->hash, hash, sizeof(out->hash)); } -static inline void parse_contract(struct parsed_contract *out, const struct contract *in) { +static inline void parse_contract(parsed_contract_t *const out, struct contract const *const in) { out->originated = in->originated; if (out->originated == 0) { // implicit - out->curve_code = in->u.implicit.curve_code; + out->signature_type = parse_raw_tezos_header_signature_type(&in->u.implicit.signature_type); memcpy(out->hash, in->u.implicit.pkh, sizeof(out->hash)); } else { // originated - out->curve_code = TEZOS_NO_CURVE; + out->signature_type = SIGNATURE_TYPE_UNSET; memcpy(out->hash, in->u.originated.pkh, sizeof(out->hash)); } } @@ -148,9 +167,9 @@ static void parse_operations_throws_parse_error( struct parsed_operation_group *const out, void const *const data, size_t length, - cx_curve_t curve, + derivation_type_t derivation_type, bip32_path_t const *const bip32_path, - allowed_operation_set ops + is_operation_allowed_t is_operation_allowed ) { check_null(out); check_null(data); @@ -159,7 +178,7 @@ static void parse_operations_throws_parse_error( out->operation.tag = OPERATION_TAG_NONE; - compute_pkh(&out->public_key, &out->signing, curve, bip32_path); + compute_pkh(&out->public_key, &out->signing, derivation_type, bip32_path); size_t ix = 0; @@ -174,32 +193,53 @@ static void parse_operations_throws_parse_error( while (ix < length) { const enum operation_tag tag = NEXT_BYTE(data, &ix, length); // 1 byte is always aligned - if (!is_operation_allowed(ops, tag)) PARSE_ERROR(); + if (!is_operation_allowed(tag)) PARSE_ERROR(); + + // Parse 'source' + switch (tag) { + // Tags that don't have "originated" byte only support tz accounts, not KT or tz. + case OPERATION_TAG_PROPOSAL: + case OPERATION_TAG_BALLOT: + case OPERATION_TAG_BABYLON_DELEGATION: + case OPERATION_TAG_BABYLON_ORIGINATION: + case OPERATION_TAG_BABYLON_REVEAL: + case OPERATION_TAG_BABYLON_TRANSACTION: { + struct implicit_contract const *const implicit_source = NEXT_TYPE(struct implicit_contract); + out->operation.source.originated = 0; + out->operation.source.signature_type = parse_raw_tezos_header_signature_type(&implicit_source->signature_type); + memcpy(out->operation.source.hash, implicit_source->pkh, sizeof(out->operation.source.hash)); + break; + } + + case OPERATION_TAG_ATHENS_DELEGATION: + case OPERATION_TAG_ATHENS_ORIGINATION: + case OPERATION_TAG_ATHENS_REVEAL: + case OPERATION_TAG_ATHENS_TRANSACTION: { + struct contract const *const source = NEXT_TYPE(struct contract); + parse_contract(&out->operation.source, source); + break; + } + + default: PARSE_ERROR(); + } - if (tag == OPERATION_TAG_PROPOSAL || tag == OPERATION_TAG_BALLOT) { - // These tags don't have the "originated" byte so we have to parse PKH differently. - const struct implicit_contract *implicit_source = NEXT_TYPE(struct implicit_contract); - out->operation.source.originated = 0; - out->operation.source.curve_code = READ_UNALIGNED_BIG_ENDIAN(uint8_t, &implicit_source->curve_code); - memcpy(out->operation.source.hash, implicit_source->pkh, sizeof(out->operation.source.hash)); - } else { - const struct contract *source = NEXT_TYPE(struct contract); - parse_contract(&out->operation.source, source); + // out->operation.source IS NORMALIZED AT THIS POINT + // Parse common fields for non-governance related operations. + if (tag != OPERATION_TAG_PROPOSAL && tag != OPERATION_TAG_BALLOT) { out->total_fee += PARSE_Z(data, &ix, length); // fee PARSE_Z(data, &ix, length); // counter PARSE_Z(data, &ix, length); // gas limit out->total_storage_limit += PARSE_Z(data, &ix, length); // storage limit } - // out->operation.source IS NORMALIZED AT THIS POINT - - if (tag == OPERATION_TAG_REVEAL) { + if (tag == OPERATION_TAG_ATHENS_REVEAL || tag == OPERATION_TAG_BABYLON_REVEAL) { // Public key up next! Ensure it matches signing key. // Ignore source :-) and do not parse it from hdr. // We don't much care about reveals, they have very little in the way of bad security // implications and any fees have already been accounted for - if (NEXT_BYTE(data, &ix, length) != out->signing.curve_code) PARSE_ERROR(); + raw_tezos_header_signature_type_t const *const sig_type = NEXT_TYPE(raw_tezos_header_signature_type_t); + if (parse_raw_tezos_header_signature_type(sig_type) != out->signing.signature_type) PARSE_ERROR(); size_t klen = out->public_key.W_len; advance_ix(&ix, length, klen); @@ -226,7 +266,7 @@ static void parse_operations_throws_parse_error( // OK, it passes muster. // This should by default be blanked out - out->operation.delegate.curve_code = TEZOS_NO_CURVE; + out->operation.delegate.signature_type = SIGNATURE_TYPE_UNSET; out->operation.delegate.originated = 0; switch (tag) { @@ -266,28 +306,30 @@ static void parse_operations_throws_parse_error( } } break; - case OPERATION_TAG_DELEGATION: + case OPERATION_TAG_ATHENS_DELEGATION: + case OPERATION_TAG_BABYLON_DELEGATION: { uint8_t delegate_present = NEXT_BYTE(data, &ix, length); if (delegate_present) { const struct delegation_contents *dlg = NEXT_TYPE(struct delegation_contents); - parse_implicit(&out->operation.destination, dlg->curve_code, dlg->hash); + parse_implicit(&out->operation.destination, &dlg->signature_type, dlg->hash); } else { // Encode "not present" out->operation.destination.originated = 0; - out->operation.destination.curve_code = TEZOS_NO_CURVE; + out->operation.destination.signature_type = SIGNATURE_TYPE_UNSET; } } break; - case OPERATION_TAG_ORIGINATION: + case OPERATION_TAG_ATHENS_ORIGINATION: + case OPERATION_TAG_BABYLON_ORIGINATION: { struct origination_header { - uint8_t curve_code; + raw_tezos_header_signature_type_t signature_type; uint8_t hash[HASH_SIZE]; } __attribute__((packed)); - const struct origination_header *hdr = NEXT_TYPE(struct origination_header); + struct origination_header const *const hdr = NEXT_TYPE(struct origination_header); - parse_implicit(&out->operation.destination, hdr->curve_code, hdr->hash); + parse_implicit(&out->operation.destination, &hdr->signature_type, hdr->hash); out->operation.amount = PARSE_Z(data, &ix, length); if (NEXT_BYTE(data, &ix, length) != 0) { out->operation.flags |= ORIGINATION_FLAG_SPENDABLE; @@ -298,12 +340,13 @@ static void parse_operations_throws_parse_error( if (NEXT_BYTE(data, &ix, length) != 0) { // Has delegate const struct delegation_contents *dlg = NEXT_TYPE(struct delegation_contents); - parse_implicit(&out->operation.delegate, dlg->curve_code, dlg->hash); + parse_implicit(&out->operation.delegate, &dlg->signature_type, dlg->hash); } if (NEXT_BYTE(data, &ix, length) != 0) PARSE_ERROR(); // Has script } break; - case OPERATION_TAG_TRANSACTION: + case OPERATION_TAG_ATHENS_TRANSACTION: + case OPERATION_TAG_BABYLON_TRANSACTION: { out->operation.amount = PARSE_Z(data, &ix, length); @@ -328,13 +371,13 @@ bool parse_operations( struct parsed_operation_group *const out, uint8_t const *const data, size_t length, - cx_curve_t curve, + derivation_type_t derivation_type, bip32_path_t const *const bip32_path, - allowed_operation_set ops + is_operation_allowed_t is_operation_allowed ) { BEGIN_TRY { TRY { - parse_operations_throws_parse_error(out, data, length, curve, bip32_path, ops); + parse_operations_throws_parse_error(out, data, length, derivation_type, bip32_path, is_operation_allowed); } CATCH(EXC_PARSE_ERROR) { return false; diff --git a/src/operations.h b/src/operations.h index 369c4e73..40552a80 100644 --- a/src/operations.h +++ b/src/operations.h @@ -10,19 +10,7 @@ #include "cx.h" #include "types.h" -typedef uint32_t allowed_operation_set; - -static inline void allow_operation(allowed_operation_set *ops, enum operation_tag tag) { - *ops |= (1 << (uint32_t)tag); -} - -static inline bool is_operation_allowed(allowed_operation_set ops, enum operation_tag tag) { - return (ops & (1 << (uint32_t)tag)) != 0; -} - -static inline void clear_operation_set(allowed_operation_set *ops) { - *ops = 0; -} +typedef bool (*is_operation_allowed_t)(enum operation_tag); // Allows arbitrarily many "REVEAL" operations but only one operation of any other type, // which is the one it puts into the group. @@ -30,7 +18,7 @@ bool parse_operations( struct parsed_operation_group *const out, uint8_t const *const data, size_t length, - cx_curve_t curve, + derivation_type_t curve, bip32_path_t const *const bip32_path, - allowed_operation_set ops + is_operation_allowed_t is_operation_allowed ); diff --git a/src/to_string.c b/src/to_string.c index 01f98b23..c9f6f1d0 100644 --- a/src/to_string.c +++ b/src/to_string.c @@ -10,36 +10,45 @@ #define TEZOS_HASH_CHECKSUM_SIZE 4 -static void pkh_to_string(char *buff, const size_t buff_size, const cx_curve_t curve, const uint8_t hash[HASH_SIZE]); +void pkh_to_string( + char *const buff, size_t const buff_size, + signature_type_t const signature_type, + uint8_t const hash[HASH_SIZE] +); // These functions output terminating null bytes, and return the ending offset. static size_t microtez_to_string(char *dest, uint64_t number); -void parsed_contract_to_string(char *buff, uint32_t buff_size, const struct parsed_contract *contract) { - if (contract->originated == 0 && contract->curve_code == TEZOS_NO_CURVE) { +void parsed_contract_to_string( + char *const buff, + size_t const buff_size, + parsed_contract_t const *const contract +) { + if (contract->originated == 0 && contract->signature_type == SIGNATURE_TYPE_UNSET) { if (buff_size < sizeof(NO_CONTRACT_STRING)) THROW(EXC_WRONG_LENGTH); strcpy(buff, NO_CONTRACT_STRING); return; } - cx_curve_t const curve = contract->originated != 0 - ? CX_CURVE_NONE - : curve_code_to_curve(contract->curve_code); - pkh_to_string(buff, buff_size, curve, contract->hash); + signature_type_t const signature_type = + contract->originated != 0 + ? SIGNATURE_TYPE_UNSET + : contract->signature_type; + pkh_to_string(buff, buff_size, signature_type, contract->hash); } void pubkey_to_pkh_string( char *const out, size_t const out_size, - cx_curve_t const curve, + derivation_type_t const derivation_type, cx_ecfp_public_key_t const *const public_key ) { check_null(out); check_null(public_key); uint8_t hash[HASH_SIZE]; - public_key_hash(hash, sizeof(hash), NULL, curve, public_key); - pkh_to_string(out, out_size, curve, hash); + public_key_hash(hash, sizeof(hash), NULL, derivation_type, public_key); + pkh_to_string(out, out_size, derivation_type_to_signature_type(derivation_type), hash); } void bip32_path_with_curve_to_pkh_string( @@ -50,8 +59,8 @@ void bip32_path_with_curve_to_pkh_string( check_null(key); cx_ecfp_public_key_t const *const pubkey = generate_public_key_return_global( - key->curve, &key->bip32_path); - pubkey_to_pkh_string(out, out_size, key->curve, pubkey); + key->derivation_type, &key->bip32_path); + pubkey_to_pkh_string(out, out_size, key->derivation_type, pubkey); } @@ -79,8 +88,11 @@ void buffer_to_base58(char *const out, size_t const out_size, buffer_t const *co bin_to_base58(out, out_size, src->bytes, src->length); } -void pkh_to_string(char *buff, const size_t buff_size, const cx_curve_t curve, - const uint8_t hash[HASH_SIZE]) { +void pkh_to_string( + char *const buff, size_t const buff_size, + signature_type_t const signature_type, + uint8_t const hash[HASH_SIZE] +) { check_null(buff); check_null(hash); if (buff_size < PKH_STRING_SIZE) THROW(EXC_WRONG_LENGTH); @@ -93,23 +105,23 @@ void pkh_to_string(char *buff, const size_t buff_size, const cx_curve_t curve, } data; // prefix - switch (curve) { - case CX_CURVE_NONE: + switch (signature_type) { + case SIGNATURE_TYPE_UNSET: data.prefix[0] = 2; data.prefix[1] = 90; data.prefix[2] = 121; break; - case CX_CURVE_Ed25519: // Ed25519 + case SIGNATURE_TYPE_ED25519: data.prefix[0] = 6; data.prefix[1] = 161; data.prefix[2] = 159; break; - case CX_CURVE_SECP256K1: // Secp256k1 + case SIGNATURE_TYPE_SECP256K1: data.prefix[0] = 6; data.prefix[1] = 161; data.prefix[2] = 161; break; - case CX_CURVE_SECP256R1: // Secp256r1 + case SIGNATURE_TYPE_SECP256R1: data.prefix[0] = 6; data.prefix[1] = 161; data.prefix[2] = 164; diff --git a/src/to_string.h b/src/to_string.h index cd0c2f1c..5ee80ab9 100644 --- a/src/to_string.h +++ b/src/to_string.h @@ -11,25 +11,25 @@ void pubkey_to_pkh_string( char *const out, size_t const out_size, - cx_curve_t const curve, + derivation_type_t const derivation_type, cx_ecfp_public_key_t const *const public_key ); void bip32_path_with_curve_to_pkh_string( char *const out, size_t const out_size, bip32_path_with_curve_t const *const key ); -void protocol_hash_to_string(char *buff, const size_t buff_size, const uint8_t hash[PROTOCOL_HASH_SIZE]); -void parsed_contract_to_string(char *buff, uint32_t buff_size, const struct parsed_contract *contract); -void chain_id_to_string(char *buff, size_t const buff_size, chain_id_t const chain_id); +void protocol_hash_to_string(char *const buff, size_t const buff_size, uint8_t const hash[PROTOCOL_HASH_SIZE]); +void parsed_contract_to_string(char *const buff, size_t const buff_size, parsed_contract_t const *const contract); +void chain_id_to_string(char *const buff, size_t const buff_size, chain_id_t const chain_id); void chain_id_to_string_with_aliases(char *const out, size_t const out_size, chain_id_t const *const chain_id); // dest must be at least MAX_INT_DIGITS size_t number_to_string(char *const dest, uint64_t number); // These take their number parameter through a pointer and take a length -void number_to_string_indirect64(char *const dest, size_t buff_size, uint64_t const *const number); -void number_to_string_indirect32(char *const dest, size_t buff_size, uint32_t const *const number); -void microtez_to_string_indirect(char *const dest, size_t buff_size, uint64_t const *const number); +void number_to_string_indirect64(char *const dest, size_t const buff_size, uint64_t const *const number); +void number_to_string_indirect32(char *const dest, size_t const buff_size, uint32_t const *const number); +void microtez_to_string_indirect(char *const dest, size_t const buff_size, uint64_t const *const number); // `src` may be unrelocated pointer to rodata. void copy_string(char *const dest, size_t const buff_size, char const *const src); diff --git a/src/types.h b/src/types.h index 7384e7f8..2925123b 100644 --- a/src/types.h +++ b/src/types.h @@ -13,6 +13,32 @@ #undef false #define false ((bool)0) +// NOTE: There are *two* ways that "key type" or "curve code" are represented in +// this code base: +// 1. `derivation_type` represents how a key will be derived from the seed. It +// is almost the same as `signature_type` but allows for multiple derivation +// strategies for ed25519. This type is often parsed from the APDU +// instruction. See `parse_derivation_type` for the mapping. +// 2. `signature_type` represents how a key will be used for signing. +// The mapping from `derivation_type` to `signature_type` is injective. +// See `derivation_type_to_signature_type`. +// This type is parsed from Tezos data headers. See the relevant parsing +// code for the mapping. +typedef enum { + DERIVATION_TYPE_SECP256K1 = 1, + DERIVATION_TYPE_SECP256R1 = 2, + DERIVATION_TYPE_ED25519 = 3, + DERIVATION_TYPE_BIP32_ED25519 = 4 +} derivation_type_t; + +typedef enum { + SIGNATURE_TYPE_UNSET = 0, + SIGNATURE_TYPE_SECP256K1 = 1, + SIGNATURE_TYPE_SECP256R1 = 2, + SIGNATURE_TYPE_ED25519 = 3 +} signature_type_t; + + // Return number of bytes to transmit (tx) typedef size_t (*apdu_handler)(uint8_t instruction); @@ -43,10 +69,10 @@ typedef bool (*ui_callback_t)(void); // return true to go back to idle screen typedef void (*string_generation_callback)(/* char *buffer, size_t buffer_size, const void *data */); // Keys -struct key_pair { +typedef struct { cx_ecfp_public_key_t public_key; cx_ecfp_private_key_t private_key; -}; +} key_pair_t; // Baking Auth #define MAX_BIP32_PATH 10 @@ -56,41 +82,57 @@ typedef struct { uint32_t components[MAX_BIP32_PATH]; } bip32_path_t; -static inline void copy_bip32_path(bip32_path_t *const out, bip32_path_t const *const in) { +static inline void copy_bip32_path( + bip32_path_t *const out, + bip32_path_t volatile const *const in +) { check_null(out); check_null(in); - memcpy(out->components, in->components, in->length * sizeof(*in->components)); + memcpy(out->components, (void *)in->components, in->length * sizeof(*in->components)); out->length = in->length; } -static inline bool bip32_paths_eq(bip32_path_t const *const a, bip32_path_t const *const b) { +static inline bool bip32_paths_eq( + bip32_path_t volatile const *const a, + bip32_path_t volatile const *const b +) { return a == b || ( a != NULL && b != NULL && a->length == b->length && - memcmp(a->components, b->components, a->length * sizeof(*a->components)) == 0 + memcmp( + (void const *)a->components, + (void const *)b->components, + a->length * sizeof(*a->components) + ) == 0 ); } typedef struct { bip32_path_t bip32_path; - cx_curve_t curve; + derivation_type_t derivation_type; } bip32_path_with_curve_t; -static inline void copy_bip32_path_with_curve(bip32_path_with_curve_t *const out, bip32_path_with_curve_t const *const in) { +static inline void copy_bip32_path_with_curve( + bip32_path_with_curve_t *const out, + bip32_path_with_curve_t volatile const *const in +) { check_null(out); check_null(in); copy_bip32_path(&out->bip32_path, &in->bip32_path); - out->curve = in->curve; + out->derivation_type = in->derivation_type; } -static inline bool bip32_path_with_curve_eq(bip32_path_with_curve_t const *const a, bip32_path_with_curve_t const *const b) { +static inline bool bip32_path_with_curve_eq( + bip32_path_with_curve_t volatile const *const a, + bip32_path_with_curve_t volatile const *const b +) { return a == b || ( a != NULL && b != NULL && bip32_paths_eq(&a->bip32_path, &b->bip32_path) && - a->curve == b->curve + a->derivation_type == b->derivation_type ); } @@ -142,12 +184,12 @@ typedef struct { level_t level; } parsed_baking_data_t; -struct parsed_contract { - uint8_t originated; // a lightweight bool - uint8_t curve_code; // TEZOS_NO_CURVE in originated case - // An implicit contract with TEZOS_NO_CURVE means not present +typedef struct parsed_contract { + uint8_t originated; // a lightweight bool + signature_type_t signature_type; // 0 in originated case + // An implicit contract with signature_type of 0 means not present uint8_t hash[HASH_SIZE]; -}; +} parsed_contract_t; struct parsed_proposal { uint32_t voting_period; @@ -171,10 +213,14 @@ enum operation_tag { OPERATION_TAG_NONE = -1, // Sentinal value, as 0 is possibly used for something OPERATION_TAG_PROPOSAL = 5, OPERATION_TAG_BALLOT = 6, - OPERATION_TAG_REVEAL = 7, - OPERATION_TAG_TRANSACTION = 8, - OPERATION_TAG_ORIGINATION = 9, - OPERATION_TAG_DELEGATION = 10, + OPERATION_TAG_ATHENS_REVEAL = 7, + OPERATION_TAG_ATHENS_TRANSACTION = 8, + OPERATION_TAG_ATHENS_ORIGINATION = 9, + OPERATION_TAG_ATHENS_DELEGATION = 10, + OPERATION_TAG_BABYLON_REVEAL = 107, + OPERATION_TAG_BABYLON_TRANSACTION = 108, + OPERATION_TAG_BABYLON_ORIGINATION = 109, + OPERATION_TAG_BABYLON_DELEGATION = 110, }; // TODO: Make this an enum. diff --git a/src/ui.h b/src/ui.h index 837603a0..d00f54ce 100644 --- a/src/ui.h +++ b/src/ui.h @@ -11,11 +11,10 @@ void ui_initial_screen(void); void ui_init(void); +void ui_refresh(void); __attribute__((noreturn)) bool exit_app(void); // Might want to send it arguments to use as callback -/// ui-prompt-------------------------- - // Displays labels (terminated with a NULL pointer) associated with data // labels must be completely static string constants while data may be dynamic // Assumes we've registered appropriate callbacks to generate the data. diff --git a/src/ui_common.c b/src/ui_common.c index 075fe902..c4f016b7 100644 --- a/src/ui_common.c +++ b/src/ui_common.c @@ -19,11 +19,20 @@ void register_ui_callback(uint32_t which, string_generation_callback cb, const v global.ui.prompt.callback_data[which] = data; } +void require_pin(void) { + bolos_ux_params_t params; + memset(¶ms, 0, sizeof(params)); + params.ux_id = BOLOS_UX_VALIDATE_PIN; + os_ux_blocking(¶ms); +} + __attribute__((noreturn)) bool exit_app(void) { -#ifdef BAKING_APP - require_pin(); -#endif +# ifdef BAKING_APP +# ifndef TARGET_NANOX + require_pin(); +# endif +# endif BEGIN_TRY_L(exit) { TRY_L(exit) { os_sched_exit(-1); diff --git a/src/ui_nano_s.c b/src/ui_nano_s.c index 26d22d4f..2221aab1 100644 --- a/src/ui_nano_s.c +++ b/src/ui_nano_s.c @@ -19,6 +19,10 @@ #define G global.ui +void ui_refresh(void) { + // DO NOTHING +} + // CALLED BY THE SDK unsigned char io_event(unsigned char channel); void io_seproxyhal_display(const bagl_element_t *element); @@ -42,13 +46,6 @@ static unsigned button_handler(unsigned button_mask, unsigned button_mask_counte #define PROMPT_CYCLES 3 -void require_pin(void) { - bolos_ux_params_t params; - memset(¶ms, 0, sizeof(params)); - params.ux_id = BOLOS_UX_VALIDATE_PIN; - os_ux_blocking(¶ms); -} - #ifdef BAKING_APP static const bagl_element_t ui_idle_screen[] = { // type userid x y w h str rad @@ -146,8 +143,9 @@ static bool do_nothing(void) { static void ui_idle(void) { # ifdef BAKING_APP update_baking_idle_screens(); - ui_display(ui_idle_screen, NUM_ELEMENTS(ui_idle_screen), - do_nothing, exit_app, 3); + ui_display( + ui_idle_screen, NUM_ELEMENTS(ui_idle_screen), + do_nothing, exit_app, 3); # else G.cxl_callback = exit_app; main_menu(); @@ -256,6 +254,7 @@ unsigned char io_event(__attribute__((unused)) unsigned char channel) { case SEPROXYHAL_TAG_DISPLAY_PROCESSED_EVENT: UX_DISPLAYED_EVENT({}); break; + case SEPROXYHAL_TAG_TICKER_EVENT: if (ux.callback_interval_ms != 0) { ux.callback_interval_ms -= MIN(ux.callback_interval_ms, 100u); diff --git a/src/ui_nano_x.c b/src/ui_nano_x.c index 94d9158b..079efb49 100644 --- a/src/ui_nano_x.c +++ b/src/ui_nano_x.c @@ -19,6 +19,10 @@ #define G global.ui +void ui_refresh(void) { + ux_stack_display(0); +} + // CALLED BY THE SDK unsigned char io_event(unsigned char channel); @@ -50,7 +54,12 @@ unsigned char io_event(__attribute__((unused)) unsigned char channel) { break; case SEPROXYHAL_TAG_TICKER_EVENT: - UX_TICKER_EVENT(G_io_seproxyhal_spi_buffer, {}); +# ifdef BAKING_APP + // Disable ticker event handling to prevent screen saver from starting. +# else + UX_TICKER_EVENT(G_io_seproxyhal_spi_buffer, {}); +# endif + break; } @@ -63,6 +72,39 @@ unsigned char io_event(__attribute__((unused)) unsigned char channel) { return 1; } + +#ifdef BAKING_APP +UX_STEP_NOCB( + ux_idle_flow_1_step, + nnn, + { + global.ui.baking_idle_screens.chain, + global.ui.baking_idle_screens.pkh, + global.ui.baking_idle_screens.hwm + }); +UX_STEP_NOCB( + ux_idle_flow_2_step, + bnn, + { + "Tezos Baking", + VERSION, + COMMIT + }); +UX_STEP_CB( + ux_idle_flow_3_step, + pb, + exit_app(), + { + &C_icon_dashboard, + "Quit", + }); + +UX_FLOW(ux_idle_flow, + &ux_idle_flow_1_step, + &ux_idle_flow_2_step, + &ux_idle_flow_3_step +); +#else UX_STEP_NOCB( ux_idle_flow_1_step, bn, @@ -83,7 +125,7 @@ UX_FLOW(ux_idle_flow, &ux_idle_flow_1_step, &ux_idle_flow_2_step ); - +#endif // prompt @@ -148,7 +190,7 @@ _Static_assert(NUM_ELEMENTS(ux_prompts_flow) - 3 /*reject + accept + end*/ == MA void ui_initial_screen(void) { # ifdef BAKING_APP - update_baking_idle_screens(); + calculate_baking_idle_screens_data(); # endif // reserve a display stack slot if none yet diff --git a/test/baking_test.sh b/test/baking_test.sh index 3ba57831..cd0fe06f 100755 --- a/test/baking_test.sh +++ b/test/baking_test.sh @@ -1,11 +1,72 @@ #!/usr/bin/env bash -set -euo pipefail +set -Eeuo pipefail DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd "$DIR" +fail() { + echo "$1" + echo + exit 1 +} + + { + echo; echo "Authorize baking" + { echo 8001000011048000002c800006c18000000080000000 # Authorize baking + } | ./apdu.sh +} + +{ + echo; echo "Self-delegation should work" + { + echo 8004000011048000002c800006c18000000080000000 + echo 800481005703cae1b71a3355e4476620d68d40356c5a4e5773d28357fea2833f24cd99c767260a0000aed011841ffbb0bcc3b51c80f2b6c333a1be3df0ec09f9ef01f44e9502ff00aed011841ffbb0bcc3b51c80f2b6c333a1be3df0 + } | ./apdu.sh +} + +{ + echo; echo "Baking a block with multiple packets should fail" + + { + echo 800681000400000000 # Reset HWM + } | ./apdu.sh + + echo; echo "Self-delegation and block header" + ({ + echo 8004000011048000002c800006c18000000080000000 + echo 800401005703cae1b71a3355e4476620d68d40356c5a4e5773d28357fea2833f24cd99c767260a0000aed011841ffbb0bcc3b51c80f2b6c333a1be3df0ec09f9ef01f44e9502ff00aed011841ffbb0bcc3b51c80f2b6c333a1be3df0 + echo 800481000a017a06a7700000000102 # Bake block at level 1 + } | ./apdu.sh && fail ">>> EXPECTED FAILURE") || true + + + echo; echo "Prefix 00 byte" + ({ + echo 8004000011048000002c800006c18000000080000000 + echo 800401000100 # Prefix packet starting with 00 + echo 800481000a017a06a7700000000102 # Bake block at level 1 + } | ./apdu.sh && fail ">>> EXPECTED FAILURE") || true + + echo; echo "Prefix 03" + ({ + echo 8004000011048000002c800006c18000000080000000 + echo 800401000103 # Prefix packet starting with 03 + echo 800481000a017a06a7700000000102 # Bake block at level 1 + } | ./apdu.sh && fail ">>> EXPECTED FAILURE") || true + + echo; echo "Postfix 00" + ({ + echo 8004000011048000002c800006c18000000080000000 + echo 800401000a017a06a7700000000102 # Bake block at level 1 + echo 800481000100 # Postfix packet starting with 00 + } | ./apdu.sh && fail ">>> EXPECTED FAILURE") || true +} + +{ + echo; echo "Endorsing a previous level should fail" + + { echo 800681000400000000 # Reset HWM echo 8004000011048000002c800006c18000000080000000 @@ -16,8 +77,10 @@ cd "$DIR" echo 8004000011048000002c800006c18000000080000000 echo 800481002a027a06a77000000000000000000000000000000000000000000000000000000000000000000000000002 # Endorse at level 2 + } | ./apdu.sh + ({ echo 8004000011048000002c800006c18000000080000000 echo 800481002a027a06a77000000000000000000000000000000000000000000000000000000000000000000000000001 # Endorse at level 1 (should fail) - -} | ./apdu.sh + } | ./apdu.sh && fail ">>> EXPECTED FAILURE") || true +} diff --git a/test/transaction.sh b/test/transaction.sh new file mode 100755 index 00000000..70e3e94b --- /dev/null +++ b/test/transaction.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "$DIR" + +{ + echo; echo "Messages with 129 prefix bytes should be Sign Unverified (ACCEPT THIS)" + + { + echo 8004000011048000002c800006c18000000080000000 + # 128 bytes + echo 80040100800300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + echo 800401000103 # magic byte + echo 80048100966a9138aff2d7207fabff0ab972d74966ae724de3878768edc3d73576df48c568070000aed011841ffbb0bcc3b51c80f2b6c333a1be3df0ea0902904e00008ae8f22f2a52b770c7f2f0d3598934aa1588e2429a8daad4ef483651b6dfebf3080000aed011841ffbb0bcc3b51c80f2b6c333a1be3df0a20903bc5000c0c393070000aed011841ffbb0bcc3b51c80f2b6c333a1be3df000 + } | ./apdu.sh +} + +{ + echo; echo "Exceptions should not allow new packets to be parsed without starting over" + + { + echo 8004000311048000002c800006c18000000080000000 + # Wrong length: should be 0x97 + echo 80040100960316d8aa98f84a30af5871642e9fab07597a14bf0a9a4f37bb8b734fd28007cee10700006fd9ff5e5aad9738883f9d291dd67f888221ad8fea0902904e000050a7e13e2ce14fd935a4258dbff7f4874421981177e083dd7a3a505d16b1e31d0800006fd9ff5e5aad9738883f9d291dd67f888221ad8fa20903bc5000c0c3930700006fd9ff5e5aad9738883f9d291dd67f888221ad8f00 + } | ./apdu.sh + + echo "After exception" + + { + + # Valid second packet, but no first packet. + echo 80048100970316d8aa98f84a30af5871642e9fab07597a14bf0a9a4f37bb8b734fd28007cee10700006fd9ff5e5aad9738883f9d291dd67f888221ad8fea0902904e000050a7e13e2ce14fd935a4258dbff7f4874421981177e083dd7a3a505d16b1e31d0800006fd9ff5e5aad9738883f9d291dd67f888221ad8fa20903bc5000c0c3930700006fd9ff5e5aad9738883f9d291dd67f888221ad8f00 + } | ./apdu.sh || echo "Pass" +} + +{ + echo; echo "Prefixed packets should cause Sign Unverified (ACCEPT THIS)" + + { + echo 8004000311048000002c800006c18000000080000000 + echo 800401000103 + echo 80048100970316d8aa98f84a30af5871642e9fab07597a14bf0a9a4f37bb8b734fd28007cee10700006fd9ff5e5aad9738883f9d291dd67f888221ad8fea0902904e000050a7e13e2ce14fd935a4258dbff7f4874421981177e083dd7a3a505d16b1e31d0800006fd9ff5e5aad9738883f9d291dd67f888221ad8fa20903bc5000c0c3930700006fd9ff5e5aad9738883f9d291dd67f888221ad8f00 + } | ./apdu.sh +} + +{ + echo; echo "Trailing packets should cause Sign Unverified (ACCEPT THIS)" + + { + echo 8004000311048000002c800006c18000000080000000 + echo 80040100970316d8aa98f84a30af5871642e9fab07597a14bf0a9a4f37bb8b734fd28007cee10700006fd9ff5e5aad9738883f9d291dd67f888221ad8fea0902904e000050a7e13e2ce14fd935a4258dbff7f4874421981177e083dd7a3a505d16b1e31d0800006fd9ff5e5aad9738883f9d291dd67f888221ad8fa20903bc5000c0c3930700006fd9ff5e5aad9738883f9d291dd67f888221ad8f00 + echo 800481000103 + } | ./apdu.sh +} + +{ + echo; echo "Prefixed and trailing packets should cause Sign Unverified (ACCEPT THIS)" + + { + echo 8004000311048000002c800006c18000000080000000 + echo 800401000103 + echo 80040100970316d8aa98f84a30af5871642e9fab07597a14bf0a9a4f37bb8b734fd28007cee10700006fd9ff5e5aad9738883f9d291dd67f888221ad8fea0902904e000050a7e13e2ce14fd935a4258dbff7f4874421981177e083dd7a3a505d16b1e31d0800006fd9ff5e5aad9738883f9d291dd67f888221ad8fa20903bc5000c0c3930700006fd9ff5e5aad9738883f9d291dd67f888221ad8f00 + echo 800481000103 + } | ./apdu.sh +} + +{ + echo; echo "Two valid packets should still cause Sign Unverified (ACCEPT THIS)" + + { + echo 8004000311048000002c800006c18000000080000000 + echo 80040100970316d8aa98f84a30af5871642e9fab07597a14bf0a9a4f37bb8b734fd28007cee10700006fd9ff5e5aad9738883f9d291dd67f888221ad8fea0902904e000050a7e13e2ce14fd935a4258dbff7f4874421981177e083dd7a3a505d16b1e31d0800006fd9ff5e5aad9738883f9d291dd67f888221ad8fa20903bc5000c0c3930700006fd9ff5e5aad9738883f9d291dd67f888221ad8f00 + echo 80048100970316d8aa98f84a30af5871642e9fab07597a14bf0a9a4f37bb8b734fd28007cee10700006fd9ff5e5aad9738883f9d291dd67f888221ad8fea0902904e000050a7e13e2ce14fd935a4258dbff7f4874421981177e083dd7a3a505d16b1e31d0800006fd9ff5e5aad9738883f9d291dd67f888221ad8fa20903bc5000c0c3930700006fd9ff5e5aad9738883f9d291dd67f888221ad8f00 + } | ./apdu.sh +} + +{ + echo; echo "Valid transaction should be parsed (ACCEPT THIS)" + + { + echo 8004000311048000002c800006c18000000080000000 + echo 80048100970316d8aa98f84a30af5871642e9fab07597a14bf0a9a4f37bb8b734fd28007cee10700006fd9ff5e5aad9738883f9d291dd67f888221ad8fea0902904e000050a7e13e2ce14fd935a4258dbff7f4874421981177e083dd7a3a505d16b1e31d0800006fd9ff5e5aad9738883f9d291dd67f888221ad8fa20903bc5000c0c3930700006fd9ff5e5aad9738883f9d291dd67f888221ad8f00 + } | ./apdu.sh +} From b386339c1d9f33f2a62eea82f73bb9c18e371476 Mon Sep 17 00:00:00 2001 From: oto Date: Mon, 21 Oct 2019 17:31:18 +0200 Subject: [PATCH 2/9] fix compilation issues against SDK 1.6.0 --- Makefile | 1 + src/ui_nano_s.c | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 3416c1d8..a69946da 100644 --- a/Makefile +++ b/Makefile @@ -64,6 +64,7 @@ DEFINES += HAVE_BAGL HAVE_SPRINTF DEFINES += HAVE_IO_USB HAVE_L4_USBLIB IO_USB_MAX_ENDPOINTS=6 IO_HID_EP_LENGTH=64 HAVE_USB_APDU DEFINES += VERSION=\"$(APPVERSION)\" APPVERSION_M=$(APPVERSION_M) DEFINES += COMMIT=\"$(COMMIT)\" APPVERSION_N=$(APPVERSION_N) APPVERSION_P=$(APPVERSION_P) +DEFINES += _Static_assert\(...\)= ifeq ($(TARGET_NAME),TARGET_NANOX) DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=300 diff --git a/src/ui_nano_s.c b/src/ui_nano_s.c index 26d22d4f..13388ac0 100644 --- a/src/ui_nano_s.c +++ b/src/ui_nano_s.c @@ -231,10 +231,11 @@ void ui_display(const bagl_element_t *elems, size_t sz, ui_callback_t ok_c, ui_c if (!is_idling()) { switch_screen(0); } - ux.elements = elems; - ux.elements_count = sz; - ux.button_push_handler = button_handler; - ux.elements_preprocessor = prepro; + ux.stack[0].element_arrays[0].element_array = elems; + ux.stack[0].element_arrays[0].element_array_count = sz; + ux.stack[0].element_arrays_count=1; + ux.stack[0].button_push_callback = button_handler; + G_ux.stack[0].screen_before_element_display_callback = prepro; UX_WAKE_UP(); UX_REDISPLAY(); } From 4bcde0f8a1ab9ac01c39d228b75fe6a0d26026b0 Mon Sep 17 00:00:00 2001 From: oto Date: Tue, 22 Oct 2019 14:10:43 +0200 Subject: [PATCH 3/9] revert previous commit about static_assert and fix the invalid static assert syntax in tezos app --- Makefile | 2 +- src/types.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index a69946da..c34a882b 100644 --- a/Makefile +++ b/Makefile @@ -64,7 +64,7 @@ DEFINES += HAVE_BAGL HAVE_SPRINTF DEFINES += HAVE_IO_USB HAVE_L4_USBLIB IO_USB_MAX_ENDPOINTS=6 IO_HID_EP_LENGTH=64 HAVE_USB_APDU DEFINES += VERSION=\"$(APPVERSION)\" APPVERSION_M=$(APPVERSION_M) DEFINES += COMMIT=\"$(COMMIT)\" APPVERSION_N=$(APPVERSION_N) APPVERSION_P=$(APPVERSION_P) -DEFINES += _Static_assert\(...\)= +# DEFINES += _Static_assert\(...\)= ifeq ($(TARGET_NAME),TARGET_NANOX) DEFINES += IO_SEPROXYHAL_BUFFER_SIZE_B=300 diff --git a/src/types.h b/src/types.h index 7384e7f8..50866837 100644 --- a/src/types.h +++ b/src/types.h @@ -125,7 +125,7 @@ typedef struct { }) #define STATIC_UI_VALUE(str) ({ \ - _Static_assert(sizeof(str) <= VALUE_WIDTH + 1/*null byte*/, str " won't fit in the UI.".); \ + _Static_assert(sizeof(str) <= VALUE_WIDTH + 1/*null byte*/, str " won't fit in the UI."); \ str; \ }) From 711698d9e50c1471300dbda5cc21385ba43ac4c0 Mon Sep 17 00:00:00 2001 From: BTChip Date: Wed, 23 Oct 2019 23:45:29 +0200 Subject: [PATCH 4/9] Compile on Nano S 1.6 SDK --- src/types.h | 2 +- src/ui_nano_s.c | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/types.h b/src/types.h index 2925123b..8b2ae84d 100644 --- a/src/types.h +++ b/src/types.h @@ -167,7 +167,7 @@ typedef struct { }) #define STATIC_UI_VALUE(str) ({ \ - _Static_assert(sizeof(str) <= VALUE_WIDTH + 1/*null byte*/, str " won't fit in the UI.".); \ + _Static_assert(sizeof(str) <= VALUE_WIDTH + 1/*null byte*/, str " won't fit in the UI."); \ str; \ }) diff --git a/src/ui_nano_s.c b/src/ui_nano_s.c index 2221aab1..08710d4a 100644 --- a/src/ui_nano_s.c +++ b/src/ui_nano_s.c @@ -229,10 +229,18 @@ void ui_display(const bagl_element_t *elems, size_t sz, ui_callback_t ok_c, ui_c if (!is_idling()) { switch_screen(0); } +#if CX_APILEVEL < 10 ux.elements = elems; ux.elements_count = sz; ux.button_push_handler = button_handler; ux.elements_preprocessor = prepro; +#else + ux.stack[0].element_arrays[0].element_array = elems; + ux.stack[0].element_arrays[0].element_array_count = sz; + ux.stack[0].element_arrays_count=1; + ux.stack[0].button_push_callback = button_handler; + G_ux.stack[0].screen_before_element_display_callback = prepro; +#endif UX_WAKE_UP(); UX_REDISPLAY(); } From 053b84af1661dcd3dbc5e108ff307a6e2360b428 Mon Sep 17 00:00:00 2001 From: BTChip Date: Thu, 24 Oct 2019 00:44:20 +0200 Subject: [PATCH 5/9] Use legacy PID to interact with daemon --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 34b9da28..f98a33e7 100644 --- a/Makefile +++ b/Makefile @@ -62,6 +62,7 @@ show-app: DEFINES += OS_IO_SEPROXYHAL DEFINES += HAVE_BAGL HAVE_SPRINTF DEFINES += HAVE_IO_USB HAVE_L4_USBLIB IO_USB_MAX_ENDPOINTS=6 IO_HID_EP_LENGTH=64 HAVE_USB_APDU +DEFINES += HAVE_LEGACY_PID DEFINES += VERSION=\"$(APPVERSION)\" APPVERSION_M=$(APPVERSION_M) DEFINES += COMMIT=\"$(COMMIT)\" APPVERSION_N=$(APPVERSION_N) APPVERSION_P=$(APPVERSION_P) From d20496de45def7015a728a034991337e2bf6a7a7 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 30 Oct 2019 14:12:28 -0400 Subject: [PATCH 6/9] Add release script --- nix/release.sh | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100755 nix/release.sh diff --git a/nix/release.sh b/nix/release.sh new file mode 100755 index 00000000..dd6988e2 --- /dev/null +++ b/nix/release.sh @@ -0,0 +1,30 @@ +#! /usr/bin/env nix-shell +#! nix-shell -i bash -p gitAndTools.hub coreutils + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")" && git rev-parse --show-toplevel)" + +nano_s_tarball=$("$root/nix/build.sh" -A "nano.s.release.all") +nano_x_tarball=$("$root/nix/build.sh" -A "nano.x.release.all") + +cp -f $nano_s_tarball nano-s-release.tar.gz +cp -f $nano_x_tarball nano-x-release.tar.gz + +# hub release create \ +# -a $nano_s_tarball'#'nano-s-release.tar.gz \ +# -a $nano_x_tarball'#'nano-x-release.tar.gz \ +# -F - + +echo '## Checksums' +echo '### nano-s-release.tar.gz' +echo 'Type | Value' +echo '-- | --' +echo "MD5 | $(md5sum nano-s-release.tar.gz | cut -f1 -d' ')" +echo "SHA256 | $(sha256sum nano-s-release.tar.gz | cut -f1 -d' ')" +echo "SHA512 | $(sha512sum nano-s-release.tar.gz | cut -f1 -d' ')" + +echo '### nano-x-release.tar.gz' +echo 'Type | Value' +echo '-- | --' +echo "MD5 | $(md5sum nano-x-release.tar.gz | cut -f1 -d' ')" +echo "SHA256 | $(sha256sum nano-x-release.tar.gz | cut -f1 -d' ')" +echo "SHA512 | $(sha512sum nano-x-release.tar.gz | cut -f1 -d' ')" From 9f9d9185d7402d7266b8fec7cc8513d3574d0bfb Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 30 Oct 2019 15:14:20 -0400 Subject: [PATCH 7/9] Fix nix/install.sh to allow extra args --- nix/install.sh | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/nix/install.sh b/nix/install.sh index bca00e47..2352fc11 100755 --- a/nix/install.sh +++ b/nix/install.sh @@ -1,26 +1,33 @@ -#!/usr/bin/env bash +#! /usr/bin/env nix-shell +#! nix-shell -i bash ./ledgerblue.nix -A shell + set -Eeuo pipefail -export target="${1:?Please specify target, either 's' for Nano S or 'x' for Nano X}" +root="$(git rev-parse --show-toplevel)" + +target="${1:?Please specify target, either 's' for Nano S or 'x' for Nano X}" shift -root="$(git rev-parse --show-toplevel)" -export root +case "$target" in + s) ;; x) ;; + *) + >&2 echo "Target must either be 's' for Nano S or 'x' for Nano X" + exit 1 +esac install() { + local app=$1 + shift local release_file - release_file=$("$root/nix/build.sh" -A "nano.${target}.release.$1") + release_file=$("$root/nix/build.sh" -A "nano.$target.release.$app" "$@") bash "$root/release-installer.sh" "$release_file" } -export -f install -nix-shell "$root/nix/ledgerblue.nix" -A shell --run "$(cat < Date: Wed, 30 Oct 2019 15:14:31 -0400 Subject: [PATCH 8/9] Allow ./nix/build.sh to work from any directory in the project --- nix/build.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nix/build.sh b/nix/build.sh index e940cd74..80ed0d38 100755 --- a/nix/build.sh +++ b/nix/build.sh @@ -1,8 +1,10 @@ #!/usr/bin/env bash set -Eeuo pipefail +root="$(git rev-parse --show-toplevel)" + # Override package set by passing --arg pkgs descr=$(git describe --tags --abbrev=8 --always --long --dirty 2>/dev/null) echo >&2 "Git description: $descr" -exec nix-build --no-out-link --argstr gitDescribe "$descr" "$@" ${NIX_BUILD_ARGS:-} +exec nix-build "$root" --no-out-link --argstr gitDescribe "$descr" "$@" ${NIX_BUILD_ARGS:-} From 76fda2257cc9a606fa9b94c293fb0befa9bd7d72 Mon Sep 17 00:00:00 2001 From: Matthew Bauer Date: Wed, 30 Oct 2019 15:55:01 -0400 Subject: [PATCH 9/9] Pass args into release.nix script as well So you can pass --arg ... --- nix/release.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nix/release.sh b/nix/release.sh index dd6988e2..c7cfd373 100755 --- a/nix/release.sh +++ b/nix/release.sh @@ -3,8 +3,8 @@ root="$(cd "$(dirname "${BASH_SOURCE[0]}")" && git rev-parse --show-toplevel)" -nano_s_tarball=$("$root/nix/build.sh" -A "nano.s.release.all") -nano_x_tarball=$("$root/nix/build.sh" -A "nano.x.release.all") +nano_s_tarball=$("$root/nix/build.sh" -A "nano.s.release.all" "$@") +nano_x_tarball=$("$root/nix/build.sh" -A "nano.x.release.all" "$@") cp -f $nano_s_tarball nano-s-release.tar.gz cp -f $nano_x_tarball nano-x-release.tar.gz