diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ec353f7..4ca4ef16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,42 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [6.0.3](TBD) - [TBD] +## [7.1.0](TBD) - [TBD] + +Message signing (CIP-8) + +### Added + +- support for basic message signing (CIP-8, CIP-30) + +### Changed + +- usage of chunks of maximum allowed size is now enforced (datums and reference scripts in outputs) +- TODO updated list of native tokens recognized by the app with correct decimal places + + +## [7.0.2](TBD) - [TBD] + +Conway era + +### Added + +- export of Conway-era keys (DReps, Constitutional Committee Hot and Cold keys) +- Conway era transaction body items (new certificates, voting procedures, treasury, donation) +- optional CBOR tag 258 in CDDL sets +- reduced features on Nano S (since Ledger app v7, due to memory limits) + +### Changed + +- updated list of native tokens recognized by the app with correct decimal places +- increased max. URL and DNS name length to 128 + +### Fixed + +- bug in checking canonical ordering of withdrawals + + +## [6.1.2](https://github.com/LedgerHQ/app-cardano/compare/v5.0.0...LedgerHQ:nanos_2.1.0_6.1.2_sdk_2.1.0-12) - [October 25th 2023] Support for CIP-36 voting @@ -15,11 +50,13 @@ Support for CIP-36 voting - export of vote keys (1694'/1815'/...) - support for CIP-36 voting (signing of vote-cast fragments with 1694 keys) - support for CIP-36 registrations (in transaction auxiliary data) +- support for the Stax device ### Changed - API for Catalyst voting registration (it is still possible to use CIP-15 in auxiliary data) - updated list of native tokens recognized by the app with correct decimal places +- multidelegation allowed (as used by Lace, i.e. stake keys do not need to end with 0 as address_index) ## [5.0.0](https://github.com/LedgerHQ/app-cardano/compare/4.1.2...LedgerHQ:nanos_2.1.0_5.0.0) - [October 11th 2022] diff --git a/Makefile b/Makefile index c03f1fee..e6ec65d5 100644 --- a/Makefile +++ b/Makefile @@ -16,9 +16,10 @@ #******************************************************************************* APPNAME = "Cardano ADA" -APPVERSION_M = 6 + +APPVERSION_M = 7 APPVERSION_N = 1 -APPVERSION_P = 2 +APPVERSION_P = 1 APPVERSION = "$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)" ifeq ($(BOLOS_SDK),) @@ -120,6 +121,31 @@ else DEFINES += PRINTF\(...\)= endif +# restricted features for Nano S +# but not in DEVEL mode where we usually want to test all features with HEADLESS +ifeq ($(TARGET_NAME), TARGET_NANOS) + ifneq ($(DEVEL), 1) + APP_XS = 1 + else + APP_XS = 0 + endif +else + APP_XS = 0 +endif + +ifeq ($(APP_XS), 1) + DEFINES += APP_XS +else + # features not included in the Nano S app + DEFINES += APP_FEATURE_OPCERT + DEFINES += APP_FEATURE_NATIVE_SCRIPT_HASH + DEFINES += APP_FEATURE_POOL_REGISTRATION + DEFINES += APP_FEATURE_POOL_RETIREMENT + DEFINES += APP_FEATURE_BYRON_ADDRESS_DERIVATION + DEFINES += APP_FEATURE_BYRON_PROTOCOL_MAGIC_CHECK +endif +# always include this, it's important for Plutus users +DEFINES += APP_FEATURE_TOKEN_MINTING ################## # Dependencies # @@ -196,5 +222,21 @@ format: size: all $(GCCPATH)arm-none-eabi-size --format=gnu bin/app.elf +############## +# Device-specific builds +############## + +nanos: clean + BOLOS_SDK=$(NANOS_SDK) make + +nanosp: clean + BOLOS_SDK=$(NANOSP_SDK) make + +nanox: clean + BOLOS_SDK=$(NANOX_SDK) make + +stax: clean + BOLOS_SDK=$(STAX_SDK) make + # import generic rules from the sdk include $(BOLOS_SDK)/Makefile.rules diff --git a/README.md b/README.md index c822fa9a..b021f825 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,7 @@ We recommend using the containerized build. See [Getting started](doc/build.md) ### Loading the app -`make load` - -Builds and loads the application into the connected device. Make sure to close the Ledger app on the device before running the command. - -Most common reason for a failed loading is the app taking too much space. Check `make size` (should be below 140K or so). +We recommend using the [Ledger VS Code plugin](https://marketplace.visualstudio.com/items?itemName=LedgerHQ.ledger-dev-tools) to load the app on a device. ### Debug version @@ -27,7 +23,7 @@ also comment out DEFINES += RESET_ON_CRASH -and then run `make clean load`. +The debug version is too big to fit on Nano S, but works on Speculos. ### Setup @@ -78,6 +74,10 @@ _Before merging a PR, one should make sure that:_ * `make clean load` runs without errors and warnings (except those reported for nanos-secure-sdk repo) for development build (see Debug version above) * `make analyze` does not report errors or warnings +## Running tests + +All the tests are initiated from the accompanying [ledgerjs package](https://github.com/vacuumlabs/ledgerjs-cardano-shelley) (see what [commands to run](https://github.com/vacuumlabs/ledgerjs-cardano-shelley?tab=readme-ov-file#tests)). You have to make sure that the version of ledgerjs correspond to the app version, otherwise some tests with fail (possibly resulting in odd errors) or test coverage will be incomplete. + ## How to get a transaction body computed by Ledger Ledger computes a rolling hash of the serialized transaction body, but the body itself is ordinarily not available. It is possible to acquire it from the development build by going through the following steps: diff --git a/doc/build.md b/doc/build.md index 14a11485..b94de09c 100644 --- a/doc/build.md +++ b/doc/build.md @@ -5,7 +5,7 @@ - Install Docker - Pull the required containers as discussed in https://github.com/LedgerHQ/ledger-app-builder/ (lite container is sufficient for a C build): - `sudo docker pull ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest` + `docker pull ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder-lite:latest` ## Compiling the app diff --git a/doc/features.md b/doc/features.md new file mode 100644 index 00000000..ec9745f3 --- /dev/null +++ b/doc/features.md @@ -0,0 +1,11 @@ +# Features (not) available on specific Ledger devices + +Nano S has a very limited space for storing applications. It is not enough to fit all Cardano features there, so some of them are only available on Nano S+ and other more spacious Ledger devices (e.g. Nano X and Stax). + +The features not supported on Nano S, Cardano app version 7 and above: +* pool registration and retirement +* signing of operational certificates +* computation of native script hashes +* details in Byron change outputs (only the address is shown) + +Details can be found in [Makefile](../Makefile) and in the code (search for compilation flags beginning with `APP_FEATURE_`). diff --git a/doc/ins_get_public_keys.md b/doc/ins_get_public_keys.md index e3035d82..3219a81f 100644 --- a/doc/ins_get_public_keys.md +++ b/doc/ins_get_public_keys.md @@ -6,7 +6,7 @@ Get an extended public key (i.e., public key + chain code) for a given BIP32 pat It is also possible to ask for a confirmation for exporting several keys (if the paths describing the keys are not suspicious, they won't be shown to the user and no further confirmation is required). -The allowed derivation paths correspond to wallet keys (accounts, spending paths, staking paths) and pool cold keys, as described in +The allowed derivation paths correspond to wallet keys (accounts, payment paths, staking paths) and pool cold keys, as described in - [CIP 1852 - HD Wallets for Cardano](https://cips.cardano.org/cips/cip1852/); - [CIP 1853 - HD Stake Pool Cold Keys for Cardano](https://cips.cardano.org/cips/cip1853/). @@ -80,8 +80,6 @@ Concatenation of `pub_key` and `chain_code` representing the extended public key - Ledger might impose more restrictions, see implementation of `policyForGetExtendedPublicKey` in [src/securityPolicy.c](../src/securityPolicy.c) for details - calculate extended public key - respond with extended public key - + **TODOs** - ❓(IOHK): Should we also support BTC app like token validation? (Note: Token validation is to prevent concurrent access to the Ledger by two different host apps which could confuse user into performing wrong actions) -- ❓(IOHK): Should we support permanent app setting where Ledger forces user to acknowledge public key retrieval before sending it to host? (Note: probably not in the first version of the app) -- ❓(IOHK): Should there be an option to show the public key on display? Is it useful in any way? (Note: probably not) diff --git a/doc/ins_sign_stake_pool_registration.md b/doc/ins_sign_stake_pool_registration.md index 70106a6e..f8ee324c 100644 --- a/doc/ins_sign_stake_pool_registration.md +++ b/doc/ins_sign_stake_pool_registration.md @@ -157,12 +157,12 @@ P2 = `0x36` |relay format | 1 | `RELAY_SINGLE_HOST_NAME=0x01` | |isPortGiven | 1 | `ITEM_INCLUDED_NO=0x01` or `ITEM_INCLUDED_YES=0x02` | |port | 2 | Big endian; included if and only if isPortGiven is `ITEM_INCLUDED_YES` -|dns name | variable | byte buffer, max size 64 +|dns name | variable | byte buffer, max size 128 |Field| Length | Comments| |-----|--------|---------| |relay format | 1 | `RELAY_MULTIPLE_HOST_NAME=0x02` | -|dns name | variable | byte buffer, max size 64 +|dns name | variable | byte buffer, max size 128 --- @@ -175,7 +175,7 @@ P2 = `0x37` |-----|--------|---------| |includeMetadata | 1 | `ITEM_INCLUDED_NO=0x01` or `ITEM_INCLUDED_YES=0x02` | |metadata hash | 32 | byte buffer; only if includeMetadata is `ITEM_INCLUDED_YES` -|metadata url | variable | byte buffer, max size 64; only if includeMetadata is `ITEM_INCLUDED_YES` +|metadata url | variable | byte buffer, max size 128; only if includeMetadata is `ITEM_INCLUDED_YES` --- diff --git a/doc/ins_sign_tx.md b/doc/ins_sign_tx.md index e8e6aeda..bc9c8f1a 100644 --- a/doc/ins_sign_tx.md +++ b/doc/ins_sign_tx.md @@ -1,8 +1,10 @@ # Sign Transaction +Note: this is somewhat incomplete (Babbage and Conway era elements are not described in detail) and some parts might be outdated. We strongly recommend to use [ledgerjs for Cardano](https://github.com/vacuumlabs/ledgerjs-cardano-shelley) for signing transactions. Check its latest API to find out what is supported. + **Description** -Given transaction inputs and transaction outputs, fee, ttl, staking certificates, reward withdrawals, metadata hash, validity interval start, and mint, construct and sign a transaction. +Given transaction inputs and transaction outputs, fee, ttl, staking certificates, reward withdrawals, metadata hash, validity interval start, mint, Plutus (Babbage) additional transaction body elements, and Conway additional elements, construct and sign a transaction. Due to Ledger constraints and potential security implications (parsing errors), Cardano Ledger app uses a custom format for streaming the transaction to be signed. The main rationale behind not streaming directly the (CBOR-encoded) cardano raw transaction to Ledger is the following: 1) The app needs to support BIP44 change address outputs (Ledger should not display user's own change addresses to the user as this degrades UX). @@ -13,7 +15,7 @@ Due to Ledger constraints and potential security implications (parsing errors), **SignTx Limitations** - Output address size is limited to 128 bytes (single APDU). (Note: IOHK is fine with address size limit of 100 bytes) -- Addresses that are not shown to the user are base addresses with spending path `m/1852'/1815'/account'/{0,1}/changeIndex` and the standard stake key `m/1852'/1815'/account'/2/0`, where values of `account` and `changeIndex` are limited (for now, `0 <= account <= 100` and `0 <= changeIndex <= 1 000 000`). This makes it feasible to brute-force all change addresses in case an attacker manages to modify change address(es). (As the user does not confirm change addresses, it is relatively easy to perform MITM attack). +- Addresses that are not shown to the user are base addresses with payment key path `m/1852'/1815'/account'/{0,1}/changeIndex` and the standard stake key `m/1852'/1815'/account'/2/0`, where values of `account` and `changeIndex` are limited (for now, `0 <= account <= 100` and `0 <= changeIndex <= 1 000 000`). This makes it feasible to brute-force all change addresses in case an attacker manages to modify change address(es). (As the user does not confirm change addresses, it is relatively easy to perform MITM attack). - Only transactions with at least one input will be signed (this provides protection against certificate replays and transaction replays on different networks). **Communication protocol non-goals:** @@ -233,7 +235,23 @@ Optional. ### Certificate -We support 4 types of certificates in ordinary transactions (signing mode `SIGN_TX_SIGNINGMODE_ORDINARY_TX` in the initial APDU message): stake key registration, stake key deregistration, stake delegation, and stake pool retirement. We support 3 types in multisig transactions (signing mode `SIGN_TX_SIGNINGMODE_MULTISIG_TX` in the initial APDU message): stake key registration, stake key deregistration, and stake delegation. +We support the following certificate types in ordinary transactions (signing mode `SIGN_TX_SIGNINGMODE_ORDINARY_TX` in the initial APDU message): +* CERTIFICATE_STAKE_REGISTRATION = 0, +* CERTIFICATE_STAKE_DEREGISTRATION = 1, +* CERTIFICATE_STAKE_DELEGATION = 2, +* CERTIFICATE_STAKE_POOL_RETIREMENT = 4, +* CERTIFICATE_STAKE_REGISTRATION_CONWAY = 7, +* CERTIFICATE_STAKE_DEREGISTRATION_CONWAY = 8, +* CERTIFICATE_VOTE_DELEGATION = 9, +* CERTIFICATE_AUTHORIZE_COMMITTEE_HOT = 14, +* CERTIFICATE_RESIGN_COMMITTEE_COLD = 15, +* CERTIFICATE_DREP_REGISTRATION = 16, +* CERTIFICATE_DREP_DEREGISTRATION = 17, +* CERTIFICATE_DREP_UPDATE = 18, + +For signing mode `SIGN_TX_SIGNINGMODE_MULTISIG_TX`, everything from the above list except `CERTIFICATE_STAKE_POOL_RETIREMENT` is allowed. + +For signing mode `SIGN_TX_SIGNINGMODE_PLUTUS_TX`, everything from the above list is allowed. In addition, a transaction using `SIGN_TX_SIGNINGMODE_POOL_REGISTRATION_OPERATOR` or `SIGN_TX_SIGNINGMODE_POOL_REGISTRATION_OWNER` as the signing mode contains a single certificate for stake pool registration which must not be accompanied by other certificates or by withdrawals (due to security concerns about cross-witnessing data between them). This certificate is processed by a state sub-machine. Instructions for this sub-machine are given in P2; see [Stake Pool Registration](ins_sign_stake_pool_registration.md) for the details on accepted P2 values and additional APDU messages needed. @@ -242,41 +260,41 @@ In addition, a transaction using `SIGN_TX_SIGNINGMODE_POOL_REGISTRATION_OPERATOR | P1 | `0x06` | | P2 | (unused / see [Stake Pool Registration](ins_sign_stake_pool_registration.md)) | -**Data for CERTIFICATE_TYPE_STAKE_REGISTRATION** +**Data for CERTIFICATE_STAKE_REGISTRATION** |Field| Length | Comments| |-----|--------|---------| -|Output type| 1 | `CERTIFICATE_TYPE_STAKE_REGISTRATION=0x00`| +|Output type| 1 | `CERTIFICATE_STAKE_REGISTRATION=0x00`| |Stake credential| variable | See stake credential explained above| -**Data for CERTIFICATE_TYPE_STAKE_DEREGISTRATION** +**Data for CERTIFICATE_STAKE_DEREGISTRATION** |Field| Length | Comments| |-----|--------|---------| -|Output type| 1 | `CERTIFICATE_TYPE_STAKE_DEREGISTRATION=0x01`| +|Output type| 1 | `CERTIFICATE_STAKE_DEREGISTRATION=0x01`| |Stake credential| variable | See stake credential explained above| -**Data for CERTIFICATE_TYPE_STAKE_DELEGATION** +**Data for CERTIFICATE_STAKE_DELEGATION** |Field| Length | Comments| |-----|--------|---------| -|Output type| 1 | `CERTIFICATE_TYPE_STAKE_DELEGATION=0x02`| +|Output type| 1 | `CERTIFICATE_STAKE_DELEGATION=0x02`| |Stake credential| variable | See stake credential explained above| |Pool key hash| 28 | Hash of staking pool public key| -**Data for CERTIFICATE_TYPE_STAKE_POOL_REGISTRATION** +**Data for CERTIFICATE_STAKE_POOL_REGISTRATION** |Field| Length | Comments| |-----|--------|---------| -|Output type| 1 | `CERTIFICATE_TYPE_STAKE_POOL_REGISTRATION=0x03`| +|Output type| 1 | `CERTIFICATE_STAKE_POOL_REGISTRATION=0x03`| This only describes the initial certificate message. All the data for this certificate are obtained via a series of additional APDU messages; see [Stake Pool Registration](ins_sign_stake_pool_registration.md) for the details. -**Data for CERTIFICATE_TYPE_STAKE_POOL_RETIREMENT** +**Data for CERTIFICATE_STAKE_POOL_RETIREMENT** |Field| Length | Comments| |-----|--------|---------| -|Output type| 1 | `CERTIFICATE_TYPE_STAKE_POOL_RETIREMENT=0x04`| +|Output type| 1 | `CERTIFICATE_STAKE_POOL_RETIREMENT=0x04`| |Stake key path| variable | BIP44 path. See [GetExtPubKey call](ins_get_public_keys.md) for a format example | |Pool key hash| 28 | Hash of staking pool public key| diff --git a/fuzzing/CMakeLists.txt b/fuzzing/CMakeLists.txt index fe7e9fe6..215c06c8 100644 --- a/fuzzing/CMakeLists.txt +++ b/fuzzing/CMakeLists.txt @@ -21,6 +21,7 @@ else() add_link_options($ENV{LIB_FUZZING_ENGINE}) endif() +add_compile_options(-g) set(SDK_PATH ${BOLOS_SDK}) @@ -77,6 +78,8 @@ set(CARDANO_SOURCE ${CARDANO_PATH}/src/securityPolicy.c ${CARDANO_PATH}/src/signCVote.c ${CARDANO_PATH}/src/signCVote_ui.c + ${CARDANO_PATH}/src/signMsg.c + ${CARDANO_PATH}/src/signMsg_ui.c ${CARDANO_PATH}/src/signOpCert.c ${CARDANO_PATH}/src/signTx.c ${CARDANO_PATH}/src/signTxCVoteRegistration.c @@ -140,11 +143,21 @@ add_compile_definitions( HAVE_ECDSA HAVE_EDDSA HAVE_HASH + HAVE_SHA224 HAVE_SHA256 HAVE_SHA3 + + # include all app features, incl. those removed from Nano S + APP_FEATURE_OPCERT + APP_FEATURE_NATIVE_SCRIPT_HASH + APP_FEATURE_POOL_REGISTRATION + APP_FEATURE_POOL_RETIREMENT + APP_FEATURE_BYRON_ADDRESS_DERIVATION + APP_FEATURE_BYRON_PROTOCOL_MAGIC_CHECK + APP_FEATURE_TOKEN_MINTING ) -set(SOURCE +set(SOURCE ${UX_SOURCE} ${CARDANO_SOURCE} ./src/os_mocks.c @@ -160,13 +173,14 @@ set(harnesses deriveNativeScriptHash_harness getPublicKeys_harness signCVote_harness + signMsg_harness signOpCert_harness signTx_harness ) foreach(harness IN LISTS harnesses) - add_executable(${harness} + add_executable(${harness} ./src/${harness}.c - ) + ) target_link_libraries(${harness} PUBLIC cardano) -endforeach() \ No newline at end of file +endforeach() diff --git a/fuzzing/README.md b/fuzzing/README.md index a4688c84..383c01ab 100644 --- a/fuzzing/README.md +++ b/fuzzing/README.md @@ -19,6 +19,7 @@ deriveAddress_harness deriveNativeScriptHash_harness getPublicKeys_harness signCVote_harness +signMsg_harness signOpCert_harness signTx_harness ``` @@ -36,4 +37,5 @@ Since there is an already existing corpus, to start fuzzing with it simply do `. ## Notes + For more context regarding fuzzing check out the app-boilerplate fuzzing [README.md](https://github.com/LedgerHQ/app-boilerplate/blob/master/fuzzing/README.md) diff --git a/fuzzing/src/signMsg_harness.c b/fuzzing/src/signMsg_harness.c new file mode 100644 index 00000000..258a0b9c --- /dev/null +++ b/fuzzing/src/signMsg_harness.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include +#include + +uint8_t G_io_apdu_buffer[IO_APDU_BUFFER_SIZE]; + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + + UX_INIT(); + + uint8_t *input = NULL; + bool is_first = true; + + while (size > 5) { + io_state = IO_EXPECT_NONE; + uint8_t ins = data[0]; + uint8_t p1 = data[1]; + uint8_t p2 = data[2]; + uint8_t lc = data[3]; + + data += sizeof(uint8_t) * 4; + size -= sizeof(uint8_t) * 4; + + if (size < lc) { + return 0; + } + + uint8_t *input = malloc(lc); + if (input == NULL) { + return 0; + } + + memcpy(input, data, lc); + + data += lc; + size -= lc; + + BEGIN_TRY { + TRY { signMsg_handleAPDU(p1, p2, input, lc, is_first); } + CATCH_ALL {} + FINALLY {} + } + END_TRY; + + is_first = false; + free(input); + } + return 0; +} \ No newline at end of file diff --git a/ledger_app.toml b/ledger_app.toml index acd4ba11..8832868a 100644 --- a/ledger_app.toml +++ b/ledger_app.toml @@ -2,3 +2,4 @@ build_directory = "./" sdk = "C" devices = ["nanos", "nanox", "nanos+", "stax"] + diff --git a/src/addressUtilsByron.c b/src/addressUtilsByron.c index 078706e8..bb3c01a1 100644 --- a/src/addressUtilsByron.c +++ b/src/addressUtilsByron.c @@ -7,6 +7,15 @@ #include "crc32.h" #include "bufView.h" +#if defined(APP_FEATURE_BYRON_ADDRESS_DERIVATION) || defined(APP_FEATURE_BYRON_PROTOCOL_MAGIC_CHECK) + +static const size_t ADDRESS_ROOT_SIZE = 28; +static const size_t PROTOCOL_MAGIC_ADDRESS_ATTRIBUTE_KEY = 2; + +#endif + +#ifdef APP_FEATURE_BYRON_ADDRESS_DERIVATION + enum { CARDANO_ADDRESS_TYPE_PUBKEY = 0, /* @@ -15,9 +24,6 @@ enum { */ }; -static const size_t ADDRESS_ROOT_SIZE = 28; -static const size_t PROTOCOL_MAGIC_ADDRESS_ATTRIBUTE_KEY = 2; - void addressRootFromExtPubKey( const extendedPublicKey_t* extPubKey, uint8_t* outBuffer, size_t outSize @@ -31,7 +37,7 @@ void addressRootFromExtPubKey( { // [0, [0, publicKey:chainCode], Map(0)] - // TODO(ppershing): what are the first two 0 constants? + // Note(ppershing): what are the first two 0 constants? view_appendToken(&cbor, CBOR_TYPE_ARRAY, 3); { view_appendToken(&cbor, CBOR_TYPE_UNSIGNED, CARDANO_ADDRESS_TYPE_PUBKEY); @@ -136,6 +142,56 @@ size_t cborPackRawAddressWithChecksum( return view_processedSize(&output); } +size_t deriveRawAddress( + const bip44_path_t* pathSpec, uint32_t protocolMagic, + uint8_t* outBuffer, size_t outSize +) +{ + ASSERT(outSize < BUFFER_SIZE_PARANOIA); + + uint8_t addressRoot[28] = {0}; + { + extendedPublicKey_t extPubKey; + + deriveExtendedPublicKey(pathSpec, &extPubKey); + + addressRootFromExtPubKey( + &extPubKey, + addressRoot, SIZEOF(addressRoot) + ); + } + + return cborEncodePubkeyAddressInner( + addressRoot, SIZEOF(addressRoot), + protocolMagic, + outBuffer, outSize + ); +} + +size_t deriveAddress_byron( + const bip44_path_t* pathSpec, uint32_t protocolMagic, + uint8_t* outBuffer, size_t outSize +) +{ + ASSERT(outSize < BUFFER_SIZE_PARANOIA); + + uint8_t rawAddressBuffer[40] = {0}; + size_t rawAddressSize = deriveRawAddress( + pathSpec, protocolMagic, + rawAddressBuffer, SIZEOF(rawAddressBuffer) + ); + + return cborPackRawAddressWithChecksum( + rawAddressBuffer, rawAddressSize, + outBuffer, outSize + ); + +} + +#endif // APP_FEATURE_BYRON_ADDRESS_DERIVATION + +#ifdef APP_FEATURE_BYRON_PROTOCOL_MAGIC_CHECK + static uint64_t parseToken(read_view_t* view, uint8_t type) { const cbor_token_t token = view_parseToken(view); @@ -166,7 +222,6 @@ static size_t parseBytesSizeToken(read_view_t* view) return parsedSizeDowncasted; } - uint32_t extractProtocolMagic( const uint8_t* addressBuffer, size_t addressSize ) @@ -252,48 +307,4 @@ uint32_t extractProtocolMagic( return protocolMagic; } -size_t deriveRawAddress( - const bip44_path_t* pathSpec, uint32_t protocolMagic, - uint8_t* outBuffer, size_t outSize -) -{ - ASSERT(outSize < BUFFER_SIZE_PARANOIA); - - uint8_t addressRoot[28] = {0}; - { - extendedPublicKey_t extPubKey; - - deriveExtendedPublicKey(pathSpec, &extPubKey); - - addressRootFromExtPubKey( - &extPubKey, - addressRoot, SIZEOF(addressRoot) - ); - } - - return cborEncodePubkeyAddressInner( - addressRoot, SIZEOF(addressRoot), - protocolMagic, - outBuffer, outSize - ); -} - -size_t deriveAddress_byron( - const bip44_path_t* pathSpec, uint32_t protocolMagic, - uint8_t* outBuffer, size_t outSize -) -{ - ASSERT(outSize < BUFFER_SIZE_PARANOIA); - - uint8_t rawAddressBuffer[40] = {0}; - size_t rawAddressSize = deriveRawAddress( - pathSpec, protocolMagic, - rawAddressBuffer, SIZEOF(rawAddressBuffer) - ); - - return cborPackRawAddressWithChecksum( - rawAddressBuffer, rawAddressSize, - outBuffer, outSize - ); - -} +#endif // APP_FEATURE_BYRON_PROTOCOL_MAGIC_CHECK diff --git a/src/addressUtilsByron.h b/src/addressUtilsByron.h index e5d275a0..d107993c 100644 --- a/src/addressUtilsByron.h +++ b/src/addressUtilsByron.h @@ -4,19 +4,27 @@ #include "common.h" #include "bip44.h" +#ifdef APP_FEATURE_BYRON_ADDRESS_DERIVATION + size_t deriveAddress_byron( const bip44_path_t* pathSpec, uint32_t protocolMagic, uint8_t* outBuffer, size_t outSize ); +#endif // APP_FEATURE_BYRON_ADDRESS_DERIVATION + +#ifdef APP_FEATURE_BYRON_PROTOCOL_MAGIC_CHECK + // Note: validates the overall address structure at the same time uint32_t extractProtocolMagic( const uint8_t* addressBuffer, size_t addressSize ); +#endif // APP_FEATURE_BYRON_PROTOCOL_MAGIC_CHECK + -#ifdef DEVEL +#if defined(DEVEL) && !defined(APP_XS) void run_addressUtilsByron_test(); #endif // DEVEL diff --git a/src/addressUtilsByron_test.c b/src/addressUtilsByron_test.c index 945418e5..444e3bb6 100644 --- a/src/addressUtilsByron_test.c +++ b/src/addressUtilsByron_test.c @@ -1,4 +1,4 @@ -#ifdef DEVEL +#if defined(DEVEL) && !defined(APP_XS) #include "addressUtilsByron.h" #include "cardano.h" diff --git a/src/addressUtilsShelley.c b/src/addressUtilsShelley.c index 7740708e..36ece7a4 100644 --- a/src/addressUtilsShelley.c +++ b/src/addressUtilsShelley.c @@ -200,17 +200,17 @@ static size_t deriveAddress_base(const addressParams_t* addressParams, uint8_t* view_appendBuffer(&out, &header, 1); ++size; } - STATIC_ASSERT(SIZEOF(addressParams->spendingScriptHash) == SCRIPT_HASH_LENGTH, "bad spending script hash size"); + STATIC_ASSERT(SIZEOF(addressParams->paymentScriptHash) == SCRIPT_HASH_LENGTH, "bad payment script hash size"); switch (addressParams->type) { case BASE_PAYMENT_KEY_STAKE_KEY: case BASE_PAYMENT_KEY_STAKE_SCRIPT: { - view_appendAddressPublicKeyHash(&out, &addressParams->spendingKeyPath); + view_appendAddressPublicKeyHash(&out, &addressParams->paymentKeyPath); size += ADDRESS_KEY_HASH_LENGTH; } break; case BASE_PAYMENT_SCRIPT_STAKE_KEY: case BASE_PAYMENT_SCRIPT_STAKE_SCRIPT: { - view_appendBuffer(&out, addressParams->spendingScriptHash, SCRIPT_HASH_LENGTH); + view_appendBuffer(&out, addressParams->paymentScriptHash, SCRIPT_HASH_LENGTH); size += SCRIPT_HASH_LENGTH; } break; @@ -296,9 +296,9 @@ static size_t deriveAddress_pointer( } { if (addressType == POINTER_KEY) { - view_appendAddressPublicKeyHash(&out, &addressParams->spendingKeyPath); + view_appendAddressPublicKeyHash(&out, &addressParams->paymentKeyPath); } else { - view_appendBuffer(&out, addressParams->spendingScriptHash, SCRIPT_HASH_LENGTH); + view_appendBuffer(&out, addressParams->paymentScriptHash, SCRIPT_HASH_LENGTH); } STATIC_ASSERT(SCRIPT_HASH_LENGTH == ADDRESS_KEY_HASH_LENGTH, "incompatible hash lengths"); @@ -332,9 +332,9 @@ static size_t deriveAddress_enterprise( } { if (addressType == ENTERPRISE_KEY) { - view_appendAddressPublicKeyHash(&out, &addressParams->spendingKeyPath); + view_appendAddressPublicKeyHash(&out, &addressParams->paymentKeyPath); } else { - view_appendBuffer(&out, addressParams->spendingScriptHash, SCRIPT_HASH_LENGTH); + view_appendBuffer(&out, addressParams->paymentScriptHash, SCRIPT_HASH_LENGTH); } } { @@ -365,7 +365,7 @@ static size_t deriveAddress_reward( view_appendBuffer(&out, &addressHeader, 1); } { - // no spending data + // no payment data } { if (addressType == REWARD_KEY) { @@ -439,8 +439,6 @@ size_t deriveAddress(const addressParams_t* addressParams, uint8_t* outBuffer, s ASSERT(outSize < BUFFER_SIZE_PARANOIA); ASSERT(isValidAddressParams(addressParams)); - const bip44_path_t* spendingPath = &addressParams->spendingKeyPath; - // shelley switch (addressParams->type) { case BASE_PAYMENT_KEY_STAKE_KEY: @@ -458,8 +456,16 @@ size_t deriveAddress(const addressParams_t* addressParams, uint8_t* outBuffer, s case REWARD_KEY: case REWARD_SCRIPT: return deriveAddress_reward(addressParams, outBuffer, outSize); + + #ifdef APP_FEATURE_BYRON_ADDRESS_DERIVATION case BYRON: - return deriveAddress_byron(spendingPath, addressParams->protocolMagic, outBuffer, outSize); + return deriveAddress_byron( + &addressParams->paymentKeyPath, + addressParams->protocolMagic, + outBuffer, outSize + ); + #endif // APP_FEATURE_BYRON_ADDRESS_DERIVATION + default: ASSERT(false); } @@ -535,7 +541,7 @@ size_t humanReadableAddress(const uint8_t* address, size_t addressSize, char* ou * protocol magic 4B * else * network id 1B - * spending public key derivation path (1B for length + [0-10] x 4B) + * payment public key derivation path (1B for length + [0-10] x 4B) * staking choice 1B * if NO_STAKING: * nothing more @@ -564,15 +570,15 @@ void view_parseAddressParams(read_view_t* view, addressParams_t* params) TRACE("Network id: 0x%x", params->networkId); VALIDATE(isValidNetworkId(params->networkId), ERR_INVALID_DATA); } - // spending part + // payment part switch (params->type) { case BASE_PAYMENT_KEY_STAKE_KEY: case BASE_PAYMENT_KEY_STAKE_SCRIPT: case POINTER_KEY: case ENTERPRISE_KEY: case BYRON: - view_skipBytes(view, bip44_parseFromWire(¶ms->spendingKeyPath, VIEW_REMAINING_TO_TUPLE_BUF_SIZE(view))); - BIP44_PRINTF(¶ms->spendingKeyPath); + view_skipBytes(view, bip44_parseFromWire(¶ms->paymentKeyPath, VIEW_REMAINING_TO_TUPLE_BUF_SIZE(view))); + BIP44_PRINTF(¶ms->paymentKeyPath); PRINTF("\n"); break; @@ -580,16 +586,16 @@ void view_parseAddressParams(read_view_t* view, addressParams_t* params) case BASE_PAYMENT_SCRIPT_STAKE_SCRIPT: case POINTER_SCRIPT: case ENTERPRISE_SCRIPT: { - STATIC_ASSERT(SIZEOF(params->spendingScriptHash) == SCRIPT_HASH_LENGTH, "Wrong address key hash length"); - view_parseBuffer(params->spendingScriptHash, view, SCRIPT_HASH_LENGTH); - TRACE("Spending script hash: "); - TRACE_BUFFER(params->spendingScriptHash, SIZEOF(params->spendingScriptHash)); + STATIC_ASSERT(SIZEOF(params->paymentScriptHash) == SCRIPT_HASH_LENGTH, "Wrong address key hash length"); + view_parseBuffer(params->paymentScriptHash, view, SCRIPT_HASH_LENGTH); + TRACE("Payment script hash: "); + TRACE_BUFFER(params->paymentScriptHash, SIZEOF(params->paymentScriptHash)); break; } case REWARD_KEY: case REWARD_SCRIPT: - // no spending info for reward address types + // no payment info for reward address types break; default: @@ -658,21 +664,21 @@ static inline bool isValidStakingInfo(const addressParams_t* params) #undef CHECK } -static inline bool isValidSpendingInfo(const addressParams_t* params) +static inline bool isValidPaymentInfo(const addressParams_t* params) { #define CHECK(cond) if (!(cond)) return false switch (params->type) { case BYRON: - CHECK(bip44_classifyPath(¶ms->spendingKeyPath) == PATH_ORDINARY_SPENDING_KEY); - CHECK(bip44_hasByronPrefix(¶ms->spendingKeyPath)); + CHECK(bip44_classifyPath(¶ms->paymentKeyPath) == PATH_ORDINARY_PAYMENT_KEY); + CHECK(bip44_hasByronPrefix(¶ms->paymentKeyPath)); break; case BASE_PAYMENT_KEY_STAKE_KEY: case BASE_PAYMENT_KEY_STAKE_SCRIPT: case POINTER_KEY: case ENTERPRISE_KEY: - CHECK(bip44_classifyPath(¶ms->spendingKeyPath) == PATH_ORDINARY_SPENDING_KEY); - CHECK(bip44_hasShelleyPrefix(¶ms->spendingKeyPath)); + CHECK(bip44_classifyPath(¶ms->paymentKeyPath) == PATH_ORDINARY_PAYMENT_KEY); + CHECK(bip44_hasShelleyPrefix(¶ms->paymentKeyPath)); break; @@ -698,16 +704,22 @@ bool isValidAddressParams(const addressParams_t* params) #define CHECK(cond) if (!(cond)) return false if (params->type != BYRON) { CHECK(isValidNetworkId(params->networkId)); + } else { + // code for Byron address derivation not available in XS app + // thus we cannot process address params + #ifndef APP_FEATURE_BYRON_ADDRESS_DERIVATION + return false; + #endif } CHECK(isValidStakingInfo(params)); - CHECK(isValidSpendingInfo(params)); + CHECK(isValidPaymentInfo(params)); return true; #undef CHECK } -spending_choice_t determineSpendingChoice(address_type_t addressType) +payment_choice_t determinePaymentChoice(address_type_t addressType) { switch (addressType) { @@ -716,19 +728,19 @@ spending_choice_t determineSpendingChoice(address_type_t addressType) case POINTER_KEY: case ENTERPRISE_KEY: case BYRON: - return SPENDING_PATH; + return PAYMENT_PATH; case BASE_PAYMENT_SCRIPT_STAKE_KEY: case BASE_PAYMENT_SCRIPT_STAKE_SCRIPT: case POINTER_SCRIPT: case ENTERPRISE_SCRIPT: - return SPENDING_SCRIPT_HASH; + return PAYMENT_SCRIPT_HASH; default: ASSERT(false); __attribute__((fallthrough)); case REWARD_KEY: case REWARD_SCRIPT: - return SPENDING_NONE; + return PAYMENT_NONE; } } diff --git a/src/addressUtilsShelley.h b/src/addressUtilsShelley.h index d1723a29..44be6f95 100644 --- a/src/addressUtilsShelley.h +++ b/src/addressUtilsShelley.h @@ -54,10 +54,10 @@ typedef enum { bool isValidStakingChoice(staking_data_source_t stakingDataSource); typedef enum { - SPENDING_PATH, - SPENDING_SCRIPT_HASH, - SPENDING_NONE, -} spending_choice_t; + PAYMENT_PATH, + PAYMENT_SCRIPT_HASH, + PAYMENT_NONE, +} payment_choice_t; typedef uint32_t blockchainIndex_t; // must be unsigned @@ -74,8 +74,8 @@ typedef struct { uint8_t networkId; // all the other types (i.e. Shelley) }; union { - bip44_path_t spendingKeyPath; - uint8_t spendingScriptHash[SCRIPT_HASH_LENGTH]; + bip44_path_t paymentKeyPath; + uint8_t paymentScriptHash[SCRIPT_HASH_LENGTH]; }; staking_data_source_t stakingDataSource; union { @@ -116,7 +116,7 @@ size_t humanReadableAddress(const uint8_t* address, size_t addressSize, char* ou void view_parseAddressParams(read_view_t* view, addressParams_t* params); bool isValidAddressParams(const addressParams_t* addressParams); -spending_choice_t determineSpendingChoice(address_type_t addressType); +payment_choice_t determinePaymentChoice(address_type_t addressType); #ifdef DEVEL void run_addressUtilsShelley_test(); diff --git a/src/addressUtilsShelley_test.c b/src/addressUtilsShelley_test.c index 9b2b91ba..1b4f54c5 100644 --- a/src/addressUtilsShelley_test.c +++ b/src/addressUtilsShelley_test.c @@ -17,7 +17,7 @@ static void pathSpec_init(bip44_path_t* pathSpec, const uint32_t* pathArray, uin // networkIdOrProtocolMagic is used as networkId for Shelley addresses and as protocol magic for Byron addresses static void testcase_deriveAddressShelley( - uint8_t type, uint32_t networkIdOrProtocolMagic, const uint32_t* spendingPathArray, size_t spendingPathLen, + uint8_t type, uint32_t networkIdOrProtocolMagic, const uint32_t* paymentPathArray, size_t paymentPathLen, uint8_t stakingDataSource, const uint32_t* stakingPathArray, size_t stakingPathLen, const char* stakingKeyHashHex, const blockchainPointer_t* stakingKeyBlockchainPointer, const char* expectedHex) @@ -56,7 +56,7 @@ static void testcase_deriveAddressShelley( }; } // the rest of params is initialized to zero - pathSpec_init(¶ms.spendingKeyPath, spendingPathArray, spendingPathLen); + pathSpec_init(¶ms.paymentKeyPath, paymentPathArray, paymentPathLen); if (stakingPathLen > 0) pathSpec_init(¶ms.stakingKeyPath, stakingPathArray, stakingPathLen); if (stakingKeyHashHex != NULL) { @@ -73,7 +73,7 @@ static void testcase_deriveAddressShelley( PRINTF("testcase_deriveAddressShelley 0x%02x ", constructShelleyAddressHeader(type, (uint8_t) networkIdOrProtocolMagic)); } - BIP44_PRINTF(¶ms.spendingKeyPath); + BIP44_PRINTF(¶ms.paymentKeyPath); PRINTF("\n"); if (stakingDataSource == STAKING_KEY_PATH) { @@ -104,16 +104,16 @@ static void testcase_deriveAddressShelley( } // test addresses for Shelley are generated by our Trezor implementation -// (from public keys derived by Ledger from the given spending and staking paths) +// (from public keys derived by Ledger from the given payment and staking paths) static void testAddressDerivation() { #define NO_STAKING_KEY_PATH () #define NO_STAKING_KEY_HASH NULL -#define TESTCASE(type_, networkIdOrProtocolMagic_, spendingPath_, stakingChoice_, stakingPath_, stakingKeyHashHex_, expected_) \ +#define TESTCASE(type_, networkIdOrProtocolMagic_, paymentPath_, stakingChoice_, stakingPath_, stakingKeyHashHex_, expected_) \ { \ - uint32_t spendingPath[] = { UNWRAP spendingPath_ }; \ + uint32_t paymentPath[] = { UNWRAP paymentPath_ }; \ uint32_t stakingPath[] = { UNWRAP stakingPath_ }; \ - testcase_deriveAddressShelley(type_, networkIdOrProtocolMagic_, spendingPath, ARRAY_LEN(spendingPath), stakingChoice_, stakingPath, ARRAY_LEN(stakingPath), stakingKeyHashHex_, NULL, expected_); \ + testcase_deriveAddressShelley(type_, networkIdOrProtocolMagic_, paymentPath, ARRAY_LEN(paymentPath), stakingChoice_, stakingPath, ARRAY_LEN(stakingPath), stakingKeyHashHex_, NULL, expected_); \ } TESTCASE( @@ -168,11 +168,11 @@ static void testAddressDerivation() #undef NO_STAKING_KEY_PATH #undef NO_STAKING_KEY_HASH -#define TESTCASE_POINTER(type_, networkId_, spendingPath_, stakingKeyBlockchainPointer_, expected_) \ +#define TESTCASE_POINTER(type_, networkId_, paymentPath_, stakingKeyBlockchainPointer_, expected_) \ { \ - uint32_t spendingPath[] = { UNWRAP spendingPath_ }; \ + uint32_t paymentPath[] = { UNWRAP paymentPath_ }; \ blockchainPointer_t stakingKeyBlockchainPointer = { UNWRAP stakingKeyBlockchainPointer_ }; \ - testcase_deriveAddressShelley(type_, networkId_, spendingPath, ARRAY_LEN(spendingPath), BLOCKCHAIN_POINTER, NULL, 0, NULL, &stakingKeyBlockchainPointer, expected_); \ + testcase_deriveAddressShelley(type_, networkId_, paymentPath, ARRAY_LEN(paymentPath), BLOCKCHAIN_POINTER, NULL, 0, NULL, &stakingKeyBlockchainPointer, expected_); \ } TESTCASE_POINTER( diff --git a/src/bip44.c b/src/bip44.c index 883a98e9..e59a597f 100644 --- a/src/bip44.c +++ b/src/bip44.c @@ -4,9 +4,12 @@ #include "hash.h" #include "keyDerivation.h" -#define CARDANO_CHAIN_EXTERNAL 0 -#define CARDANO_CHAIN_INTERNAL 1 -#define CARDANO_CHAIN_STAKING_KEY 2 +static const uint32_t CARDANO_CHAIN_EXTERNAL = 0; +static const uint32_t CARDANO_CHAIN_INTERNAL = 1; +static const uint32_t CARDANO_CHAIN_STAKING_KEY = 2; +static const uint32_t CARDANO_CHAIN_DREP_KEY = 3; +static const uint32_t CARDANO_CHAIN_COMMITTEE_COLD_KEY = 4; +static const uint32_t CARDANO_CHAIN_COMMITTEE_HOT_KEY = 5; static const uint32_t MAX_REASONABLE_ACCOUNT = 100; static const uint32_t MAX_REASONABLE_ADDRESS = 1000000; @@ -213,6 +216,20 @@ static bool bip44_hasReasonableAddress(const bip44_path_t* pathSpec) return (address <= MAX_REASONABLE_ADDRESS); } +static bool bip44_isConwayPathRecommended(const bip44_path_t* pathSpec) +{ + switch (bip44_classifyPath(pathSpec)) { + case PATH_DREP_KEY: + case PATH_COMMITTEE_COLD_KEY: + case PATH_COMMITTEE_HOT_KEY: + // strongly recommended in CIP-0105 to only use 0 as address + return (bip44_getAddressValue(pathSpec) == 0); + default: + ASSERT(false); + return false; + } +} + static bool bip44_containsMoreThanAddress(const bip44_path_t* pathSpec) { return (pathSpec->length > BIP44_I_ADDRESS + 1); @@ -252,6 +269,48 @@ bool bip44_isMultidelegationStakingKeyPath(const bip44_path_t* pathSpec) && (bip44_getAddressValue(pathSpec) > 0); } +bool bip44_isDRepKeyPath(const bip44_path_t* pathSpec) +{ +#define CHECK(cond) if (!(cond)) return false + CHECK(bip44_containsAddress(pathSpec)); + CHECK(!bip44_containsMoreThanAddress(pathSpec)); + CHECK(bip44_hasShelleyPrefix(pathSpec)); + CHECK(isHardened(bip44_getAccount(pathSpec))); + CHECK(bip44_getChainTypeValue(pathSpec) == CARDANO_CHAIN_DREP_KEY); + // is it strongly recommended (but not forbidden) to only use 0 as address + CHECK(!isHardened(bip44_getAddressValue(pathSpec))); + return true; +#undef CHECK +} + +bool bip44_isCommitteeColdKeyPath(const bip44_path_t* pathSpec) +{ +#define CHECK(cond) if (!(cond)) return false + CHECK(bip44_containsAddress(pathSpec)); + CHECK(!bip44_containsMoreThanAddress(pathSpec)); + CHECK(bip44_hasShelleyPrefix(pathSpec)); + CHECK(isHardened(bip44_getAccount(pathSpec))); + CHECK(bip44_getChainTypeValue(pathSpec) == CARDANO_CHAIN_COMMITTEE_COLD_KEY); + // is it strongly recommended (but not forbidden) to only use 0 as address + CHECK(!isHardened(bip44_getAddressValue(pathSpec))); + return true; +#undef CHECK +} + +bool bip44_isCommitteeHotKeyPath(const bip44_path_t* pathSpec) +{ +#define CHECK(cond) if (!(cond)) return false + CHECK(bip44_containsAddress(pathSpec)); + CHECK(!bip44_containsMoreThanAddress(pathSpec)); + CHECK(bip44_hasShelleyPrefix(pathSpec)); + CHECK(isHardened(bip44_getAccount(pathSpec))); + CHECK(bip44_getChainTypeValue(pathSpec) == CARDANO_CHAIN_COMMITTEE_HOT_KEY); + // is it strongly recommended (but not forbidden) to only use 0 as address + CHECK(!isHardened(bip44_getAddressValue(pathSpec))); + return true; +#undef CHECK +} + bool bip44_isMintKeyPath(const bip44_path_t* pathSpec) { #define CHECK(cond) if (!(cond)) return false @@ -360,13 +419,28 @@ static bip44_path_type_t bip44_classifyOrdinaryWalletPath(const bip44_path_t* pa // so we don't want to make users' funds on such addresses unavailable. // But such addresses are given a warning // and are never hidden from users (see bip44_isPathReasonable). - return PATH_ORDINARY_SPENDING_KEY; + return PATH_ORDINARY_PAYMENT_KEY; case CARDANO_CHAIN_STAKING_KEY: return bip44_isOrdinaryStakingKeyPath(pathSpec) ? PATH_ORDINARY_STAKING_KEY : PATH_INVALID; + case CARDANO_CHAIN_DREP_KEY: + return bip44_isDRepKeyPath(pathSpec) ? + PATH_DREP_KEY : + PATH_INVALID; + + case CARDANO_CHAIN_COMMITTEE_COLD_KEY: + return bip44_isCommitteeColdKeyPath(pathSpec) ? + PATH_COMMITTEE_COLD_KEY : + PATH_INVALID; + + case CARDANO_CHAIN_COMMITTEE_HOT_KEY: + return bip44_isCommitteeHotKeyPath(pathSpec) ? + PATH_COMMITTEE_HOT_KEY : + PATH_INVALID; + default: return PATH_INVALID; } @@ -401,7 +475,7 @@ static bip44_path_type_t bip44_classifyMultisigWalletPath(const bip44_path_t* pa // address index must not be hardened (CIP 1854) return PATH_INVALID; } - return PATH_MULTISIG_SPENDING_KEY; + return PATH_MULTISIG_PAYMENT_KEY; case CARDANO_CHAIN_STAKING_KEY: return bip44_isMultisigStakingKeyPath(pathSpec) ? @@ -484,14 +558,21 @@ bool bip44_isPathReasonable(const bip44_path_t* pathSpec) case PATH_MULTISIG_ACCOUNT: return bip44_hasReasonableAccount(pathSpec); - case PATH_ORDINARY_SPENDING_KEY: - case PATH_MULTISIG_SPENDING_KEY: + case PATH_ORDINARY_PAYMENT_KEY: + case PATH_MULTISIG_PAYMENT_KEY: return bip44_hasReasonableAccount(pathSpec) && bip44_hasReasonableAddress(pathSpec); case PATH_ORDINARY_STAKING_KEY: case PATH_MULTISIG_STAKING_KEY: return bip44_hasReasonableAccount(pathSpec) && bip44_hasReasonableAddress(pathSpec); + case PATH_DREP_KEY: + case PATH_COMMITTEE_COLD_KEY: + case PATH_COMMITTEE_HOT_KEY: + return bip44_hasReasonableAccount(pathSpec) + && bip44_hasReasonableAddress(pathSpec) + && bip44_isConwayPathRecommended(pathSpec); + case PATH_MINT_KEY: return bip44_hasReasonableMintPolicy(pathSpec); diff --git a/src/bip44.h b/src/bip44.h index f8629356..800763ca 100644 --- a/src/bip44.h +++ b/src/bip44.h @@ -79,12 +79,17 @@ bool bip44_isOrdinaryStakingKeyPath(const bip44_path_t* pathSpec); bool bip44_isMultisigStakingKeyPath(const bip44_path_t* pathSpec); bool bip44_isMultidelegationStakingKeyPath(const bip44_path_t* pathSpec); +bool bip44_isDRepKeyPath(const bip44_path_t* pathSpec); +bool bip44_isCommitteeColdKeyPath(const bip44_path_t* pathSpec); +bool bip44_isCommitteeHotKeyPath(const bip44_path_t* pathSpec); + bool bip44_isMintKeyPath(const bip44_path_t* pathSpec); bool bip44_isPoolColdKeyPath(const bip44_path_t* pathSpec); bool bip44_isCVoteKeyPath(const bip44_path_t* pathSpec); + size_t bip44_printToStr(const bip44_path_t*, char* out, size_t outSize); @@ -94,13 +99,24 @@ typedef enum { PATH_MULTISIG_ACCOUNT, // hd wallet address (payment part in shelley) - PATH_ORDINARY_SPENDING_KEY, - PATH_MULTISIG_SPENDING_KEY, + PATH_ORDINARY_PAYMENT_KEY, + PATH_MULTISIG_PAYMENT_KEY, // hd wallet reward address, withdrawal witness, pool owner PATH_ORDINARY_STAKING_KEY, PATH_MULTISIG_STAKING_KEY, + // DRep key + // m / 1852' / 1815' / account' / 3 / address_index + PATH_DREP_KEY, + + // constitutional committee hot key + // m / 1852' / 1815' / account' / 4 / address_index + PATH_COMMITTEE_COLD_KEY, + // constitutional committee cold key + // m / 1852' / 1815' / account' / 5 / address_index + PATH_COMMITTEE_HOT_KEY, + // native token minting/burning PATH_MINT_KEY, diff --git a/src/bufView.h b/src/bufView.h index 53c1d9ef..00735a41 100644 --- a/src/bufView.h +++ b/src/bufView.h @@ -142,5 +142,18 @@ static inline int64_t parse_int64be(read_view_t* view) return (int64_t) parse_u8be(view); }; +static inline bool parse_bool(read_view_t* view) +{ + uint8_t value = parse_u1be(view); + + switch (value) { + case 0: + return false; + case 1: + return true; + default: + THROW(ERR_INVALID_DATA); + } +} #endif // H_CARDANO_APP_BUF_VIEW diff --git a/src/cardano.h b/src/cardano.h index f0cfe29b..390b9f8a 100644 --- a/src/cardano.h +++ b/src/cardano.h @@ -13,6 +13,8 @@ STATIC_ASSERT(LOVELACE_MAX_SUPPLY < LOVELACE_INVALID, "bad LOVELACE_INVALID"); +#define ED25519_SIGNATURE_LENGTH 64 + #define ADDRESS_KEY_HASH_LENGTH 28 #define POOL_KEY_HASH_LENGTH 28 #define VRF_KEY_HASH_LENGTH 32 @@ -24,6 +26,7 @@ STATIC_ASSERT(LOVELACE_MAX_SUPPLY < LOVELACE_INVALID, "bad LOVELACE_INVALID"); #define SCRIPT_HASH_LENGTH 28 #define SCRIPT_DATA_HASH_LENGTH 32 #define OUTPUT_DATUM_HASH_LENGTH 32 +#define ANCHOR_HASH_LENGTH 32 #define MINTING_POLICY_ID_SIZE (SCRIPT_HASH_LENGTH) #define ASSET_NAME_SIZE_MAX 32 @@ -100,8 +103,10 @@ typedef struct { // ============================== CERTIFICATES ============================== -#define POOL_METADATA_URL_LENGTH_MAX 64 -#define DNS_NAME_SIZE_MAX 64 +#define ANCHOR_URL_LENGTH_MAX 128 + +#define POOL_METADATA_URL_LENGTH_MAX 128 +#define DNS_NAME_SIZE_MAX 128 #define IPV4_SIZE 4 #define IPV6_SIZE 16 @@ -111,20 +116,21 @@ typedef struct { // there may be other types we do not support typedef enum { - CERTIFICATE_TYPE_STAKE_REGISTRATION = 0, - CERTIFICATE_TYPE_STAKE_DEREGISTRATION = 1, - CERTIFICATE_TYPE_STAKE_DELEGATION = 2, - CERTIFICATE_TYPE_STAKE_POOL_REGISTRATION = 3, - CERTIFICATE_TYPE_STAKE_POOL_RETIREMENT = 4, + CERTIFICATE_STAKE_REGISTRATION = 0, + CERTIFICATE_STAKE_DEREGISTRATION = 1, + CERTIFICATE_STAKE_DELEGATION = 2, + CERTIFICATE_STAKE_POOL_REGISTRATION = 3, + CERTIFICATE_STAKE_POOL_RETIREMENT = 4, + CERTIFICATE_STAKE_REGISTRATION_CONWAY = 7, + CERTIFICATE_STAKE_DEREGISTRATION_CONWAY = 8, + CERTIFICATE_VOTE_DELEGATION = 9, + CERTIFICATE_AUTHORIZE_COMMITTEE_HOT = 14, + CERTIFICATE_RESIGN_COMMITTEE_COLD = 15, + CERTIFICATE_DREP_REGISTRATION = 16, + CERTIFICATE_DREP_DEREGISTRATION = 17, + CERTIFICATE_DREP_UPDATE = 18, } certificate_type_t; -typedef enum { - // enum values are affected by backwards-compatibility - STAKE_CREDENTIAL_KEY_PATH = 0, - STAKE_CREDENTIAL_KEY_HASH = 2, - STAKE_CREDENTIAL_SCRIPT_HASH = 1, -} stake_credential_type_t; - typedef enum { RELAY_SINGLE_HOST_IP = 0, RELAY_SINGLE_HOST_NAME = 1, @@ -160,6 +166,8 @@ typedef struct { // ============================== NATIVE SCRIPTS ============================== +#ifdef APP_FEATURE_NATIVE_SCRIPT_HASH + // depth of n means it can handle up to n-1 levels of nesting #define MAX_SCRIPT_DEPTH 11 @@ -172,4 +180,13 @@ typedef enum { NATIVE_SCRIPT_INVALID_HEREAFTER = 5, } native_script_type; +#endif // APP_FEATURE_NATIVE_SCRIPT_HASH + +// ============================== CIP8 MESSAGE SIGNING ============================== + +typedef enum { + CIP8_ADDRESS_FIELD_ADDRESS = 1, + CIP8_ADDRESS_FIELD_KEYHASH = 2, +} cip8_address_field_type_t; + #endif // H_CARDANO_APP_CARDANO diff --git a/src/cbor.h b/src/cbor.h index 2614c882..1c7066eb 100644 --- a/src/cbor.h +++ b/src/cbor.h @@ -40,6 +40,8 @@ typedef enum { enum { CBOR_TAG_EMBEDDED_CBOR_BYTE_STRING = 24, + CBOR_TAG_UNIT_INTERVAL = 30, + CBOR_TAG_SET = 258, }; typedef struct { diff --git a/src/deriveAddress.c b/src/deriveAddress.c index 193f9df9..93c3cf4f 100644 --- a/src/deriveAddress.c +++ b/src/deriveAddress.c @@ -58,16 +58,16 @@ static void _displayExportAddress(ui_callback_fn_t* this_fn) #endif // HAVE_BAGL } -static void _displaySpendingInfo_returnAddr(ui_callback_fn_t* this_fn) +static void _displayPaymentInfo_returnAddr(ui_callback_fn_t* this_fn) { #ifdef HAVE_BAGL - ui_displaySpendingInfoScreen(&ctx->addressParams, this_fn); + ui_displayPaymentInfoScreen(&ctx->addressParams, this_fn); #elif defined(HAVE_NBGL) -#define SPENDING_INFO_SIZE MAX(BECH32_STRING_SIZE_MAX, BIP44_PATH_STRING_SIZE_MAX) +#define PAYMENT_INFO_SIZE MAX(BECH32_STRING_SIZE_MAX, BIP44_PATH_STRING_SIZE_MAX) char line1[30] = {0}; - char spendingInfo[SPENDING_INFO_SIZE] = {0}; - ui_getSpendingInfoScreen(line1, SIZEOF(line1), spendingInfo, SIZEOF(spendingInfo), &ctx->addressParams); - fill_and_display_if_required(line1, spendingInfo, this_fn, respond_with_user_reject); + char paymentInfo[PAYMENT_INFO_SIZE] = {0}; + ui_getPaymentInfoScreen(line1, SIZEOF(line1), paymentInfo, SIZEOF(paymentInfo), &ctx->addressParams); + fill_and_display_if_required(line1, paymentInfo, this_fn, respond_with_user_reject); #endif // HAVE_BAGL } @@ -108,7 +108,7 @@ static void deriveAddress_return_ui_runStep(); enum { RETURN_UI_STEP_WARNING = 100, RETURN_UI_STEP_BEGIN, - RETURN_UI_STEP_SPENDING_PATH, + RETURN_UI_STEP_PAYMENT_PATH, RETURN_UI_STEP_STAKING_INFO, RETURN_UI_STEP_CONFIRM, RETURN_UI_STEP_RESPOND, @@ -150,12 +150,12 @@ static void deriveAddress_return_ui_runStep() UI_STEP(RETURN_UI_STEP_BEGIN) { _displayExportAddress(this_fn); } - UI_STEP(RETURN_UI_STEP_SPENDING_PATH) { - if (determineSpendingChoice(ctx->addressParams.type) == SPENDING_NONE) { + UI_STEP(RETURN_UI_STEP_PAYMENT_PATH) { + if (determinePaymentChoice(ctx->addressParams.type) == PAYMENT_NONE) { // reward address UI_STEP_JUMP(RETURN_UI_STEP_STAKING_INFO); } - _displaySpendingInfo_returnAddr(this_fn); + _displayPaymentInfo_returnAddr(this_fn); } UI_STEP(RETURN_UI_STEP_STAKING_INFO) { _displayStakingInfo_returnAddr(this_fn); @@ -175,16 +175,16 @@ static void deriveAddress_return_ui_runStep() /* ========================== DISPLAY ADDRESS ========================== */ -static void _displaySpendingInfo_displayAddr(ui_callback_fn_t* this_fn) +static void _displaypaymentInfo_displayAddr(ui_callback_fn_t* this_fn) { #ifdef HAVE_BAGL - ui_displaySpendingInfoScreen(&ctx->addressParams, this_fn); + ui_displayPaymentInfoScreen(&ctx->addressParams, this_fn); #elif defined(HAVE_NBGL) -#define SPENDING_INFO_SIZE MAX(BECH32_STRING_SIZE_MAX, BIP44_PATH_STRING_SIZE_MAX) +#define PAYMENT_INFO_SIZE MAX(BECH32_STRING_SIZE_MAX, BIP44_PATH_STRING_SIZE_MAX) char line1[30] = {0}; - char spendingInfo[SPENDING_INFO_SIZE] = {0}; - ui_getSpendingInfoScreen(line1, SIZEOF(line1), spendingInfo, SIZEOF(spendingInfo), &ctx->addressParams); - fill_address_data(line1, spendingInfo, this_fn); + char paymentInfo[PAYMENT_INFO_SIZE] = {0}; + ui_getPaymentInfoScreen(line1, SIZEOF(line1), paymentInfo, SIZEOF(paymentInfo), &ctx->addressParams); + fill_address_data(line1, paymentInfo, this_fn); #endif // HAVE_BAGL } @@ -241,7 +241,7 @@ static void _displayConfirmAddressPrompt(ui_callback_fn_t* this_fn) static void deriveAddress_display_ui_runStep(); enum { DISPLAY_UI_STEP_WARNING = 200, - DISPLAY_UI_STEP_SPENDING_INFO, + DISPLAY_UI_STEP_PAYMENT_INFO, DISPLAY_UI_STEP_STAKING_INFO, DISPLAY_UI_STEP_ADDRESS, DISPLAY_UI_STEP_CONFIRM, @@ -261,7 +261,7 @@ static void deriveAddress_handleDisplay() switch (policy) { #define CASE(policy, step) case policy: {ctx->ui_step=step; break;} CASE(POLICY_PROMPT_WARN_UNUSUAL, DISPLAY_UI_STEP_WARNING); - CASE(POLICY_SHOW_BEFORE_RESPONSE, DISPLAY_UI_STEP_SPENDING_INFO); + CASE(POLICY_SHOW_BEFORE_RESPONSE, DISPLAY_UI_STEP_PAYMENT_INFO); #undef CASE default: THROW(ERR_NOT_IMPLEMENTED); @@ -279,12 +279,12 @@ static void deriveAddress_display_ui_runStep() UI_STEP(DISPLAY_UI_STEP_WARNING) { ui_displayUnusualWarning(this_fn); } - UI_STEP(DISPLAY_UI_STEP_SPENDING_INFO) { - if (determineSpendingChoice(ctx->addressParams.type) == SPENDING_NONE) { + UI_STEP(DISPLAY_UI_STEP_PAYMENT_INFO) { + if (determinePaymentChoice(ctx->addressParams.type) == PAYMENT_NONE) { // reward address UI_STEP_JUMP(DISPLAY_UI_STEP_STAKING_INFO); } - _displaySpendingInfo_displayAddr(this_fn); + _displaypaymentInfo_displayAddr(this_fn); } UI_STEP(DISPLAY_UI_STEP_STAKING_INFO) { _displayStakingInfo_displayAddr(this_fn); diff --git a/src/deriveNativeScriptHash.c b/src/deriveNativeScriptHash.c index cac061f9..68ffe28d 100644 --- a/src/deriveNativeScriptHash.c +++ b/src/deriveNativeScriptHash.c @@ -1,3 +1,5 @@ +#ifdef APP_FEATURE_NATIVE_SCRIPT_HASH + #include "deriveNativeScriptHash.h" #include "deriveNativeScriptHash_ui.h" #include "state.h" @@ -20,11 +22,13 @@ static inline bool areMoreScriptsExpected() // if the number of remaining scripts is not bigger than 0, then this request // is invalid in the current context, as Ledger was not expecting another // script to be parsed + ASSERT(ctx->level < MAX_SCRIPT_DEPTH); return ctx->complexScripts[ctx->level].remainingScripts > 0; } static inline bool isComplexScriptFinished() { + ASSERT(ctx->level < MAX_SCRIPT_DEPTH); return ctx->level > 0 && ctx->complexScripts[ctx->level].remainingScripts == 0; } @@ -34,6 +38,7 @@ static inline void complexScriptFinished() ASSERT(ctx->level > 0); ctx->level--; + ASSERT(ctx->level < MAX_SCRIPT_DEPTH); ASSERT(ctx->complexScripts[ctx->level].remainingScripts > 0); ctx->complexScripts[ctx->level].remainingScripts--; @@ -43,6 +48,7 @@ static inline void complexScriptFinished() static inline void simpleScriptFinished() { + ASSERT(ctx->level < MAX_SCRIPT_DEPTH); ASSERT(ctx->complexScripts[ctx->level].remainingScripts > 0); ctx->complexScripts[ctx->level].remainingScripts--; @@ -67,6 +73,7 @@ static void deriveNativeScriptHash_handleAll(read_view_t* view) TRACE_WITH_CTX(""); VALIDATE(view_remainingSize(view) == 0, ERR_INVALID_DATA); + ASSERT(ctx->level < MAX_SCRIPT_DEPTH); nativeScriptHashBuilder_startComplexScript_all(&ctx->hashBuilder, ctx->complexScripts[ctx->level].remainingScripts); UI_DISPLAY_SCRIPT(UI_SCRIPT_ALL); @@ -77,6 +84,7 @@ static void deriveNativeScriptHash_handleAny(read_view_t* view) TRACE_WITH_CTX(""); VALIDATE(view_remainingSize(view) == 0, ERR_INVALID_DATA); + ASSERT(ctx->level < MAX_SCRIPT_DEPTH); nativeScriptHashBuilder_startComplexScript_any(&ctx->hashBuilder, ctx->complexScripts[ctx->level].remainingScripts); UI_DISPLAY_SCRIPT(UI_SCRIPT_ANY); @@ -91,6 +99,7 @@ static void deriveNativeScriptHash_handleNofK(read_view_t* view) VALIDATE(view_remainingSize(view) == 0, ERR_INVALID_DATA); // validate that the received requiredScripts count makes sense + ASSERT(ctx->level < MAX_SCRIPT_DEPTH); VALIDATE(ctx->complexScripts[ctx->level].remainingScripts >= ctx->scriptContent.requiredScripts, ERR_INVALID_DATA); nativeScriptHashBuilder_startComplexScript_n_of_k(&ctx->hashBuilder, ctx->scriptContent.requiredScripts, ctx->complexScripts[ctx->level].remainingScripts); @@ -110,6 +119,7 @@ static void deriveNativeScriptHash_handleComplexScriptStart(read_view_t* view) uint8_t nativeScriptType = parse_u1be(view); TRACE("native complex script type = %u", nativeScriptType); + ASSERT(ctx->level < MAX_SCRIPT_DEPTH); ctx->complexScripts[ctx->level].remainingScripts = parse_u4be(view); ctx->complexScripts[ctx->level].totalScripts = ctx->complexScripts[ctx->level].remainingScripts; @@ -304,3 +314,5 @@ void deriveNativeScriptHash_handleAPDU( } #undef TRACE_WITH_CTX + +#endif // APP_FEATURE_NATIVE_SCRIPT_HASH diff --git a/src/deriveNativeScriptHash.h b/src/deriveNativeScriptHash.h index 2044a1d8..b26e59c4 100644 --- a/src/deriveNativeScriptHash.h +++ b/src/deriveNativeScriptHash.h @@ -1,6 +1,8 @@ #ifndef H_CARDANO_APP_DERIVE_NATIVE_SCRIPT_HASH #define H_CARDANO_APP_DERIVE_NATIVE_SCRIPT_HASH +#ifdef APP_FEATURE_NATIVE_SCRIPT_HASH + #include "bip44.h" #include "cardano.h" #include "common.h" @@ -48,4 +50,6 @@ typedef struct { ui_native_script_type ui_scriptType; } ins_derive_native_script_hash_context_t; +#endif // APP_FEATURE_NATIVE_SCRIPT_HASH + #endif // H_CARDANO_APP_DERIVE_NATIVE_SCRIPT_HASH diff --git a/src/deriveNativeScriptHash_ui.c b/src/deriveNativeScriptHash_ui.c index 716d3023..7d8dda22 100644 --- a/src/deriveNativeScriptHash_ui.c +++ b/src/deriveNativeScriptHash_ui.c @@ -1,3 +1,5 @@ +#ifdef APP_FEATURE_NATIVE_SCRIPT_HASH + #include "deriveNativeScriptHash.h" #include "deriveNativeScriptHash_ui.h" #include "state.h" @@ -88,6 +90,7 @@ static void deriveScriptHash_display_ui_runStep_cb(void) // where n and k is 2^32-1 char text[87] = {0}; explicit_bzero(text, SIZEOF(text)); + ASSERT(ctx->level < MAX_SCRIPT_DEPTH); snprintf(text, SIZEOF(text), "%u nested scripts", ctx->complexScripts[ctx->level].remainingScripts); // make sure all the information is displayed to the user ASSERT(strlen(text) + 1 < SIZEOF(text)); @@ -134,6 +137,7 @@ static void _displayScriptContent(ui_callback_fn_t* this_fn) // where n is 2^32-1 char text[37] = {0}; explicit_bzero(text, SIZEOF(text)); + ASSERT(ctx->level < MAX_SCRIPT_DEPTH); STATIC_ASSERT(sizeof(ctx->complexScripts[ctx->level].remainingScripts) <= sizeof(unsigned), "oversized type for %u"); STATIC_ASSERT(!IS_SIGNED(ctx->complexScripts[ctx->level].remainingScripts), "signed type for %u"); #ifdef HAVE_BAGL @@ -155,6 +159,7 @@ static void _displayScriptContent(ui_callback_fn_t* this_fn) break; } case UI_SCRIPT_N_OF_K: { + ASSERT(ctx->level < MAX_SCRIPT_DEPTH); STATIC_ASSERT(sizeof(ctx->scriptContent.requiredScripts) <= sizeof(unsigned), "oversized type for %u"); STATIC_ASSERT(!IS_SIGNED(ctx->scriptContent.requiredScripts), "signed type for %u"); STATIC_ASSERT(sizeof(ctx->complexScripts[ctx->level].remainingScripts) <= sizeof(unsigned), "oversized type for %u"); @@ -338,3 +343,5 @@ void deriveNativeScriptHash_displayNativeScriptHash_policyId() fill_and_display_if_required("Policy ID", bufferHex, deriveNativeScriptHash_displayNativeScriptHash_finish, respond_with_user_reject); #endif // HAVE_BAGL } + +#endif // APP_FEATURE_NATIVE_SCRIPT_HASH diff --git a/src/deriveNativeScriptHash_ui.h b/src/deriveNativeScriptHash_ui.h index 95460806..b4193c34 100644 --- a/src/deriveNativeScriptHash_ui.h +++ b/src/deriveNativeScriptHash_ui.h @@ -1,5 +1,8 @@ #ifndef H_CARDANO_APP_DERIVE_NATIVE_SCRIPT_HASH_UI #define H_CARDANO_APP_DERIVE_NATIVE_SCRIPT_HASH_UI + +#ifdef APP_FEATURE_NATIVE_SCRIPT_HASH + enum { DISPLAY_UI_STEP_POSITION = 200, #ifdef HAVE_NBGL @@ -17,4 +20,7 @@ void deriveNativeScriptHash_displayNativeScriptHash_callback(); void deriveNativeScriptHash_displayNativeScriptHash_bech32(); void deriveNativeScriptHash_displayNativeScriptHash_policyId(); + +#endif // APP_FEATURE_NATIVE_SCRIPT_HASH + #endif // H_CARDANO_APP_DERIVE_NATIVE_SCRIPT_HASH_UI diff --git a/src/getPublicKeys.c b/src/getPublicKeys.c index dc2cee7f..e1615236 100644 --- a/src/getPublicKeys.c +++ b/src/getPublicKeys.c @@ -109,8 +109,6 @@ static void getPublicKeys_handleInitAPDU(const uint8_t* wireDataBuffer, size_t w { { CHECK_STAGE(GET_KEYS_STAGE_INIT); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } { // parse data @@ -186,8 +184,6 @@ void getPublicKeys_handleGetNextKeyAPDU( { CHECK_STAGE(GET_KEYS_STAGE_GET_KEYS); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); - VALIDATE(ctx->currentPath < ctx->numPaths, ERR_INVALID_STATE); read_view_t view = make_read_view(wireDataBuffer, wireDataBuffer + wireDataSize); @@ -222,6 +218,7 @@ void getPublicKeys_handleAPDU( bool isNewCall ) { + ASSERT(wireDataBuffer != NULL); ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); if (isNewCall) { diff --git a/src/getVersion.c b/src/getVersion.c index 3edf206b..2c831dfc 100644 --- a/src/getVersion.c +++ b/src/getVersion.c @@ -6,8 +6,9 @@ enum { - FLAG_DEVEL = 1, - + FLAG_DEVEL = 1 << 0, +// FLAG_HEADLESS = 1 << 1, + FLAG_APP_XS = 1 << 2, }; void getVersion_handleAPDU( @@ -40,12 +41,15 @@ void getVersion_handleAPDU( .major = MAJOR_VERSION, .minor = MINOR_VERSION, .patch = PATCH_VERSION, - .flags = 0, + .flags = 0, // see below }; #ifdef DEVEL response.flags |= FLAG_DEVEL; #endif // DEVEL + #ifdef APP_XS + response.flags |= FLAG_APP_XS; + #endif // APP_XS io_send_buf(SUCCESS, (uint8_t*) &response, sizeof(response)); ui_idle(); diff --git a/src/handlers.c b/src/handlers.c index 4c6cc5a1..dd90fdb5 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -12,6 +12,7 @@ #include "deriveAddress.h" #include "deriveNativeScriptHash.h" #include "signTx.h" +#include "signMsg.h" #include "signOpCert.h" #include "signCVote.h" @@ -29,12 +30,17 @@ handler_fn_t* lookupHandler(uint8_t ins) // 0x1* - public-key/address related CASE(0x10, getPublicKeys_handleAPDU); CASE(0x11, deriveAddress_handleAPDU); + #ifdef APP_FEATURE_NATIVE_SCRIPT_HASH CASE(0x12, deriveNativeScriptHash_handleAPDU); + #endif // APP_FEATURE_NATIVE_SCRIPT_HASH // 0x2* - signing related CASE(0x21, signTx_handleAPDU); + #ifdef APP_FEATURE_OPCERT CASE(0x22, signOpCert_handleAPDU); + #endif // APP_FEATURE_OPCERT CASE(0x23, signCVote_handleAPDU); + CASE(0x24, signMsg_handleAPDU); #ifdef DEVEL // 0xF* - debug_mode related diff --git a/src/io.c b/src/io.c index a85bfc50..02b32e9b 100644 --- a/src/io.c +++ b/src/io.c @@ -127,7 +127,7 @@ unsigned char io_event(unsigned char channel MARK_UNUSED) case SEPROXYHAL_TAG_FINGER_EVENT: UX_FINGER_EVENT(G_io_seproxyhal_spi_buffer); break; - #endif // HAVE_NBGL + #endif // HAVE_NBGL case SEPROXYHAL_TAG_TICKER_EVENT: UX_TICKER_EVENT(G_io_seproxyhal_spi_buffer, { diff --git a/src/ipUtils.c b/src/ipUtils.c index 7b6fc89f..2cbcfc31 100644 --- a/src/ipUtils.c +++ b/src/ipUtils.c @@ -1,3 +1,5 @@ +#ifdef APP_FEATURE_POOL_REGISTRATION + /* * Taken from glibc: * https://www.gnu.org/software/libc/sources.html @@ -159,3 +161,5 @@ void inet_ntop6 (const uint8_t* src, char* dst, size_t dstSize) strncpy(dst, tmp, dstSize); } + +#endif // APP_FEATURE_POOL_REGISTRATION diff --git a/src/ipUtils.h b/src/ipUtils.h index 111fa076..333945e8 100644 --- a/src/ipUtils.h +++ b/src/ipUtils.h @@ -1,6 +1,8 @@ #ifndef H_CARDANO_APP_IP_UTILS #define H_CARDANO_APP_IP_UTILS +#ifdef APP_FEATURE_POOL_REGISTRATION + #include "os.h" #define IPV4_STR_SIZE_MAX (sizeof "255.255.255.255") @@ -13,4 +15,6 @@ void inet_ntop6 (const uint8_t* src, char* dst, size_t dstSize); void run_ipUtils_test(); #endif // DEVEL -#endif // H_CARDANO_APP_SIGN_TX_UTILS +#endif // APP_FEATURE_POOL_REGISTRATION + +#endif // H_CARDANO_APP_IP_UTILS diff --git a/src/ipUtils_test.c b/src/ipUtils_test.c index 413bef7c..b54e019f 100644 --- a/src/ipUtils_test.c +++ b/src/ipUtils_test.c @@ -1,4 +1,4 @@ -#ifdef DEVEL +#if defined(DEVEL) && defined(APP_FEATURE_POOL_REGISTRATION) #include "ipUtils.h" #include "utils.h" @@ -55,4 +55,5 @@ void run_ipUtils_test() test3(); } -#endif // DEVEL +#endif // DEVEL && APP_FEATURE_POOL_REGISTRATION + diff --git a/src/messageSigning.c b/src/messageSigning.c index 41e5c00d..4c2d4fd6 100644 --- a/src/messageSigning.c +++ b/src/messageSigning.c @@ -6,9 +6,9 @@ #include "securityPolicy.h" #include "crypto.h" -static void signRawMessageWithPath(bip44_path_t* pathSpec, - const uint8_t* messageBuffer, size_t messageSize, - uint8_t* outBuffer, size_t outSize) +void signRawMessageWithPath(bip44_path_t* pathSpec, + const uint8_t* messageBuffer, size_t messageSize, + uint8_t* outBuffer, size_t outSize) { size_t sigLen = outSize; @@ -23,6 +23,10 @@ static void signRawMessageWithPath(bip44_path_t* pathSpec, #ifndef FUZZING { + TRACE("signing with path:"); + BIP44_PRINTF(pathSpec); + PRINTF("\n"); + cx_err_t error = crypto_eddsa_sign(pathSpec->path, pathSpec->length, messageBuffer, @@ -37,7 +41,6 @@ static void signRawMessageWithPath(bip44_path_t* pathSpec, #endif ASSERT(sigLen == ED25519_SIGNATURE_LENGTH); - } // sign the given hash by the private key derived according to the given path @@ -64,6 +67,7 @@ void getCVoteRegistrationSignature(bip44_path_t* pathSpec, #endif } +#ifdef APP_FEATURE_OPCERT void getOpCertSignature(bip44_path_t* pathSpec, const uint8_t* opCertBodyBuffer, size_t opCertBodySize, uint8_t* outBuffer, size_t outSize) @@ -74,3 +78,4 @@ void getOpCertSignature(bip44_path_t* pathSpec, signRawMessageWithPath(pathSpec, opCertBodyBuffer, opCertBodySize, outBuffer, outSize); } +#endif // APP_FEATURE_OPCERT diff --git a/src/messageSigning.h b/src/messageSigning.h index fbf18f89..4984df72 100644 --- a/src/messageSigning.h +++ b/src/messageSigning.h @@ -3,6 +3,10 @@ #include "bip44.h" +void signRawMessageWithPath(bip44_path_t* pathSpec, + const uint8_t* messageBuffer, size_t messageSize, + uint8_t* outBuffer, size_t outSize); + void getWitness(bip44_path_t* pathSpec, const uint8_t* txHashBuffer, size_t txHashSize, uint8_t* outBuffer, size_t outSize); @@ -11,8 +15,10 @@ void getCVoteRegistrationSignature(bip44_path_t* pathSpec, const uint8_t* payloadHashBuffer, size_t payloadHashSize, uint8_t* outBuffer, size_t outSize); +#ifdef APP_FEATURE_OPCERT void getOpCertSignature(bip44_path_t* pathSpec, const uint8_t* opCertBodyBuffer, size_t opCertBodySize, uint8_t* outBuffer, size_t outSize); +#endif // APP_FEATURE_OPCERT #endif // H_CARDANO_APP_MESSAGE_SIGNING diff --git a/src/nativeScriptHashBuilder.c b/src/nativeScriptHashBuilder.c index 6bea3f82..7e4a80b0 100644 --- a/src/nativeScriptHashBuilder.c +++ b/src/nativeScriptHashBuilder.c @@ -1,3 +1,5 @@ +#ifdef APP_FEATURE_NATIVE_SCRIPT_HASH + #include "cbor.h" #include "nativeScriptHashBuilder.h" @@ -227,3 +229,5 @@ void nativeScriptHashBuilder_finalize( #undef APPEND_CBOR #undef _TRACE_BUFFER #undef _TRACE + +#endif // APP_FEATURE_NATIVE_SCRIPT_HASH diff --git a/src/nativeScriptHashBuilder.h b/src/nativeScriptHashBuilder.h index de2653e4..a5508fb1 100644 --- a/src/nativeScriptHashBuilder.h +++ b/src/nativeScriptHashBuilder.h @@ -1,6 +1,8 @@ #ifndef H_CARDANO_APP_NATIVE_SCRIPT_HASH_BUILDER #define H_CARDANO_APP_NATIVE_SCRIPT_HASH_BUILDER +#ifdef APP_FEATURE_NATIVE_SCRIPT_HASH + #include "cardano.h" #include "hash.h" @@ -59,4 +61,6 @@ void nativeScriptHashBuilder_finalize( void run_nativeScriptHashBuilder_test(); /* #endif // DEVEL */ +#endif // APP_FEATURE_NATIVE_SCRIPT_HASH + #endif // H_CARDANO_APP_NATIVE_SCRIPT_HASH_BUILDER diff --git a/src/nativeScriptHashBuilder_test.c b/src/nativeScriptHashBuilder_test.c index c3f2e88c..5bcc340c 100644 --- a/src/nativeScriptHashBuilder_test.c +++ b/src/nativeScriptHashBuilder_test.c @@ -1,4 +1,4 @@ -#ifdef DEVEL +#if defined(DEVEL) && defined(APP_FEATURE_NATIVE_SCRIPT_HASH) #include "hexUtils.h" #include "nativeScriptHashBuilder.h" @@ -105,4 +105,5 @@ void run_nativeScriptHashBuilder_test() #undef FINALIZE #undef BEFORE_EACH #undef BUF_FROM_STR -#endif // DEVEL + +#endif // DEVEL && APP_FEATURE_NATIVE_SCRIPT_HASH diff --git a/src/runTests.c b/src/runTests.c index 8134edaa..29ab61c2 100644 --- a/src/runTests.c +++ b/src/runTests.c @@ -32,6 +32,7 @@ void handleRunTests( // Note: Make sure to have RESET_ON_CRASH flag disabled // as it interferes with tests verifying assertions BEGIN_ASSERT_NOEXCEPT { + PRINTF("Running tests\n"); run_hex_test(); run_base58_test(); @@ -40,17 +41,23 @@ void handleRunTests( run_endian_test(); run_textUtils_test(); run_tokens_test(); + #if defined(APP_FEATURE_POOL_REGISTRATION) run_ipUtils_test(); + #endif run_hash_test(); run_cbor_test(); run_bip44_test(); run_key_derivation_test(); + #if !defined(APP_XS) run_addressUtilsByron_test(); + #endif run_addressUtilsShelley_test(); - run_txHashBuilder_test(); run_auxDataHashBuilder_test(); + #if defined(APP_FEATURE_NATIVE_SCRIPT_HASH) run_nativeScriptHashBuilder_test(); + #endif PRINTF("All tests done\n"); + } END_ASSERT_NOEXCEPT; io_send_buf(SUCCESS, NULL, 0); diff --git a/src/runTests.h b/src/runTests.h index 4dec40ed..3c49170e 100644 --- a/src/runTests.h +++ b/src/runTests.h @@ -1,12 +1,12 @@ -#ifdef DEVEL - #ifndef H_CARDANO_APP_RUN_TESTS #define H_CARDANO_APP_RUN_TESTS +#ifdef DEVEL + #include "handlers.h" handler_fn_t handleRunTests; -#endif // H_CARDANO_APP_RUN_TESTS - #endif // DEVEL + +#endif // H_CARDANO_APP_RUN_TESTS diff --git a/src/securityPolicy.c b/src/securityPolicy.c index a694a749..332f2ef2 100644 --- a/src/securityPolicy.c +++ b/src/securityPolicy.c @@ -9,7 +9,7 @@ // helper functions -// stake key path has the same account as the spending key path +// stake key path has the same account as the payment key path static inline bool is_standard_base_address(const addressParams_t* addressParams) { ASSERT(isValidAddressParams(addressParams)); @@ -18,8 +18,8 @@ static inline bool is_standard_base_address(const addressParams_t* addressParams CHECK(addressParams->type == BASE_PAYMENT_KEY_STAKE_KEY); CHECK(addressParams->stakingDataSource == STAKING_KEY_PATH); - CHECK(bip44_classifyPath(&addressParams->spendingKeyPath) == PATH_ORDINARY_SPENDING_KEY); - CHECK(bip44_isPathReasonable(&addressParams->spendingKeyPath)); + CHECK(bip44_classifyPath(&addressParams->paymentKeyPath) == PATH_ORDINARY_PAYMENT_KEY); + CHECK(bip44_isPathReasonable(&addressParams->paymentKeyPath)); CHECK(bip44_classifyPath(&addressParams->stakingKeyPath) == PATH_ORDINARY_STAKING_KEY); CHECK(bip44_isPathReasonable(&addressParams->stakingKeyPath)); @@ -29,7 +29,7 @@ static inline bool is_standard_base_address(const addressParams_t* addressParams CHECK( bip44_getAccount(&addressParams->stakingKeyPath) == - bip44_getAccount(&addressParams->spendingKeyPath) + bip44_getAccount(&addressParams->paymentKeyPath) ); return true; @@ -78,13 +78,17 @@ security_policy_t policyForDerivePrivateKey(const bip44_path_t* path) switch (bip44_classifyPath(path)) { case PATH_ORDINARY_ACCOUNT: - case PATH_ORDINARY_SPENDING_KEY: + case PATH_ORDINARY_PAYMENT_KEY: case PATH_ORDINARY_STAKING_KEY: case PATH_MULTISIG_ACCOUNT: - case PATH_MULTISIG_SPENDING_KEY: + case PATH_MULTISIG_PAYMENT_KEY: case PATH_MULTISIG_STAKING_KEY: + case PATH_DREP_KEY: + case PATH_COMMITTEE_COLD_KEY: + case PATH_COMMITTEE_HOT_KEY: + case PATH_MINT_KEY: case PATH_POOL_COLD_KEY: @@ -158,10 +162,13 @@ security_policy_t policyForGetExtendedPublicKey(const bip44_path_t* pathSpec) ALLOW(); break; - case PATH_ORDINARY_SPENDING_KEY: + case PATH_ORDINARY_PAYMENT_KEY: case PATH_ORDINARY_STAKING_KEY: - case PATH_MULTISIG_SPENDING_KEY: + case PATH_MULTISIG_PAYMENT_KEY: case PATH_MULTISIG_STAKING_KEY: + case PATH_DREP_KEY: + case PATH_COMMITTEE_COLD_KEY: + case PATH_COMMITTEE_HOT_KEY: case PATH_CVOTE_KEY: WARN_UNLESS(bip44_isPathReasonable(pathSpec)); // ask for permission (it is unusual if client asks this instead of the account key) @@ -182,11 +189,14 @@ security_policy_t policyForGetExtendedPublicKeyBulkExport(const bip44_path_t* pa switch (bip44_classifyPath(pathSpec)) { case PATH_ORDINARY_ACCOUNT: - case PATH_ORDINARY_SPENDING_KEY: + case PATH_ORDINARY_PAYMENT_KEY: case PATH_ORDINARY_STAKING_KEY: case PATH_MULTISIG_ACCOUNT: - case PATH_MULTISIG_SPENDING_KEY: + case PATH_MULTISIG_PAYMENT_KEY: case PATH_MULTISIG_STAKING_KEY: + case PATH_DREP_KEY: + case PATH_COMMITTEE_COLD_KEY: + case PATH_COMMITTEE_HOT_KEY: case PATH_MINT_KEY: case PATH_CVOTE_ACCOUNT: case PATH_CVOTE_KEY: @@ -197,7 +207,7 @@ security_policy_t policyForGetExtendedPublicKeyBulkExport(const bip44_path_t* pa case PATH_POOL_COLD_KEY: WARN_UNLESS(bip44_isPathReasonable(pathSpec)); - // but ask for permission when pool cold key is requested + // but ask for permission PROMPT(); break; @@ -219,7 +229,7 @@ static security_policy_t _policyForDeriveAddress(const addressParams_t* addressP case BASE_PAYMENT_KEY_STAKE_KEY: // unusual path - WARN_UNLESS(bip44_isPathReasonable(&addressParams->spendingKeyPath)); + WARN_UNLESS(bip44_isPathReasonable(&addressParams->paymentKeyPath)); WARN_IF( addressParams->stakingDataSource == STAKING_KEY_PATH && !bip44_isPathReasonable(&addressParams->stakingKeyPath) @@ -231,7 +241,7 @@ static security_policy_t _policyForDeriveAddress(const addressParams_t* addressP case ENTERPRISE_KEY: case BYRON: // unusual path - WARN_UNLESS(bip44_isPathReasonable(&addressParams->spendingKeyPath)); + WARN_UNLESS(bip44_isPathReasonable(&addressParams->paymentKeyPath)); break; case BASE_PAYMENT_SCRIPT_STAKE_KEY: @@ -357,7 +367,10 @@ security_policy_t policyForSignTxInit( bool includeNetworkId, bool includeCollateralOutput, bool includeTotalCollateral, - uint16_t numReferenceInputs + uint16_t numReferenceInputs, + uint16_t numVotingProcedures, + bool includeTreasury, + bool includeDonation ) { DENY_UNLESS(isValidNetworkId(networkId)); @@ -389,6 +402,12 @@ security_policy_t policyForSignTxInit( DENY_IF(includeCollateralOutput); DENY_IF(includeTotalCollateral); DENY_IF(numReferenceInputs > 0); + + // no voting, treasuries, donations for pool registrations + // we don't need them and we want to avoid overlap in witnesses + DENY_IF(numVotingProcedures > 0); + DENY_IF(includeTreasury); + DENY_IF(includeDonation); break; case SIGN_TX_SIGNINGMODE_ORDINARY_TX: @@ -463,7 +482,7 @@ security_policy_t policyForSignTxInput(sign_tx_signingmode_t txSigningMode) static bool is_addressBytes_suitable_for_tx_output( const uint8_t* addressBuffer, size_t addressSize, - const uint8_t networkId, const uint32_t protocolMagic + const uint8_t networkId, const uint32_t protocolMagic __attribute__((unused)) ) { ASSERT(addressSize < BUFFER_SIZE_PARANOIA); @@ -482,7 +501,9 @@ static bool is_addressBytes_suitable_for_tx_output( return false; case BYRON: + #ifdef APP_FEATURE_BYRON_PROTOCOL_MAGIC_CHECK CHECK(extractProtocolMagic(addressBuffer, addressSize) == protocolMagic); + #endif // APP_FEATURE_BYRON_PROTOCOL_MAGIC_CHECK break; default: { @@ -549,7 +570,7 @@ static bool contains_forbidden_plutus_elements( bool needsMissingDatumWarning(const tx_output_destination_t* destination, bool includeDatum) { - const bool mightRequireDatum = determineSpendingChoice(getDestinationAddressType(destination)) == SPENDING_SCRIPT_HASH; + const bool mightRequireDatum = determinePaymentChoice(getDestinationAddressType(destination)) == PAYMENT_SCRIPT_HASH; return mightRequireDatum && !includeDatum; } @@ -626,12 +647,12 @@ static bool is_addressParams_suitable_for_tx_output( // this captures the essence of a change output: money stays // on an address where payment is fully controlled by this device - CHECK(determineSpendingChoice(params->type) == SPENDING_PATH); - // Note: if we allowed script hash in spending part, we must add a warning + CHECK(determinePaymentChoice(params->type) == PAYMENT_PATH); + // Note: if we allowed script hash in payment part, we must add a warning // for missing datum (see policyForSignTxOutputAddressBytes) - ASSERT(determineSpendingChoice(params->type) == SPENDING_PATH); - CHECK(!violatesSingleAccountOrStoreIt(¶ms->spendingKeyPath)); + ASSERT(determinePaymentChoice(params->type) == PAYMENT_PATH); + CHECK(!violatesSingleAccountOrStoreIt(¶ms->paymentKeyPath)); } return true; @@ -656,7 +677,7 @@ security_policy_t policyForSignTxOutputAddressParams( case SIGN_TX_SIGNINGMODE_POOL_REGISTRATION_OPERATOR: case SIGN_TX_SIGNINGMODE_ORDINARY_TX: { - // unusual paths or spending and staking path mismatch + // unusual paths or payment and staking path mismatch SHOW_UNLESS(is_standard_base_address(params)); // outputs (eUTXOs) with datum or ref script are not interchangeable @@ -673,7 +694,7 @@ security_policy_t policyForSignTxOutputAddressParams( case SIGN_TX_SIGNINGMODE_MULTISIG_TX: { // for simplicity, all outputs should be given as external addresses; // generally, more than one party is needed to sign - // spending from a multisig address, so we do not expect + // payment from a multisig address, so we do not expect // there will be 1852 outputs (that would be considered change) DENY(); break; @@ -871,6 +892,8 @@ security_policy_t policyForSignTxCollateralOutputAdaAmount( bool isTotalCollateralPresent ) { + // WARNING: policies for collateral inputs, collateral return output and total collateral are interdependent + if (outputPolicy == POLICY_ALLOW_WITHOUT_PROMPT) { // output not shown, so none of its elements should be shown ALLOW(); @@ -887,6 +910,8 @@ security_policy_t policyForSignTxCollateralOutputTokens( const tx_output_description_t* output ) { + // WARNING: policies for collateral inputs, collateral return output and total collateral are interdependent + if (outputPolicy == POLICY_ALLOW_WITHOUT_PROMPT) { // output not shown, so none of its elements should be shown ALLOW(); @@ -980,22 +1005,22 @@ security_policy_t policyForSignTxCertificate( case SIGN_TX_SIGNINGMODE_PLUTUS_TX: case SIGN_TX_SIGNINGMODE_ORDINARY_TX: // pool registration is allowed only in POOL_REGISTRATION signing modes - DENY_IF(certificateType == CERTIFICATE_TYPE_STAKE_POOL_REGISTRATION); + DENY_IF(certificateType == CERTIFICATE_STAKE_POOL_REGISTRATION); ALLOW(); break; case SIGN_TX_SIGNINGMODE_MULTISIG_TX: // pool registration is allowed only in POOL_REGISTRATION signing modes - DENY_IF(certificateType == CERTIFICATE_TYPE_STAKE_POOL_REGISTRATION); + DENY_IF(certificateType == CERTIFICATE_STAKE_POOL_REGISTRATION); // pool retirement is impossible with multisig keys - DENY_IF(certificateType == CERTIFICATE_TYPE_STAKE_POOL_RETIREMENT); + DENY_IF(certificateType == CERTIFICATE_STAKE_POOL_RETIREMENT); ALLOW(); break; case SIGN_TX_SIGNINGMODE_POOL_REGISTRATION_OPERATOR: case SIGN_TX_SIGNINGMODE_POOL_REGISTRATION_OWNER: // only pool registration is allowed - DENY_UNLESS(certificateType == CERTIFICATE_TYPE_STAKE_POOL_REGISTRATION); + DENY_UNLESS(certificateType == CERTIFICATE_STAKE_POOL_REGISTRATION); ALLOW(); break; @@ -1006,94 +1031,224 @@ security_policy_t policyForSignTxCertificate( DENY(); // should not be reached } +// applicable to credentials that are witnessed in this tx +static bool _forbiddenCredential( + sign_tx_signingmode_t txSigningMode, + const ext_credential_t* credential +) +{ + // certain combinations of tx signing mode and credential type are not allowed + // either because they don't make sense or are dangerous + switch (txSigningMode) { + case SIGN_TX_SIGNINGMODE_MULTISIG_TX: + switch (credential->type) { + case EXT_CREDENTIAL_KEY_PATH: + case EXT_CREDENTIAL_KEY_HASH: + // everything is expected to be governed by native scripts + return true; + case EXT_CREDENTIAL_SCRIPT_HASH: + break; + default: + ASSERT(false); + } + break; + + case SIGN_TX_SIGNINGMODE_PLUTUS_TX: + // everything allowed, txs are too complex for a HW wallet to understand + // and there might be third-party key hashes in the tx + break; + + case SIGN_TX_SIGNINGMODE_ORDINARY_TX: + switch (credential->type) { + case EXT_CREDENTIAL_KEY_HASH: + case EXT_CREDENTIAL_SCRIPT_HASH: + // keys must be given by path, otherwise the user does not know + // if the hash corresponds to some of his keys, + // and might inadvertently sign several certificates with a single witness + return true; + case EXT_CREDENTIAL_KEY_PATH: + break; + default: + ASSERT(false); + } + break; + + default: + // this should not be called in POOL_REGISTRATION signing modes + ASSERT(false); + } + + return false; +} + +security_policy_t _policyForSignTxCertificateStakeCredential( + sign_tx_signingmode_t txSigningMode, + const ext_credential_t* stakeCredential +) +{ + DENY_IF(_forbiddenCredential(txSigningMode, stakeCredential)); + + switch (stakeCredential->type) { + case EXT_CREDENTIAL_KEY_PATH: + DENY_UNLESS(bip44_isOrdinaryStakingKeyPath(&stakeCredential->keyPath)); + DENY_IF(violatesSingleAccountOrStoreIt(&stakeCredential->keyPath)); + break; + case EXT_CREDENTIAL_KEY_HASH: + case EXT_CREDENTIAL_SCRIPT_HASH: + // the rest is OK, forbidden credentials have been dealt with above + break; + + default: + ASSERT(false); + } + + PROMPT(); +} + // for certificates concerning stake keys and stake delegation security_policy_t policyForSignTxCertificateStaking( sign_tx_signingmode_t txSigningMode, const certificate_type_t certificateType, - const stake_credential_t* stakeCredential + const ext_credential_t* stakeCredential ) { switch (certificateType) { - case CERTIFICATE_TYPE_STAKE_REGISTRATION: - case CERTIFICATE_TYPE_STAKE_DEREGISTRATION: - case CERTIFICATE_TYPE_STAKE_DELEGATION: + case CERTIFICATE_STAKE_REGISTRATION: + case CERTIFICATE_STAKE_REGISTRATION_CONWAY: + case CERTIFICATE_STAKE_DEREGISTRATION: + case CERTIFICATE_STAKE_DEREGISTRATION_CONWAY: + case CERTIFICATE_STAKE_DELEGATION: break; // these are allowed default: ASSERT(false); } - switch (stakeCredential->type) { - case STAKE_CREDENTIAL_KEY_PATH: - DENY_UNLESS(bip44_isOrdinaryStakingKeyPath(&stakeCredential->keyPath)); - DENY_IF(violatesSingleAccountOrStoreIt(&stakeCredential->keyPath)); - switch (txSigningMode) { - case SIGN_TX_SIGNINGMODE_ORDINARY_TX: - case SIGN_TX_SIGNINGMODE_PLUTUS_TX: - PROMPT(); - break; + return _policyForSignTxCertificateStakeCredential(txSigningMode, stakeCredential); +} - case SIGN_TX_SIGNINGMODE_MULTISIG_TX: - DENY(); - break; +security_policy_t policyForSignTxCertificateVoteDelegation( + sign_tx_signingmode_t txSigningMode, + const ext_credential_t* stakeCredential, + const ext_drep_t* drep +) +{ + switch (drep->type) { + case EXT_DREP_KEY_PATH: + // DRep can be anything, but if given by key path, it should be a valid path + DENY_UNLESS(bip44_isDRepKeyPath(&drep->keyPath)); + break; - default: - // in POOL_REGISTRATION signing modes, this certificate should have already been - // reported as invalid (only pool registration certificate is allowed) - ASSERT(false); - break; - } + case EXT_DREP_KEY_HASH: + case EXT_DREP_SCRIPT_HASH: + case EXT_DREP_ABSTAIN: + case EXT_DREP_NO_CONFIDENCE: + // nothing to deny break; - case STAKE_CREDENTIAL_KEY_HASH: - switch (txSigningMode) { - case SIGN_TX_SIGNINGMODE_PLUTUS_TX: - PROMPT(); - break; + default: + ASSERT(false); + } - case SIGN_TX_SIGNINGMODE_ORDINARY_TX: - case SIGN_TX_SIGNINGMODE_MULTISIG_TX: - DENY(); - break; + return _policyForSignTxCertificateStakeCredential(txSigningMode, stakeCredential); +} - default: - // in POOL_REGISTRATION signing modes, this certificate should have already been - // reported as invalid (only pool registration certificate is allowed) - ASSERT(false); - break; - } +security_policy_t policyForSignTxCertificateCommitteeAuth( + sign_tx_signingmode_t txSigningMode, + const ext_credential_t* coldCredential, + const ext_credential_t* hotCredential +) +{ + DENY_IF(_forbiddenCredential(txSigningMode, coldCredential)); + + switch (coldCredential->type) { + case EXT_CREDENTIAL_KEY_PATH: + DENY_UNLESS(bip44_isCommitteeColdKeyPath(&coldCredential->keyPath)); + DENY_IF(violatesSingleAccountOrStoreIt(&coldCredential->keyPath)); break; - case STAKE_CREDENTIAL_SCRIPT_HASH: - switch (txSigningMode) { - case SIGN_TX_SIGNINGMODE_MULTISIG_TX: - case SIGN_TX_SIGNINGMODE_PLUTUS_TX: - PROMPT(); - break; + case EXT_CREDENTIAL_KEY_HASH: + case EXT_CREDENTIAL_SCRIPT_HASH: + // the rest is OK, forbidden credentials have been dealt with above + break; - case SIGN_TX_SIGNINGMODE_ORDINARY_TX: - DENY(); - break; + default: + ASSERT(false); + } - default: - // in POOL_REGISTRATION signing modes, this certificate should have already been - // reported as invalid (only pool registration certificate is allowed) - ASSERT(false); - break; - } + switch (hotCredential->type) { + + case EXT_CREDENTIAL_SCRIPT_HASH: + case EXT_CREDENTIAL_KEY_HASH: + // keys might be governed outside of this device + break; + + case EXT_CREDENTIAL_KEY_PATH: + DENY_UNLESS(bip44_isCommitteeHotKeyPath(&hotCredential->keyPath)); + break; + + default: + ASSERT(false); + } + + PROMPT(); +} + +security_policy_t policyForSignTxCertificateCommitteeResign( + sign_tx_signingmode_t txSigningMode, + const ext_credential_t* coldCredential +) +{ + DENY_IF(_forbiddenCredential(txSigningMode, coldCredential)); + + switch (coldCredential->type) { + case EXT_CREDENTIAL_KEY_PATH: + DENY_UNLESS(bip44_isCommitteeColdKeyPath(&coldCredential->keyPath)); + DENY_IF(violatesSingleAccountOrStoreIt(&coldCredential->keyPath)); + break; + + case EXT_CREDENTIAL_KEY_HASH: + case EXT_CREDENTIAL_SCRIPT_HASH: + // the rest is OK, forbidden credentials have been dealt with above break; default: ASSERT(false); + } + + PROMPT(); +} + +security_policy_t policyForSignTxCertificateDRep( + sign_tx_signingmode_t txSigningMode, + const ext_credential_t* dRepCredential +) +{ + DENY_IF(_forbiddenCredential(txSigningMode, dRepCredential)); + + switch (dRepCredential->type) { + case EXT_CREDENTIAL_KEY_PATH: + DENY_UNLESS(bip44_isDRepKeyPath(&dRepCredential->keyPath)); + DENY_IF(violatesSingleAccountOrStoreIt(&dRepCredential->keyPath)); + break; + + case EXT_CREDENTIAL_KEY_HASH: + case EXT_CREDENTIAL_SCRIPT_HASH: + // the rest is OK, forbidden credentials have been dealt with above break; + + default: + ASSERT(false); } - DENY(); // should not be reached + PROMPT(); } +#ifdef APP_FEATURE_POOL_RETIREMENT + security_policy_t policyForSignTxCertificateStakePoolRetirement( sign_tx_signingmode_t txSigningMode, - const bip44_path_t* poolIdPath, + const ext_credential_t* poolCredential, uint64_t epoch MARK_UNUSED ) { @@ -1102,7 +1257,8 @@ security_policy_t policyForSignTxCertificateStakePoolRetirement( case SIGN_TX_SIGNINGMODE_ORDINARY_TX: // pool retirement may only be present in ORDINARY_TX signing mode // the path hash should be a valid pool cold key path - DENY_UNLESS(bip44_isPoolColdKeyPath(poolIdPath)); + DENY_UNLESS(poolCredential->type == EXT_CREDENTIAL_KEY_PATH); + DENY_UNLESS(bip44_isPoolColdKeyPath(&poolCredential->keyPath)); PROMPT(); break; @@ -1115,6 +1271,10 @@ security_policy_t policyForSignTxCertificateStakePoolRetirement( DENY(); // should not be reached } +#endif // APP_FEATURE_POOL_RETIREMENT + +#ifdef APP_FEATURE_POOL_REGISTRATION + security_policy_t policyForSignTxStakePoolRegistrationInit( sign_tx_signingmode_t txSigningMode, uint32_t numOwners @@ -1139,7 +1299,6 @@ security_policy_t policyForSignTxStakePoolRegistrationInit( default: ASSERT(false); - break; } DENY(); // should not be reached @@ -1285,14 +1444,16 @@ security_policy_t policyForSignTxStakePoolRegistrationConfirm( ALLOW(); } +#endif // APP_FEATURE_POOL_REGISTRATION + // For each withdrawal security_policy_t policyForSignTxWithdrawal( sign_tx_signingmode_t txSigningMode, - const stake_credential_t* stakeCredential + const ext_credential_t* stakeCredential ) { switch (stakeCredential->type) { - case STAKE_CREDENTIAL_KEY_PATH: + case EXT_CREDENTIAL_KEY_PATH: DENY_UNLESS(bip44_isOrdinaryStakingKeyPath(&stakeCredential->keyPath)); DENY_IF(violatesSingleAccountOrStoreIt(&stakeCredential->keyPath)); switch (txSigningMode) { @@ -1311,11 +1472,10 @@ security_policy_t policyForSignTxWithdrawal( // in POOL_REGISTRATION signing modes, this certificate should have already been // reported as invalid (only pool registration certificate is allowed) ASSERT(false); - break; } break; - case STAKE_CREDENTIAL_KEY_HASH: + case EXT_CREDENTIAL_KEY_HASH: switch (txSigningMode) { case SIGN_TX_SIGNINGMODE_PLUTUS_TX: SHOW_IF(app_mode_expert()); @@ -1339,11 +1499,10 @@ security_policy_t policyForSignTxWithdrawal( // in POOL_REGISTRATION signing modes, this certificate should have already been // reported as invalid (only pool registration certificate is allowed) ASSERT(false); - break; } break; - case STAKE_CREDENTIAL_SCRIPT_HASH: + case EXT_CREDENTIAL_SCRIPT_HASH: switch (txSigningMode) { case SIGN_TX_SIGNINGMODE_MULTISIG_TX: case SIGN_TX_SIGNINGMODE_PLUTUS_TX: @@ -1360,7 +1519,6 @@ security_policy_t policyForSignTxWithdrawal( // in POOL_REGISTRATION signing modes, this certificate should have already been // reported as invalid (only pool registration certificate is allowed) ASSERT(false); - break; } break; @@ -1368,7 +1526,6 @@ security_policy_t policyForSignTxWithdrawal( // in POOL_REGISTRATION signing modes, non-zero number of withdrawals // should have already been reported as invalid ASSERT(false); - break; } DENY(); // should not be reached @@ -1378,17 +1535,33 @@ security_policy_t policyForSignTxWithdrawal( static inline security_policy_t _ordinaryWitnessPolicy(const bip44_path_t* path, bool mintPresent) { switch (bip44_classifyPath(path)) { - case PATH_ORDINARY_SPENDING_KEY: + case PATH_ORDINARY_PAYMENT_KEY: case PATH_ORDINARY_STAKING_KEY: + // ordinary key paths can be hidden if they are not unusual + // (the user saw all outputs not belonging to him, withdrawals and certificates, + // those belong to him in an ORDINARY txs thanks to + // keys being displayed by paths instead of hashes) DENY_IF(violatesSingleAccountOrStoreIt(path)); WARN_UNLESS(bip44_isPathReasonable(path)); SHOW_IF(app_mode_expert()); ALLOW(); break; + case PATH_DREP_KEY: + case PATH_COMMITTEE_COLD_KEY: + case PATH_COMMITTEE_HOT_KEY: + // used to sign certificates and voting procedures + // these won't occur often, so little benefit from hiding them + // better to show them at least while they are new + // in the future, we might want to hide some of them in non-expert mode + DENY_IF(violatesSingleAccountOrStoreIt(path)); + WARN_UNLESS(bip44_isPathReasonable(path)); + SHOW(); + break; + case PATH_POOL_COLD_KEY: - // ordinary key paths and pool cold key paths can be hidden if they are not unusual - // (the user saw all outputs, withdrawals and pool certificates and they all belong to him) + // could be hidden perhaps, but it's safer to let the user to know + // the SW wallet wants to sign with the stake pool key WARN_UNLESS(bip44_isPathReasonable(path)); SHOW(); break; @@ -1410,7 +1583,7 @@ static inline security_policy_t _ordinaryWitnessPolicy(const bip44_path_t* path, static inline security_policy_t _multisigWitnessPolicy(const bip44_path_t* path, bool mintPresent) { switch (bip44_classifyPath(path)) { - case PATH_MULTISIG_SPENDING_KEY: + case PATH_MULTISIG_PAYMENT_KEY: case PATH_MULTISIG_STAKING_KEY: // multisig key paths are allowed, but hiding them would make impossible for the user to // distinguish what funds are being spent (multisig UTXOs sharing a signer are not @@ -1428,6 +1601,7 @@ static inline security_policy_t _multisigWitnessPolicy(const bip44_path_t* path, default: // ordinary and pool cold keys forbidden + // DRep and committee keys forbidden DENY(); break; } @@ -1437,10 +1611,13 @@ static inline security_policy_t _plutusWitnessPolicy(const bip44_path_t* path, b { switch (bip44_classifyPath(path)) { // in PLUTUS_TX, we allow signing with any path, but it must be shown - case PATH_ORDINARY_SPENDING_KEY: + case PATH_ORDINARY_PAYMENT_KEY: case PATH_ORDINARY_STAKING_KEY: - case PATH_MULTISIG_SPENDING_KEY: + case PATH_MULTISIG_PAYMENT_KEY: case PATH_MULTISIG_STAKING_KEY: + case PATH_DREP_KEY: + case PATH_COMMITTEE_COLD_KEY: + case PATH_COMMITTEE_HOT_KEY: WARN_UNLESS(bip44_isPathReasonable(path)); SHOW(); break; @@ -1461,6 +1638,8 @@ static inline security_policy_t _plutusWitnessPolicy(const bip44_path_t* path, b } } +#ifdef APP_FEATURE_POOL_REGISTRATION + static inline security_policy_t _poolRegistrationOwnerWitnessPolicy(const bip44_path_t* witnessPath, const bip44_path_t* poolOwnerPath) { switch (bip44_classifyPath(witnessPath)) { @@ -1489,11 +1668,12 @@ static inline security_policy_t _poolRegistrationOperatorWitnessPolicy(const bip { switch (bip44_classifyPath(path)) { - case PATH_ORDINARY_SPENDING_KEY: + case PATH_ORDINARY_PAYMENT_KEY: case PATH_POOL_COLD_KEY: - // only ordinary spending key paths (because of inputs) and pool cold key path are allowed + // only ordinary payment key paths (because of inputs) and pool cold key path are allowed WARN_UNLESS(bip44_isPathReasonable(path)); - // TODO is there a reason to show the witnesses? + // it might be safe to hide the witnesses, but txs related to stake pools + // are rare, so it would not help much and might introduce some unknown risk SHOW(); break; @@ -1503,6 +1683,8 @@ static inline security_policy_t _poolRegistrationOperatorWitnessPolicy(const bip } } +#endif // APP_FEATURE_POOL_REGISTRATION + // For each transaction witness // Note: witnesses reveal public key of an address and Ledger *does not* check // whether they correspond to previously declared inputs and certificates @@ -1510,7 +1692,7 @@ security_policy_t policyForSignTxWitness( sign_tx_signingmode_t txSigningMode, const bip44_path_t* witnessPath, bool mintPresent, - const bip44_path_t* poolOwnerPath + const bip44_path_t* poolOwnerPath __attribute__((unused)) ) { switch (txSigningMode) { @@ -1523,12 +1705,16 @@ security_policy_t policyForSignTxWitness( case SIGN_TX_SIGNINGMODE_PLUTUS_TX: return _plutusWitnessPolicy(witnessPath, mintPresent); + #ifdef APP_FEATURE_POOL_REGISTRATION + case SIGN_TX_SIGNINGMODE_POOL_REGISTRATION_OWNER: return _poolRegistrationOwnerWitnessPolicy(witnessPath, poolOwnerPath); case SIGN_TX_SIGNINGMODE_POOL_REGISTRATION_OPERATOR: return _poolRegistrationOperatorWitnessPolicy(witnessPath); + #endif // APP_FEATURE_POOL_REGISTRATION + default: ASSERT(false); } @@ -1594,7 +1780,7 @@ security_policy_t policyForSignTxMintConfirm(security_policy_t mintInitPolicy) break; case POLICY_SHOW_BEFORE_RESPONSE: - // all minted coins were shown, show a final confirmation prompt as well + // all minted tokens were shown, show a final confirmation prompt as well PROMPT(); break; @@ -1680,15 +1866,21 @@ static bool is_required_signer_allowed(bip44_path_t* path) { switch (bip44_classifyPath(path)) { case PATH_ORDINARY_ACCOUNT: - case PATH_ORDINARY_SPENDING_KEY: + case PATH_ORDINARY_PAYMENT_KEY: case PATH_ORDINARY_STAKING_KEY: return bip44_hasShelleyPrefix(path); case PATH_MULTISIG_ACCOUNT: - case PATH_MULTISIG_SPENDING_KEY: + case PATH_MULTISIG_PAYMENT_KEY: case PATH_MULTISIG_STAKING_KEY: return true; + case PATH_DREP_KEY: + case PATH_COMMITTEE_COLD_KEY: + case PATH_COMMITTEE_HOT_KEY: + // no known use case, but also no reason to deny + return true; + case PATH_MINT_KEY: return true; @@ -1755,6 +1947,100 @@ security_policy_t policyForSignTxReferenceInput(const sign_tx_signingmode_t txSi DENY(); } +// For voting procedures +security_policy_t policyForSignTxVotingProcedure( + sign_tx_signingmode_t txSigningMode, + ext_voter_t* voter +) +{ + // gov action id and vote can be arbitrary + // we only restrict voter because that determines witnesses + // certain combinations of tx signing mode and credential type are not allowed + // either because they don't make sense or are dangerous + switch (txSigningMode) { + + case SIGN_TX_SIGNINGMODE_ORDINARY_TX: + switch (voter->type) { + case EXT_VOTER_COMMITTEE_HOT_KEY_HASH: + case EXT_VOTER_COMMITTEE_HOT_SCRIPT_HASH: + case EXT_VOTER_DREP_KEY_HASH: + case EXT_VOTER_DREP_SCRIPT_HASH: + case EXT_VOTER_STAKE_POOL_KEY_HASH: + // keys must be given by path, otherwise the user does not know + // if the hash corresponds to one of his keys + DENY(); + break; + + case EXT_VOTER_COMMITTEE_HOT_KEY_PATH: + DENY_UNLESS(bip44_isCommitteeHotKeyPath(&voter->keyPath)); + break; + + case EXT_VOTER_DREP_KEY_PATH: + DENY_UNLESS(bip44_isDRepKeyPath(&voter->keyPath)); + break; + + case EXT_VOTER_STAKE_POOL_KEY_PATH: + DENY_UNLESS(bip44_isPoolColdKeyPath(&voter->keyPath)); + break; + + default: + ASSERT(false); + } + break; + + case SIGN_TX_SIGNINGMODE_MULTISIG_TX: + switch (voter->type) { + case EXT_VOTER_COMMITTEE_HOT_KEY_PATH: + case EXT_VOTER_COMMITTEE_HOT_KEY_HASH: + case EXT_VOTER_DREP_KEY_PATH: + case EXT_VOTER_DREP_KEY_HASH: + case EXT_VOTER_STAKE_POOL_KEY_PATH: + case EXT_VOTER_STAKE_POOL_KEY_HASH: + // everything is expected to be governed by native scripts + DENY(); + break; + + case EXT_VOTER_COMMITTEE_HOT_SCRIPT_HASH: + case EXT_VOTER_DREP_SCRIPT_HASH: + // scripts are OK + break; + + default: + ASSERT(false); + } + break; + + case SIGN_TX_SIGNINGMODE_PLUTUS_TX: + // everything allowed, txs are too complex for a HW wallet to understand + // and there might be third-party key hashes in the tx + break; + + default: + // this should not be called in POOL_REGISTRATION signing modes + ASSERT(false); + } + + SHOW(); +} + +// For treasury +security_policy_t policyForSignTxTreasury( + sign_tx_signingmode_t txSigningMode MARK_UNUSED, + uint64_t treasury MARK_UNUSED +) +{ + SHOW(); +} + +// For donation +security_policy_t policyForSignTxDonation( + sign_tx_signingmode_t txSigningMode MARK_UNUSED, + uint64_t donation MARK_UNUSED +) +{ + SHOW(); +} + security_policy_t policyForSignTxConfirm() { PROMPT(); @@ -1828,7 +2114,6 @@ security_policy_t policyForCVoteRegistrationPaymentDestination( default: ASSERT(false); - break; } DENY(); // should not be reached @@ -1852,6 +2137,7 @@ security_policy_t policyForCVoteRegistrationConfirm() PROMPT(); } +#ifdef APP_FEATURE_OPCERT security_policy_t policyForSignOpCert(const bip44_path_t* poolColdKeyPathSpec) { switch (bip44_classifyPath(poolColdKeyPathSpec)) { @@ -1871,6 +2157,7 @@ security_policy_t policyForSignOpCert(const bip44_path_t* poolColdKeyPathSpec) DENY(); // should not be reached } +#endif // APP_FEATURE_OPCERT security_policy_t policyForSignCVoteInit() { @@ -1896,3 +2183,46 @@ security_policy_t policyForSignCVoteWitness(bip44_path_t* path) break; } } + +security_policy_t policyForSignMsg( + const bip44_path_t* witnessPath, + cip8_address_field_type_t addressFieldType, + const addressParams_t* addressParams +) +{ + switch (bip44_classifyPath(witnessPath)) { + case PATH_ORDINARY_PAYMENT_KEY: + case PATH_ORDINARY_STAKING_KEY: + case PATH_MULTISIG_PAYMENT_KEY: + case PATH_MULTISIG_STAKING_KEY: + case PATH_MINT_KEY: + case PATH_DREP_KEY: + case PATH_COMMITTEE_COLD_KEY: + case PATH_COMMITTEE_HOT_KEY: + case PATH_POOL_COLD_KEY: + // OK + break; + default: + DENY(); + break; + } + + if (addressFieldType == CIP8_ADDRESS_FIELD_ADDRESS) { + DENY_UNLESS(isValidAddressParams(addressParams)); + + switch (addressParams->type) { + case BASE_PAYMENT_KEY_STAKE_KEY: + case BASE_PAYMENT_KEY_STAKE_SCRIPT: + case REWARD_KEY: + case ENTERPRISE_KEY: + // OK + break; + + default: + DENY(); + break; + } + } + + PROMPT(); +} diff --git a/src/securityPolicy.h b/src/securityPolicy.h index 37613d7c..7bcbd776 100644 --- a/src/securityPolicy.h +++ b/src/securityPolicy.h @@ -44,7 +44,10 @@ security_policy_t policyForSignTxInit( bool includeNetworkId, bool includeCollateralOutput, bool includeTotalCollateral, - uint16_t numReferenceInputs + uint16_t numReferenceInputs, + uint16_t numVotingProcedures, + bool includeTreasury, + bool includeDonation ); security_policy_t policyForSignTxInput(sign_tx_signingmode_t txSigningMode); @@ -111,13 +114,34 @@ security_policy_t policyForSignTxCertificate( security_policy_t policyForSignTxCertificateStaking( sign_tx_signingmode_t txSigningMode, const certificate_type_t certificateType, - const stake_credential_t* stakeCredential + const ext_credential_t* stakeCredential ); +security_policy_t policyForSignTxCertificateVoteDelegation( + sign_tx_signingmode_t txSigningMode, + const ext_credential_t* stakeCredential, + const ext_drep_t* drep +); +security_policy_t policyForSignTxCertificateCommitteeAuth( + sign_tx_signingmode_t txSigningMode, + const ext_credential_t* coldCredential, + const ext_credential_t* hotCredential +); +security_policy_t policyForSignTxCertificateCommitteeResign( + sign_tx_signingmode_t txSigningMode, + const ext_credential_t* coldCredential +); +security_policy_t policyForSignTxCertificateDRep( + sign_tx_signingmode_t txSigningMode, + const ext_credential_t* dRepCredential +); +#ifdef APP_FEATURE_POOL_RETIREMENT security_policy_t policyForSignTxCertificateStakePoolRetirement( sign_tx_signingmode_t txSigningMode, - const bip44_path_t* stakeCredential, + const ext_credential_t* poolCredential, uint64_t epoch ); +#endif // APP_FEATURE_POOL_RETIREMENT +#ifdef APP_FEATURE_POOL_REGISTRATION security_policy_t policyForSignTxStakePoolRegistrationInit( sign_tx_signingmode_t txSigningMode, uint32_t numOwners @@ -147,10 +171,10 @@ security_policy_t policyForSignTxStakePoolRegistrationNoMetadata(); security_policy_t policyForSignTxStakePoolRegistrationConfirm( uint32_t numOwners, uint32_t numRelays ); - +#endif // APP_FEATURE_POOL_REGISTRATION security_policy_t policyForSignTxWithdrawal( sign_tx_signingmode_t txSigningMode, - const stake_credential_t* stakeCredential + const ext_credential_t* stakeCredential ); security_policy_t policyForSignTxAuxData(aux_data_type_t auxDataType); @@ -183,9 +207,20 @@ security_policy_t policyForSignTxTotalCollateral(); security_policy_t policyForSignTxReferenceInput(const sign_tx_signingmode_t txSigningMode); +security_policy_t policyForSignTxVotingProcedure( + sign_tx_signingmode_t txSigningMode, + ext_voter_t* voter +); + +security_policy_t policyForSignTxTreasury(sign_tx_signingmode_t txSigningMode, uint64_t treasury); + +security_policy_t policyForSignTxDonation(sign_tx_signingmode_t txSigningMode, uint64_t donation); + security_policy_t policyForSignTxConfirm(); +#ifdef APP_FEATURE_OPCERT security_policy_t policyForSignOpCert(const bip44_path_t* poolColdKeyPathSpec); +#endif // APP_FEATURE_OPCERT security_policy_t policyForCVoteRegistrationVoteKey(); security_policy_t policyForCVoteRegistrationVoteKeyPath( @@ -207,4 +242,10 @@ security_policy_t policyForSignCVoteInit(); security_policy_t policyForSignCVoteConfirm(); security_policy_t policyForSignCVoteWitness(bip44_path_t* path); +security_policy_t policyForSignMsg( + const bip44_path_t* witnessPath, + cip8_address_field_type_t addressFieldType, + const addressParams_t* addressParams +); + #endif // H_CARDANO_APP_SECURITY_POLICY diff --git a/src/signCVote.c b/src/signCVote.c index 5cdb7b70..4d51e92e 100644 --- a/src/signCVote.c +++ b/src/signCVote.c @@ -60,9 +60,8 @@ void signCVote_handleInitAPDU( ) { { - //sanity checks + // sanity checks CHECK_STAGE(VOTECAST_STAGE_INIT); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } { TRACE_BUFFER(wireDataBuffer, wireDataSize); @@ -116,7 +115,7 @@ void signCVote_handleInitAPDU( default: THROW(ERR_NOT_IMPLEMENTED); } - handleInit_ui_runStep(); + signCVote_handleInit_ui_runStep(); } // ============================== VOTECAST CHUNK ============================== @@ -127,9 +126,8 @@ void signCVote_handleVotecastChunkAPDU( ) { { - //sanity checks + // sanity checks CHECK_STAGE(VOTECAST_STAGE_CHUNK); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } { read_view_t view = make_read_view(wireDataBuffer, wireDataBuffer + wireDataSize); @@ -159,9 +157,8 @@ void signCVote_handleConfirmAPDU( { TRACE_STACK_USAGE(); { - //sanity checks + // sanity checks CHECK_STAGE(VOTECAST_STAGE_CONFIRM); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } { // no data to receive @@ -193,7 +190,7 @@ void signCVote_handleConfirmAPDU( } } - handleConfirm_ui_runStep(); + signCVote_handleConfirm_ui_runStep(); } // ============================== WITNESS ============================== @@ -207,7 +204,6 @@ void signCVote_handleWitnessAPDU( { // sanity checks CHECK_STAGE(VOTECAST_STAGE_WITNESS); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } { @@ -282,6 +278,7 @@ void signCVote_handleAPDU( bool isNewCall ) { + ASSERT(wireDataBuffer != NULL); ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); if (isNewCall) { diff --git a/src/signCVote_ui.c b/src/signCVote_ui.c index be0fe563..d1b3c48e 100644 --- a/src/signCVote_ui.c +++ b/src/signCVote_ui.c @@ -17,9 +17,9 @@ static ins_sign_cvote_context_t* ctx = &(instructionState.signCVoteContext); // ============================== INIT ============================== -void handleInit_ui_runStep() +void signCVote_handleInit_ui_runStep() { - ui_callback_fn_t* this_fn = handleInit_ui_runStep; + ui_callback_fn_t* this_fn = signCVote_handleInit_ui_runStep; UI_STEP_BEGIN(ctx->ui_step, this_fn); @@ -92,11 +92,11 @@ void handleInit_ui_runStep() // ============================== CONFIRM ============================== -void handleConfirm_ui_runStep() +void signCVote_handleConfirm_ui_runStep() { TRACE("UI step %d", ctx->ui_step); TRACE_STACK_USAGE(); - ui_callback_fn_t* this_fn = handleConfirm_ui_runStep; + ui_callback_fn_t* this_fn = signCVote_handleConfirm_ui_runStep; UI_STEP_BEGIN(ctx->ui_step, this_fn); diff --git a/src/signCVote_ui.h b/src/signCVote_ui.h index d18a1fb0..ca1edc3d 100644 --- a/src/signCVote_ui.h +++ b/src/signCVote_ui.h @@ -2,6 +2,7 @@ #define H_CARDANO_APP_SIGN_CVOTE_UI #include "uiHelpers.h" + // ============================== INIT ============================== enum { @@ -13,7 +14,7 @@ enum { HANDLE_INIT_INVALID, }; -void handleInit_ui_runStep(); +void signCVote_handleInit_ui_runStep(); // ============================== CONFIRM ============================== @@ -23,7 +24,7 @@ enum { HANDLE_CONFIRM_STEP_INVALID, }; -void handleConfirm_ui_runStep(); +void signCVote_handleConfirm_ui_runStep(); // ============================== WITNESS ============================== diff --git a/src/signMsg.c b/src/signMsg.c new file mode 100644 index 00000000..4e5397e5 --- /dev/null +++ b/src/signMsg.c @@ -0,0 +1,374 @@ +#include "common.h" + +#include "signMsg.h" +#include "signMsg_ui.h" +#include "keyDerivation.h" +#include "endian.h" +#include "state.h" +#include "uiHelpers.h" +#include "securityPolicy.h" +#include "messageSigning.h" +#include "textUtils.h" +#include "signTxUtils.h" + +#ifdef HAVE_BAGL +#include "uiScreens_bagl.h" +#elif defined(HAVE_NBGL) +#include "uiScreens_nbgl.h" +#endif + +static ins_sign_msg_context_t* ctx = &(instructionState.signMsgContext); + +void signMsg_handleInitAPDU( + const uint8_t* wireDataBuffer, + size_t wireDataSize +) +{ + { + TRACE_BUFFER(wireDataBuffer, wireDataSize); + read_view_t view = make_read_view(wireDataBuffer, wireDataBuffer + wireDataSize); + + ctx->msgLength = parse_u4be(&view); + TRACE("Msg length: %d", ctx->msgLength); + ctx->remainingBytes = ctx->msgLength; + + view_skipBytes(&view, bip44_parseFromWire(&ctx->signingPath, VIEW_REMAINING_TO_TUPLE_BUF_SIZE(&view))); + TRACE("Signing path:"); + BIP44_PRINTF(&ctx->signingPath); + PRINTF("\n"); + + ctx->hashPayload = parse_bool(&view); + TRACE("Hash payload: %d", ctx->hashPayload); + + ctx->isAscii = parse_bool(&view); + TRACE("Is ascii: %d", ctx->isAscii); + + ctx->addressFieldType = parse_u1be(&view); + TRACE("Address field type: %d", ctx->addressFieldType); + switch (ctx->addressFieldType) { + case CIP8_ADDRESS_FIELD_ADDRESS: + view_parseAddressParams(&view, &ctx->addressParams); + break; + case CIP8_ADDRESS_FIELD_KEYHASH: + // no address field data to parse + break; + default: + THROW(ERR_INVALID_DATA); + } + + VALIDATE(view_remainingSize(&view) == 0, ERR_INVALID_DATA); + } + + // Check security policy + security_policy_t policy = policyForSignMsg( + &ctx->signingPath, + ctx->addressFieldType, + &ctx->addressParams + ); + ENSURE_NOT_DENIED(policy); + + // always compute message hash + blake2b_224_init(&ctx->msgHashCtx); + + { + // key is sent back at the end and possibly needed when displaying address field + extendedPublicKey_t extPubKey; + deriveExtendedPublicKey( + &ctx->signingPath, + &extPubKey + ); + STATIC_ASSERT(SIZEOF(extPubKey.pubKey) == SIZEOF(ctx->witnessKey), "wrong witness key size"); + memmove(ctx->witnessKey, extPubKey.pubKey, SIZEOF(extPubKey.pubKey)); + } + + // this must always be shown + ASSERT(policy == POLICY_PROMPT_BEFORE_RESPONSE); + ctx->ui_step = HANDLE_INIT_HASH_PAYLOAD; + signMsg_handleInit_ui_runStep(); +} + +static void signMsg_handleMsgChunkAPDU(const uint8_t* wireDataBuffer, size_t wireDataSize) +{ + { + ASSERT(ctx->stage == SIGN_MSG_STAGE_CHUNKS); + if (!ctx->hashPayload) { + // only a single chunk is to be received + ASSERT(ctx->receivedChunks == 0); + } + } + { + TRACE_BUFFER(wireDataBuffer, wireDataSize); + + ctx->receivedChunks += 1; + + read_view_t view = make_read_view(wireDataBuffer, wireDataBuffer + wireDataSize); + + const size_t chunkSize = parse_u4be(&view); + TRACE("chunkSize = %u", chunkSize); + + VALIDATE(chunkSize <= ctx->remainingBytes, ERR_INVALID_DATA); + + // the current chunk should have maximum allowed size; + // there is no point in allowing unnecessarily small chunks + // and it is a security risk if the first chunk (possibly the only one displayed) + // is artificially small + if (ctx->receivedChunks == 1) { + // the first chunk must be displayable + // the check below works for empty message (with special UI) too + if (ctx->isAscii) { + VALIDATE( + chunkSize == MIN(ctx->msgLength, MAX_CIP8_MSG_FIRST_CHUNK_ASCII_SIZE), + ERR_INVALID_DATA + ); + } else { + VALIDATE( + chunkSize == MIN(ctx->msgLength, MAX_CIP8_MSG_FIRST_CHUNK_HEX_SIZE), + ERR_INVALID_DATA + ); + } + } else { + // ctx->receivedChunks >= 2 + VALIDATE( + chunkSize == MIN(ctx->remainingBytes, MAX_CIP8_MSG_HIDDEN_CHUNK_SIZE), + ERR_INVALID_DATA + ); + } + + ASSERT(chunkSize <= ctx->remainingBytes); + ctx->remainingBytes -= chunkSize; + ctx->chunkSize = chunkSize; + + ASSERT(chunkSize <= SIZEOF(ctx->chunk)); + view_parseBuffer(ctx->chunk, &view, chunkSize); + if (ctx->isAscii) { + VALIDATE(str_isUnambiguousAscii(ctx->chunk, ctx->chunkSize), ERR_INVALID_DATA); + } + + VALIDATE(view_remainingSize(&view) == 0, ERR_INVALID_DATA); + } + { + TRACE("Adding msg chunk to msg hash"); + blake2b_224_append(&ctx->msgHashCtx, ctx->chunk, ctx->chunkSize); + } + + if (ctx->receivedChunks == 1) { + if (!ctx->hashPayload) { + // for non-hashed payload, we expect only a single chunk + VALIDATE(ctx->remainingBytes == 0, ERR_INVALID_DATA); + } + ctx->ui_step = HANDLE_CHUNK_STEP_INTRO; + signMsg_handleChunk_ui_runStep(); + } else { + // for non-hashed payload, we expect only a single chunk, + // so the state should be SIGN_MSG_STAGE_CONFIRM already + ASSERT(ctx->hashPayload); + + // the chunk has been added to msg hash, nothing more to do, and no UI + respondSuccessEmptyMsg(); + + if (ctx->remainingBytes == 0) { + ctx->stage = SIGN_MSG_STAGE_CONFIRM; + } + } +} + +static void _prepareAddressField() +{ + switch (ctx->addressFieldType) { + + case CIP8_ADDRESS_FIELD_ADDRESS: { + ctx->addressFieldSize = deriveAddress(&ctx->addressParams, ctx->addressField, SIZEOF(ctx->addressField)); + break; + } + + case CIP8_ADDRESS_FIELD_KEYHASH: { + STATIC_ASSERT(SIZEOF(ctx->addressField) >= ADDRESS_KEY_HASH_LENGTH, "wrong address field size"); + bip44_pathToKeyHash(&ctx->signingPath, ctx->addressField, ADDRESS_KEY_HASH_LENGTH); + ctx->addressFieldSize = ADDRESS_KEY_HASH_LENGTH; + break; + } + + default: + ASSERT(false); + } +} + +__noinline_due_to_stack__ +static size_t _createProtectedHeader(uint8_t* protectedHeaderBuffer, size_t maxSize) +{ + // protectedHeader = { + // 1 : -8, // set algorithm to EdDSA + // “address” : address_bytes // raw address given by the user, or key hash + // } + uint8_t* p = protectedHeaderBuffer; + uint8_t* end = protectedHeaderBuffer + maxSize; + + { + size_t len = cbor_writeToken(CBOR_TYPE_MAP, 2, p, end - p); + p += len; + ASSERT(p < end); + } + { + size_t len = cbor_writeToken(CBOR_TYPE_UNSIGNED, 1, p, end - p); + p += len; + ASSERT(p < end); + } + { + size_t len = cbor_writeToken(CBOR_TYPE_NEGATIVE, -8, p, end - p); + p += len; + ASSERT(p < end); + } + { + size_t len = cbor_writeToken(CBOR_TYPE_TEXT, 7, p, end - p); + p += len; + ASSERT(p < end); + } + { + const char* text = "address"; + const size_t len = strlen(text); + ASSERT(p + len < end); + memmove(p, text, len); + p += len; + ASSERT(p < end); + } + { + _prepareAddressField(); + ASSERT(ctx->addressFieldSize > 0); + + size_t len = cbor_writeToken(CBOR_TYPE_BYTES, ctx->addressFieldSize, p, end - p); + p += len; + ASSERT(p + ctx->addressFieldSize < end); + memmove(p, ctx->addressField, ctx->addressFieldSize); + p += ctx->addressFieldSize; + ASSERT(p < end); + } + + const size_t protectedHeaderSize = p - protectedHeaderBuffer; + ASSERT(protectedHeaderSize > 0); + ASSERT(protectedHeaderSize < maxSize); + + return protectedHeaderSize; +} + +static void signMsg_handleConfirmAPDU(const uint8_t* wireDataBuffer MARK_UNUSED, size_t wireDataSize) +{ + VALIDATE(wireDataSize == 0, ERR_INVALID_DATA); + + // it seems Ledger can sign 400 B, more is not needed since non-hashed msg is capped at 200 B + uint8_t sigStructure[400] = {0}; + explicit_bzero(sigStructure, SIZEOF(sigStructure)); + size_t written = 0; + const size_t maxWritten = SIZEOF(sigStructure); + + // Sig_structure = [ + // context : “Signature1”, + // body_protected : CBOR_encode(protectedHeader), + // external_aad : bstr, // empty buffer here + // payload : bstr // message or its hash as bytes + // ] + + written += cbor_writeToken(CBOR_TYPE_ARRAY, 4, sigStructure + written, maxWritten - written); + ASSERT(written < maxWritten); + + { + const char* firstElement = "Signature1"; + const size_t len = strlen(firstElement); + written += cbor_writeToken(CBOR_TYPE_TEXT, len, sigStructure + written, maxWritten - written); + ASSERT(written + len < maxWritten); + memmove(sigStructure + written, firstElement, len); + written += len; + ASSERT(written < maxWritten); + } + { + uint8_t protectedHeaderBuffer[100] = {0}; // address of max 57 bytes plus a couple of small items + const size_t len = _createProtectedHeader(protectedHeaderBuffer, SIZEOF(protectedHeaderBuffer)); + ASSERT(len < BUFFER_SIZE_PARANOIA); + written += cbor_writeToken(CBOR_TYPE_BYTES, len, sigStructure + written, maxWritten - written); + ASSERT(written + len < maxWritten); + memmove(sigStructure + written, protectedHeaderBuffer, len); + written += len; + ASSERT(written < maxWritten); + } + { + written += cbor_writeToken(CBOR_TYPE_BYTES, 0, sigStructure + written, maxWritten - written); + ASSERT(written < maxWritten); + } + STATIC_ASSERT(SIZEOF(ctx->msgHash) * 8 == 224, "inconsistent message hash size"); + blake2b_224_finalize(&ctx->msgHashCtx, ctx->msgHash, SIZEOF(ctx->msgHash)); + { + if (ctx->hashPayload) { + written += cbor_writeToken(CBOR_TYPE_BYTES, SIZEOF(ctx->msgHash), sigStructure + written, maxWritten - written); + ASSERT(written < maxWritten); + + ASSERT(SIZEOF(ctx->msgHash) < maxWritten - written); + memmove(sigStructure + written, ctx->msgHash, SIZEOF(ctx->msgHash)); + written += SIZEOF(ctx->msgHash); + ASSERT(written < maxWritten); + } else { + // for non-hashed payload, the chunk from the previous APDU is used + ASSERT(ctx->receivedChunks == 1); + + written += cbor_writeToken(CBOR_TYPE_BYTES, ctx->chunkSize, sigStructure + written, maxWritten - written); + ASSERT(written < maxWritten); + + ASSERT(ctx->chunkSize < maxWritten - written); + memmove(sigStructure + written, ctx->chunk, ctx->chunkSize); + written += ctx->chunkSize; + ASSERT(written < maxWritten); + } + } + + const size_t sigStructureSize = written; + TRACE_BUFFER(sigStructure, sigStructureSize); + + // we do not sign anything that could be a transaction hash + // Note: in the v7.1 implementation Sig_structure has more than 40 bytes + ASSERT(sigStructureSize != TX_HASH_LENGTH); + + signRawMessageWithPath(&ctx->signingPath, sigStructure, sigStructureSize, ctx->signature, SIZEOF(ctx->signature)); + + ctx->ui_step = HANDLE_CONFIRM_STEP_MSG_HASH; + signMsg_handleConfirm_ui_runStep(); +} + +// ============================== MAIN HANDLER ============================== + +typedef void subhandler_fn_t(const uint8_t* dataBuffer, size_t dataSize); + +static subhandler_fn_t* lookup_subhandler(uint8_t p1) +{ + switch (p1) { +#define CASE(P1, HANDLER) case P1: return HANDLER; +#define DEFAULT(HANDLER) default: return HANDLER; + CASE(0x01, signMsg_handleInitAPDU); + CASE(0x02, signMsg_handleMsgChunkAPDU); + CASE(0x03, signMsg_handleConfirmAPDU); + DEFAULT(NULL) +#undef CASE +#undef DEFAULT + } +} + +void signMsg_handleAPDU( + uint8_t p1, + uint8_t p2, + const uint8_t* wireDataBuffer, + size_t wireDataSize, + bool isNewCall +) +{ + TRACE("P1 = 0x%x, P2 = 0x%x, isNewCall = %d", p1, p2, isNewCall); + ASSERT(wireDataBuffer != NULL); + ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); + + VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS); + + if (isNewCall) { + explicit_bzero(ctx, SIZEOF(*ctx)); + ctx->stage = SIGN_MSG_STAGE_INIT; + } + + subhandler_fn_t* subhandler = lookup_subhandler(p1); + VALIDATE(subhandler != NULL, ERR_INVALID_REQUEST_PARAMETERS); + subhandler(wireDataBuffer, wireDataSize); +} diff --git a/src/signMsg.h b/src/signMsg.h new file mode 100644 index 00000000..2da3d2ea --- /dev/null +++ b/src/signMsg.h @@ -0,0 +1,56 @@ +#ifndef H_CARDANO_APP_SIGN_MSG +#define H_CARDANO_APP_SIGN_MSG + +#include "addressUtilsShelley.h" +#include "cardano.h" +#include "common.h" +#include "handlers.h" +#include "hash.h" +#include "bip44.h" +#include "keyDerivation.h" + +handler_fn_t signMsg_handleAPDU; + +#define CIP8_MSG_HASH_LENGTH 28 + +// Note: this cannot be increased, there is a limit of 200 chars in the UI +#define MAX_CIP8_MSG_FIRST_CHUNK_ASCII_SIZE 198 +#define MAX_CIP8_MSG_FIRST_CHUNK_HEX_SIZE 99 +#define MAX_CIP8_MSG_HIDDEN_CHUNK_SIZE 250 + +typedef enum { + SIGN_MSG_STAGE_NONE = 0, + SIGN_MSG_STAGE_INIT = 43, + SIGN_MSG_STAGE_CHUNKS = 44, + SIGN_MSG_STAGE_CONFIRM = 45, +} sign_msg_stage_t; + +typedef struct { + bip44_path_t signingPath; + cip8_address_field_type_t addressFieldType; + addressParams_t addressParams; + + bool isAscii; + bool hashPayload; + + size_t msgLength; + size_t remainingBytes; + size_t receivedChunks; + + uint8_t chunk[MAX_CIP8_MSG_HIDDEN_CHUNK_SIZE]; + size_t chunkSize; + + blake2b_224_context_t msgHashCtx; + uint8_t msgHash[CIP8_MSG_HASH_LENGTH]; + uint8_t signature[ED25519_SIGNATURE_LENGTH]; + uint8_t witnessKey[PUBLIC_KEY_SIZE]; + uint8_t addressField[MAX_ADDRESS_SIZE]; + size_t addressFieldSize; + + sign_msg_stage_t stage; + int ui_step; +} ins_sign_msg_context_t; + +handler_fn_t signMsg_handleAPDU; + +#endif // H_CARDANO_APP_SIGN_MSG \ No newline at end of file diff --git a/src/signMsg_ui.c b/src/signMsg_ui.c new file mode 100644 index 00000000..da0c60df --- /dev/null +++ b/src/signMsg_ui.c @@ -0,0 +1,308 @@ +#include "hexUtils.h" +#include "messageSigning.h" +#include "securityPolicy.h" +#include "signMsg.h" +#include "signTxUtils.h" +#include "state.h" +#include "signMsg_ui.h" + +#ifdef HAVE_BAGL +#include "uiScreens_bagl.h" +#elif defined(HAVE_NBGL) +#include "uiScreens_nbgl.h" +#include "nbgl_use_case.h" +#endif + + +static ins_sign_msg_context_t* ctx = &(instructionState.signMsgContext); + +// ============================== INIT ============================== + +__noinline_due_to_stack__ +static void _displayAddressField(ui_callback_fn_t* callback) +{ + switch (ctx->addressFieldType) { + case CIP8_ADDRESS_FIELD_ADDRESS: { + uint8_t addressBuffer[MAX_ADDRESS_SIZE] = {0}; + size_t addressSize = deriveAddress(&ctx->addressParams, addressBuffer, SIZEOF(addressBuffer)); + ASSERT(addressSize > 0); + ASSERT(addressSize <= MAX_ADDRESS_SIZE); + + #ifdef HAVE_BAGL + ui_displayAddressScreen( + "Address field", + addressBuffer, addressSize, + callback + ); + #elif defined(HAVE_NBGL) + char humanAddress[MAX_HUMAN_ADDRESS_SIZE] = {0}; + ui_getAddressScreen( + humanAddress, + SIZEOF(humanAddress), + addressBuffer, + addressSize + ); + fill_and_display_if_required("Address field", humanAddress, callback, respond_with_user_reject); + #endif // HAVE_BAGL + return; + } + + case CIP8_ADDRESS_FIELD_KEYHASH: { + uint8_t hash[28]; + blake2b_224_hash( + ctx->witnessKey, SIZEOF(ctx->witnessKey), + hash, SIZEOF(hash) + ); + + #ifdef HAVE_BAGL + ui_displayHexBufferScreen( + "Address field (hex)", + hash, + SIZEOF(hash), + callback + ); + #elif defined(HAVE_NBGL) + char bufferHex[2 * SCRIPT_HASH_LENGTH + 1] = {0}; + ui_getHexBufferScreen(bufferHex, SIZEOF(bufferHex), hash, SIZEOF(hash)); + fill_and_display_if_required("Address field (hex)", bufferHex, callback, respond_with_user_reject); + #endif // HAVE_BAGL + return; + } + + default: + ASSERT(false); + return; + } +} + +void signMsg_handleInit_ui_runStep() +{ + TRACE("UI step %d", ctx->ui_step); + TRACE_STACK_USAGE(); + ui_callback_fn_t* this_fn = signMsg_handleInit_ui_runStep; + + UI_STEP_BEGIN(ctx->ui_step, this_fn); + + UI_STEP(HANDLE_INIT_HASH_PAYLOAD) { + #ifdef HAVE_BAGL + const char* firstLine = (ctx->hashPayload) ? "Sign hashed" : "Sign non-hashed"; + ui_displayPrompt( + firstLine, + "message? (CIP-8)", + this_fn, + respond_with_user_reject + ); + #elif defined(HAVE_NBGL) + set_light_confirmation(true); + const char* text = (ctx->hashPayload) ? "Sign hashed\nmessage? (CIP-8)" : "Sign non-hashed\nmessage? (CIP-8)"; + display_prompt(text, "", this_fn, respond_with_user_reject); + #endif // HAVE_BAGL + } + UI_STEP(HANDLE_INIT_WITNESS_PATH) { + #ifdef HAVE_BAGL + ui_displayPathScreen("Signing path", &ctx->signingPath, this_fn); + #elif defined(HAVE_NBGL) + char pathStr[BIP44_PATH_STRING_SIZE_MAX + 1] = {0}; + ui_getPathScreen(pathStr, SIZEOF(pathStr), &ctx->signingPath); + fill_and_display_if_required("Signing path", pathStr, this_fn, respond_with_user_reject); + #endif // HAVE_BAGL + } + UI_STEP(HANDLE_INIT_ADDRESS_FIELD) { + _displayAddressField(this_fn); + } + UI_STEP(HANDLE_INIT_RESPOND) { + respondSuccessEmptyMsg(); + ctx->stage = SIGN_MSG_STAGE_CHUNKS; + } + UI_STEP_END(HANDLE_INIT_INVALID); +} + +// ============================== CHUNK ============================== + +void _displayMsgIntro(ui_callback_fn_t* callback) +{ + char l1[30] = {0}; + if (ctx->isAscii) { + snprintf(l1, SIZEOF(l1), "Message (ASCII)"); + } else { + snprintf(l1, SIZEOF(l1), "Message (hex)"); + } + ASSERT(strlen(l1) + 1 < SIZEOF(l1)); + + char l2[30] = {0}; + ASSERT(ctx->msgLength < UINT32_MAX); + snprintf(l2, SIZEOF(l2), "%u bytes", (uint32_t)ctx->msgLength); + ASSERT(strlen(l2) + 1 < SIZEOF(l2)); + + #ifdef HAVE_BAGL + ui_displayPaginatedText( + l1, + l2, + callback + ); + #elif defined(HAVE_NBGL) + fill_and_display_if_required(l1, l2, callback, respond_with_user_reject); + #endif // HAVE_BAGL +} + +__noinline_due_to_stack__ +void _displayMsgFull(ui_callback_fn_t* callback) +{ + char l1[30]; + if (ctx->isAscii) { + snprintf(l1, SIZEOF(l1), "Message (ASCII)"); + } else { + snprintf(l1, SIZEOF(l1), "Message (hex)"); + } + ASSERT(strlen(l1) + 1 < SIZEOF(l1)); + + char l2[200]; + if (ctx->isAscii) { + ASSERT(ctx->chunkSize + 1 < SIZEOF(l2)); + memmove(l2, ctx->chunk, ctx->chunkSize); + l2[ctx->chunkSize] = '\0'; + } else { + encode_hex(ctx->chunk, ctx->chunkSize, l2, SIZEOF(l2)); + } + ASSERT(strlen(l2) + 1 < SIZEOF(l2)); + + #ifdef HAVE_BAGL + ui_displayPaginatedText( + l1, + l2, + callback + ); + #elif defined(HAVE_NBGL) + fill_and_display_if_required(l1, l2, callback, respond_with_user_reject); + #endif // HAVE_BAGL +} + +__noinline_due_to_stack__ +void _displayMsgChunk(ui_callback_fn_t* callback) +{ + const char* l1 = "Message starts with"; + + char l2[200]; + if (ctx->isAscii) { + ASSERT(ctx->chunkSize + 1 < SIZEOF(l2)); + memmove(l2, ctx->chunk, ctx->chunkSize); + l2[ctx->chunkSize] = '\0'; + } else { + encode_hex(ctx->chunk, ctx->chunkSize, l2, SIZEOF(l2)); + } + ASSERT(strlen(l2) + 1 < SIZEOF(l2)); + + #ifdef HAVE_BAGL + ui_displayPaginatedText( + l1, + l2, + callback + ); + #elif defined(HAVE_NBGL) + fill_and_display_if_required(l1, l2, callback, respond_with_user_reject); + #endif // HAVE_BAGL +} + +void signMsg_handleChunk_ui_runStep() +{ + TRACE("UI step %d", ctx->ui_step); + TRACE_STACK_USAGE(); + ui_callback_fn_t* this_fn = signMsg_handleChunk_ui_runStep; + + ASSERT(ctx->receivedChunks == 1); + + UI_STEP_BEGIN(ctx->ui_step, this_fn); + + UI_STEP(HANDLE_CHUNK_STEP_INTRO) { + _displayMsgIntro(this_fn); + } + UI_STEP(HANDLE_CHUNK_STEP_DISPLAY) { + if (ctx->msgLength == 0) { + UI_STEP_JUMP(HANDLE_CHUNK_STEP_RESPOND); + } + if (ctx->remainingBytes == 0) { + _displayMsgFull(this_fn); + } else { + _displayMsgChunk(this_fn); + } + } + UI_STEP(HANDLE_CHUNK_STEP_RESPOND) { + respondSuccessEmptyMsg(); + + if (ctx->remainingBytes == 0) { + ctx->stage = SIGN_MSG_STAGE_CONFIRM; + } + } + UI_STEP_END(HANDLE_CHUNK_STEP_INVALID); +} + +// ============================== CONFIRM ============================== + +void signMsg_handleConfirm_ui_runStep() +{ + TRACE("UI step %d", ctx->ui_step); + TRACE_STACK_USAGE(); + ui_callback_fn_t* this_fn = signMsg_handleConfirm_ui_runStep; + + UI_STEP_BEGIN(ctx->ui_step, this_fn); + + UI_STEP(HANDLE_CONFIRM_STEP_MSG_HASH) { + #ifdef HAVE_BAGL + ui_displayHexBufferScreen( + "Message hash", + ctx->msgHash, + SIZEOF(ctx->msgHash), + this_fn + ); + #elif defined(HAVE_NBGL) + char bufferHex[2 * CIP8_MSG_HASH_LENGTH + 1] = {0}; + ui_getHexBufferScreen(bufferHex, SIZEOF(bufferHex), ctx->msgHash, SIZEOF(ctx->msgHash)); + fill_and_display_if_required("Message hash", bufferHex, this_fn, respond_with_user_reject); + #endif // HAVE_BAGL + } + UI_STEP(HANDLE_CONFIRM_STEP_FINAL_CONFIRM) { + #ifdef HAVE_BAGL + ui_displayPrompt( + "Sign", + "message?", + this_fn, + respond_with_user_reject + ); + #elif defined(HAVE_NBGL) + display_confirmation("Sign\n message?", "", "MESSAGE\nSIGNED", "Message\nrejected", this_fn, respond_with_user_reject); + #endif // HAVE_BAGL + } + UI_STEP(HANDLE_CONFIRM_STEP_RESPOND) { + struct { + uint8_t signature[ED25519_SIGNATURE_LENGTH]; + uint8_t witnessKey[PUBLIC_KEY_SIZE]; + uint32_t addressFieldSize; + uint8_t addressField[MAX_ADDRESS_SIZE]; + } wireResponse = {0}; + STATIC_ASSERT(SIZEOF(wireResponse) <= 255, "too large msg signing wire response"); + + STATIC_ASSERT(SIZEOF(ctx->signature) == ED25519_SIGNATURE_LENGTH, "wrong signature buffer size"); + memmove(wireResponse.signature, ctx->signature, ED25519_SIGNATURE_LENGTH); + + STATIC_ASSERT(SIZEOF(ctx->witnessKey) == PUBLIC_KEY_SIZE, "wrong key buffer size"); + memmove(wireResponse.witnessKey, ctx->witnessKey, PUBLIC_KEY_SIZE); + + #ifndef FUZZING + STATIC_ASSERT(sizeof(wireResponse.addressFieldSize) == 4, "wrong address field size type"); + STATIC_ASSERT(sizeof(ctx->addressFieldSize) == 4, "wrong address field size type"); + u4be_write((uint8_t*) &wireResponse.addressFieldSize, ctx->addressFieldSize); + #endif + + STATIC_ASSERT(SIZEOF(ctx->addressField) == SIZEOF(wireResponse.addressField), "wrong address field size"); + memmove(wireResponse.addressField, ctx->addressField, ctx->addressFieldSize); + + io_send_buf(SUCCESS, (uint8_t*) &wireResponse, SIZEOF(wireResponse)); + #ifdef HAVE_BAGL + ui_displayBusy(); // displays dots, called only after I/O to avoid freezing + #endif // HAVE_BAGL + + ctx->stage = SIGN_MSG_STAGE_NONE; + ui_idle(); + } + UI_STEP_END(HANDLE_CONFIRM_STEP_INVALID); +} diff --git a/src/signMsg_ui.h b/src/signMsg_ui.h new file mode 100644 index 00000000..e09681d7 --- /dev/null +++ b/src/signMsg_ui.h @@ -0,0 +1,40 @@ +#ifndef H_CARDANO_APP_SIGN_MSG_UI +#define H_CARDANO_APP_SIGN_MSG_UI + +#include "uiHelpers.h" + +// ============================== INIT ============================== + +enum { + HANDLE_INIT_HASH_PAYLOAD = 100, + HANDLE_INIT_WITNESS_PATH, + HANDLE_INIT_ADDRESS_FIELD, + HANDLE_INIT_RESPOND, + HANDLE_INIT_INVALID, +}; + +void signMsg_handleInit_ui_runStep(); + +// ============================== CHUNK ============================== + +enum { + HANDLE_CHUNK_STEP_INTRO = 200, + HANDLE_CHUNK_STEP_DISPLAY, + HANDLE_CHUNK_STEP_RESPOND, + HANDLE_CHUNK_STEP_INVALID, +}; + +void signMsg_handleChunk_ui_runStep(); + +// ============================== CONFIRM ============================== + +enum { + HANDLE_CONFIRM_STEP_MSG_HASH = 300, + HANDLE_CONFIRM_STEP_FINAL_CONFIRM, + HANDLE_CONFIRM_STEP_RESPOND, + HANDLE_CONFIRM_STEP_INVALID, +}; + +void signMsg_handleConfirm_ui_runStep(); + +#endif // H_CARDANO_APP_SIGN_MSG_UI diff --git a/src/signOpCert.c b/src/signOpCert.c index 9a3302b4..2d42dfde 100644 --- a/src/signOpCert.c +++ b/src/signOpCert.c @@ -1,3 +1,5 @@ +#ifdef APP_FEATURE_OPCERT + #include "common.h" #include "signOpCert.h" @@ -130,6 +132,8 @@ void signOpCert_handleAPDU( static void signOpCert_ui_runStep() { + TRACE("UI step %d", ctx->ui_step); + TRACE_STACK_USAGE(); ui_callback_fn_t* this_fn = signOpCert_ui_runStep; UI_STEP_BEGIN(ctx->ui_step, this_fn); @@ -243,3 +247,5 @@ static void signOpCert_ui_runStep() } UI_STEP_END(UI_STEP_INVALID); } + +#endif // APP_FEATURE_OPCERT diff --git a/src/signOpCert.h b/src/signOpCert.h index af844792..29eb9d1f 100644 --- a/src/signOpCert.h +++ b/src/signOpCert.h @@ -1,6 +1,9 @@ #ifndef H_CARDANO_APP_SIGN_OP_CERT #define H_CARDANO_APP_SIGN_OP_CERT +#ifdef APP_FEATURE_OPCERT + +#include "cardano.h" #include "common.h" #include "handlers.h" #include "bip44.h" @@ -16,8 +19,10 @@ typedef struct { uint64_t kesPeriod; uint64_t issueCounter; bip44_path_t poolColdKeyPathSpec; - uint8_t signature[64]; + uint8_t signature[ED25519_SIGNATURE_LENGTH]; int ui_step; } ins_sign_op_cert_context_t; +#endif // APP_FEATURE_OPCERT + #endif // H_CARDANO_APP_SIGN_OP_CERT \ No newline at end of file diff --git a/src/signTx.c b/src/signTx.c index f26bec2d..873d7851 100644 --- a/src/signTx.c +++ b/src/signTx.c @@ -30,6 +30,8 @@ static inline void initTxBodyCtx() BODY_CTX->currentWithdrawal = 0; BODY_CTX->currentCollateral = 0; BODY_CTX->currentRequiredSigner = 0; + BODY_CTX->currentReferenceInput = 0; + BODY_CTX->currentVotingProcedure = 0; BODY_CTX->feeReceived = false; BODY_CTX->ttlReceived = false; BODY_CTX->validityIntervalStartReceived = false; @@ -37,6 +39,8 @@ static inline void initTxBodyCtx() BODY_CTX->scriptDataHashReceived = false; BODY_CTX->collateralOutputReceived = false; BODY_CTX->totalCollateralReceived = false; + BODY_CTX->treasuryReceived = false; + BODY_CTX->donationReceived = false; } } @@ -87,6 +91,7 @@ void tx_advanceStage() // Note: make sure that everything in ctx is initialized properly txHashBuilder_init( &BODY_CTX->txHashBuilder, + ctx->commonTxData.tagCborSets, ctx->numInputs, ctx->numOutputs, ctx->includeTtl, @@ -101,7 +106,10 @@ void tx_advanceStage() ctx->includeNetworkId, ctx->includeCollateralOutput, ctx->includeTotalCollateral, - ctx->numReferenceInputs + ctx->numReferenceInputs, + ctx->numVotingProcedures, + ctx->includeTreasury, + ctx->includeDonation ); txHashBuilder_enterInputs(&BODY_CTX->txHashBuilder); } @@ -112,6 +120,7 @@ void tx_advanceStage() ASSERT(BODY_CTX->currentInput == ctx->numInputs); txHashBuilder_enterOutputs(&BODY_CTX->txHashBuilder); initializeOutputSubmachine(); + ctx->stage = SIGN_STAGE_BODY_OUTPUTS; if (ctx->numOutputs > 0) { @@ -123,6 +132,7 @@ void tx_advanceStage() case SIGN_STAGE_BODY_OUTPUTS: // we should have received all outputs ASSERT(BODY_CTX->currentOutput == ctx->numOutputs); + ctx->stage = SIGN_STAGE_BODY_FEE; break; @@ -176,6 +186,7 @@ void tx_advanceStage() } ctx->stage = SIGN_STAGE_BODY_VALIDITY_INTERVAL; + if (ctx->includeValidityIntervalStart) { // wait for Validity interval start APDU break; @@ -186,12 +197,18 @@ void tx_advanceStage() if (ctx->includeValidityIntervalStart) { ASSERT(BODY_CTX->validityIntervalStartReceived); } + ctx->stage = SIGN_STAGE_BODY_MINT; + if (ctx->includeMint) { + #ifdef APP_FEATURE_TOKEN_MINTING txHashBuilder_enterMint(&BODY_CTX->txHashBuilder); signTxMint_init(); // wait for mint APDU break; + #else + ASSERT(false); + #endif // APP_FEATURE_TOKEN_MINTING } __attribute__((fallthrough)); @@ -199,7 +216,9 @@ void tx_advanceStage() if (ctx->includeMint) { ASSERT(BODY_CTX->mintReceived); } + ctx->stage = SIGN_STAGE_BODY_SCRIPT_DATA_HASH; + if (ctx->includeScriptDataHash) { break; } @@ -209,7 +228,9 @@ void tx_advanceStage() if (ctx->includeScriptDataHash) { ASSERT(BODY_CTX->scriptDataHashReceived); } + ctx->stage = SIGN_STAGE_BODY_COLLATERAL_INPUTS; + if (ctx->numCollateralInputs > 0) { txHashBuilder_enterCollateralInputs(&BODY_CTX->txHashBuilder); break; @@ -218,7 +239,9 @@ void tx_advanceStage() __attribute__((fallthrough)); case SIGN_STAGE_BODY_COLLATERAL_INPUTS: ASSERT(BODY_CTX->currentCollateral == ctx->numCollateralInputs); + ctx->stage = SIGN_STAGE_BODY_REQUIRED_SIGNERS; + if (ctx->numRequiredSigners > 0) { txHashBuilder_enterRequiredSigners(&BODY_CTX->txHashBuilder); break; @@ -231,7 +254,9 @@ void tx_advanceStage() // we are not waiting for any APDU here, network id is already known from the init APDU txHashBuilder_addNetworkId(&BODY_CTX->txHashBuilder, ctx->commonTxData.networkId); } + ctx->stage = SIGN_STAGE_BODY_COLLATERAL_OUTPUT; + if (ctx->includeCollateralOutput) { break; } @@ -241,7 +266,9 @@ void tx_advanceStage() if (ctx->includeCollateralOutput) { ASSERT(BODY_CTX->collateralOutputReceived); } + ctx->stage = SIGN_STAGE_BODY_TOTAL_COLLATERAL; + if (ctx->includeTotalCollateral) { break; } @@ -251,7 +278,9 @@ void tx_advanceStage() if (ctx->includeTotalCollateral) { ASSERT(BODY_CTX->totalCollateralReceived); } + ctx->stage = SIGN_STAGE_BODY_REFERENCE_INPUTS; + if (ctx->numReferenceInputs > 0) { txHashBuilder_enterReferenceInputs(&BODY_CTX->txHashBuilder); break; @@ -260,6 +289,42 @@ void tx_advanceStage() __attribute__((fallthrough)); case SIGN_STAGE_BODY_REFERENCE_INPUTS: ASSERT(BODY_CTX->currentReferenceInput == ctx->numReferenceInputs); + + ctx->stage = SIGN_STAGE_BODY_VOTING_PROCEDURES; + + if (ctx->numVotingProcedures > 0) { + txHashBuilder_enterVotingProcedures(&BODY_CTX->txHashBuilder); + break; + } + + __attribute__((fallthrough)); + case SIGN_STAGE_BODY_VOTING_PROCEDURES: + ASSERT(BODY_CTX->currentVotingProcedure == ctx->numVotingProcedures); + + ctx->stage = SIGN_STAGE_BODY_TREASURY; + + if (ctx->includeTreasury) { + break; + } + + __attribute__((fallthrough)); + case SIGN_STAGE_BODY_TREASURY: + if (ctx->includeTreasury) { + ASSERT(BODY_CTX->treasuryReceived); + } + + ctx->stage = SIGN_STAGE_BODY_DONATION; + + if (ctx->includeDonation) { + break; + } + + __attribute__((fallthrough)); + case SIGN_STAGE_BODY_DONATION: + if (ctx->includeDonation) { + ASSERT(BODY_CTX->donationReceived); + } + ctx->stage = SIGN_STAGE_CONFIRM; break; @@ -312,7 +377,11 @@ void tx_advanceCertificatesStateIfAppropriate() break; default: + #ifdef APP_FEATURE_POOL_REGISTRATION ASSERT(ctx->stage == SIGN_STAGE_BODY_CERTIFICATES_POOL_SUBMACHINE); + #else + ASSERT(false); + #endif // APP_FEATURE_POOL_REGISTRATION } } @@ -343,6 +412,8 @@ static inline void checkForFinishedSubmachines() } break; + #ifdef APP_FEATURE_POOL_REGISTRATION + case SIGN_STAGE_BODY_CERTIFICATES_POOL_SUBMACHINE: if (signTxPoolRegistration_isFinished()) { TRACE(); @@ -353,6 +424,8 @@ static inline void checkForFinishedSubmachines() } break; + #endif // APP_FEATURE_POOL_REGISTRATION + case SIGN_STAGE_AUX_DATA_CVOTE_REGISTRATION_SUBMACHINE: if (signTxCVoteRegistration_isFinished()) { TRACE(); @@ -367,6 +440,8 @@ static inline void checkForFinishedSubmachines() } break; + #ifdef APP_FEATURE_TOKEN_MINTING + case SIGN_STAGE_BODY_MINT_SUBMACHINE: if (signTxMint_isFinished()) { TRACE(); @@ -376,6 +451,8 @@ static inline void checkForFinishedSubmachines() } break; + #endif // APP_FEATURE_TOKEN_MINTING + case SIGN_STAGE_BODY_COLLATERAL_OUTPUT_SUBMACHINE: if (isCurrentOutputFinished()) { TRACE(); @@ -399,6 +476,16 @@ static inline void CHECK_STAGE(sign_tx_stage_t expected) // ============================== INIT ============================== +static void _parseTxOptions(uint64_t options) +{ + ctx->commonTxData.tagCborSets = options & TX_OPTIONS_TAG_CBOR_SETS; + options &= ~TX_OPTIONS_TAG_CBOR_SETS; + TRACE("tagCborSets = %d", ctx->commonTxData.tagCborSets); + + // we only accept known flags + VALIDATE(options == 0, ERR_INVALID_DATA); +} + __noinline_due_to_stack__ static void signTx_handleInitAPDU(uint8_t p2, const uint8_t* wireDataBuffer, size_t wireDataSize) { @@ -408,7 +495,6 @@ static void signTx_handleInitAPDU(uint8_t p2, const uint8_t* wireDataBuffer, siz CHECK_STAGE(SIGN_STAGE_INIT); VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } { @@ -417,6 +503,8 @@ static void signTx_handleInitAPDU(uint8_t p2, const uint8_t* wireDataBuffer, siz TRACE_BUFFER(wireDataBuffer, wireDataSize); struct { + uint8_t txOptions[8]; + uint8_t networkId; uint8_t protocolMagic[4]; @@ -428,6 +516,8 @@ static void signTx_handleInitAPDU(uint8_t p2, const uint8_t* wireDataBuffer, siz uint8_t includeNetworkId; uint8_t includeCollateralOutput; uint8_t includeTotalCollateral; + uint8_t includeTreasury; + uint8_t includeDonation; uint8_t txSigningMode; uint8_t numInputs[4]; @@ -437,12 +527,16 @@ static void signTx_handleInitAPDU(uint8_t p2, const uint8_t* wireDataBuffer, siz uint8_t numCollateralInputs[4]; uint8_t numRequiredSigners[4]; uint8_t numReferenceInputs[4]; + uint8_t numVotingProcedures[4]; uint8_t numWitnesses[4]; }* wireHeader = (void*) wireDataBuffer; VALIDATE(SIZEOF(*wireHeader) == wireDataSize, ERR_INVALID_DATA); + uint64_t txOptions = u8be_read(wireHeader->txOptions); + _parseTxOptions(txOptions); + ASSERT_TYPE(ctx->commonTxData.networkId, uint8_t); ctx->commonTxData.networkId = wireHeader->networkId; TRACE("network id %d", ctx->commonTxData.networkId); @@ -476,17 +570,29 @@ static void signTx_handleInitAPDU(uint8_t p2, const uint8_t* wireDataBuffer, siz ctx->includeTotalCollateral = signTx_parseIncluded(wireHeader->includeTotalCollateral); TRACE("Include total collateral %d", ctx->includeTotalCollateral); + ctx->includeTreasury = signTx_parseIncluded(wireHeader->includeTreasury); + TRACE("Include treasury %d", ctx->includeTreasury); + + ctx->includeDonation = signTx_parseIncluded(wireHeader->includeDonation); + TRACE("Include donation %d", ctx->includeDonation); + ctx->commonTxData.txSigningMode = wireHeader->txSigningMode; TRACE("Signing mode %d", (int) ctx->commonTxData.txSigningMode); switch (ctx->commonTxData.txSigningMode) { case SIGN_TX_SIGNINGMODE_ORDINARY_TX: - case SIGN_TX_SIGNINGMODE_POOL_REGISTRATION_OWNER: - case SIGN_TX_SIGNINGMODE_POOL_REGISTRATION_OPERATOR: case SIGN_TX_SIGNINGMODE_MULTISIG_TX: case SIGN_TX_SIGNINGMODE_PLUTUS_TX: // these signing modes are allowed break; + case SIGN_TX_SIGNINGMODE_POOL_REGISTRATION_OWNER: + case SIGN_TX_SIGNINGMODE_POOL_REGISTRATION_OPERATOR: + // these are allowed unless we have the XS app which does not have code for handling them + #ifndef APP_FEATURE_POOL_REGISTRATION + THROW(ERR_INVALID_DATA); + #endif // APP_FEATURE_POOL_REGISTRATION + break; + default: THROW(ERR_INVALID_DATA); } @@ -498,21 +604,24 @@ static void signTx_handleInitAPDU(uint8_t p2, const uint8_t* wireDataBuffer, siz ASSERT_TYPE(ctx->numCollateralInputs, uint16_t); ASSERT_TYPE(ctx->numRequiredSigners, uint16_t); ASSERT_TYPE(ctx->numReferenceInputs, uint16_t); + ASSERT_TYPE(ctx->numVotingProcedures, uint16_t); ASSERT_TYPE(ctx->numWitnesses, uint16_t); ctx->numInputs = (uint16_t) u4be_read(wireHeader->numInputs); ctx->numOutputs = (uint16_t) u4be_read(wireHeader->numOutputs); ctx->numCertificates = (uint16_t) u4be_read(wireHeader->numCertificates); ctx->numWithdrawals = (uint16_t) u4be_read(wireHeader->numWithdrawals); - ctx->numCollateralInputs = (uint16_t) u4be_read(wireHeader->numCollateralInputs); + ctx->numCollateralInputs = (uint16_t) u4be_read(wireHeader->numCollateralInputs); ctx->numRequiredSigners = (uint16_t) u4be_read(wireHeader->numRequiredSigners); ctx->numReferenceInputs = (uint16_t) u4be_read(wireHeader->numReferenceInputs); + ctx->numVotingProcedures = (uint16_t) u4be_read(wireHeader->numVotingProcedures); ctx->numWitnesses = (uint16_t) u4be_read(wireHeader->numWitnesses); TRACE( - "num inputs, outputs, certificates, withdrawals, collateral inputs, required signers, reference inputs, witnesses: %d %d %d %d %d %d %d %d", + "num inputs, outputs, certificates, withdrawals, collateral inputs, required signers, reference inputs, voting procedures, witnesses: %d %d %d %d %d %d %d %d %d", ctx->numInputs, ctx->numOutputs, ctx->numCertificates, ctx->numWithdrawals, - ctx->numCollateralInputs, ctx->numRequiredSigners, ctx->numReferenceInputs, ctx->numWitnesses + ctx->numCollateralInputs, ctx->numRequiredSigners, ctx->numReferenceInputs, ctx->numVotingProcedures, + ctx->numWitnesses ); VALIDATE(ctx->numInputs <= SIGN_MAX_INPUTS, ERR_INVALID_DATA); VALIDATE(ctx->numOutputs <= SIGN_MAX_OUTPUTS, ERR_INVALID_DATA); @@ -521,6 +630,7 @@ static void signTx_handleInitAPDU(uint8_t p2, const uint8_t* wireDataBuffer, siz VALIDATE(ctx->numCollateralInputs <= SIGN_MAX_COLLATERAL_INPUTS, ERR_INVALID_DATA); VALIDATE(ctx->numRequiredSigners <= SIGN_MAX_REQUIRED_SIGNERS, ERR_INVALID_DATA); VALIDATE(ctx->numReferenceInputs <= SIGN_MAX_REFERENCE_INPUTS, ERR_INVALID_DATA); + VALIDATE(ctx->numVotingProcedures <= SIGN_MAX_VOTING_PROCEDURES, ERR_INVALID_DATA); // Current code design assumes at least one input. // If this is to be relaxed, stage switching logic needs to be re-visited. @@ -535,6 +645,13 @@ static void signTx_handleInitAPDU(uint8_t p2, const uint8_t* wireDataBuffer, siz ctx->shouldDisplayTxid = false; } + // minting not included in the XS app + #ifndef APP_FEATURE_TOKEN_MINTING + if (ctx->includeMint) { + THROW(ERR_INVALID_DATA); + } + #endif // APP_FEATURE_TOKEN_MINTING + security_policy_t policy = policyForSignTxInit( ctx->commonTxData.txSigningMode, ctx->commonTxData.networkId, @@ -549,7 +666,10 @@ static void signTx_handleInitAPDU(uint8_t p2, const uint8_t* wireDataBuffer, siz ctx->includeNetworkId, ctx->includeCollateralOutput, ctx->includeTotalCollateral, - ctx->numReferenceInputs + ctx->numReferenceInputs, + ctx->numVotingProcedures, + ctx->includeTreasury, + ctx->includeDonation ); TRACE("Policy: %d", (int) policy); ENSURE_NOT_DENIED(policy); @@ -576,7 +696,6 @@ static void signTx_handleAuxDataAPDU(uint8_t p2, const uint8_t* wireDataBuffer, { { TRACE_STACK_USAGE(); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); ASSERT(ctx->includeAuxData == true); // delegate to state sub-machine for CIP-36 voting registration data @@ -720,7 +839,6 @@ static void signTx_handleInputAPDU(uint8_t p2, const uint8_t* wireDataBuffer, si ASSERT(BODY_CTX->currentInput < ctx->numInputs); VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } parseInput(wireDataBuffer, wireDataSize); @@ -754,7 +872,6 @@ static void signTx_handleOutputAPDU(uint8_t p2, const uint8_t* wireDataBuffer, s { { TRACE("p2 = %d", p2); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); TRACE_BUFFER(wireDataBuffer, wireDataSize); } @@ -783,7 +900,6 @@ static void signTx_handleFeeAPDU(uint8_t p2, const uint8_t* wireDataBuffer, size CHECK_STAGE(SIGN_STAGE_BODY_FEE); VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } { // parse data @@ -831,7 +947,6 @@ static void signTx_handleTtlAPDU(uint8_t p2, const uint8_t* wireDataBuffer, size ASSERT(ctx->includeTtl == true); VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } { // parse data @@ -878,21 +993,21 @@ static void _parsePathSpec(read_view_t* view, bip44_path_t* pathSpec) PRINTF("\n"); } -static void _parseStakeCredential(read_view_t* view, stake_credential_t* stakeCredential) +static void _parseCredential(read_view_t* view, ext_credential_t* credential) { - stakeCredential->type = parse_u1be(view); - switch (stakeCredential->type) { - case STAKE_CREDENTIAL_KEY_PATH: - _parsePathSpec(view, &stakeCredential->keyPath); + credential->type = parse_u1be(view); + switch (credential->type) { + case EXT_CREDENTIAL_KEY_PATH: + _parsePathSpec(view, &credential->keyPath); break; - case STAKE_CREDENTIAL_KEY_HASH: { - STATIC_ASSERT(SIZEOF(stakeCredential->keyHash) == ADDRESS_KEY_HASH_LENGTH, "bad key hash container size"); - view_parseBuffer(stakeCredential->keyHash, view, SIZEOF(stakeCredential->keyHash)); + case EXT_CREDENTIAL_KEY_HASH: { + STATIC_ASSERT(SIZEOF(credential->keyHash) == ADDRESS_KEY_HASH_LENGTH, "bad key hash container size"); + view_parseBuffer(credential->keyHash, view, SIZEOF(credential->keyHash)); break; } - case STAKE_CREDENTIAL_SCRIPT_HASH: { - STATIC_ASSERT(SIZEOF(stakeCredential->scriptHash) == SCRIPT_HASH_LENGTH, "bad script hash container size"); - view_parseBuffer(stakeCredential->scriptHash, view, SIZEOF(stakeCredential->scriptHash)); + case EXT_CREDENTIAL_SCRIPT_HASH: { + STATIC_ASSERT(SIZEOF(credential->scriptHash) == SCRIPT_HASH_LENGTH, "bad script hash container size"); + view_parseBuffer(credential->scriptHash, view, SIZEOF(credential->scriptHash)); break; } default: @@ -900,9 +1015,63 @@ static void _parseStakeCredential(read_view_t* view, stake_credential_t* stakeCr } } +static void _parseDRep(read_view_t* view, ext_drep_t* drep) +{ + drep->type = parse_u1be(view); + switch (drep->type) { + case EXT_DREP_KEY_PATH: + _parsePathSpec(view, &drep->keyPath); + break; + case EXT_DREP_KEY_HASH: { + STATIC_ASSERT(SIZEOF(drep->keyHash) == ADDRESS_KEY_HASH_LENGTH, "bad key hash container size"); + view_parseBuffer(drep->keyHash, view, SIZEOF(drep->keyHash)); + break; + } + case EXT_DREP_SCRIPT_HASH: { + STATIC_ASSERT(SIZEOF(drep->scriptHash) == SCRIPT_HASH_LENGTH, "bad script hash container size"); + view_parseBuffer(drep->scriptHash, view, SIZEOF(drep->scriptHash)); + break; + } + case EXT_DREP_ABSTAIN: + case EXT_DREP_NO_CONFIDENCE: { + // nothing more to parse + break; + } + default: + THROW(ERR_INVALID_DATA); + } +} + +static void _parseAnchor(read_view_t* view, anchor_t* anchor) +{ + { + uint8_t includeAnchorByte = parse_u1be(view); + anchor->isIncluded = signTx_parseIncluded(includeAnchorByte); + + if (!anchor->isIncluded) { + VALIDATE(view_remainingSize(view) == 0, ERR_INVALID_DATA); + return; + } + } + { + STATIC_ASSERT(SIZEOF(anchor->hash) == ANCHOR_HASH_LENGTH, "wrong anchor buffer size"); + view_parseBuffer(anchor->hash, view, ANCHOR_HASH_LENGTH); + } + { + anchor->urlLength = view_remainingSize(view); + VALIDATE(anchor->urlLength <= ANCHOR_URL_LENGTH_MAX, ERR_INVALID_DATA); + STATIC_ASSERT(SIZEOF(anchor->url) >= ANCHOR_URL_LENGTH_MAX, "wrong anchor url length"); + view_parseBuffer(anchor->url, view, anchor->urlLength); + + // whitespace not allowed + VALIDATE(str_isPrintableAsciiWithoutSpaces(anchor->url, anchor->urlLength), ERR_INVALID_DATA); + } + + VALIDATE(view_remainingSize(view) == 0, ERR_INVALID_DATA); +} + static void _parseCertificateData(const uint8_t* wireDataBuffer, size_t wireDataSize, sign_tx_certificate_data_t* certificateData) { - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); TRACE_BUFFER(wireDataBuffer, wireDataSize); read_view_t view = make_read_view(wireDataBuffer, wireDataBuffer + wireDataSize); @@ -911,30 +1080,74 @@ static void _parseCertificateData(const uint8_t* wireDataBuffer, size_t wireData TRACE("Certificate type: %d", certificateData->type); switch (certificateData->type) { - case CERTIFICATE_TYPE_STAKE_REGISTRATION: - _parseStakeCredential(&view, &certificateData->stakeCredential); + case CERTIFICATE_STAKE_REGISTRATION: + case CERTIFICATE_STAKE_DEREGISTRATION: + _parseCredential(&view, &certificateData->stakeCredential); + break; + + case CERTIFICATE_STAKE_REGISTRATION_CONWAY: + case CERTIFICATE_STAKE_DEREGISTRATION_CONWAY: + _parseCredential(&view, &certificateData->stakeCredential); + certificateData->deposit = parse_u8be(&view); + break; + + case CERTIFICATE_STAKE_DELEGATION: + _parseCredential(&view, &certificateData->stakeCredential); + certificateData->poolCredential.type = EXT_CREDENTIAL_KEY_HASH; + STATIC_ASSERT(SIZEOF(certificateData->poolCredential.keyHash) == POOL_KEY_HASH_LENGTH, "wrong poolKeyHash size"); + view_parseBuffer(certificateData->poolCredential.keyHash, &view, POOL_KEY_HASH_LENGTH); break; - case CERTIFICATE_TYPE_STAKE_DEREGISTRATION: - _parseStakeCredential(&view, &certificateData->stakeCredential); + case CERTIFICATE_VOTE_DELEGATION: + _parseCredential(&view, &certificateData->stakeCredential); + _parseDRep(&view, &certificateData->drep); break; - case CERTIFICATE_TYPE_STAKE_DELEGATION: - _parseStakeCredential(&view, &certificateData->stakeCredential); - STATIC_ASSERT(SIZEOF(certificateData->poolKeyHash) == POOL_KEY_HASH_LENGTH, "wrong poolKeyHash size"); - view_parseBuffer(certificateData->poolKeyHash, &view, POOL_KEY_HASH_LENGTH); + case CERTIFICATE_AUTHORIZE_COMMITTEE_HOT: + _parseCredential(&view, &certificateData->committeeColdCredential); + _parseCredential(&view, &certificateData->committeeHotCredential); break; - case CERTIFICATE_TYPE_STAKE_POOL_REGISTRATION: + case CERTIFICATE_RESIGN_COMMITTEE_COLD: + _parseCredential(&view, &certificateData->committeeColdCredential); + _parseAnchor(&view, &certificateData->anchor); + break; + + case CERTIFICATE_DREP_REGISTRATION: + _parseCredential(&view, &certificateData->dRepCredential); + certificateData->deposit = parse_u8be(&view); + _parseAnchor(&view, &certificateData->anchor); + break; + + case CERTIFICATE_DREP_DEREGISTRATION: + _parseCredential(&view, &certificateData->dRepCredential); + certificateData->deposit = parse_u8be(&view); + break; + + case CERTIFICATE_DREP_UPDATE: + _parseCredential(&view, &certificateData->dRepCredential); + _parseAnchor(&view, &certificateData->anchor); + break; + + #ifdef APP_FEATURE_POOL_REGISTRATION + + case CERTIFICATE_STAKE_POOL_REGISTRATION: // nothing more to parse, certificate data will be provided // in additional APDUs processed by a submachine return; - case CERTIFICATE_TYPE_STAKE_POOL_RETIREMENT: - _parsePathSpec(&view, &certificateData->poolIdPath); + #endif // APP_FEATURE_POOL_REGISTRATION + + #ifdef APP_FEATURE_POOL_RETIREMENT + + case CERTIFICATE_STAKE_POOL_RETIREMENT: + certificateData->poolCredential.type = EXT_CREDENTIAL_KEY_PATH; + _parsePathSpec(&view, &certificateData->poolCredential.keyPath); certificateData->epoch = parse_u8be(&view); break; + #endif // APP_FEATURE_POOL_RETIREMENT + default: THROW(ERR_INVALID_DATA); } @@ -942,102 +1155,223 @@ static void _parseCertificateData(const uint8_t* wireDataBuffer, size_t wireData VALIDATE(view_remainingSize(&view) == 0, ERR_INVALID_DATA); } -static void _fillHashFromPath(const bip44_path_t* path, - uint8_t* hash, size_t hashSize) +static void _setCredential( + credential_t* credential, + const ext_credential_t* extCredential +) { - ASSERT(ADDRESS_KEY_HASH_LENGTH <= hashSize); - ASSERT(hashSize < BUFFER_SIZE_PARANOIA); + switch (extCredential->type) { - bip44_pathToKeyHash( - path, - hash, hashSize - ); + case EXT_CREDENTIAL_KEY_PATH: + credential->type = CREDENTIAL_KEY_HASH; + bip44_pathToKeyHash( + &extCredential->keyPath, + credential->keyHash, SIZEOF(credential->keyHash) + ); + break; + + case EXT_CREDENTIAL_KEY_HASH: + credential->type = CREDENTIAL_KEY_HASH; + STATIC_ASSERT(SIZEOF(credential->keyHash) == SIZEOF(extCredential->keyHash), "bad script hash container size"); + memmove(credential->keyHash, extCredential->keyHash, SIZEOF(extCredential->keyHash)); + break; + + case EXT_CREDENTIAL_SCRIPT_HASH: + credential->type = CREDENTIAL_SCRIPT_HASH; + STATIC_ASSERT(SIZEOF(credential->scriptHash) == SIZEOF(extCredential->scriptHash), "bad script hash container size"); + memmove(credential->scriptHash, extCredential->scriptHash, SIZEOF(extCredential->scriptHash)); + break; + + default: + ASSERT(false); + break; + } } -static void _fillHashFromStakeCredential(const stake_credential_t* stakeCredential, - uint8_t* hash, size_t hashSize) +static void _setDRep( + drep_t* drep, + const ext_drep_t* extDRep +) { - ASSERT(hashSize < BUFFER_SIZE_PARANOIA); + switch (extDRep->type) { - switch (stakeCredential->type) { - case STAKE_CREDENTIAL_KEY_PATH: - _fillHashFromPath(&stakeCredential->keyPath, hash, hashSize); + case EXT_DREP_KEY_PATH: + drep->type = DREP_KEY_HASH; + bip44_pathToKeyHash( + &extDRep->keyPath, + drep->keyHash, SIZEOF(drep->keyHash) + ); break; - case STAKE_CREDENTIAL_KEY_HASH: - ASSERT(ADDRESS_KEY_HASH_LENGTH <= hashSize); - STATIC_ASSERT(SIZEOF(stakeCredential->keyHash) == ADDRESS_KEY_HASH_LENGTH, "bad key hash container size"); - memmove(hash, stakeCredential->keyHash, SIZEOF(stakeCredential->keyHash)); + + case EXT_DREP_KEY_HASH: + drep->type = DREP_KEY_HASH; + STATIC_ASSERT(SIZEOF(drep->keyHash) == SIZEOF(extDRep->keyHash), "bad script hash container size"); + memmove(drep->keyHash, extDRep->keyHash, SIZEOF(extDRep->keyHash)); break; - case STAKE_CREDENTIAL_SCRIPT_HASH: - ASSERT(SCRIPT_HASH_LENGTH <= hashSize); - STATIC_ASSERT(SIZEOF(stakeCredential->scriptHash) == SCRIPT_HASH_LENGTH, "bad script hash container size"); - memmove(hash, stakeCredential->scriptHash, SIZEOF(stakeCredential->scriptHash)); + + case EXT_DREP_SCRIPT_HASH: + drep->type = DREP_SCRIPT_HASH; + STATIC_ASSERT(SIZEOF(drep->scriptHash) == SIZEOF(extDRep->scriptHash), "bad script hash container size"); + memmove(drep->scriptHash, extDRep->scriptHash, SIZEOF(extDRep->scriptHash)); break; + + case EXT_DREP_ABSTAIN: + drep->type = DREP_ALWAYS_ABSTAIN; + break; + + case EXT_DREP_NO_CONFIDENCE: + drep->type = DREP_ALWAYS_NO_CONFIDENCE; + break; + default: ASSERT(false); break; } } - __noinline_due_to_stack__ static void _addCertificateDataToTx( sign_tx_certificate_data_t* certificateData, tx_hash_builder_t* txHashBuilder ) { - // data only added in the sub-machine, see signTxPoolRegistration.c - ASSERT(BODY_CTX->stageData.certificate.type != CERTIFICATE_TYPE_STAKE_POOL_REGISTRATION); - TRACE("Adding certificate (type %d) to tx hash", certificateData->type); - STATIC_ASSERT(ADDRESS_KEY_HASH_LENGTH == SCRIPT_HASH_LENGTH, "incompatible hash sizes"); - uint8_t stakingHash[ADDRESS_KEY_HASH_LENGTH] = {0}; + // declared here to save the stack space compiler allocates for this function + credential_t tmpCredential; switch (BODY_CTX->stageData.certificate.type) { - case CERTIFICATE_TYPE_STAKE_REGISTRATION: - case CERTIFICATE_TYPE_STAKE_DEREGISTRATION: { - _fillHashFromStakeCredential(&BODY_CTX->stageData.certificate.stakeCredential, stakingHash, SIZEOF(stakingHash)); - txHashBuilder_addCertificate_stakingHash( - txHashBuilder, certificateData->type, certificateData->stakeCredential.type, - stakingHash, SIZEOF(stakingHash) + case CERTIFICATE_STAKE_REGISTRATION: + case CERTIFICATE_STAKE_DEREGISTRATION: { + _setCredential(&tmpCredential, &certificateData->stakeCredential); + txHashBuilder_addCertificate_stakingOld( + txHashBuilder, + certificateData->type, + &tmpCredential + ); + break; + } + + case CERTIFICATE_STAKE_REGISTRATION_CONWAY: + case CERTIFICATE_STAKE_DEREGISTRATION_CONWAY: { + _setCredential(&tmpCredential, &certificateData->stakeCredential); + txHashBuilder_addCertificate_staking( + txHashBuilder, + certificateData->type, + &tmpCredential, + certificateData->deposit ); break; } - case CERTIFICATE_TYPE_STAKE_DELEGATION: { - _fillHashFromStakeCredential(&BODY_CTX->stageData.certificate.stakeCredential, stakingHash, SIZEOF(stakingHash)); - txHashBuilder_addCertificate_delegation( - txHashBuilder, certificateData->stakeCredential.type, - stakingHash, SIZEOF(stakingHash), - certificateData->poolKeyHash, SIZEOF(certificateData->poolKeyHash) + case CERTIFICATE_STAKE_DELEGATION: { + _setCredential(&tmpCredential, &certificateData->stakeCredential); + ASSERT(certificateData->poolCredential.type == EXT_CREDENTIAL_KEY_HASH); + txHashBuilder_addCertificate_stakeDelegation( + txHashBuilder, + &tmpCredential, + certificateData->poolCredential.keyHash, SIZEOF(certificateData->poolCredential.keyHash) ); break; } - case CERTIFICATE_TYPE_STAKE_POOL_RETIREMENT: { - _fillHashFromPath(&BODY_CTX->stageData.certificate.poolIdPath, certificateData->poolKeyHash, SIZEOF(certificateData->poolKeyHash)); + case CERTIFICATE_VOTE_DELEGATION: { + drep_t drep; + _setCredential(&tmpCredential, &certificateData->stakeCredential); + _setDRep(&drep, &certificateData->drep); + txHashBuilder_addCertificate_voteDelegation( + txHashBuilder, + &tmpCredential, + &drep + ); + break; + } + + case CERTIFICATE_AUTHORIZE_COMMITTEE_HOT: { + credential_t hotCredential; + _setCredential(&tmpCredential, &certificateData->committeeColdCredential); + _setCredential(&hotCredential, &certificateData->committeeHotCredential); + txHashBuilder_addCertificate_committeeAuthHot( + txHashBuilder, + &tmpCredential, + &hotCredential + ); + break; + } + + case CERTIFICATE_RESIGN_COMMITTEE_COLD: { + _setCredential(&tmpCredential, &certificateData->committeeColdCredential); + txHashBuilder_addCertificate_committeeResign( + txHashBuilder, + &tmpCredential, + &certificateData->anchor + ); + break; + } + + case CERTIFICATE_DREP_REGISTRATION: { + _setCredential(&tmpCredential, &certificateData->dRepCredential); + txHashBuilder_addCertificate_dRepRegistration( + txHashBuilder, + &tmpCredential, + certificateData->deposit, + &certificateData->anchor + ); + break; + } + + case CERTIFICATE_DREP_DEREGISTRATION: { + _setCredential(&tmpCredential, &certificateData->dRepCredential); + txHashBuilder_addCertificate_dRepDeregistration( + txHashBuilder, + &tmpCredential, + certificateData->deposit + ); + break; + } + + case CERTIFICATE_DREP_UPDATE: { + _setCredential(&tmpCredential, &certificateData->dRepCredential); + txHashBuilder_addCertificate_dRepUpdate( + txHashBuilder, + &tmpCredential, + &certificateData->anchor + ); + break; + } + + #ifdef APP_FEATURE_POOL_RETIREMENT + + case CERTIFICATE_STAKE_POOL_RETIREMENT: { + uint8_t hash[ADDRESS_KEY_HASH_LENGTH] = {0}; + ext_credential_t* extCredential = &BODY_CTX->stageData.certificate.poolCredential; + ASSERT(extCredential->type == EXT_CREDENTIAL_KEY_PATH); + bip44_pathToKeyHash( + &extCredential->keyPath, + hash, SIZEOF(hash) + ); txHashBuilder_addCertificate_poolRetirement( txHashBuilder, - certificateData->poolKeyHash, SIZEOF(certificateData->poolKeyHash), + hash, SIZEOF(hash), certificateData->epoch ); break; } + #endif // APP_FEATURE_POOL_RETIREMENT + default: + // stake pool registration data only added in the sub-machine, not here + // see signTxPoolRegistration.c ASSERT(false); } } -__noinline_due_to_stack__ -static void signTx_handleCertificateAPDU(uint8_t p2, const uint8_t* wireDataBuffer, size_t wireDataSize) -{ - TRACE_STACK_USAGE(); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); - ASSERT(BODY_CTX->currentCertificate < ctx->numCertificates); +#ifdef APP_FEATURE_POOL_REGISTRATION +static bool _handlePoolRegistrationIfNeeded(uint8_t p2, const uint8_t* wireDataBuffer, size_t wireDataSize) +{ // delegate to state sub-machine for stake pool registration certificate data if (signTxPoolRegistration_isValidInstruction(p2)) { TRACE(); @@ -1046,19 +1380,197 @@ static void signTx_handleCertificateAPDU(uint8_t p2, const uint8_t* wireDataBuff TRACE_STACK_USAGE(); signTxPoolRegistration_handleAPDU(p2, wireDataBuffer, wireDataSize); - return; + return true; + } + + return false; +} + +#endif // APP_FEATURE_POOL_REGISTRATION + +static void _handleCertificateStaking() +{ + security_policy_t policy = policyForSignTxCertificateStaking( + ctx->commonTxData.txSigningMode, + BODY_CTX->stageData.certificate.type, + &BODY_CTX->stageData.certificate.stakeCredential + ); + TRACE("Policy: %d", (int) policy); + ENSURE_NOT_DENIED(policy); + + _addCertificateDataToTx(&BODY_CTX->stageData.certificate, &BODY_CTX->txHashBuilder); + + switch (policy) { +#define CASE(POLICY, UI_STEP) case POLICY: {ctx->ui_step=UI_STEP; break;} + CASE(POLICY_PROMPT_BEFORE_RESPONSE, HANDLE_CERTIFICATE_STAKING_STEP_DISPLAY_OPERATION); + CASE(POLICY_ALLOW_WITHOUT_PROMPT, HANDLE_CERTIFICATE_STAKING_STEP_RESPOND); +#undef CASE + default: + THROW(ERR_NOT_IMPLEMENTED); } + signTx_handleCertificateStaking_ui_runStep(); +} + +static void _handleCertificateVoteDeleg() +{ + security_policy_t policy = policyForSignTxCertificateVoteDelegation( + ctx->commonTxData.txSigningMode, + &BODY_CTX->stageData.certificate.stakeCredential, + &BODY_CTX->stageData.certificate.drep + ); + TRACE("Policy: %d", (int) policy); + ENSURE_NOT_DENIED(policy); + + _addCertificateDataToTx(&BODY_CTX->stageData.certificate, &BODY_CTX->txHashBuilder); + + switch (policy) { +#define CASE(POLICY, UI_STEP) case POLICY: {ctx->ui_step=UI_STEP; break;} + CASE(POLICY_PROMPT_BEFORE_RESPONSE, HANDLE_CERTIFICATE_VOTE_DELEGATION_STEP_DISPLAY_OPERATION); + CASE(POLICY_ALLOW_WITHOUT_PROMPT, HANDLE_CERTIFICATE_VOTE_DELEGATION_STEP_RESPOND); +#undef CASE + default: + THROW(ERR_NOT_IMPLEMENTED); + } + + signTx_handleCertificateVoteDeleg_ui_runStep(); +} + +static void _handleCertificateCommitteeAuth() +{ + security_policy_t policy = policyForSignTxCertificateCommitteeAuth( + ctx->commonTxData.txSigningMode, + &BODY_CTX->stageData.certificate.committeeColdCredential, + &BODY_CTX->stageData.certificate.committeeHotCredential + ); + TRACE("Policy: %d", (int) policy); + ENSURE_NOT_DENIED(policy); + + _addCertificateDataToTx(&BODY_CTX->stageData.certificate, &BODY_CTX->txHashBuilder); + + switch (policy) { +#define CASE(POLICY, UI_STEP) case POLICY: {ctx->ui_step=UI_STEP; break;} + CASE(POLICY_PROMPT_BEFORE_RESPONSE, HANDLE_CERTIFICATE_COMM_AUTH_STEP_DISPLAY_OPERATION); + CASE(POLICY_ALLOW_WITHOUT_PROMPT, HANDLE_CERTIFICATE_COMM_AUTH_STEP_RESPOND); +#undef CASE + default: + THROW(ERR_NOT_IMPLEMENTED); + } + + signTx_handleCertificateCommitteeAuth_ui_runStep(); +} + +static void _handleCertificateCommitteeResign() +{ + security_policy_t policy = policyForSignTxCertificateCommitteeResign( + ctx->commonTxData.txSigningMode, + &BODY_CTX->stageData.certificate.committeeColdCredential + ); + TRACE("Policy: %d", (int) policy); + ENSURE_NOT_DENIED(policy); + + _addCertificateDataToTx(&BODY_CTX->stageData.certificate, &BODY_CTX->txHashBuilder); + + switch (policy) { +#define CASE(POLICY, UI_STEP) case POLICY: {ctx->ui_step=UI_STEP; break;} + CASE(POLICY_PROMPT_BEFORE_RESPONSE, HANDLE_CERTIFICATE_COMM_RESIGN_STEP_DISPLAY_OPERATION); + CASE(POLICY_ALLOW_WITHOUT_PROMPT, HANDLE_CERTIFICATE_COMM_RESIGN_STEP_RESPOND); +#undef CASE + default: + THROW(ERR_NOT_IMPLEMENTED); + } + + signTx_handleCertificateCommitteeResign_ui_runStep(); +} + +static void _handleCertificateDRep() +{ + security_policy_t policy = policyForSignTxCertificateDRep( + ctx->commonTxData.txSigningMode, + &BODY_CTX->stageData.certificate.dRepCredential + ); + TRACE("Policy: %d", (int) policy); + ENSURE_NOT_DENIED(policy); + + _addCertificateDataToTx(&BODY_CTX->stageData.certificate, &BODY_CTX->txHashBuilder); + + switch (policy) { +#define CASE(POLICY, UI_STEP) case POLICY: {ctx->ui_step=UI_STEP; break;} + CASE(POLICY_PROMPT_BEFORE_RESPONSE, HANDLE_CERTIFICATE_DREP_STEP_DISPLAY_OPERATION); + CASE(POLICY_ALLOW_WITHOUT_PROMPT, HANDLE_CERTIFICATE_DREP_STEP_RESPOND); +#undef CASE + default: + THROW(ERR_NOT_IMPLEMENTED); + } + + signTx_handleCertificateDRep_ui_runStep(); +} + +#ifdef APP_FEATURE_POOL_REGISTRATION + +static void _handleCertificatePoolRegistration() +{ + // pool registration certificates have a separate sub-machine for handling APDU and UI + // nothing more to be done with them here, we just init the sub-machine + ctx->stage = SIGN_STAGE_BODY_CERTIFICATES_POOL_SUBMACHINE; + signTxPoolRegistration_init(); + + respondSuccessEmptyMsg(); +} + +#endif // APP_FEATURE_POOL_REGISTRATION + + +#ifdef APP_FEATURE_POOL_RETIREMENT + +static void _handleCertificatePoolRetirement() +{ + security_policy_t policy = policyForSignTxCertificateStakePoolRetirement( + ctx->commonTxData.txSigningMode, + &BODY_CTX->stageData.certificate.poolCredential, + BODY_CTX->stageData.certificate.epoch + ); + TRACE("Policy: %d", (int) policy); + ENSURE_NOT_DENIED(policy); + + _addCertificateDataToTx(&BODY_CTX->stageData.certificate, &BODY_CTX->txHashBuilder); + + switch (policy) { +#define CASE(POLICY, UI_STEP) case POLICY: {ctx->ui_step=UI_STEP; break;} + CASE(POLICY_PROMPT_BEFORE_RESPONSE, HANDLE_CERTIFICATE_POOL_RETIREMENT_STEP_DISPLAY_OPERATION); + CASE(POLICY_ALLOW_WITHOUT_PROMPT, HANDLE_CERTIFICATE_POOL_RETIREMENT_STEP_RESPOND); +#undef CASE + default: + THROW(ERR_NOT_IMPLEMENTED); + } + signTx_handleCertificatePoolRetirement_ui_runStep(); +} + +#endif // APP_FEATURE_POOL_RETIREMENT + +// Note(JM): it is possible to treat every certificate separately, +// which makes the code somewhat more readable if read per certificate, +// but it increases code size and that creates problems for Nano S +__noinline_due_to_stack__ +static void signTx_handleCertificateAPDU(uint8_t p2, const uint8_t* wireDataBuffer, size_t wireDataSize) +{ + TRACE_STACK_USAGE(); + ASSERT(BODY_CTX->currentCertificate < ctx->numCertificates); + + #ifdef APP_FEATURE_POOL_REGISTRATION + // usage of P2 determines if we are in the pool registration submachine + if (_handlePoolRegistrationIfNeeded(p2, wireDataBuffer, wireDataSize)) { + return; + } + #endif // APP_FEATURE_POOL_REGISTRATION VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS); CHECK_STAGE(SIGN_STAGE_BODY_CERTIFICATES); // a new certificate arrived explicit_bzero(&BODY_CTX->stageData.certificate, SIZEOF(BODY_CTX->stageData.certificate)); - _parseCertificateData(wireDataBuffer, wireDataSize, &BODY_CTX->stageData.certificate); - { - // basic policy that just decides if the certificate is allowed + // basic policy that just decides if the certificate type is allowed security_policy_t policy = policyForSignTxCertificate( ctx->commonTxData.txSigningMode, BODY_CTX->stageData.certificate.type @@ -1067,68 +1579,51 @@ static void signTx_handleCertificateAPDU(uint8_t p2, const uint8_t* wireDataBuff ENSURE_NOT_DENIED(policy); } - // TODO refactor --- does it make sense to process different certificate types entirely separately? - // or perhaps group registration with deregistration? - // notice that _parseCertificateData and _addCertificateDataToTx already do a big switch on cert type switch (BODY_CTX->stageData.certificate.type) { - case CERTIFICATE_TYPE_STAKE_REGISTRATION: - case CERTIFICATE_TYPE_STAKE_DEREGISTRATION: - case CERTIFICATE_TYPE_STAKE_DELEGATION: { - security_policy_t policy = policyForSignTxCertificateStaking( - ctx->commonTxData.txSigningMode, - BODY_CTX->stageData.certificate.type, - &BODY_CTX->stageData.certificate.stakeCredential - ); - TRACE("Policy: %d", (int) policy); - ENSURE_NOT_DENIED(policy); - - _addCertificateDataToTx(&BODY_CTX->stageData.certificate, &BODY_CTX->txHashBuilder); - - switch (policy) { -#define CASE(POLICY, UI_STEP) case POLICY: {ctx->ui_step=UI_STEP; break;} - CASE(POLICY_PROMPT_BEFORE_RESPONSE, HANDLE_CERTIFICATE_STEP_DISPLAY_OPERATION); - CASE(POLICY_ALLOW_WITHOUT_PROMPT, HANDLE_CERTIFICATE_STEP_RESPOND); -#undef CASE - default: - THROW(ERR_NOT_IMPLEMENTED); - } + case CERTIFICATE_STAKE_REGISTRATION: + case CERTIFICATE_STAKE_DEREGISTRATION: + case CERTIFICATE_STAKE_REGISTRATION_CONWAY: + case CERTIFICATE_STAKE_DEREGISTRATION_CONWAY: + case CERTIFICATE_STAKE_DELEGATION: { + _handleCertificateStaking(); + return; + } - signTx_handleCertificate_ui_runStep(); + case CERTIFICATE_VOTE_DELEGATION: { + _handleCertificateVoteDeleg(); return; } - case CERTIFICATE_TYPE_STAKE_POOL_REGISTRATION: { - // pool registration certificates have a separate sub-machine for handling APDU and UI - // nothing more to be done with them here, we just init the sub-machine - ctx->stage = SIGN_STAGE_BODY_CERTIFICATES_POOL_SUBMACHINE; - signTxPoolRegistration_init(); + case CERTIFICATE_AUTHORIZE_COMMITTEE_HOT: { + _handleCertificateCommitteeAuth(); + return; + } - respondSuccessEmptyMsg(); + case CERTIFICATE_RESIGN_COMMITTEE_COLD: { + _handleCertificateCommitteeResign(); return; } - case CERTIFICATE_TYPE_STAKE_POOL_RETIREMENT: { - security_policy_t policy = policyForSignTxCertificateStakePoolRetirement( - ctx->commonTxData.txSigningMode, - &BODY_CTX->stageData.certificate.poolIdPath, - BODY_CTX->stageData.certificate.epoch - ); - TRACE("Policy: %d", (int) policy); - ENSURE_NOT_DENIED(policy); + case CERTIFICATE_DREP_REGISTRATION: + case CERTIFICATE_DREP_DEREGISTRATION: + case CERTIFICATE_DREP_UPDATE: { + _handleCertificateDRep(); + return; + } - _addCertificateDataToTx(&BODY_CTX->stageData.certificate, &BODY_CTX->txHashBuilder); + #ifdef APP_FEATURE_POOL_REGISTRATION + case CERTIFICATE_STAKE_POOL_REGISTRATION: { + _handleCertificatePoolRegistration(); + return; + } + #endif // APP_FEATURE_POOL_REGISTRATION - switch (policy) { -#define CASE(POLICY, UI_STEP) case POLICY: {ctx->ui_step=UI_STEP; break;} - CASE(POLICY_PROMPT_BEFORE_RESPONSE, HANDLE_CERTIFICATE_POOL_RETIREMENT_STEP_DISPLAY_OPERATION); - CASE(POLICY_ALLOW_WITHOUT_PROMPT, HANDLE_CERTIFICATE_POOL_RETIREMENT_STEP_RESPOND); -#undef CASE - default: - THROW(ERR_NOT_IMPLEMENTED); - } - signTx_handleCertificatePoolRetirement_ui_runStep(); + #ifdef APP_FEATURE_POOL_RETIREMENT + case CERTIFICATE_STAKE_POOL_RETIREMENT: { + _handleCertificatePoolRetirement(); return; } + #endif // APP_FEATURE_POOL_RETIREMENT default: ASSERT(false); @@ -1143,7 +1638,7 @@ static void _addWithdrawalToTxHash(bool validateCanonicalOrdering) uint8_t rewardAddress[REWARD_ACCOUNT_SIZE] = {0}; switch (BODY_CTX->stageData.withdrawal.stakeCredential.type) { - case STAKE_CREDENTIAL_KEY_PATH: + case EXT_CREDENTIAL_KEY_PATH: constructRewardAddressFromKeyPath( &BODY_CTX->stageData.withdrawal.stakeCredential.keyPath, ctx->commonTxData.networkId, @@ -1151,7 +1646,7 @@ static void _addWithdrawalToTxHash(bool validateCanonicalOrdering) SIZEOF(rewardAddress) ); break; - case STAKE_CREDENTIAL_KEY_HASH: + case EXT_CREDENTIAL_KEY_HASH: constructRewardAddressFromHash( ctx->commonTxData.networkId, REWARD_HASH_SOURCE_KEY, @@ -1161,7 +1656,7 @@ static void _addWithdrawalToTxHash(bool validateCanonicalOrdering) SIZEOF(rewardAddress) ); break; - case STAKE_CREDENTIAL_SCRIPT_HASH: + case EXT_CREDENTIAL_SCRIPT_HASH: constructRewardAddressFromHash( ctx->commonTxData.networkId, REWARD_HASH_SOURCE_SCRIPT, @@ -1210,10 +1705,12 @@ static void signTx_handleWithdrawalAPDU(uint8_t p2, const uint8_t* wireDataBuffe ASSERT(BODY_CTX->currentWithdrawal < ctx->numWithdrawals); VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } - explicit_bzero(&BODY_CTX->stageData.withdrawal, SIZEOF(BODY_CTX->stageData.withdrawal)); + // we can't bzero the whole stageData.withdrawal since + // we need to compare it with the previous one (canonical ordering check) + BODY_CTX->stageData.withdrawal.amount = 0; + explicit_bzero(&BODY_CTX->stageData.withdrawal.stakeCredential, SIZEOF(BODY_CTX->stageData.withdrawal.stakeCredential)); { // parse input @@ -1222,7 +1719,7 @@ static void signTx_handleWithdrawalAPDU(uint8_t p2, const uint8_t* wireDataBuffe read_view_t view = make_read_view(wireDataBuffer, wireDataBuffer + wireDataSize); BODY_CTX->stageData.withdrawal.amount = parse_u8be(&view); - _parseStakeCredential(&view, &BODY_CTX->stageData.withdrawal.stakeCredential); + _parseCredential(&view, &BODY_CTX->stageData.withdrawal.stakeCredential); VALIDATE(view_remainingSize(&view) == 0, ERR_INVALID_DATA); @@ -1263,7 +1760,6 @@ static void signTx_handleValidityIntervalStartAPDU(uint8_t p2, const uint8_t* wi ASSERT(ctx->includeValidityIntervalStart == true); VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } { @@ -1305,11 +1801,12 @@ static void signTx_handleValidityIntervalStartAPDU(uint8_t p2, const uint8_t* wi // ============================== MINT ============================== +#ifdef APP_FEATURE_TOKEN_MINTING + static void signTx_handleMintAPDU(uint8_t p2, const uint8_t* wireDataBuffer, size_t wireDataSize) { { TRACE("p2 = %d", p2); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); TRACE_BUFFER(wireDataBuffer, wireDataSize); } @@ -1324,6 +1821,8 @@ static void signTx_handleMintAPDU(uint8_t p2, const uint8_t* wireDataBuffer, siz signTxMint_handleAPDU(p2, wireDataBuffer, wireDataSize); } +#endif // APP_FEATURE_TOKEN_MINTING + // ========================= SCRIPT DATA HASH ========================== static void signTx_handleScriptDataHashAPDU(uint8_t p2, const uint8_t* wireDataBuffer, size_t wireDataSize) @@ -1331,9 +1830,9 @@ static void signTx_handleScriptDataHashAPDU(uint8_t p2, const uint8_t* wireDataB { // sanity checks CHECK_STAGE(SIGN_STAGE_BODY_SCRIPT_DATA_HASH); + ASSERT(ctx->includeScriptDataHash == true); VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } { // parse data @@ -1395,7 +1894,6 @@ static void signTx_handleCollateralInputAPDU(uint8_t p2, const uint8_t* wireData ASSERT(BODY_CTX->currentCollateral < ctx->numCollateralInputs); VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } parseInput(wireDataBuffer, wireDataSize); @@ -1437,7 +1935,6 @@ static void signTx_handleRequiredSignerAPDU(uint8_t p2, const uint8_t* wireDataB ASSERT(BODY_CTX->currentRequiredSigner < ctx->numRequiredSigners); VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } { @@ -1495,13 +1992,13 @@ static void signTx_handleRequiredSignerAPDU(uint8_t p2, const uint8_t* wireDataB } signTx_handleRequiredSigner_ui_runStep(); } + // ========================= COLLATERAL RETURN OUTPUT =========================== static void signTx_handleCollateralOutputAPDU(uint8_t p2, const uint8_t* wireDataBuffer, size_t wireDataSize) { { TRACE("p2 = %d", p2); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); TRACE_BUFFER(wireDataBuffer, wireDataSize); } @@ -1526,9 +2023,9 @@ static void signTx_handleTotalCollateralAPDU(uint8_t p2, const uint8_t* wireData { // sanity checks CHECK_STAGE(SIGN_STAGE_BODY_TOTAL_COLLATERAL); + ASSERT(ctx->includeTotalCollateral == true); VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } { // parse data @@ -1556,8 +2053,8 @@ static void signTx_handleTotalCollateralAPDU(uint8_t p2, const uint8_t* wireData // select UI steps switch (policy) { #define CASE(POLICY, UI_STEP) case POLICY: {ctx->ui_step=UI_STEP; break;} - CASE(POLICY_SHOW_BEFORE_RESPONSE, HANDLE_FEE_STEP_DISPLAY); - CASE(POLICY_ALLOW_WITHOUT_PROMPT, HANDLE_FEE_STEP_RESPOND); + CASE(POLICY_SHOW_BEFORE_RESPONSE, HANDLE_TOTAL_COLLATERAL_STEP_DISPLAY); + CASE(POLICY_ALLOW_WITHOUT_PROMPT, HANDLE_TOTAL_COLLATERAL_STEP_RESPOND); #undef CASE default: THROW(ERR_NOT_IMPLEMENTED); @@ -1581,7 +2078,7 @@ static void ui_advanceState_ReferenceInput() } __noinline_due_to_stack__ -static void signTx_handleReferenceInputsAPDU(uint8_t p2, const uint8_t* wireDataBuffer, size_t wireDataSize) +static void signTx_handleReferenceInputAPDU(uint8_t p2, const uint8_t* wireDataBuffer, size_t wireDataSize) { TRACE_STACK_USAGE(); { @@ -1590,7 +2087,6 @@ static void signTx_handleReferenceInputsAPDU(uint8_t p2, const uint8_t* wireData ASSERT(BODY_CTX->currentReferenceInput < ctx->numReferenceInputs); VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } // Parsed in same way as the inputs parseInput(wireDataBuffer, wireDataSize); @@ -1617,6 +2113,276 @@ static void signTx_handleReferenceInputsAPDU(uint8_t p2, const uint8_t* wireData } } + +// ========================= VOTING PROCEDURES =========================== + +static void _setVoter( + voter_t* voter, + const ext_voter_t* extVoter +) +{ + switch (extVoter->type) { + + case EXT_VOTER_COMMITTEE_HOT_KEY_PATH: + voter->type = VOTER_COMMITTEE_HOT_KEY_HASH; + bip44_pathToKeyHash( + &extVoter->keyPath, + voter->keyHash, SIZEOF(voter->keyHash) + ); + break; + + case EXT_VOTER_DREP_KEY_PATH: + voter->type = VOTER_DREP_KEY_HASH; + bip44_pathToKeyHash( + &extVoter->keyPath, + voter->keyHash, SIZEOF(voter->keyHash) + ); + break; + + case EXT_VOTER_STAKE_POOL_KEY_PATH: + voter->type = VOTER_STAKE_POOL_KEY_HASH; + bip44_pathToKeyHash( + &extVoter->keyPath, + voter->keyHash, SIZEOF(voter->keyHash) + ); + break; + + case EXT_VOTER_COMMITTEE_HOT_KEY_HASH: + voter->type = VOTER_COMMITTEE_HOT_KEY_HASH; + STATIC_ASSERT(SIZEOF(voter->keyHash) == SIZEOF(extVoter->keyHash), "bad script hash container size"); + memmove(voter->keyHash, extVoter->keyHash, SIZEOF(extVoter->keyHash)); + break; + + case EXT_VOTER_DREP_KEY_HASH: + voter->type = VOTER_DREP_KEY_HASH; + STATIC_ASSERT(SIZEOF(voter->keyHash) == SIZEOF(extVoter->keyHash), "bad script hash container size"); + memmove(voter->keyHash, extVoter->keyHash, SIZEOF(extVoter->keyHash)); + break; + + case EXT_VOTER_STAKE_POOL_KEY_HASH: + voter->type = VOTER_STAKE_POOL_KEY_HASH; + STATIC_ASSERT(SIZEOF(voter->keyHash) == SIZEOF(extVoter->keyHash), "bad script hash container size"); + memmove(voter->keyHash, extVoter->keyHash, SIZEOF(extVoter->keyHash)); + break; + + case EXT_VOTER_COMMITTEE_HOT_SCRIPT_HASH: + voter->type = VOTER_COMMITTEE_HOT_SCRIPT_HASH; + STATIC_ASSERT(SIZEOF(voter->scriptHash) == SIZEOF(extVoter->scriptHash), "bad script hash container size"); + memmove(voter->scriptHash, extVoter->scriptHash, SIZEOF(extVoter->scriptHash)); + break; + + case EXT_VOTER_DREP_SCRIPT_HASH: + voter->type = VOTER_DREP_SCRIPT_HASH; + STATIC_ASSERT(SIZEOF(voter->scriptHash) == SIZEOF(extVoter->scriptHash), "bad script hash container size"); + memmove(voter->scriptHash, extVoter->scriptHash, SIZEOF(extVoter->scriptHash)); + break; + + default: + ASSERT(false); + break; + } +} + +__noinline_due_to_stack__ +static void signTx_handleVotingProcedureAPDU(uint8_t p2, const uint8_t* wireDataBuffer, size_t wireDataSize) +{ + TRACE_STACK_USAGE(); + { + // sanity checks + CHECK_STAGE(SIGN_STAGE_BODY_VOTING_PROCEDURES); + ASSERT(BODY_CTX->currentVotingProcedure < ctx->numVotingProcedures); + + VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS); + } + + { + TRACE_BUFFER(wireDataBuffer, wireDataSize); + + read_view_t view = make_read_view(wireDataBuffer, wireDataBuffer + wireDataSize); + { + // voter + ext_voter_t* voter = &BODY_CTX->stageData.votingProcedure.voter; + voter->type = parse_u1be(&view); + switch (voter->type) { + case EXT_VOTER_COMMITTEE_HOT_KEY_PATH: + case EXT_VOTER_DREP_KEY_PATH: + case EXT_VOTER_STAKE_POOL_KEY_PATH: { + _parsePathSpec(&view, &voter->keyPath); + break; + } + case EXT_VOTER_COMMITTEE_HOT_KEY_HASH: + case EXT_VOTER_DREP_KEY_HASH: + case EXT_VOTER_STAKE_POOL_KEY_HASH: { + STATIC_ASSERT(SIZEOF(voter->keyHash) == ADDRESS_KEY_HASH_LENGTH, "bad key hash container size"); + view_parseBuffer(voter->keyHash, &view, SIZEOF(voter->keyHash)); + break; + } + case EXT_VOTER_COMMITTEE_HOT_SCRIPT_HASH: + case EXT_VOTER_DREP_SCRIPT_HASH: { + STATIC_ASSERT(SIZEOF(voter->scriptHash) == SCRIPT_HASH_LENGTH, "bad script hash container size"); + view_parseBuffer(voter->scriptHash, &view, SIZEOF(voter->scriptHash)); + break; + } + default: + THROW(ERR_INVALID_DATA); + } + } + { + // gov action id + gov_action_id_t* actionId = &BODY_CTX->stageData.votingProcedure.govActionId; + view_parseBuffer(actionId->txHashBuffer, &view, TX_HASH_LENGTH); + actionId->govActionIndex = parse_u4be(&view); + } + { + // voting procedure + voting_procedure_t* procedure = &BODY_CTX->stageData.votingProcedure.votingProcedure; + procedure->vote = parse_u1be(&view); + switch (procedure->vote) { + case VOTE_NO: + case VOTE_YES: + case VOTE_ABSTAIN: + // OK + break; + default: + THROW(ERR_INVALID_DATA); + } + _parseAnchor(&view, &procedure->anchor); + } + VALIDATE(view_remainingSize(&view) == 0, ERR_INVALID_DATA); + } + + security_policy_t policy = policyForSignTxVotingProcedure( + ctx->commonTxData.txSigningMode, + &BODY_CTX->stageData.votingProcedure.voter + ); + TRACE("Policy: %d", (int) policy); + ENSURE_NOT_DENIED(policy); + + // Note: if more than one voter is ever allowed, we need to check canonical ordering + // of voters and possibly canonical ordering of governance actions in the subordinated map + { + // add to tx + TRACE("Adding voting procedure to tx hash"); + voter_t voter; + _setVoter(&voter, &BODY_CTX->stageData.votingProcedure.voter); + txHashBuilder_addVotingProcedure( + &BODY_CTX->txHashBuilder, + &voter, + &BODY_CTX->stageData.votingProcedure.govActionId, + &BODY_CTX->stageData.votingProcedure.votingProcedure + ); + } + + { + // select UI steps + switch (policy) { +# define CASE(POLICY, UI_STEP) case POLICY: {ctx->ui_step=UI_STEP; break;} + CASE(POLICY_SHOW_BEFORE_RESPONSE, HANDLE_VOTING_PROCEDURE_STEP_INTRO); + CASE(POLICY_ALLOW_WITHOUT_PROMPT, HANDLE_VOTING_PROCEDURE_STEP_RESPOND); +# undef CASE + default: + THROW(ERR_NOT_IMPLEMENTED); + } + } + signTx_handleVotingProcedure_ui_runStep(); +} + + +// ============================== TREASURY ============================== + +__noinline_due_to_stack__ +static void signTx_handleTreasuryAPDU(uint8_t p2, const uint8_t* wireDataBuffer, size_t wireDataSize) +{ + { + // sanity checks + CHECK_STAGE(SIGN_STAGE_BODY_TREASURY); + ASSERT(ctx->includeTreasury == true); + + VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS); + } + { + // parse data + TRACE_BUFFER(wireDataBuffer, wireDataSize); + + VALIDATE(wireDataSize == 8, ERR_INVALID_DATA); + BODY_CTX->stageData.treasury = u8be_read(wireDataBuffer); + BODY_CTX->treasuryReceived = true; + } + + security_policy_t policy = policyForSignTxTreasury(ctx->commonTxData.txSigningMode, BODY_CTX->stageData.treasury); + TRACE("Policy: %d", (int) policy); + ENSURE_NOT_DENIED(policy); + + { + // add to tx + TRACE("Adding treasury to tx hash"); + txHashBuilder_addTreasury(&BODY_CTX->txHashBuilder, BODY_CTX->stageData.treasury); + } + + { + // select UI steps + switch (policy) { +#define CASE(POLICY, UI_STEP) case POLICY: {ctx->ui_step=UI_STEP; break;} + CASE(POLICY_SHOW_BEFORE_RESPONSE, HANDLE_TREASURY_STEP_DISPLAY); + CASE(POLICY_ALLOW_WITHOUT_PROMPT, HANDLE_TREASURY_STEP_RESPOND); +#undef CASE + default: + THROW(ERR_NOT_IMPLEMENTED); + } + } + + signTx_handleTreasury_ui_runStep(); +} + + +// ============================== DONATION ============================== + +__noinline_due_to_stack__ +static void signTx_handleDonationAPDU(uint8_t p2, const uint8_t* wireDataBuffer, size_t wireDataSize) +{ + { + // sanity checks + CHECK_STAGE(SIGN_STAGE_BODY_DONATION); + ASSERT(ctx->includeDonation == true); + + VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS); + } + { + // parse data + TRACE_BUFFER(wireDataBuffer, wireDataSize); + + VALIDATE(wireDataSize == 8, ERR_INVALID_DATA); + BODY_CTX->stageData.donation = u8be_read(wireDataBuffer); + VALIDATE(BODY_CTX->stageData.donation > 0, ERR_INVALID_DATA); + BODY_CTX->donationReceived = true; + } + + security_policy_t policy = policyForSignTxDonation(ctx->commonTxData.txSigningMode, BODY_CTX->stageData.donation); + TRACE("Policy: %d", (int) policy); + ENSURE_NOT_DENIED(policy); + + { + // add to tx + TRACE("Adding donation to tx hash"); + txHashBuilder_addDonation(&BODY_CTX->txHashBuilder, BODY_CTX->stageData.donation); + } + + { + // select UI steps + switch (policy) { +#define CASE(POLICY, UI_STEP) case POLICY: {ctx->ui_step=UI_STEP; break;} + CASE(POLICY_SHOW_BEFORE_RESPONSE, HANDLE_DONATION_STEP_DISPLAY); + CASE(POLICY_ALLOW_WITHOUT_PROMPT, HANDLE_DONATION_STEP_RESPOND); +#undef CASE + default: + THROW(ERR_NOT_IMPLEMENTED); + } + } + + signTx_handleDonation_ui_runStep(); +} + + // ============================== CONFIRM ============================== static bool _shouldDisplayTxId(sign_tx_signingmode_t signingMode) @@ -1646,7 +2412,6 @@ static void signTx_handleConfirmAPDU(uint8_t p2, const uint8_t* wireDataBuffer M CHECK_STAGE(SIGN_STAGE_CONFIRM); VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } { @@ -1694,11 +2459,10 @@ static void signTx_handleWitnessAPDU(uint8_t p2, const uint8_t* wireDataBuffer, { // sanity checks CHECK_STAGE(SIGN_STAGE_WITNESSES); - VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); - TRACE("Witness no. %d out of %d", WITNESS_CTX->currentWitness + 1, ctx->numWitnesses); ASSERT(WITNESS_CTX->currentWitness < ctx->numWitnesses); + + VALIDATE(p2 == P2_UNUSED, ERR_INVALID_REQUEST_PARAMETERS); } explicit_bzero(&WITNESS_CTX->stageData.witness, SIZEOF(WITNESS_CTX->stageData.witness)); @@ -1778,13 +2542,18 @@ static subhandler_fn_t* lookup_subhandler(uint8_t p1) CASE(0x06, signTx_handleCertificateAPDU); CASE(0x07, signTx_handleWithdrawalAPDU); CASE(0x09, signTx_handleValidityIntervalStartAPDU); + #ifdef APP_FEATURE_TOKEN_MINTING CASE(0x0b, signTx_handleMintAPDU); + #endif // APP_FEATURE_TOKEN_MINTING CASE(0x0c, signTx_handleScriptDataHashAPDU); CASE(0x0d, signTx_handleCollateralInputAPDU); CASE(0x0e, signTx_handleRequiredSignerAPDU); - CASE(0x12, signTx_handleCollateralOutputAPDU); // TODO perhaps change the numbers for the newly added items? + CASE(0x12, signTx_handleCollateralOutputAPDU); CASE(0x10, signTx_handleTotalCollateralAPDU); - CASE(0x11, signTx_handleReferenceInputsAPDU); + CASE(0x11, signTx_handleReferenceInputAPDU); + CASE(0x13, signTx_handleVotingProcedureAPDU); + CASE(0x15, signTx_handleTreasuryAPDU); + CASE(0x16, signTx_handleDonationAPDU); CASE(0x0a, signTx_handleConfirmAPDU); CASE(0x0f, signTx_handleWitnessAPDU); DEFAULT(NULL) @@ -1819,11 +2588,14 @@ void signTx_handleAPDU( case SIGN_STAGE_BODY_FEE: case SIGN_STAGE_BODY_TTL: case SIGN_STAGE_BODY_CERTIFICATES: + #ifdef APP_FEATURE_POOL_REGISTRATION case SIGN_STAGE_BODY_CERTIFICATES_POOL_SUBMACHINE: - case SIGN_STAGE_BODY_WITHDRAWALS: + #endif // APP_FEATURE_POOL_REGISTRATION case SIGN_STAGE_BODY_VALIDITY_INTERVAL: case SIGN_STAGE_BODY_MINT: + #ifdef APP_FEATURE_TOKEN_MINTING case SIGN_STAGE_BODY_MINT_SUBMACHINE: + #endif // APP_FEATURE_TOKEN_MINTING case SIGN_STAGE_BODY_SCRIPT_DATA_HASH: case SIGN_STAGE_BODY_COLLATERAL_INPUTS: case SIGN_STAGE_BODY_REQUIRED_SIGNERS: @@ -1834,6 +2606,11 @@ void signTx_handleAPDU( explicit_bzero(&BODY_CTX->stageData, SIZEOF(BODY_CTX->stageData)); break; } + + case SIGN_STAGE_BODY_WITHDRAWALS: + // we need to keep previous data for checking canonical ordering + break; + default: break; } @@ -1852,9 +2629,8 @@ ins_sign_tx_aux_data_context_t* accessAuxDataContext() return &(ctx->txPartCtx.aux_data_ctx); default: - #ifndef DEVEL + PRINTF("accessAuxDataContext() bug\n"); ASSERT(false); - #endif THROW(ERR_ASSERT); } } @@ -1869,11 +2645,15 @@ ins_sign_tx_body_context_t* accessBodyContext() case SIGN_STAGE_BODY_FEE: case SIGN_STAGE_BODY_TTL: case SIGN_STAGE_BODY_CERTIFICATES: + #ifdef APP_FEATURE_POOL_REGISTRATION case SIGN_STAGE_BODY_CERTIFICATES_POOL_SUBMACHINE: + #endif // APP_FEATURE_POOL_REGISTRATION case SIGN_STAGE_BODY_WITHDRAWALS: case SIGN_STAGE_BODY_VALIDITY_INTERVAL: case SIGN_STAGE_BODY_MINT: + #ifdef APP_FEATURE_TOKEN_MINTING case SIGN_STAGE_BODY_MINT_SUBMACHINE: + #endif // APP_FEATURE_TOKEN_MINTING case SIGN_STAGE_BODY_SCRIPT_DATA_HASH: case SIGN_STAGE_BODY_COLLATERAL_INPUTS: case SIGN_STAGE_BODY_REQUIRED_SIGNERS: @@ -1881,13 +2661,15 @@ ins_sign_tx_body_context_t* accessBodyContext() case SIGN_STAGE_BODY_COLLATERAL_OUTPUT_SUBMACHINE: case SIGN_STAGE_BODY_TOTAL_COLLATERAL: case SIGN_STAGE_BODY_REFERENCE_INPUTS: + case SIGN_STAGE_BODY_VOTING_PROCEDURES: + case SIGN_STAGE_BODY_TREASURY: + case SIGN_STAGE_BODY_DONATION: case SIGN_STAGE_CONFIRM: return &(ctx->txPartCtx.body_ctx); default: - #ifndef DEVEL + PRINTF("accessBodyContext() bug\n"); ASSERT(false); - #endif THROW(ERR_ASSERT); } } @@ -1900,9 +2682,8 @@ ins_sign_tx_witness_context_t* accessWitnessContext() return &(ctx->txPartCtx.witnesses_ctx); default: - #ifndef DEVEL + PRINTF("accessWitnessContext() bug\n"); ASSERT(false); - #endif THROW(ERR_ASSERT); } } diff --git a/src/signTx.h b/src/signTx.h index 32a8751b..8ff04c32 100644 --- a/src/signTx.h +++ b/src/signTx.h @@ -1,6 +1,7 @@ #ifndef H_CARDANO_APP_SIGN_TX #define H_CARDANO_APP_SIGN_TX +#include "cardano.h" #include "common.h" #include "hash.h" #include "handlers.h" @@ -33,11 +34,15 @@ typedef enum { SIGN_STAGE_BODY_FEE = 29, SIGN_STAGE_BODY_TTL = 30, SIGN_STAGE_BODY_CERTIFICATES = 31, + #ifdef APP_FEATURE_POOL_REGISTRATION SIGN_STAGE_BODY_CERTIFICATES_POOL_SUBMACHINE = 32, // pool registration certificate sub-machine + #endif // APP_FEATURE_POOL_REGISTRATION SIGN_STAGE_BODY_WITHDRAWALS = 33, SIGN_STAGE_BODY_VALIDITY_INTERVAL = 34, SIGN_STAGE_BODY_MINT = 35, + #ifdef APP_FEATURE_TOKEN_MINTING SIGN_STAGE_BODY_MINT_SUBMACHINE = 36, + #endif // APP_FEATURE_TOKEN_MINTING SIGN_STAGE_BODY_SCRIPT_DATA_HASH = 37, SIGN_STAGE_BODY_COLLATERAL_INPUTS = 38, SIGN_STAGE_BODY_REQUIRED_SIGNERS = 39, @@ -45,8 +50,11 @@ typedef enum { SIGN_STAGE_BODY_COLLATERAL_OUTPUT_SUBMACHINE = 41, SIGN_STAGE_BODY_TOTAL_COLLATERAL = 42, SIGN_STAGE_BODY_REFERENCE_INPUTS = 43, - SIGN_STAGE_CONFIRM = 44, - SIGN_STAGE_WITNESSES = 45, + SIGN_STAGE_BODY_VOTING_PROCEDURES = 44, + SIGN_STAGE_BODY_TREASURY = 45, + SIGN_STAGE_BODY_DONATION = 46, + SIGN_STAGE_CONFIRM = 47, + SIGN_STAGE_WITNESSES = 48, } sign_tx_stage_t; enum { @@ -57,7 +65,7 @@ enum { SIGN_MAX_COLLATERAL_INPUTS = UINT16_MAX, SIGN_MAX_REQUIRED_SIGNERS = UINT16_MAX, SIGN_MAX_REFERENCE_INPUTS = UINT16_MAX, - SIGN_MAX_WITNESSES = SIGN_MAX_INPUTS + SIGN_MAX_OUTPUTS + SIGN_MAX_CERTIFICATES + SIGN_MAX_REWARD_WITHDRAWALS, + SIGN_MAX_VOTING_PROCEDURES = 1, // we only support a single vote per tx }; #define UI_INPUT_LABEL_SIZE 20 @@ -68,6 +76,10 @@ typedef struct { uint32_t accountNumber; } single_account_data_t; +enum { + TX_OPTIONS_TAG_CBOR_SETS = 1, +}; + typedef struct { // significantly affects restrictions on the tx sign_tx_signingmode_t txSigningMode; @@ -76,27 +88,66 @@ typedef struct { uint32_t protocolMagic; // part of Byron address single_account_data_t singleAccountData; + + // there is only one flag and no more flags planned for the future + // but if there were many, it might be necessary to keep them + // packed in a single uint variable + bool tagCborSets; } common_tx_data_t; +// credentials are extended to allow key derivation paths +typedef enum { + // enum values are affected by backwards-compatibility + EXT_CREDENTIAL_KEY_PATH = 0, + EXT_CREDENTIAL_KEY_HASH = 2, + EXT_CREDENTIAL_SCRIPT_HASH = 1, +} ext_credential_type_t; + typedef struct { - stake_credential_type_t type; + ext_credential_type_t type; union { bip44_path_t keyPath; uint8_t keyHash[ADDRESS_KEY_HASH_LENGTH]; uint8_t scriptHash[SCRIPT_HASH_LENGTH]; }; -} stake_credential_t; +} ext_credential_t; + +// DReps are extended to allow key derivation paths +typedef enum { + EXT_DREP_KEY_HASH = 0, + EXT_DREP_KEY_PATH = 0 + 100, + EXT_DREP_SCRIPT_HASH = 1, + EXT_DREP_ABSTAIN = 2, + EXT_DREP_NO_CONFIDENCE = 3, +} ext_drep_type_t; + +typedef struct { + ext_drep_type_t type; + union { + bip44_path_t keyPath; + uint8_t keyHash[ADDRESS_KEY_HASH_LENGTH]; + uint8_t scriptHash[SCRIPT_HASH_LENGTH]; + }; +} ext_drep_t; typedef struct { certificate_type_t type; union { - stake_credential_t stakeCredential; - bip44_path_t poolIdPath; + ext_credential_t stakeCredential; + ext_credential_t committeeColdCredential; + ext_credential_t dRepCredential; + }; + union { + ext_credential_t poolCredential; + ext_credential_t committeeHotCredential; + ext_drep_t drep; + anchor_t anchor; + }; + union { + uint64_t epoch; // in pool retirement + uint64_t deposit; // not in pool retirement }; - uint64_t epoch; - uint8_t poolKeyHash[POOL_KEY_HASH_LENGTH]; - } sign_tx_certificate_data_t; typedef struct { @@ -106,11 +157,11 @@ typedef struct { typedef struct { bip44_path_t path; - uint8_t signature[64]; + uint8_t signature[ED25519_SIGNATURE_LENGTH]; } sign_tx_witness_data_t; typedef struct { - stake_credential_t stakeCredential; + ext_credential_t stakeCredential; uint64_t amount; uint8_t previousRewardAccount[REWARD_ACCOUNT_SIZE]; } sign_tx_withdrawal_data_t; @@ -138,7 +189,37 @@ typedef struct { }; } sign_tx_required_signer_t; +// voters are extended to allow key derivation paths +typedef enum { + EXT_VOTER_COMMITTEE_HOT_KEY_HASH = 0, + EXT_VOTER_COMMITTEE_HOT_KEY_PATH = 0 + 100, + EXT_VOTER_COMMITTEE_HOT_SCRIPT_HASH = 1, + EXT_VOTER_DREP_KEY_HASH = 2, + EXT_VOTER_DREP_KEY_PATH = 2 + 100, + EXT_VOTER_DREP_SCRIPT_HASH = 3, + EXT_VOTER_STAKE_POOL_KEY_HASH = 4, + EXT_VOTER_STAKE_POOL_KEY_PATH = 4 + 100, +} ext_voter_type_t; + +typedef struct { + ext_voter_type_t type; + union { + bip44_path_t keyPath; + uint8_t keyHash[ADDRESS_KEY_HASH_LENGTH]; + uint8_t scriptHash[SCRIPT_HASH_LENGTH]; + }; +} ext_voter_t; + typedef struct { + ext_voter_t voter; + gov_action_id_t govActionId; + voting_procedure_t votingProcedure; +} sign_tx_voting_procedure_t; + + +typedef struct { + tx_hash_builder_t txHashBuilder; + uint16_t currentInput; uint16_t currentOutput; uint16_t currentCertificate; @@ -146,6 +227,7 @@ typedef struct { uint16_t currentCollateral; uint16_t currentRequiredSigner; uint16_t currentReferenceInput; + uint16_t currentVotingProcedure; bool feeReceived; bool ttlReceived; @@ -154,11 +236,9 @@ typedef struct { bool scriptDataHashReceived; bool collateralOutputReceived; bool totalCollateralReceived; + bool treasuryReceived; + bool donationReceived; - // TODO move these to commonTxData? - tx_hash_builder_t txHashBuilder; - - // this holds data valid only through the processing of a single APDU union { sign_tx_transaction_input_t input; uint64_t fee; @@ -169,12 +249,19 @@ typedef struct { uint8_t scriptDataHash[SCRIPT_DATA_HASH_LENGTH]; sign_tx_required_signer_t requiredSigner; uint64_t totalCollateral; - } stageData; // TODO rename to reflect single-APDU scope + sign_tx_voting_procedure_t votingProcedure; + uint64_t treasury; + uint64_t donation; + } stageData; union { + #ifdef APP_FEATURE_POOL_REGISTRATION pool_registration_context_t pool_registration_subctx; + #endif // APP_FEATURE_POOL_REGISTRATION output_context_t output_subctx; + #ifdef APP_FEATURE_TOKEN_MINTING mint_context_t mint_subctx; + #endif // APP_FEATURE_TOKEN_MINTING } stageContext; } ins_sign_tx_body_context_t; @@ -206,6 +293,9 @@ typedef struct { bool includeTotalCollateral; uint64_t totalCollateral; uint16_t numReferenceInputs; + uint16_t numVotingProcedures; + bool includeTreasury; + bool includeDonation; uint16_t numWitnesses; diff --git a/src/signTxCVoteRegistration.c b/src/signTxCVoteRegistration.c index cb06ffb5..501adc81 100644 --- a/src/signTxCVoteRegistration.c +++ b/src/signTxCVoteRegistration.c @@ -118,8 +118,6 @@ static void signTxCVoteRegistration_handleInitAPDU(const uint8_t* wireDataBuffer { { CHECK_STATE(STATE_CVOTE_REGISTRATION_INIT); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } cvote_registration_context_t* subctx = accessSubContext(); { @@ -229,8 +227,6 @@ static void signTxCVoteRegistration_handleVoteKeyAPDU(const uint8_t* wireDataBuf { { CHECK_STATE(STATE_CVOTE_REGISTRATION_VOTE_KEY); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } cvote_registration_context_t* subctx = accessSubContext(); { @@ -379,7 +375,6 @@ static void signTxCVoteRegistration_handleStakingKeyAPDU(const uint8_t* wireData { // sanity checks CHECK_STATE(STATE_CVOTE_REGISTRATION_STAKING_KEY); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } cvote_registration_context_t* subctx = accessSubContext(); { @@ -452,7 +447,7 @@ size_t _destinationToAddress( case DESTINATION_THIRD_PARTY: addressSize = destination->address.size; ASSERT(addressSize <= addressBufferSize); - memcpy( + memmove( addressBuffer, destination->address.buffer, addressSize @@ -472,8 +467,6 @@ static void signTxCVoteRegistration_handlePaymentAddressAPDU(const uint8_t* wire { // safety checks CHECK_STATE(STATE_CVOTE_REGISTRATION_PAYMENT_ADDRESS); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } cvote_registration_context_t* subctx = accessSubContext(); { @@ -535,8 +528,6 @@ static void signTxCVoteRegistration_handleNonceAPDU(const uint8_t* wireDataBuffe { // sanity checks CHECK_STATE(STATE_CVOTE_REGISTRATION_NONCE); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } cvote_registration_context_t* subctx = accessSubContext(); { @@ -583,8 +574,6 @@ static void signTxCVoteRegistration_handleVotingPurposeAPDU(const uint8_t* wireD { { CHECK_STATE(STATE_CVOTE_REGISTRATION_VOTING_PURPOSE); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } cvote_registration_context_t* subctx = accessSubContext(); { @@ -652,10 +641,8 @@ __noinline_due_to_stack__ static void signTxCVoteRegistration_handleConfirmAPDU(const uint8_t* wireDataBuffer MARK_UNUSED, size_t wireDataSize) { { - //sanity checks + // sanity checks CHECK_STATE(STATE_CVOTE_REGISTRATION_CONFIRM); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } cvote_registration_context_t* subctx = accessSubContext(); { @@ -737,6 +724,7 @@ bool signTxCVoteRegistration_isValidInstruction(uint8_t p2) void signTxCVoteRegistration_handleAPDU(uint8_t p2, const uint8_t* wireDataBuffer, size_t wireDataSize) { + ASSERT(wireDataBuffer != NULL); ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); switch (p2) { diff --git a/src/signTxMint.c b/src/signTxMint.c index 589b499b..5957588b 100644 --- a/src/signTxMint.c +++ b/src/signTxMint.c @@ -1,3 +1,5 @@ +#ifdef APP_FEATURE_TOKEN_MINTING + #include "signTxMint.h" #include "signTxMint_ui.h" #include "signTxUtils.h" @@ -33,7 +35,6 @@ static void signTxMint_handleTopLevelDataAPDU(const uint8_t* wireDataBuffer, siz { // safety checks CHECK_STATE(STATE_MINT_TOP_LEVEL_DATA); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } TRACE_BUFFER(wireDataBuffer, wireDataSize); mint_context_t* subctx = accessSubcontext(); @@ -65,8 +66,6 @@ static void signTxMint_handleAssetGroupAPDU(const uint8_t* wireDataBuffer, size_ { // sanity checks CHECK_STATE(STATE_MINT_ASSET_GROUP); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } mint_context_t* subctx = accessSubcontext(); { @@ -120,8 +119,6 @@ static void signTxMint_handleTokenAPDU(const uint8_t* wireDataBuffer, size_t wir { // sanity checks CHECK_STATE(STATE_MINT_TOKEN); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } mint_context_t* subctx = accessSubcontext(); { @@ -183,10 +180,8 @@ static void signTxMint_handleTokenAPDU(const uint8_t* wireDataBuffer, size_t wir static void signTxMint_handleConfirmAPDU(const uint8_t* wireDataBuffer MARK_UNUSED, size_t wireDataSize) { { - //sanity checks + // sanity checks CHECK_STATE(STATE_MINT_CONFIRM); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } { @@ -246,6 +241,7 @@ void signTxMint_init() void signTxMint_handleAPDU(uint8_t p2, const uint8_t* wireDataBuffer, size_t wireDataSize) { + ASSERT(wireDataBuffer != NULL); ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); switch (p2) { @@ -290,3 +286,5 @@ bool signTxMint_isFinished() ASSERT(false); } } + +#endif // APP_FEATURE_TOKEN_MINTING diff --git a/src/signTxMint.h b/src/signTxMint.h index 97958082..51389107 100644 --- a/src/signTxMint.h +++ b/src/signTxMint.h @@ -1,6 +1,8 @@ #ifndef H_CARDANO_APP_SIGN_TX_MINT #define H_CARDANO_APP_SIGN_TX_MINT +#ifdef APP_FEATURE_TOKEN_MINTING + #include "common.h" #include "cardano.h" #include "addressUtilsShelley.h" @@ -55,4 +57,6 @@ void signTxMint_handleAPDU(uint8_t p2, const uint8_t* wireDataBuffer, size_t wir bool signTxMint_isFinished(); +#endif // APP_FEATURE_TOKEN_MINTING + #endif // H_CARDANO_APP_SIGN_TX_MINT diff --git a/src/signTxMint_ui.c b/src/signTxMint_ui.c index 5638b5e8..456871ec 100644 --- a/src/signTxMint_ui.c +++ b/src/signTxMint_ui.c @@ -1,3 +1,5 @@ +#ifdef APP_FEATURE_TOKEN_MINTING + #include "signTxMint.h" #include "signTxMint_ui.h" #include "signTxUtils.h" @@ -203,3 +205,5 @@ void signTxMint_handleConfirm_ui_runStep() } UI_STEP_END(HANDLE_CONFIRM_STEP_INVALID); } + +#endif // APP_FEATURE_TOKEN_MINTING \ No newline at end of file diff --git a/src/signTxMint_ui.h b/src/signTxMint_ui.h index be87f434..58f5aeaf 100644 --- a/src/signTxMint_ui.h +++ b/src/signTxMint_ui.h @@ -1,6 +1,8 @@ #ifndef H_CARDANO_APP_SIGN_TX_MINT_UI #define H_CARDANO_APP_SIGN_TX_MINT_UI +#ifdef APP_FEATURE_TOKEN_MINTING + enum { HANDLE_MINT_TOP_LEVEL_DATA_DISPLAY = 9200, HANDLE_MINT_TOP_LEVEL_DATA_RESPOND, @@ -34,4 +36,6 @@ enum { void signTxMint_handleConfirm_ui_runStep(); +#endif // APP_FEATURE_TOKEN_MINTING + #endif // H_CARDANO_APP_SIGN_TX_MINT_UI diff --git a/src/signTxOutput.c b/src/signTxOutput.c index 57954284..54e6b559 100644 --- a/src/signTxOutput.c +++ b/src/signTxOutput.c @@ -307,8 +307,6 @@ static void parseTopLevelData(const uint8_t* wireDataBuffer, size_t wireDataSize { // safety checks CHECK_STATE(STATE_OUTPUT_TOP_LEVEL_DATA); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } output_context_t* subctx = accessSubcontext(); @@ -398,7 +396,6 @@ static void handleCollateralOutput_addressBytes() .includeRefScript = subctx->includeRefScript, }; - // TODO maybe restrict to specific address types? we don't support datum in coll ret outputs security_policy_t policy = policyForSignTxCollateralOutputAddressBytes( &output, commonTxData->txSigningMode, @@ -545,8 +542,6 @@ static void handleAssetGroupAPDU(const uint8_t* wireDataBuffer, size_t wireDataS { // sanity checks CHECK_STATE(STATE_OUTPUT_ASSET_GROUP); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } output_context_t* subctx = accessSubcontext(); { @@ -621,8 +616,6 @@ static void handleTokenAPDU(const uint8_t* wireDataBuffer, size_t wireDataSize) { // sanity checks CHECK_STATE(STATE_OUTPUT_TOKEN); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } output_context_t* subctx = accessSubcontext(); { @@ -750,6 +743,10 @@ static void handleDatumInline(read_view_t* view) VALIDATE(chunkSize > 0, ERR_INVALID_DATA); VALIDATE(chunkSize <= MAX_CHUNK_SIZE, ERR_INVALID_DATA); VALIDATE(chunkSize <= subctx->stateData.datumRemainingBytes, ERR_INVALID_DATA); + if (subctx->stateData.datumRemainingBytes >= MAX_CHUNK_SIZE) { + // forces to use chunks of maximum allowed size + VALIDATE(chunkSize == MAX_CHUNK_SIZE, ERR_INVALID_DATA); + } view_parseBuffer(subctx->stateData.datumChunk, view, chunkSize); VALIDATE(view_remainingSize(view) == 0, ERR_INVALID_DATA); @@ -797,7 +794,6 @@ static void handleDatumAPDU(const uint8_t* wireDataBuffer, size_t wireDataSize) { // sanity checks CHECK_STATE(STATE_OUTPUT_DATUM); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } output_context_t* subctx = accessSubcontext(); { @@ -829,7 +825,6 @@ static void handleDatumChunkAPDU(const uint8_t* wireDataBuffer, size_t wireDataS { // sanity checks CHECK_STATE(STATE_OUTPUT_DATUM_INLINE_CHUNKS); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } output_context_t* subctx = accessSubcontext(); { @@ -871,7 +866,6 @@ static void handleRefScriptAPDU(const uint8_t* wireDataBuffer, size_t wireDataSi { // sanity checks CHECK_STATE(STATE_OUTPUT_REFERENCE_SCRIPT); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } output_context_t* subctx = accessSubcontext(); { @@ -881,7 +875,7 @@ static void handleRefScriptAPDU(const uint8_t* wireDataBuffer, size_t wireDataSi // parse data subctx->stateData.refScriptRemainingBytes = parse_u4be(&view); - TRACE("refScriptRemainingBytes = %u", subctx->stateData.datumRemainingBytes); + TRACE("refScriptRemainingBytes = %u", subctx->stateData.refScriptRemainingBytes); VALIDATE(subctx->stateData.refScriptRemainingBytes > 0, ERR_INVALID_DATA); size_t chunkSize = parse_u4be(&view); @@ -935,7 +929,6 @@ static void handleRefScriptChunkAPDU(const uint8_t* wireDataBuffer, size_t wireD { // sanity checks CHECK_STATE(STATE_OUTPUT_REFERENCE_SCRIPT_CHUNKS); - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } output_context_t* subctx = accessSubcontext(); { @@ -947,7 +940,10 @@ static void handleRefScriptChunkAPDU(const uint8_t* wireDataBuffer, size_t wireD TRACE("chunkSize = %u", chunkSize); VALIDATE(chunkSize > 0, ERR_INVALID_DATA); VALIDATE(chunkSize <= MAX_CHUNK_SIZE, ERR_INVALID_DATA); - + if (subctx->stateData.refScriptRemainingBytes >= MAX_CHUNK_SIZE) { + // forces to use chunks of maximum allowed size + VALIDATE(chunkSize == MAX_CHUNK_SIZE, ERR_INVALID_DATA); + } VALIDATE(chunkSize <= subctx->stateData.refScriptRemainingBytes, ERR_INVALID_DATA); subctx->stateData.refScriptRemainingBytes -= chunkSize; @@ -958,7 +954,7 @@ static void handleRefScriptChunkAPDU(const uint8_t* wireDataBuffer, size_t wireD } { // add to tx - TRACE("Adding inline datum chunk to tx hash"); + TRACE("Adding reference script chunk to tx hash"); txHashBuilder_addOutput_referenceScript_dataChunk( &BODY_CTX->txHashBuilder, subctx->stateData.scriptChunk, subctx->stateData.refScriptChunkSize @@ -1075,6 +1071,7 @@ bool signTxOutput_isValidInstruction(uint8_t p2) void signTxOutput_handleAPDU(uint8_t p2, const uint8_t* wireDataBuffer, size_t wireDataSize) { + ASSERT(wireDataBuffer != NULL); ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); switch (p2) { @@ -1136,6 +1133,7 @@ bool signTxCollateralOutput_isValidInstruction(uint8_t p2) void signTxCollateralOutput_handleAPDU(uint8_t p2, const uint8_t* wireDataBuffer, size_t wireDataSize) { + ASSERT(wireDataBuffer != NULL); ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); switch (p2) { diff --git a/src/signTxOutput_ui.c b/src/signTxOutput_ui.c index de75d286..ff955d92 100644 --- a/src/signTxOutput_ui.c +++ b/src/signTxOutput_ui.c @@ -66,7 +66,7 @@ void signTx_handleOutput_address_bytes_ui_runStep() #endif // HAVE_BAGL } UI_STEP(HANDLE_OUTPUT_ADDRESS_BYTES_STEP_WARNING_DATUM) { - // this warning does not apply to address given by params where we only allow key hash spending part + // this warning does not apply to address given by params where we only allow key hash payment part // in which case datum is just optional and rarely used if (_needsMissingDatumWarning()) { #ifdef HAVE_BAGL @@ -124,15 +124,15 @@ void signTx_handleOutput_addressParams_ui_runStep() display_prompt(msg, "", this_fn, respond_with_user_reject); #endif // HAVE_BAGL } - UI_STEP(HANDLE_OUTPUT_ADDRESS_PARAMS_STEP_DISPLAY_SPENDING_PATH) { + UI_STEP(HANDLE_OUTPUT_ADDRESS_PARAMS_STEP_DISPLAY_PAYMENT_PATH) { #ifdef HAVE_BAGL - ui_displaySpendingInfoScreen(&subctx->stateData.destination.params, this_fn); + ui_displayPaymentInfoScreen(&subctx->stateData.destination.params, this_fn); #elif defined(HAVE_NBGL) -#define SPENDING_INFO_SIZE MAX(BECH32_STRING_SIZE_MAX, BIP44_PATH_STRING_SIZE_MAX) +#define PAYMENT_INFO_SIZE MAX(BECH32_STRING_SIZE_MAX, BIP44_PATH_STRING_SIZE_MAX) char line1[30]; - char spendingInfo[SPENDING_INFO_SIZE] = {0}; - ui_getSpendingInfoScreen(line1, SIZEOF(line1), spendingInfo, SIZEOF(spendingInfo), &subctx->stateData.destination.params); - fill_and_display_if_required(line1, spendingInfo, this_fn, respond_with_user_reject); + char paymentInfoInfo[PAYMENT_INFO_SIZE] = {0}; + ui_getPaymentInfoScreen(line1, SIZEOF(line1), paymentInfoInfo, SIZEOF(paymentInfoInfo), &subctx->stateData.destination.params); + fill_and_display_if_required(line1, paymentInfoInfo, this_fn, respond_with_user_reject); #endif // HAVE_BAGL } UI_STEP(HANDLE_OUTPUT_ADDRESS_PARAMS_STEP_DISPLAY_STAKING_INFO) { @@ -356,7 +356,8 @@ void signTxOutput_handleDatumInline_ui_runStep() size_t datumSize = subctx->stateData.datumRemainingBytes + subctx->stateData.datumChunkSize; // datumSize with 6 digits fits on the screen, less than max tx size // if more is needed, "bytes" can be replaced by "B" for those larger numbers - snprintf(l1, SIZEOF(l1), "Datum %u bytes", datumSize); + ASSERT(datumSize < UINT32_MAX); + snprintf(l1, SIZEOF(l1), "Datum %u bytes", (uint32_t)datumSize); ASSERT(strlen(l1) + 1 < SIZEOF(l1)); char l2[20]; @@ -397,7 +398,8 @@ void handleRefScript_ui_runStep() size_t scriptSize = subctx->stateData.refScriptRemainingBytes + subctx->stateData.refScriptChunkSize; // scriptSize with 6 digits fits on the screen, less than max tx size // if more is needed, "bytes" can be replaced by "B" for those larger numbers - snprintf(l1, SIZEOF(l1), "Script %u bytes", scriptSize); + ASSERT(scriptSize < UINT32_MAX); + snprintf(l1, SIZEOF(l1), "Script %u bytes", (uint32_t)scriptSize); ASSERT(strlen(l1) + 1 < SIZEOF(l1)); char l2[20]; diff --git a/src/signTxOutput_ui.h b/src/signTxOutput_ui.h index e61755a5..d9494d06 100644 --- a/src/signTxOutput_ui.h +++ b/src/signTxOutput_ui.h @@ -1,6 +1,5 @@ #ifndef H_CARDANO_APP_SIGN_TX_OUTPUT_UI #define H_CARDANO_APP_SIGN_TX_OUTPUT_UI -#endif // H_CARDANO_APP_SIGN_TX_OUTPUT_UI #ifdef HAVE_BAGL #include "uiScreens_bagl.h" @@ -23,7 +22,7 @@ void signTx_handleOutput_address_bytes_ui_runStep(); enum { HANDLE_OUTPUT_ADDRESS_PARAMS_STEP_DISPLAY_BEGIN = 3200, HANDLE_OUTPUT_ADDRESS_PARAMS_STEP_WARNING_DATUM, - HANDLE_OUTPUT_ADDRESS_PARAMS_STEP_DISPLAY_SPENDING_PATH, + HANDLE_OUTPUT_ADDRESS_PARAMS_STEP_DISPLAY_PAYMENT_PATH, HANDLE_OUTPUT_ADDRESS_PARAMS_STEP_DISPLAY_STAKING_INFO, HANDLE_OUTPUT_ADDRESS_PARAMS_STEP_DISPLAY_ADDRESS, HANDLE_OUTPUT_ADDRESS_PARAMS_STEP_DISPLAY_AMOUNT, @@ -91,3 +90,5 @@ enum { }; void signTxOutput_handleConfirm_ui_runStep(); + +#endif // H_CARDANO_APP_SIGN_TX_OUTPUT_UI diff --git a/src/signTxPoolRegistration.c b/src/signTxPoolRegistration.c index 80fc4935..d91863e8 100644 --- a/src/signTxPoolRegistration.c +++ b/src/signTxPoolRegistration.c @@ -1,3 +1,5 @@ +#ifdef APP_FEATURE_POOL_REGISTRATION + #include "signTx.h" #include "signTxPoolRegistration_ui.h" #include "state.h" @@ -73,8 +75,6 @@ static void signTxPoolRegistration_handleInitAPDU(const uint8_t* wireDataBuffer, { // sanity checks CHECK_STATE(STAKE_POOL_REGISTRATION_INIT); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } pool_registration_context_t* subctx = accessSubcontext(); { @@ -183,8 +183,6 @@ static void signTxPoolRegistration_handlePoolKeyAPDU(const uint8_t* wireDataBuff { // sanity checks CHECK_STATE(STAKE_POOL_REGISTRATION_POOL_KEY); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } { // parse data @@ -256,8 +254,6 @@ static void signTxPoolRegistration_handleVrfKeyAPDU(const uint8_t* wireDataBuffe { // sanity checks CHECK_STATE(STAKE_POOL_REGISTRATION_VRF_KEY); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } pool_registration_context_t* subctx = accessSubcontext(); { @@ -311,8 +307,6 @@ static void signTxPoolRegistration_handlePoolFinancialsAPDU(const uint8_t* wireD { // sanity checks CHECK_STATE(STAKE_POOL_REGISTRATION_FINANCIALS); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } pool_registration_context_t* subctx = accessSubcontext(); { @@ -405,8 +399,6 @@ static void signTxPoolRegistration_handleRewardAccountAPDU(const uint8_t* wireDa { // sanity checks CHECK_STATE(STAKE_POOL_REGISTRATION_REWARD_ACCOUNT); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } { // parse data @@ -492,8 +484,6 @@ static void signTxPoolRegistration_handleOwnerAPDU(const uint8_t* wireDataBuffer { // sanity checks CHECK_STATE(STAKE_POOL_REGISTRATION_OWNERS); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } pool_registration_context_t* subctx = accessSubcontext(); @@ -610,7 +600,7 @@ static void _parseDnsName(pool_relay_t* relay, read_view_t* view) { relay->dnsNameSize = view_remainingSize(view); VALIDATE(relay->dnsNameSize <= DNS_NAME_SIZE_MAX, ERR_INVALID_DATA); - VALIDATE(str_isAllowedDnsName(VIEW_REMAINING_TO_TUPLE_BUF_SIZE(view)), ERR_INVALID_DATA); + VALIDATE(str_isUnambiguousAscii(VIEW_REMAINING_TO_TUPLE_BUF_SIZE(view)), ERR_INVALID_DATA); STATIC_ASSERT(SIZEOF(relay->dnsName) == DNS_NAME_SIZE_MAX, "wrong dns name buffer size"); view_parseBuffer(relay->dnsName, view, relay->dnsNameSize); @@ -636,8 +626,6 @@ static void signTxPoolRegistration_handleRelayAPDU(const uint8_t* wireDataBuffer { // sanity checks CHECK_STATE(STAKE_POOL_REGISTRATION_RELAYS); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } pool_relay_t* relay = &accessSubcontext()->stateData.relay; @@ -774,8 +762,6 @@ static void signTxPoolRegistration_handlePoolMetadataAPDU(const uint8_t* wireDat { // sanity checks CHECK_STATE(STAKE_POOL_REGISTRATION_METADATA); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } pool_registration_context_t* subctx = accessSubcontext(); @@ -855,8 +841,6 @@ static void signTxPoolRegistration_handleConfirmAPDU(const uint8_t* wireDataBuff { //sanity checks CHECK_STATE(STAKE_POOL_REGISTRATION_CONFIRM); - - ASSERT(wireDataSize < BUFFER_SIZE_PARANOIA); } { @@ -969,3 +953,5 @@ void signTxPoolRegistration_handleAPDU(uint8_t p2, const uint8_t* wireDataBuffer ASSERT(false); } } + +#endif // APP_FEATURE_POOL_REGISTRATION diff --git a/src/signTxPoolRegistration.h b/src/signTxPoolRegistration.h index 7363147b..24417ade 100644 --- a/src/signTxPoolRegistration.h +++ b/src/signTxPoolRegistration.h @@ -1,6 +1,8 @@ #ifndef H_CARDANO_APP_SIGN_TX_POOL_REGISTRATION #define H_CARDANO_APP_SIGN_TX_POOL_REGISTRATION +#ifdef APP_FEATURE_POOL_REGISTRATION + #include "common.h" #include "cardano.h" #include "txHashBuilder.h" @@ -9,7 +11,7 @@ #define POOL_MAX_RELAYS 1000 // SIGN_STAGE_BODY_CERTIFICATES = 28 -// CERTIFICATE_TYPE_STAKE_POOL_REGISTRATION = 3 +// CERTIFICATE_STAKE_POOL_REGISTRATION = 3 typedef enum { STAKE_POOL_REGISTRATION_INIT = 2830, STAKE_POOL_REGISTRATION_POOL_KEY = 2831, @@ -83,4 +85,6 @@ void signTxPoolRegistration_handleAPDU(uint8_t p2, const uint8_t* wireDataBuffer bool signTxPoolRegistration_isFinished(); +#endif // APP_FEATURE_POOL_REGISTRATION + #endif // H_CARDANO_APP_SIGN_TX_POOL_REGISTRATION diff --git a/src/signTxPoolRegistration_ui.c b/src/signTxPoolRegistration_ui.c index 04ece815..433555d5 100644 --- a/src/signTxPoolRegistration_ui.c +++ b/src/signTxPoolRegistration_ui.c @@ -1,3 +1,5 @@ +#ifdef APP_FEATURE_POOL_REGISTRATION + #include "signTx.h" #include "signTxPoolRegistration_ui.h" #include "state.h" @@ -684,7 +686,7 @@ void handleMetadata_ui_runStep() UI_STEP(HANDLE_METADATA_STEP_DISPLAY_HASH) { char metadataHashHex[1 + 2 * POOL_METADATA_HASH_LENGTH] = {0}; explicit_bzero(metadataHashHex, SIZEOF(metadataHashHex)); - size_t len = str_formatMetadata( + size_t len = encode_hex( md->hash, SIZEOF(md->hash), metadataHashHex, SIZEOF(metadataHashHex) ); @@ -792,3 +794,5 @@ void signTxPoolRegistration_handleConfirm_ui_runStep() } UI_STEP_END(HANDLE_CONFIRM_STEP_INVALID); } + +#endif // APP_FEATURE_POOL_REGISTRATION diff --git a/src/signTxPoolRegistration_ui.h b/src/signTxPoolRegistration_ui.h index b155805f..b9b60ea2 100644 --- a/src/signTxPoolRegistration_ui.h +++ b/src/signTxPoolRegistration_ui.h @@ -1,6 +1,8 @@ #ifndef H_CARDANO_APP_SIGN_TX_POOL_REGISATRATION_UI #define H_CARDANO_APP_SIGN_TX_POOL_REGISATRATION_UI +#ifdef APP_FEATURE_POOL_REGISTRATION + // ============================== INIT ============================== enum { @@ -122,4 +124,7 @@ enum { }; void signTxPoolRegistration_handleConfirm_ui_runStep(); + +#endif // APP_FEATURE_POOL_REGISTRATION + #endif // H_CARDANO_APP_SIGN_TX_POOL_REGISATRATION_UI diff --git a/src/signTxUtils.h b/src/signTxUtils.h index 8f5baf3f..fcb63149 100644 --- a/src/signTxUtils.h +++ b/src/signTxUtils.h @@ -18,4 +18,4 @@ bool violatesSingleAccountOrStoreIt(const bip44_path_t* path); void view_parseDestination(read_view_t* view, tx_output_destination_storage_t* destination); -#endif // H_CARDANO_APP_SIGN_TX_UTILS +#endif // H_CARDANO_APP_SIGN_TX_UTILS diff --git a/src/signTx_ui.c b/src/signTx_ui.c index 97b71c73..a500fcba 100644 --- a/src/signTx_ui.c +++ b/src/signTx_ui.c @@ -74,12 +74,17 @@ static const char* _newTxLine1(sign_tx_signingmode_t txSigningMode) #ifdef HAVE_NBGL static void signTx_handleInit_ui_runStep_cb(void) { + // if the protocol magic check is not enabled, + // displaying the protocol magic might be misleading, + // so we must not show it + #ifdef APP_FEATURE_BYRON_PROTOCOL_MAGIC_CHECK char networkParams[100] = {0}; ui_getNetworkParamsScreen_2( networkParams, SIZEOF(networkParams), ctx->commonTxData.protocolMagic); fill_and_display_if_required("Protocol magic", networkParams, signTx_handleInit_ui_runStep, respond_with_user_reject); + #endif } #endif // HAVE_NBGL @@ -118,7 +123,8 @@ void signTx_handleInit_ui_runStep() #ifdef HAVE_BAGL ui_displayNetworkParamsScreen( "Network details", - ctx->commonTxData.networkId, ctx->commonTxData.protocolMagic, + ctx->commonTxData.networkId, + ctx->commonTxData.protocolMagic, this_fn ); #elif defined(HAVE_NBGL) @@ -347,22 +353,213 @@ void signTx_handleTtl_ui_runStep() #ifdef HAVE_NBGL static void signTx_handleCertificate_ui_delegation_cb(void) { + sign_tx_certificate_data_t* cert = &BODY_CTX->stageData.certificate; + char encodedStr[BECH32_STRING_SIZE_MAX] = {0}; - ui_getBech32Screen(encodedStr, SIZEOF(encodedStr), "pool", BODY_CTX->stageData.certificate.poolKeyHash, SIZEOF(BODY_CTX->stageData.certificate.poolKeyHash)); - fill_and_display_if_required("Pool", encodedStr, signTx_handleCertificate_ui_runStep, respond_with_user_reject); + ASSERT(cert->poolCredential.type == EXT_CREDENTIAL_KEY_HASH); + ui_getBech32Screen( + encodedStr, SIZEOF(encodedStr), + "pool", + cert->poolCredential.keyHash, SIZEOF(cert->poolCredential.keyHash) + ); + fill_and_display_if_required("Pool", encodedStr, signTx_handleCertificateStaking_ui_runStep, respond_with_user_reject); } #endif -void signTx_handleCertificate_ui_runStep() +static void _displayKeyPath( + ui_callback_fn_t* callback, + bip44_path_t* path, + const char* label +) +{ + #ifdef HAVE_BAGL + ui_displayPathScreen( + label, + path, + callback + ); + #elif defined(HAVE_NBGL) + { + char pathStr[BIP44_PATH_STRING_SIZE_MAX + 1] = {0}; + ui_getPathScreen(pathStr, SIZEOF(pathStr), path); + fill_and_display_if_required(label, pathStr, callback, respond_with_user_reject); + } + #endif // HAVE_BAGL +} + +static void _displayKeyHash( + ui_callback_fn_t* callback, + uint8_t keyHash[static ADDRESS_KEY_HASH_LENGTH], + const char* label, + const char* bech32Prefix +) +{ + #ifdef HAVE_BAGL + ui_displayBech32Screen( + label, + bech32Prefix, + keyHash, + ADDRESS_KEY_HASH_LENGTH, + callback + ); + #elif defined(HAVE_NBGL) + { + char encodedStr[BECH32_STRING_SIZE_MAX] = {0}; + ui_getBech32Screen(encodedStr, SIZEOF(encodedStr), bech32Prefix, keyHash, ADDRESS_KEY_HASH_LENGTH); + fill_and_display_if_required(label, encodedStr, callback, respond_with_user_reject); + } + #endif // HAVE_BAGL +} + +static void _displayScriptHash( + ui_callback_fn_t* callback, + uint8_t scriptHash[static SCRIPT_HASH_LENGTH], + const char* label, + const char* bech32Prefix +) +{ + #ifdef HAVE_BAGL + ui_displayBech32Screen( + label, + bech32Prefix, + scriptHash, + SCRIPT_HASH_LENGTH, + callback + ); + #elif defined(HAVE_NBGL) + { + char encodedStr[BECH32_STRING_SIZE_MAX] = {0}; + ui_getBech32Screen(encodedStr, SIZEOF(encodedStr), bech32Prefix, scriptHash, SCRIPT_HASH_LENGTH); + fill_and_display_if_required(label, encodedStr, callback, respond_with_user_reject); + } + #endif // HAVE_BAGL +} + +static void _displayCredential( + ui_callback_fn_t* callback, + ext_credential_t* credential, + const char* keyPathLabel, + const char* keyHashLabel, + const char* keyHashPrefix, + const char* scriptHashLabel, + const char* scriptHashPrefix +) +{ + switch (credential->type) { + case EXT_CREDENTIAL_KEY_PATH: + _displayKeyPath(callback, &credential->keyPath, keyPathLabel); + break; + case EXT_CREDENTIAL_KEY_HASH: + _displayKeyHash(callback, credential->keyHash, keyHashLabel, keyHashPrefix); + break; + case EXT_CREDENTIAL_SCRIPT_HASH: + _displayScriptHash(callback, credential->scriptHash, scriptHashLabel, scriptHashPrefix); + break; + default: + ASSERT(false); + break; + } +} + +static void _displayDeposit( + ui_callback_fn_t* callback, + uint64_t deposit +) +{ + #ifdef HAVE_BAGL + ui_displayAdaAmountScreen( + "Deposit", + deposit, + callback + ); + #elif defined(HAVE_NBGL) + char adaAmountStr[50] = {0}; + ui_getAdaAmountScreen(adaAmountStr, SIZEOF(adaAmountStr), deposit); + fill_and_display_if_required("Deposit", adaAmountStr, callback, respond_with_user_reject); + #endif // HAVE_BAGL +} + +static void _displayAnchorNull(ui_callback_fn_t* callback) +{ + #ifdef HAVE_BAGL + ui_displayPaginatedText( + "Anchor", + "null", + callback + ); + #elif defined(HAVE_NBGL) + fill_and_display_if_required( + "Anchor", + "null", + callback, + respond_with_user_reject + ); + #endif // HAVE_BAGL +} + +static void _displayAnchorUrl(ui_callback_fn_t* callback, anchor_t* anchor) +{ + char urlStr[1 + ANCHOR_URL_LENGTH_MAX] = {0}; + explicit_bzero(urlStr, SIZEOF(urlStr)); + ASSERT(anchor->urlLength <= ANCHOR_URL_LENGTH_MAX); + memmove(urlStr, anchor->url, anchor->urlLength); + urlStr[anchor->urlLength] = '\0'; + ASSERT(strlen(urlStr) == anchor->urlLength); + + #ifdef HAVE_BAGL + ui_displayPaginatedText( + "Anchor url", + urlStr, + callback + ); + #elif defined(HAVE_NBGL) + fill_and_display_if_required( + "Anchor url", + urlStr, + callback, + respond_with_user_reject + ); + #endif // HAVE_BAGL +} + +static void _displayAnchorHash(ui_callback_fn_t* callback, anchor_t* anchor) +{ + char hex[1 + 2 * ANCHOR_HASH_LENGTH] = {0}; + explicit_bzero(hex, SIZEOF(hex)); + size_t len = encode_hex( + anchor->hash, SIZEOF(anchor->hash), + hex, SIZEOF(hex) + ); + ASSERT(len + 1 == SIZEOF(hex)); + + #ifdef HAVE_BAGL + ui_displayPaginatedText( + "Anchor data hash", + hex, + callback + ); + #elif defined(HAVE_NBGL) + fill_and_display_if_required( + "Anchor data hash", + hex, + callback, + respond_with_user_reject + ); + #endif // HAVE_BAGL +} + +void signTx_handleCertificateStaking_ui_runStep() { TRACE("UI step %d", ctx->ui_step); - ui_callback_fn_t* this_fn = signTx_handleCertificate_ui_runStep; + ui_callback_fn_t* this_fn = signTx_handleCertificateStaking_ui_runStep; + sign_tx_certificate_data_t* cert = &BODY_CTX->stageData.certificate; UI_STEP_BEGIN(ctx->ui_step, this_fn); - UI_STEP(HANDLE_CERTIFICATE_STEP_DISPLAY_OPERATION) { - switch (BODY_CTX->stageData.certificate.type) { - case CERTIFICATE_TYPE_STAKE_REGISTRATION: + UI_STEP(HANDLE_CERTIFICATE_STAKING_STEP_DISPLAY_OPERATION) { + switch (cert->type) { + case CERTIFICATE_STAKE_REGISTRATION: + case CERTIFICATE_STAKE_REGISTRATION_CONWAY: #ifdef HAVE_BAGL ui_displayPaginatedText( "Register", @@ -375,7 +572,8 @@ void signTx_handleCertificate_ui_runStep() #endif // HAVE_BAGL break; - case CERTIFICATE_TYPE_STAKE_DEREGISTRATION: + case CERTIFICATE_STAKE_DEREGISTRATION: + case CERTIFICATE_STAKE_DEREGISTRATION_CONWAY: #ifdef HAVE_BAGL ui_displayPaginatedText( "Deregister", @@ -388,12 +586,13 @@ void signTx_handleCertificate_ui_runStep() #endif // HAVE_BAGL break; - case CERTIFICATE_TYPE_STAKE_DELEGATION: + case CERTIFICATE_STAKE_DELEGATION: #ifdef HAVE_BAGL + ASSERT(cert->poolCredential.type == EXT_CREDENTIAL_KEY_HASH); ui_displayBech32Screen( "Delegate stake", - "to pool", - BODY_CTX->stageData.certificate.poolKeyHash, SIZEOF(BODY_CTX->stageData.certificate.poolKeyHash), + "pool", + cert->poolCredential.keyHash, SIZEOF(cert->poolCredential.keyHash), this_fn ); #elif defined(HAVE_NBGL) @@ -403,74 +602,421 @@ void signTx_handleCertificate_ui_runStep() break; default: - // includes CERTIFICATE_TYPE_STAKE_POOL_REGISTRATION - // and CERTIFICATE_TYPE_STAKE_POOL_RETIREMENT + // includes CERTIFICATE_STAKE_POOL_REGISTRATION + // and CERTIFICATE_STAKE_POOL_RETIREMENT // which have separate UI; this handler must not be used ASSERT(false); } } - UI_STEP(HANDLE_CERTIFICATE_STEP_DISPLAY_STAKING_KEY) { - switch (BODY_CTX->stageData.certificate.stakeCredential.type) { - case STAKE_CREDENTIAL_KEY_PATH: + UI_STEP(HANDLE_CERTIFICATE_STAKING_STEP_DISPLAY_STAKE_CRED) { + _displayCredential( + this_fn, + &cert->stakeCredential, + "Stake key", + "Stake key hash", + "stake_vkh", + "Stake script hash", + "script" + ); + } + UI_STEP(HANDLE_CERTIFICATE_STAKING_STEP_DISPLAY_DEPOSIT) { + switch (cert->type) { + case CERTIFICATE_STAKE_REGISTRATION: + case CERTIFICATE_STAKE_DEREGISTRATION: + case CERTIFICATE_STAKE_DELEGATION: + // no deposit in these + UI_STEP_JUMP(HANDLE_CERTIFICATE_STAKING_STEP_CONFIRM); + break; + + case CERTIFICATE_STAKE_REGISTRATION_CONWAY: + case CERTIFICATE_STAKE_DEREGISTRATION_CONWAY: + _displayDeposit(this_fn, cert->deposit); + break; + + default: + ASSERT(false); + } + } + UI_STEP(HANDLE_CERTIFICATE_STAKING_STEP_CONFIRM) { + char description[50] = {0}; + explicit_bzero(description, SIZEOF(description)); + + switch (cert->type) { + case CERTIFICATE_STAKE_REGISTRATION: + case CERTIFICATE_STAKE_REGISTRATION_CONWAY: + #ifdef HAVE_BAGL + snprintf(description, SIZEOF(description), "registration?"); + #elif defined(HAVE_NBGL) + display_confirmation("Confirm\nregistration", "", "REGISTRATION\nACCEPTED", "Registration\nrejected", this_fn, respond_with_user_reject); + #endif // HAVE_BAGL + break; + + case CERTIFICATE_STAKE_DEREGISTRATION: + case CERTIFICATE_STAKE_DEREGISTRATION_CONWAY: + #ifdef HAVE_BAGL + snprintf(description, SIZEOF(description), "deregistration?"); + #elif defined(HAVE_NBGL) + display_confirmation("Confirm\nderegistration", "", "DEREGISTRATION\nACCEPTED", "Deregistration\nrejected", this_fn, respond_with_user_reject); + #endif // HAVE_BAGL + break; + + case CERTIFICATE_STAKE_DELEGATION: #ifdef HAVE_BAGL - ui_displayPathScreen( - "Stake key", - &BODY_CTX->stageData.certificate.stakeCredential.keyPath, + snprintf(description, SIZEOF(description), "delegation?"); + #elif defined(HAVE_NBGL) + display_confirmation("Confirm\ndelegation", "", "DELEGATION\nACCEPTED", "Delegation\nrejected", this_fn, respond_with_user_reject); + #endif // HAVE_BAGL + break; + + default: + ASSERT(false); + } + // make sure all the information is displayed to the user + ASSERT(strlen(description) + 1 < SIZEOF(description)); + + #ifdef HAVE_BAGL + ui_displayPrompt( + "Confirm", + description, + this_fn, + respond_with_user_reject + ); + #elif defined(HAVE_NBGL) + #endif // HAVE_BAGL + } + UI_STEP(HANDLE_CERTIFICATE_STAKING_STEP_RESPOND) { + respondSuccessEmptyMsg(); + + tx_advanceCertificatesStateIfAppropriate(); + } + UI_STEP_END(HANDLE_CERTIFICATE_STAKING_STEP_INVALID); +} + +void signTx_handleCertificateVoteDeleg_ui_runStep() +{ + TRACE("UI step %d", ctx->ui_step); + ui_callback_fn_t* this_fn = signTx_handleCertificateVoteDeleg_ui_runStep; + sign_tx_certificate_data_t* cert = &BODY_CTX->stageData.certificate; + + UI_STEP_BEGIN(ctx->ui_step, this_fn); + + UI_STEP(HANDLE_CERTIFICATE_VOTE_DELEGATION_STEP_DISPLAY_OPERATION) { + #ifdef HAVE_BAGL + ui_displayPaginatedText( + "Delegate", + "vote", + this_fn + ); + #elif defined(HAVE_NBGL) + set_light_confirmation(true); + display_prompt("Delegate\nvote", "", this_fn, respond_with_user_reject); + #endif // HAVE_BAGL + } + UI_STEP(HANDLE_CERTIFICATE_VOTE_DELEGATION_STEP_DISPLAY_STAKE_CRED) { + _displayCredential( + this_fn, + &cert->stakeCredential, + "Stake key", + "Stake key hash", + "stake_vkh", + "Stake script hash", + "script" + ); + } + UI_STEP(HANDLE_CERTIFICATE_VOTE_DELEGATION_STEP_DISPLAY_DREP) { + switch (cert->drep.type) { + case EXT_DREP_KEY_PATH: + _displayKeyPath(this_fn, &cert->drep.keyPath, "DRep key"); + break; + case EXT_DREP_KEY_HASH: + _displayKeyHash(this_fn, cert->drep.keyHash, "DRep key hash", "drep"); + break; + case EXT_DREP_SCRIPT_HASH: + _displayScriptHash(this_fn, cert->drep.scriptHash, "DRep script hash", "drep_script"); + break; + case DREP_ALWAYS_ABSTAIN: + #ifdef HAVE_BAGL + ui_displayPaginatedText( + "Always", + "abstain", this_fn ); #elif defined(HAVE_NBGL) - { - char pathStr[BIP44_PATH_STRING_SIZE_MAX + 1] = {0}; - ui_getPathScreen(pathStr, SIZEOF(pathStr), &BODY_CTX->stageData.certificate.stakeCredential.keyPath); - fill_and_display_if_required("Stake key", pathStr, this_fn, respond_with_user_reject); - } + set_light_confirmation(true); + display_prompt("Always\nabstain", "", this_fn, respond_with_user_reject); #endif // HAVE_BAGL break; - case STAKE_CREDENTIAL_KEY_HASH: + case DREP_ALWAYS_NO_CONFIDENCE: #ifdef HAVE_BAGL - ui_displayBech32Screen( - "Stake key hash", - "stake_vkh", - BODY_CTX->stageData.certificate.stakeCredential.keyHash, - SIZEOF(BODY_CTX->stageData.certificate.stakeCredential.keyHash), + ui_displayPaginatedText( + "Always", + "no confidence", this_fn ); #elif defined(HAVE_NBGL) - { - char encodedStr[BECH32_STRING_SIZE_MAX] = {0}; - ui_getBech32Screen(encodedStr, SIZEOF(encodedStr), "stake_vkh", BODY_CTX->stageData.certificate.stakeCredential.keyHash, SIZEOF(BODY_CTX->stageData.certificate.stakeCredential.keyHash)); - fill_and_display_if_required("Stake key hash", encodedStr, this_fn, respond_with_user_reject); - } + set_light_confirmation(true); + display_prompt("Always\nno confidence", "", this_fn, respond_with_user_reject); #endif // HAVE_BAGL break; - case STAKE_CREDENTIAL_SCRIPT_HASH: + default: + ASSERT(false); + break; + } + } + UI_STEP(HANDLE_CERTIFICATE_VOTE_DELEGATION_STEP_CONFIRM) { + #ifdef HAVE_BAGL + ui_displayPrompt( + "Confirm vote", + "delegation", + this_fn, + respond_with_user_reject + ); + #elif defined(HAVE_NBGL) + display_confirmation("Confirm vote\ndelegation", "", "DELEGATION\nACCEPTED", "Delegation\nrejected", this_fn, respond_with_user_reject); + #endif // HAVE_BAGL + } + UI_STEP(HANDLE_CERTIFICATE_VOTE_DELEGATION_STEP_RESPOND) { + respondSuccessEmptyMsg(); + + tx_advanceCertificatesStateIfAppropriate(); + } + UI_STEP_END(HANDLE_CERTIFICATE_VOTE_DELEGATION_STEP_INVALID); +} + +void signTx_handleCertificateCommitteeAuth_ui_runStep() +{ + TRACE("UI step %d", ctx->ui_step); + ui_callback_fn_t* this_fn = signTx_handleCertificateCommitteeAuth_ui_runStep; + sign_tx_certificate_data_t* cert = &BODY_CTX->stageData.certificate; + + UI_STEP_BEGIN(ctx->ui_step, this_fn); + + UI_STEP(HANDLE_CERTIFICATE_COMM_AUTH_STEP_DISPLAY_OPERATION) { + #ifdef HAVE_BAGL + ui_displayPaginatedText( + "Authorize", + "committee", + this_fn + ); + #elif defined(HAVE_NBGL) + set_light_confirmation(true); + display_prompt("Authorize committee", "", this_fn, respond_with_user_reject); + #endif // HAVE_BAGL + } + UI_STEP(HANDLE_CERTIFICATE_COMM_AUTH_STEP_DISPLAY_COLD_CRED) { + _displayCredential( + this_fn, + &cert->committeeColdCredential, + "Cmte. cold key", + "Cmte. cold key hash", + "cc_cold", + "Cmte. cold script", + "cc_cold_script" + ); + } + UI_STEP(HANDLE_CERTIFICATE_COMM_AUTH_STEP_DISPLAY_HOT_CRED) { + _displayCredential( + this_fn, + &cert->committeeHotCredential, + "Cmte. hot key", + "Cmte. hot key hash", + "cc_hot", + "Cmte. hot script", + "cc_hot_script" + ); + } + UI_STEP(HANDLE_CERTIFICATE_COMM_AUTH_STEP_CONFIRM) { + #ifdef HAVE_BAGL + ui_displayPrompt( + "Confirm", + "authorization?", + this_fn, + respond_with_user_reject + ); + #elif defined(HAVE_NBGL) + display_confirmation("Confirm\nauthorization", "", "AUTHORIZATION\nACCEPTED", "Authorization\nrejected", this_fn, respond_with_user_reject); + #endif // HAVE_BAGL + } + UI_STEP(HANDLE_CERTIFICATE_COMM_AUTH_STEP_RESPOND) { + respondSuccessEmptyMsg(); + + tx_advanceCertificatesStateIfAppropriate(); + } + UI_STEP_END(HANDLE_CERTIFICATE_COMM_AUTH_STEP_INVALID); +} + +void signTx_handleCertificateCommitteeResign_ui_runStep() +{ + TRACE("UI step %d", ctx->ui_step); + ui_callback_fn_t* this_fn = signTx_handleCertificateCommitteeResign_ui_runStep; + sign_tx_certificate_data_t* cert = &BODY_CTX->stageData.certificate; + + UI_STEP_BEGIN(ctx->ui_step, this_fn); + + UI_STEP(HANDLE_CERTIFICATE_COMM_RESIGN_STEP_DISPLAY_OPERATION) { + #ifdef HAVE_BAGL + ui_displayPaginatedText( + "Resign from", + "committee", + this_fn + ); + #elif defined(HAVE_NBGL) + set_light_confirmation(true); + display_prompt("Resign from\ncommittee", "", this_fn, respond_with_user_reject); + #endif // HAVE_BAGL + } + UI_STEP(HANDLE_CERTIFICATE_COMM_RESIGN_STEP_DISPLAY_COLD_CRED) { + _displayCredential( + this_fn, + &cert->committeeColdCredential, + "Cmte. cold key", + "Cmte. cold key hash", + "cc_cold", + "Cmte. cold script", + "cc_cold" + ); + } + UI_STEP(HANDLE_CERTIFICATE_COMM_RESIGN_STEP_DISPLAY_ANCHOR_NULL) { + if (cert->anchor.isIncluded) { + UI_STEP_JUMP(HANDLE_CERTIFICATE_COMM_RESIGN_STEP_DISPLAY_ANCHOR_URL); + } + _displayAnchorNull(this_fn); + } + UI_STEP(HANDLE_CERTIFICATE_COMM_RESIGN_STEP_DISPLAY_ANCHOR_URL) { + if (!cert->anchor.isIncluded) { + UI_STEP_JUMP(HANDLE_CERTIFICATE_COMM_RESIGN_STEP_CONFIRM); + } + _displayAnchorUrl(this_fn, &cert->anchor); + } + UI_STEP(HANDLE_CERTIFICATE_COMM_RESIGN_STEP_DISPLAY_ANCHOR_HASH) { + _displayAnchorHash(this_fn, &cert->anchor); + } + UI_STEP(HANDLE_CERTIFICATE_COMM_RESIGN_STEP_CONFIRM) { + #ifdef HAVE_BAGL + ui_displayPrompt( + "Confirm", + "resignation", + this_fn, + respond_with_user_reject + ); + #elif defined(HAVE_NBGL) + display_confirmation("Confirm\nresignation", "", "RESIGNATION\nACCEPTED", "Resignation\nrejected", this_fn, respond_with_user_reject); + #endif // HAVE_BAGL + } + UI_STEP(HANDLE_CERTIFICATE_COMM_RESIGN_STEP_RESPOND) { + respondSuccessEmptyMsg(); + + tx_advanceCertificatesStateIfAppropriate(); + } + UI_STEP_END(HANDLE_CERTIFICATE_COMM_RESIGN_STEP_INVALID); +} + +void signTx_handleCertificateDRep_ui_runStep() +{ + TRACE("UI step %d", ctx->ui_step); + ui_callback_fn_t* this_fn = signTx_handleCertificateDRep_ui_runStep; + sign_tx_certificate_data_t* cert = &BODY_CTX->stageData.certificate; + + UI_STEP_BEGIN(ctx->ui_step, this_fn); + + UI_STEP(HANDLE_CERTIFICATE_DREP_STEP_DISPLAY_OPERATION) { + switch (cert->type) { + case CERTIFICATE_DREP_REGISTRATION: #ifdef HAVE_BAGL - ui_displayBech32Screen( - "Stake script hash", - "script", - BODY_CTX->stageData.certificate.stakeCredential.scriptHash, - SIZEOF(BODY_CTX->stageData.certificate.stakeCredential.scriptHash), + ui_displayPaginatedText( + "Register", + "DRep", this_fn ); #elif defined(HAVE_NBGL) - { - char encodedStr[BECH32_STRING_SIZE_MAX] = {0}; - ui_getBech32Screen(encodedStr, SIZEOF(encodedStr), "script", BODY_CTX->stageData.certificate.stakeCredential.scriptHash, SIZEOF(BODY_CTX->stageData.certificate.stakeCredential.scriptHash)); - fill_and_display_if_required("Stake script hash", encodedStr, this_fn, respond_with_user_reject); - } + set_light_confirmation(true); + display_prompt("Register\nDRep", "", this_fn, respond_with_user_reject); + #endif // HAVE_BAGL + break; + + case CERTIFICATE_DREP_DEREGISTRATION: + case CERTIFICATE_STAKE_DEREGISTRATION_CONWAY: + #ifdef HAVE_BAGL + ui_displayPaginatedText( + "Deregister", + "DRep", + this_fn + ); + #elif defined(HAVE_NBGL) + set_light_confirmation(true); + display_prompt("Deregister\nDRep", "", this_fn, respond_with_user_reject); + #endif // HAVE_BAGL + break; + + case CERTIFICATE_DREP_UPDATE: + #ifdef HAVE_BAGL + ui_displayPaginatedText( + "Update", + "DRep", + this_fn + ); + #elif defined(HAVE_NBGL) + set_light_confirmation(true); + display_prompt("Update\nDRep", "", this_fn, respond_with_user_reject); #endif // HAVE_BAGL break; + default: ASSERT(false); + } + } + UI_STEP(HANDLE_CERTIFICATE_DREP_STEP_DISPLAY_CREDENTIAL) { + _displayCredential( + this_fn, + &cert->dRepCredential, + "DRep key", + "DRep key hash", + "drep", + "DRep script hash", + "drep" + ); + } + UI_STEP(HANDLE_CERTIFICATE_DREP_STEP_DISPLAY_DEPOSIT) { + switch (cert->type) { + case CERTIFICATE_DREP_UPDATE: + // no deposit in these + UI_STEP_JUMP(HANDLE_CERTIFICATE_DREP_STEP_DISPLAY_ANCHOR_NULL); break; + + case CERTIFICATE_DREP_REGISTRATION: + case CERTIFICATE_DREP_DEREGISTRATION: + _displayDeposit(this_fn, cert->deposit); + break; + + default: + ASSERT(false); + } + } + UI_STEP(HANDLE_CERTIFICATE_DREP_STEP_DISPLAY_ANCHOR_NULL) { + if (cert->type == CERTIFICATE_DREP_DEREGISTRATION) { + // no anchor for this type + UI_STEP_JUMP(HANDLE_CERTIFICATE_DREP_STEP_CONFIRM); + } + if (cert->anchor.isIncluded) { + UI_STEP_JUMP(HANDLE_CERTIFICATE_DREP_STEP_DISPLAY_ANCHOR_URL); + } + _displayAnchorNull(this_fn); + } + UI_STEP(HANDLE_CERTIFICATE_DREP_STEP_DISPLAY_ANCHOR_URL) { + if (!cert->anchor.isIncluded) { + UI_STEP_JUMP(HANDLE_CERTIFICATE_DREP_STEP_CONFIRM); } + _displayAnchorUrl(this_fn, &cert->anchor); + } + UI_STEP(HANDLE_CERTIFICATE_DREP_STEP_DISPLAY_ANCHOR_HASH) { + _displayAnchorHash(this_fn, &cert->anchor); } - UI_STEP(HANDLE_CERTIFICATE_STEP_CONFIRM) { + UI_STEP(HANDLE_CERTIFICATE_DREP_STEP_CONFIRM) { char description[50] = {0}; explicit_bzero(description, SIZEOF(description)); - switch (BODY_CTX->stageData.certificate.type) { - case CERTIFICATE_TYPE_STAKE_REGISTRATION: + switch (cert->type) { + case CERTIFICATE_DREP_REGISTRATION: #ifdef HAVE_BAGL snprintf(description, SIZEOF(description), "registration?"); #elif defined(HAVE_NBGL) @@ -478,7 +1024,7 @@ void signTx_handleCertificate_ui_runStep() #endif // HAVE_BAGL break; - case CERTIFICATE_TYPE_STAKE_DEREGISTRATION: + case CERTIFICATE_DREP_DEREGISTRATION: #ifdef HAVE_BAGL snprintf(description, SIZEOF(description), "deregistration?"); #elif defined(HAVE_NBGL) @@ -486,11 +1032,11 @@ void signTx_handleCertificate_ui_runStep() #endif // HAVE_BAGL break; - case CERTIFICATE_TYPE_STAKE_DELEGATION: + case CERTIFICATE_DREP_UPDATE: #ifdef HAVE_BAGL - snprintf(description, SIZEOF(description), "delegation?"); + snprintf(description, SIZEOF(description), "update?"); #elif defined(HAVE_NBGL) - display_confirmation("Confirm\ndelegation", "", "DELEGATION\nACCEPTED", "Delegation\nrejected", this_fn, respond_with_user_reject); + display_confirmation("Confirm\nupdate", "", "UPDATE\nACCEPTED", "Update\nrejected", this_fn, respond_with_user_reject); #endif // HAVE_BAGL break; @@ -510,20 +1056,23 @@ void signTx_handleCertificate_ui_runStep() #elif defined(HAVE_NBGL) #endif // HAVE_BAGL } - UI_STEP(HANDLE_CERTIFICATE_STEP_RESPOND) { + UI_STEP(HANDLE_CERTIFICATE_DREP_STEP_RESPOND) { respondSuccessEmptyMsg(); tx_advanceCertificatesStateIfAppropriate(); } - UI_STEP_END(HANDLE_CERTIFICATE_STEP_INVALID); + UI_STEP_END(HANDLE_CERTIFICATE_DREP_STEP_INVALID); } +#ifdef APP_FEATURE_POOL_RETIREMENT + void signTx_handleCertificatePoolRetirement_ui_runStep() { TRACE("UI step %d", ctx->ui_step); - ASSERT(BODY_CTX->stageData.certificate.type == CERTIFICATE_TYPE_STAKE_POOL_RETIREMENT); ui_callback_fn_t* this_fn = signTx_handleCertificatePoolRetirement_ui_runStep; + sign_tx_certificate_data_t* cert = &BODY_CTX->stageData.certificate; + ASSERT(cert->type == CERTIFICATE_STAKE_POOL_RETIREMENT); UI_STEP_BEGIN(ctx->ui_step, this_fn); @@ -532,13 +1081,13 @@ void signTx_handleCertificatePoolRetirement_ui_runStep() ui_displayBech32Screen( "Retire stake pool", "pool", - BODY_CTX->stageData.certificate.poolKeyHash, SIZEOF(BODY_CTX->stageData.certificate.poolKeyHash), + cert->poolCredential.keyHash, SIZEOF(cert->poolCredential.keyHash), this_fn ); #elif defined(HAVE_NBGL) set_light_confirmation(true); char encodedStr[BECH32_STRING_SIZE_MAX] = {0}; - ui_getBech32Screen(encodedStr, SIZEOF(encodedStr), "pool", BODY_CTX->stageData.certificate.poolKeyHash, SIZEOF(BODY_CTX->stageData.certificate.poolKeyHash)); + ui_getBech32Screen(encodedStr, SIZEOF(encodedStr), "pool", cert->poolCredential.keyHash, SIZEOF(cert->poolCredential.keyHash)); fill_and_display_if_required("Retire stake pool", encodedStr, this_fn, respond_with_user_reject); #endif // HAVE_BAGL } @@ -546,7 +1095,7 @@ void signTx_handleCertificatePoolRetirement_ui_runStep() #ifdef HAVE_BAGL ui_displayUint64Screen( "at the start of epoch", - BODY_CTX->stageData.certificate.epoch, + cert->epoch, this_fn ); #elif defined(HAVE_NBGL) @@ -554,7 +1103,7 @@ void signTx_handleCertificatePoolRetirement_ui_runStep() ui_getUint64Screen( line, SIZEOF(line), - BODY_CTX->stageData.certificate.epoch + cert->epoch ); fill_and_display_if_required("Start of epoch", line, this_fn, respond_with_user_reject); #endif // HAVE_BAGL @@ -579,6 +1128,8 @@ void signTx_handleCertificatePoolRetirement_ui_runStep() UI_STEP_END(HANDLE_CERTIFICATE_POOL_RETIREMENT_STEP_INVALID); } +#endif // APP_FEATURE_POOL_RETIREMENT + // ============================== WITHDRAWALS ============================== void signTx_handleWithdrawal_ui_runStep() @@ -601,12 +1152,12 @@ void signTx_handleWithdrawal_ui_runStep() UI_STEP(HANDLE_WITHDRAWAL_STEP_DISPLAY_PATH) { reward_account_t rewardAccount; switch (BODY_CTX->stageData.withdrawal.stakeCredential.type) { - case STAKE_CREDENTIAL_KEY_PATH: { + case EXT_CREDENTIAL_KEY_PATH: { rewardAccount.keyReferenceType = KEY_REFERENCE_PATH; rewardAccount.path = BODY_CTX->stageData.withdrawal.stakeCredential.keyPath; break; } - case STAKE_CREDENTIAL_KEY_HASH: { + case EXT_CREDENTIAL_KEY_HASH: { rewardAccount.keyReferenceType = KEY_REFERENCE_HASH; constructRewardAddressFromHash( ctx->commonTxData.networkId, @@ -618,7 +1169,7 @@ void signTx_handleWithdrawal_ui_runStep() ); break; } - case STAKE_CREDENTIAL_SCRIPT_HASH: { + case EXT_CREDENTIAL_SCRIPT_HASH: { rewardAccount.keyReferenceType = KEY_REFERENCE_HASH; constructRewardAddressFromHash( ctx->commonTxData.networkId, @@ -764,7 +1315,7 @@ void signTx_handleRequiredSigner_ui_runStep() UI_STEP(HANDLE_REQUIRED_SIGNERS_STEP_RESPOND) { respondSuccessEmptyMsg(); - // Advance stage to the next input + // Advance stage to the next required signer ASSERT(BODY_CTX->currentRequiredSigner < ctx->numRequiredSigners); BODY_CTX->currentRequiredSigner++; @@ -802,6 +1353,228 @@ void signTx_handleTotalCollateral_ui_runStep() UI_STEP_END(HANDLE_TOTAL_COLLATERAL_STEP_INVALID); } +// ========================= VOTING PROCEDURES =========================== + +void signTx_handleVotingProcedure_ui_runStep() +{ + TRACE("UI step %d", ctx->ui_step); + ui_callback_fn_t* this_fn = signTx_handleVotingProcedure_ui_runStep; + sign_tx_voting_procedure_t* vp = &BODY_CTX->stageData.votingProcedure; + + UI_STEP_BEGIN(ctx->ui_step, this_fn); + + UI_STEP(HANDLE_VOTING_PROCEDURE_STEP_INTRO) { + #ifdef HAVE_BAGL + ui_displayPaginatedText( + "Vote for", + "governance action", + this_fn + ); + #elif defined(HAVE_NBGL) + set_light_confirmation(true); + display_prompt("Vote for\ngovernance action", "", this_fn, respond_with_user_reject); + #endif // HAVE_BAGL + } + UI_STEP(HANDLE_VOTING_PROCEDURE_STEP_VOTER) { + switch (vp->voter.type) { + case EXT_VOTER_DREP_KEY_PATH: + case EXT_VOTER_COMMITTEE_HOT_KEY_PATH: + case EXT_VOTER_STAKE_POOL_KEY_PATH: + _displayKeyPath(this_fn, &vp->voter.keyPath, "Voter key"); + break; + case EXT_VOTER_DREP_KEY_HASH: + _displayKeyHash(this_fn, vp->voter.keyHash, "Voter key hash", "drep"); + break; + case EXT_VOTER_COMMITTEE_HOT_KEY_HASH: + _displayKeyHash(this_fn, vp->voter.keyHash, "Voter key hash", "cc_hot"); + break; + case EXT_VOTER_STAKE_POOL_KEY_HASH: + _displayKeyHash(this_fn, vp->voter.keyHash, "Voter key hash", "pool"); + break; + case EXT_VOTER_COMMITTEE_HOT_SCRIPT_HASH: + _displayScriptHash(this_fn, vp->voter.scriptHash, "Voter script hash", "cc_hot"); + break; + case EXT_VOTER_DREP_SCRIPT_HASH: + _displayScriptHash(this_fn, vp->voter.scriptHash, "Voter script hash", "drep"); + break; + default: + ASSERT(false); + break; + } + } + UI_STEP(HANDLE_VOTING_PROCEDURE_STEP_GOV_ACTION_ID_TXHASH) { + char txHashHex[1 + 2 * TX_HASH_LENGTH] = {0}; + explicit_bzero(txHashHex, SIZEOF(txHashHex)); + size_t len = encode_hex( + vp->govActionId.txHashBuffer, SIZEOF(vp->govActionId.txHashBuffer), + txHashHex, SIZEOF(txHashHex) + ); + ASSERT(len + 1 == SIZEOF(txHashHex)); + + #ifdef HAVE_BAGL + ui_displayPaginatedText( + "Action tx hash", + txHashHex, + this_fn + ); + #elif defined(HAVE_NBGL) + fill_and_display_if_required( + "Action tx hash", + txHashHex, + this_fn, + respond_with_user_reject + ); + #endif // HAVE_BAGL + } + UI_STEP(HANDLE_VOTING_PROCEDURE_STEP_GOV_ACTION_ID_INDEX) { + char indexStr[30] = {0}; + explicit_bzero(indexStr, SIZEOF(indexStr)); + snprintf(indexStr, SIZEOF(indexStr), "%d", vp->govActionId.govActionIndex); + ASSERT(indexStr[SIZEOF(indexStr) - 1] == '\0'); + + #ifdef HAVE_BAGL + ui_displayPaginatedText( + "Action tx index", + indexStr, + this_fn + ); + #elif defined(HAVE_NBGL) + fill_and_display_if_required( + "Action tx index", + indexStr, + this_fn, + respond_with_user_reject + ); + #endif // HAVE_BAGL + } + UI_STEP(HANDLE_VOTING_PROCEDURE_STEP_VOTE) { + char voteStr[30] = {0}; + explicit_bzero(voteStr, SIZEOF(voteStr)); + switch (vp->votingProcedure.vote) { + case VOTE_NO: + snprintf(voteStr, SIZEOF(voteStr), "NO"); + break; + case VOTE_YES: + snprintf(voteStr, SIZEOF(voteStr), "YES"); + break; + case VOTE_ABSTAIN: + snprintf(voteStr, SIZEOF(voteStr), "ABSTAIN"); + break; + default: + ASSERT(false); + } + ASSERT(voteStr[SIZEOF(voteStr) - 1] == '\0'); + + #ifdef HAVE_BAGL + ui_displayPaginatedText( + "Vote", + voteStr, + this_fn + ); + #elif defined(HAVE_NBGL) + fill_and_display_if_required( + "Vote", + voteStr, + this_fn, + respond_with_user_reject + ); + #endif // HAVE_BAGL + } + UI_STEP(HANDLE_VOTING_PROCEDURE_STEP_ANCHOR_NULL) { + if (vp->votingProcedure.anchor.isIncluded) { + UI_STEP_JUMP(HANDLE_VOTING_PROCEDURE_STEP_ANCHOR_URL); + } + _displayAnchorNull(this_fn); + } + UI_STEP(HANDLE_VOTING_PROCEDURE_STEP_ANCHOR_URL) { + if (!vp->votingProcedure.anchor.isIncluded) { + UI_STEP_JUMP(HANDLE_VOTING_PROCEDURE_STEP_CONFIRM); + } + _displayAnchorUrl(this_fn, &vp->votingProcedure.anchor); + } + UI_STEP(HANDLE_VOTING_PROCEDURE_STEP_ANCHOR_HASH) { + _displayAnchorHash(this_fn, &vp->votingProcedure.anchor); + } + UI_STEP(HANDLE_VOTING_PROCEDURE_STEP_CONFIRM) { + #ifdef HAVE_BAGL + ui_displayPrompt( + "Confirm", + "vote?", + this_fn, + respond_with_user_reject + ); + #elif defined(HAVE_NBGL) + display_confirmation("Confirm\nvote?", "", "VOTE\nACCEPTED", "Vote\nrejected", this_fn, respond_with_user_reject); + #endif // HAVE_BAGL + } + UI_STEP(HANDLE_VOTING_PROCEDURE_STEP_RESPOND) { + respondSuccessEmptyMsg(); + + // Advance stage to the next vote + ASSERT(BODY_CTX->currentVotingProcedure < ctx->numVotingProcedures); + BODY_CTX->currentVotingProcedure++; + + if (BODY_CTX->currentVotingProcedure == ctx->numVotingProcedures) { + tx_advanceStage(); + } + } + UI_STEP_END(HANDLE_VOTING_PROCEDURE_STEP_INVALID); +} + +// ============================== TREASURY ============================== + +void signTx_handleTreasury_ui_runStep() +{ + TRACE("UI step %d", ctx->ui_step); + ui_callback_fn_t* this_fn = signTx_handleTreasury_ui_runStep; + + TRACE_ADA_AMOUNT("treasury ", BODY_CTX->stageData.treasury); + + UI_STEP_BEGIN(ctx->ui_step, this_fn); + + UI_STEP(HANDLE_TREASURY_STEP_DISPLAY) { + #ifdef HAVE_BAGL + ui_displayAdaAmountScreen("Treasury amount", BODY_CTX->stageData.treasury, this_fn); + #elif defined(HAVE_NBGL) + char adaAmountStr[50] = {0}; + ui_getAdaAmountScreen(adaAmountStr, SIZEOF(adaAmountStr), BODY_CTX->stageData.treasury); + fill_and_display_if_required("Treasury amount", adaAmountStr, this_fn, respond_with_user_reject); + #endif // HAVE_BAGL + } + UI_STEP(HANDLE_TREASURY_STEP_RESPOND) { + respondSuccessEmptyMsg(); + tx_advanceStage(); + } + UI_STEP_END(HANDLE_TREASURY_STEP_INVALID); +} + +// ============================== DONATION ============================== + +void signTx_handleDonation_ui_runStep() +{ + TRACE("UI step %d", ctx->ui_step); + ui_callback_fn_t* this_fn = signTx_handleDonation_ui_runStep; + + TRACE_ADA_AMOUNT("donation ", BODY_CTX->stageData.donation); + + UI_STEP_BEGIN(ctx->ui_step, this_fn); + + UI_STEP(HANDLE_DONATION_STEP_DISPLAY) { + #ifdef HAVE_BAGL + ui_displayAdaAmountScreen("Donation", BODY_CTX->stageData.donation, this_fn); + #elif defined(HAVE_NBGL) + char adaAmountStr[50] = {0}; + ui_getAdaAmountScreen(adaAmountStr, SIZEOF(adaAmountStr), BODY_CTX->stageData.donation); + fill_and_display_if_required("Donation", adaAmountStr, this_fn, respond_with_user_reject); + #endif // HAVE_BAGL + } + UI_STEP(HANDLE_DONATION_STEP_RESPOND) { + respondSuccessEmptyMsg(); + tx_advanceStage(); + } + UI_STEP_END(HANDLE_DONATION_STEP_INVALID); +} + // ============================== CONFIRM ============================== void signTx_handleConfirm_ui_runStep() diff --git a/src/signTx_ui.h b/src/signTx_ui.h index 775517d0..3d87867f 100644 --- a/src/signTx_ui.h +++ b/src/signTx_ui.h @@ -67,23 +67,67 @@ void signTx_handleTtl_ui_runStep(); // ============================== CERTIFICATES ============================== enum { - HANDLE_CERTIFICATE_STEP_DISPLAY_OPERATION = 600, - HANDLE_CERTIFICATE_STEP_DISPLAY_STAKING_KEY, - HANDLE_CERTIFICATE_STEP_CONFIRM, - HANDLE_CERTIFICATE_STEP_RESPOND, - HANDLE_CERTIFICATE_STEP_INVALID, + HANDLE_CERTIFICATE_STAKING_STEP_DISPLAY_OPERATION = 600, + HANDLE_CERTIFICATE_STAKING_STEP_DISPLAY_STAKE_CRED, + HANDLE_CERTIFICATE_STAKING_STEP_DISPLAY_DEPOSIT, + HANDLE_CERTIFICATE_STAKING_STEP_CONFIRM, + HANDLE_CERTIFICATE_STAKING_STEP_RESPOND, + HANDLE_CERTIFICATE_STAKING_STEP_INVALID, }; +void signTx_handleCertificateStaking_ui_runStep(); -void signTx_handleCertificate_ui_runStep(); +enum { + HANDLE_CERTIFICATE_VOTE_DELEGATION_STEP_DISPLAY_OPERATION = 610, + HANDLE_CERTIFICATE_VOTE_DELEGATION_STEP_DISPLAY_STAKE_CRED, + HANDLE_CERTIFICATE_VOTE_DELEGATION_STEP_DISPLAY_DREP, + HANDLE_CERTIFICATE_VOTE_DELEGATION_STEP_CONFIRM, + HANDLE_CERTIFICATE_VOTE_DELEGATION_STEP_RESPOND, + HANDLE_CERTIFICATE_VOTE_DELEGATION_STEP_INVALID, +}; +void signTx_handleCertificateVoteDeleg_ui_runStep(); + +enum { + HANDLE_CERTIFICATE_COMM_AUTH_STEP_DISPLAY_OPERATION = 620, + HANDLE_CERTIFICATE_COMM_AUTH_STEP_DISPLAY_COLD_CRED, + HANDLE_CERTIFICATE_COMM_AUTH_STEP_DISPLAY_HOT_CRED, + HANDLE_CERTIFICATE_COMM_AUTH_STEP_CONFIRM, + HANDLE_CERTIFICATE_COMM_AUTH_STEP_RESPOND, + HANDLE_CERTIFICATE_COMM_AUTH_STEP_INVALID, +}; +void signTx_handleCertificateCommitteeAuth_ui_runStep(); + +enum { + HANDLE_CERTIFICATE_COMM_RESIGN_STEP_DISPLAY_OPERATION = 640, + HANDLE_CERTIFICATE_COMM_RESIGN_STEP_DISPLAY_COLD_CRED, + HANDLE_CERTIFICATE_COMM_RESIGN_STEP_DISPLAY_ANCHOR_NULL, + HANDLE_CERTIFICATE_COMM_RESIGN_STEP_DISPLAY_ANCHOR_URL, + HANDLE_CERTIFICATE_COMM_RESIGN_STEP_DISPLAY_ANCHOR_HASH, + HANDLE_CERTIFICATE_COMM_RESIGN_STEP_CONFIRM, + HANDLE_CERTIFICATE_COMM_RESIGN_STEP_RESPOND, + HANDLE_CERTIFICATE_COMM_RESIGN_STEP_INVALID, +}; +void signTx_handleCertificateCommitteeResign_ui_runStep(); + +enum { + HANDLE_CERTIFICATE_DREP_STEP_DISPLAY_OPERATION = 660, + HANDLE_CERTIFICATE_DREP_STEP_DISPLAY_CREDENTIAL, + HANDLE_CERTIFICATE_DREP_STEP_DISPLAY_DEPOSIT, + HANDLE_CERTIFICATE_DREP_STEP_DISPLAY_ANCHOR_NULL, + HANDLE_CERTIFICATE_DREP_STEP_DISPLAY_ANCHOR_URL, + HANDLE_CERTIFICATE_DREP_STEP_DISPLAY_ANCHOR_HASH, + HANDLE_CERTIFICATE_DREP_STEP_CONFIRM, + HANDLE_CERTIFICATE_DREP_STEP_RESPOND, + HANDLE_CERTIFICATE_DREP_STEP_INVALID, +}; +void signTx_handleCertificateDRep_ui_runStep(); enum { - HANDLE_CERTIFICATE_POOL_RETIREMENT_STEP_DISPLAY_OPERATION = 650, + HANDLE_CERTIFICATE_POOL_RETIREMENT_STEP_DISPLAY_OPERATION = 680, HANDLE_CERTIFICATE_POOL_RETIREMENT_STEP_DISPLAY_EPOCH, HANDLE_CERTIFICATE_POOL_RETIREMENT_STEP_CONFIRM, HANDLE_CERTIFICATE_POOL_RETIREMENT_STEP_RESPOND, HANDLE_CERTIFICATE_POOL_RETIREMENT_STEP_INVALID, }; - void signTx_handleCertificatePoolRetirement_ui_runStep(); // ============================== WITHDRAWALS ============================== @@ -131,13 +175,51 @@ void signTx_handleRequiredSigner_ui_runStep(); // ========================= TOTAL COLLATERAL =========================== enum { - HANDLE_TOTAL_COLLATERAL_STEP_DISPLAY = 400, + HANDLE_TOTAL_COLLATERAL_STEP_DISPLAY = 1700, HANDLE_TOTAL_COLLATERAL_STEP_RESPOND, HANDLE_TOTAL_COLLATERAL_STEP_INVALID, }; void signTx_handleTotalCollateral_ui_runStep(); +// ========================= VOTING PROCEDURES =========================== + +enum { + HANDLE_VOTING_PROCEDURE_STEP_INTRO = 1900, + HANDLE_VOTING_PROCEDURE_STEP_VOTER, + HANDLE_VOTING_PROCEDURE_STEP_GOV_ACTION_ID_TXHASH, + HANDLE_VOTING_PROCEDURE_STEP_GOV_ACTION_ID_INDEX, + HANDLE_VOTING_PROCEDURE_STEP_VOTE, + HANDLE_VOTING_PROCEDURE_STEP_ANCHOR_NULL, + HANDLE_VOTING_PROCEDURE_STEP_ANCHOR_URL, + HANDLE_VOTING_PROCEDURE_STEP_ANCHOR_HASH, + HANDLE_VOTING_PROCEDURE_STEP_CONFIRM, + HANDLE_VOTING_PROCEDURE_STEP_RESPOND, + HANDLE_VOTING_PROCEDURE_STEP_INVALID, +}; + +void signTx_handleVotingProcedure_ui_runStep(); + +// ============================== TREASURY ============================== + +enum { + HANDLE_TREASURY_STEP_DISPLAY = 2100, + HANDLE_TREASURY_STEP_RESPOND, + HANDLE_TREASURY_STEP_INVALID, +}; + +void signTx_handleTreasury_ui_runStep(); + +// ============================== DONATION ============================== + +enum { + HANDLE_DONATION_STEP_DISPLAY = 2200, + HANDLE_DONATION_STEP_RESPOND, + HANDLE_DONATION_STEP_INVALID, +}; + +void signTx_handleDonation_ui_runStep(); + // ============================== CONFIRM ============================== enum { diff --git a/src/state.h b/src/state.h index 8ca7f18a..59ff02a3 100644 --- a/src/state.h +++ b/src/state.h @@ -5,6 +5,7 @@ #include "getPublicKeys.h" #include "deriveAddress.h" #include "deriveNativeScriptHash.h" +#include "signMsg.h" #include "signTx.h" #include "signOpCert.h" #include "signCVote.h" @@ -14,10 +15,15 @@ typedef union { // Here should go states of all instructions ins_get_keys_context_t getKeysContext; ins_derive_address_context_t deriveAddressContext; + #ifdef APP_FEATURE_NATIVE_SCRIPT_HASH ins_derive_native_script_hash_context_t deriveNativeScriptHashContext; + #endif // APP_FEATURE_NATIVE_SCRIPT_HASH ins_sign_tx_context_t signTxContext; + #ifdef APP_FEATURE_OPCERT ins_sign_op_cert_context_t signOpCertContext; + #endif // APP_FEATURE_OPCERT ins_sign_cvote_context_t signCVoteContext; + ins_sign_msg_context_t signMsgContext; } instructionState_t; // Note(instructions are uint8_t but we have a special INS_NONE value diff --git a/src/textUtils.c b/src/textUtils.c index 1eac4c93..f2c987de 100644 --- a/src/textUtils.c +++ b/src/textUtils.c @@ -190,7 +190,7 @@ void str_traceInt64(int64_t number) #endif // DEVEL -// TODO: This is valid only for mainnet +// Note: This is valid only for mainnet static struct { uint64_t startSlotNumber; uint64_t startEpoch; @@ -237,14 +237,6 @@ size_t str_formatValidityBoundary(uint64_t slotNumber, char* out, size_t outSize return strlen(out); } -// returns length of the resulting string -size_t str_formatMetadata(const uint8_t* metadataHash, size_t metadataHashSize, char* out, size_t outSize) -{ - ASSERT(outSize < BUFFER_SIZE_PARANOIA); - - return encode_hex(metadataHash, metadataHashSize, out, outSize); -} - // check if a non-null-terminated buffer contains printable ASCII between 33 and 126 (inclusive) bool str_isPrintableAsciiWithoutSpaces(const uint8_t* buffer, size_t bufferSize) { @@ -271,7 +263,8 @@ bool str_isPrintableAsciiWithSpaces(const uint8_t* buffer, size_t bufferSize) return true; } -bool str_isAllowedDnsName(const uint8_t* buffer, size_t bufferSize) +// check if the string can be unambiguously displayed to the user +bool str_isUnambiguousAscii(const uint8_t* buffer, size_t bufferSize) { ASSERT(bufferSize < BUFFER_SIZE_PARANOIA); diff --git a/src/textUtils.h b/src/textUtils.h index 971f4807..69b5ec56 100644 --- a/src/textUtils.h +++ b/src/textUtils.h @@ -43,12 +43,10 @@ void str_traceInt64(int64_t number); size_t str_formatValidityBoundary(uint64_t slotNumber, char* out, size_t outSize); -size_t str_formatMetadata(const uint8_t* metadataHash, size_t metadataHashSize, char* out, size_t outSize); - bool str_isPrintableAsciiWithoutSpaces(const uint8_t* buffer, size_t bufferSize); bool str_isPrintableAsciiWithSpaces(const uint8_t* buffer, size_t bufferSize); -bool str_isAllowedDnsName(const uint8_t* buffer, size_t bufferSize); +bool str_isUnambiguousAscii(const uint8_t* buffer, size_t bufferSize); #ifdef DEVEL diff --git a/src/tokens_test.c b/src/tokens_test.c index 4abd3860..71ee56c4 100644 --- a/src/tokens_test.c +++ b/src/tokens_test.c @@ -104,7 +104,7 @@ void test_decimalPlaces() for (size_t i = 0; i < ARRAY_LEN(tokenTestCases); i++) { char tokenAmountStr[60]; token_group_t group; - memcpy(group.policyId, tokenTestCases[i].policyId, MINTING_POLICY_ID_SIZE); + memmove(group.policyId, tokenTestCases[i].policyId, MINTING_POLICY_ID_SIZE); str_formatTokenAmountOutput( &group, diff --git a/src/txHashBuilder.c b/src/txHashBuilder.c index 8c50e1b5..72f9d0b3 100644 --- a/src/txHashBuilder.c +++ b/src/txHashBuilder.c @@ -51,6 +51,12 @@ static void blake2b_256_append_cbor_tx_body( blake2b_256_append(hashCtx, buffer, size); } +#define BUILDER_TAG_CBOR_SET() \ + if (builder->tagCborSets) { \ + TRACE("appending set tag 258"); \ + BUILDER_APPEND_CBOR(CBOR_TYPE_TAG, CBOR_TAG_SET); \ + } + /* End of hash computation utilities. */ static void cbor_append_txInput( @@ -176,7 +182,7 @@ static void processOutputTopLevel(tx_hash_builder_t* builder, const tx_output_de static void assertCanLeaveCurrentOutput(tx_hash_builder_t* builder) { - switch (builder->outputState) { + switch (builder->outputData.outputState) { case TX_OUTPUT_INIT: case TX_OUTPUT_TOP_LEVEL_DATA: // no tokens @@ -221,6 +227,7 @@ static void assertCanLeaveCurrentOutput(tx_hash_builder_t* builder) void txHashBuilder_init( tx_hash_builder_t* builder, + bool tagCborSets, uint16_t numInputs, uint16_t numOutputs, bool includeTtl, @@ -235,9 +242,13 @@ void txHashBuilder_init( bool includeNetworkId, bool includeCollateralOutput, bool includeTotalCollateral, - uint16_t numReferenceInputs + uint16_t numReferenceInputs, + uint16_t numVotingProcedures, + bool includeTreasury, + bool includeDonation ) { + TRACE("tagCborSets = %u", tagCborSets); TRACE("numInputs = %u", numInputs); TRACE("numOutputs = %u", numOutputs); TRACE("includeTtl = %u", includeTtl); @@ -253,6 +264,11 @@ void txHashBuilder_init( TRACE("includeCollateralOutput = %u", includeCollateralOutput); TRACE("includeTotalCollateral = %u", includeTotalCollateral); TRACE("numReferenceInputs = %u", numReferenceInputs); + TRACE("numVotingProcedures = %u", numReferenceInputs); + TRACE("includeTreasury = %u", includeTreasury); + TRACE("includeDonation = %u", includeDonation); + + builder->tagCborSets = tagCborSets; blake2b_256_init(&builder->txHash); @@ -307,7 +323,16 @@ void txHashBuilder_init( builder->remainingReferenceInputs = numReferenceInputs; if (numReferenceInputs > 0) numItems++; - ASSERT((3 <= numItems) && (numItems <= 16)); + builder->remainingVotingProcedures = numVotingProcedures; + if (numVotingProcedures > 0) numItems++; + + builder->includeTreasury = includeTreasury; + if (includeTreasury) numItems++; + + builder->includeDonation = includeDonation; + if (includeDonation) numItems++; + + ASSERT((3 <= numItems) && (numItems <= 19)); _TRACE("Serializing tx body with %u items", numItems); BUILDER_APPEND_CBOR(CBOR_TYPE_MAP, numItems); @@ -332,6 +357,7 @@ void txHashBuilder_enterInputs(tx_hash_builder_t* builder) { // Enter inputs BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, TX_BODY_KEY_INPUTS); + BUILDER_TAG_CBOR_SET(); BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, builder->remainingInputs); } builder->state = TX_HASH_BUILDER_IN_INPUTS; @@ -371,7 +397,7 @@ void txHashBuilder_enterOutputs(tx_hash_builder_t* builder) BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, builder->remainingOutputs); } builder->state = TX_HASH_BUILDER_IN_OUTPUTS; - builder->outputState = TX_OUTPUT_INIT; + builder->outputData.outputState = TX_OUTPUT_INIT; } void txHashBuilder_addOutput_topLevelData( @@ -381,7 +407,7 @@ void txHashBuilder_addOutput_topLevelData( { _TRACE( "state = %d, outputState = %d, remainingOutputs = %u", - builder->state, builder->outputState, builder->remainingOutputs + builder->state, builder->outputData.outputState, builder->remainingOutputs ); ASSERT(builder->state == TX_HASH_BUILDER_IN_OUTPUTS); @@ -392,7 +418,7 @@ void txHashBuilder_addOutput_topLevelData( processOutputTopLevel(builder, output); - builder->outputState = TX_OUTPUT_TOP_LEVEL_DATA; + builder->outputData.outputState = TX_OUTPUT_TOP_LEVEL_DATA; } __noinline_due_to_stack__ @@ -404,10 +430,10 @@ static void addTokenGroup( { _TRACE( "state = %d, outputState = %d, remainingAssetGroups = %u", - builder->state, builder->outputState, builder->outputData.multiassetData.remainingAssetGroups + builder->state, builder->outputData.outputState, builder->outputData.multiassetData.remainingAssetGroups ); - switch (builder->outputState) { + switch (builder->outputData.outputState) { case TX_OUTPUT_ASSET_GROUP: // we have been adding tokens into the previous asset group ASSERT(builder->outputData.multiassetData.remainingTokens == 0); @@ -443,7 +469,7 @@ static void addTokenGroup( } } - builder->outputState = TX_OUTPUT_ASSET_GROUP; + builder->outputData.outputState = TX_OUTPUT_ASSET_GROUP; } __noinline_due_to_stack__ @@ -456,10 +482,10 @@ static void addToken( { _TRACE( "state = %d, outputState = %d, remainingTokens = %u", - builder->state, builder->outputState, builder->outputData.multiassetData.remainingTokens + builder->state, builder->outputData.outputState, builder->outputData.multiassetData.remainingTokens ); - switch (builder->outputState) { + switch (builder->outputData.outputState) { case TX_OUTPUT_ASSET_GROUP: // we have been adding tokens into an asset group break; @@ -485,7 +511,7 @@ static void addToken( } } - builder->outputState = TX_OUTPUT_ASSET_GROUP; + builder->outputData.outputState = TX_OUTPUT_ASSET_GROUP; } void txHashBuilder_addOutput_tokenGroup( @@ -518,9 +544,9 @@ void txHashBuilder_addOutput_datum( { ASSERT(builder->outputData.includeDatum); - TRACE("%d", builder->outputState); + TRACE("%d", builder->outputData.outputState); - switch (builder->outputState) { + switch (builder->outputData.outputState) { case TX_OUTPUT_TOP_LEVEL_DATA: // top level data has been added instantaneously // so we only check there are no asset groups left out @@ -537,8 +563,6 @@ void txHashBuilder_addOutput_datum( ASSERT(false); } - //TODO: MAX_DATUM_SIZE?? - // the babbage output format serializes some preliminary stuff if (builder->outputData.serializationFormat == MAP_BABBAGE) { // datum_option = [ 0, $hash32 // 1, data ] @@ -566,7 +590,7 @@ void txHashBuilder_addOutput_datum( BUILDER_APPEND_DATA(buffer, bufferSize); } // Hash is transmitted in one chunk, and datumType stage is finished - builder->outputState = TX_OUTPUT_DATUM_HASH; + builder->outputData.outputState = TX_OUTPUT_DATUM_HASH; break; case DATUM_INLINE: @@ -580,7 +604,7 @@ void txHashBuilder_addOutput_datum( BUILDER_APPEND_CBOR(CBOR_TYPE_BYTES, bufferSize); // byte chunks will be added later } - builder->outputState = TX_OUTPUT_DATUM_INLINE; + builder->outputData.outputState = TX_OUTPUT_DATUM_INLINE; break; default: @@ -593,7 +617,7 @@ void txHashBuilder_addOutput_datum_inline_chunk( const uint8_t* buffer, size_t bufferSize ) { - ASSERT(builder->outputState == TX_OUTPUT_DATUM_INLINE); + ASSERT(builder->outputData.outputState == TX_OUTPUT_DATUM_INLINE); ASSERT(bufferSize <= builder->outputData.datumData.remainingBytes); builder->outputData.datumData.remainingBytes -= bufferSize; { @@ -603,11 +627,9 @@ void txHashBuilder_addOutput_datum_inline_chunk( void txHashBuilder_addOutput_referenceScript(tx_hash_builder_t* builder, size_t scriptSize) { - // TODO: MAX_SCRIPT_SIZE?? maybe we don't need to limit it - ASSERT(builder->outputData.includeRefScript); - switch (builder->outputState) { + switch (builder->outputData.outputState) { case TX_OUTPUT_TOP_LEVEL_DATA: // top level data has been added instantaneously // so we only check there are no asset groups left out @@ -643,7 +665,7 @@ void txHashBuilder_addOutput_referenceScript(tx_hash_builder_t* builder, size_t // byte chunks will be added later } builder->outputData.referenceScriptData.remainingBytes = scriptSize; - builder->outputState = TX_OUTPUT_SCRIPT_REFERENCE_CHUNKS; + builder->outputData.outputState = TX_OUTPUT_SCRIPT_REFERENCE_CHUNKS; } void txHashBuilder_addOutput_referenceScript_dataChunk( @@ -651,7 +673,7 @@ void txHashBuilder_addOutput_referenceScript_dataChunk( const uint8_t* buffer, size_t bufferSize ) { - ASSERT(builder->outputState == TX_OUTPUT_SCRIPT_REFERENCE_CHUNKS); + ASSERT(builder->outputData.outputState == TX_OUTPUT_SCRIPT_REFERENCE_CHUNKS); { BUILDER_APPEND_DATA(buffer, bufferSize); } @@ -737,6 +759,7 @@ void txHashBuilder_enterCertificates(tx_hash_builder_t* builder) { // Enter certificates BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, TX_BODY_KEY_CERTIFICATES); + BUILDER_TAG_CBOR_SET(); BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, builder->remainingCertificates); } @@ -746,42 +769,67 @@ void txHashBuilder_enterCertificates(tx_hash_builder_t* builder) builder->state = TX_HASH_BUILDER_IN_CERTIFICATES; } -static uint32_t getStakeCredentialSource(const stake_credential_type_t stakeCredentialType) +static void _initNewCertificate(tx_hash_builder_t* builder) { - enum { - KEY = 0, - SCRIPT = 1 - }; - switch (stakeCredentialType) { - case STAKE_CREDENTIAL_KEY_PATH: - case STAKE_CREDENTIAL_KEY_HASH: - return KEY; - case STAKE_CREDENTIAL_SCRIPT_HASH: - return SCRIPT; + _TRACE("state = %d, remainingCertificates = %u", builder->state, builder->remainingCertificates); + + ASSERT(builder->state == TX_HASH_BUILDER_IN_CERTIFICATES); + ASSERT(builder->remainingCertificates > 0); + builder->remainingCertificates--; +} + +static const uint8_t* _getCredentialHashBuffer(const credential_t* credential) +{ + switch (credential->type) { + case CREDENTIAL_KEY_HASH: + return credential->keyHash; + case CREDENTIAL_SCRIPT_HASH: + return credential->scriptHash; + default: + ASSERT(false); + } +} + +static size_t _getCredentialHashSize(const credential_t* credential) +{ + switch (credential->type) { + case CREDENTIAL_KEY_HASH: + return SIZEOF(credential->keyHash); + case CREDENTIAL_SCRIPT_HASH: + return SIZEOF(credential->scriptHash); default: ASSERT(false); - break; + } +} + +static void _appendCredential( + tx_hash_builder_t* builder, + const credential_t* credential +) +{ + BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 2); + { + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, credential->type); + } + { + const size_t size = _getCredentialHashSize(credential); + BUILDER_APPEND_CBOR(CBOR_TYPE_BYTES, size); + BUILDER_APPEND_DATA(_getCredentialHashBuffer(credential), size); } } // stake key certificate registration or deregistration -void txHashBuilder_addCertificate_stakingHash( +// will be deprecated after Conway +void txHashBuilder_addCertificate_stakingOld( tx_hash_builder_t* builder, const certificate_type_t certificateType, - const stake_credential_type_t stakeCredentialType, - const uint8_t* stakingHash, size_t stakingHashSize + const credential_t* stakeCredential ) { - _TRACE("state = %d, remainingCertificates = %u", builder->state, builder->remainingCertificates); - - ASSERT(builder->state == TX_HASH_BUILDER_IN_CERTIFICATES); - ASSERT(builder->remainingCertificates > 0); - builder->remainingCertificates--; - - ASSERT((certificateType == CERTIFICATE_TYPE_STAKE_REGISTRATION) - || (certificateType == CERTIFICATE_TYPE_STAKE_DEREGISTRATION)); + _initNewCertificate(builder); - ASSERT(stakingHashSize == ADDRESS_KEY_HASH_LENGTH); + ASSERT((certificateType == CERTIFICATE_STAKE_REGISTRATION) + || (certificateType == CERTIFICATE_STAKE_DEREGISTRATION)); // Array(2)[ // Unsigned[certificateType] @@ -796,32 +844,55 @@ void txHashBuilder_addCertificate_stakingHash( BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, certificateType); } { - BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 2); - { - BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, getStakeCredentialSource(stakeCredentialType)); - } - { - BUILDER_APPEND_CBOR(CBOR_TYPE_BYTES, stakingHashSize); - BUILDER_APPEND_DATA(stakingHash, stakingHashSize); - } + _appendCredential(builder, stakeCredential); } } } -void txHashBuilder_addCertificate_delegation( +// stake key certificate registration or deregistration +// exists since Conway +void txHashBuilder_addCertificate_staking( tx_hash_builder_t* builder, - const stake_credential_type_t stakeCredentialType, - const uint8_t* stakingKeyHash, size_t stakingKeyHashSize, - const uint8_t* poolKeyHash, size_t poolKeyHashSize + const certificate_type_t certificateType, + const credential_t* stakeCredential, + uint64_t deposit ) { - _TRACE("state = %d, remainingCertificates = %u", builder->state, builder->remainingCertificates); + _initNewCertificate(builder); - ASSERT(builder->state == TX_HASH_BUILDER_IN_CERTIFICATES); - ASSERT(builder->remainingCertificates > 0); - builder->remainingCertificates--; + ASSERT((certificateType == CERTIFICATE_STAKE_REGISTRATION_CONWAY) + || (certificateType == CERTIFICATE_STAKE_DEREGISTRATION_CONWAY)); + + // Array(3)[ + // Unsigned[certificateType] + // Array(2)[ + // Unsigned[0] + // Bytes[stakingKeyHash] + // ] + // Unsigned[deposit] + // ] + { + BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 3); + { + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, certificateType); + } + { + _appendCredential(builder, stakeCredential); + } + { + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, deposit); + } + } +} + +void txHashBuilder_addCertificate_stakeDelegation( + tx_hash_builder_t* builder, + const credential_t* stakeCredential, + const uint8_t* poolKeyHash, size_t poolKeyHashSize +) +{ + _initNewCertificate(builder); - ASSERT(stakingKeyHashSize == ADDRESS_KEY_HASH_LENGTH); ASSERT(poolKeyHashSize == POOL_KEY_HASH_LENGTH); // Array(3)[ @@ -835,17 +906,10 @@ void txHashBuilder_addCertificate_delegation( { BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 3); { - BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, 2); + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, CERTIFICATE_STAKE_DELEGATION); } { - BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 2); - { - BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, getStakeCredentialSource(stakeCredentialType)); - } - { - BUILDER_APPEND_CBOR(CBOR_TYPE_BYTES, stakingKeyHashSize); - BUILDER_APPEND_DATA(stakingKeyHash, stakingKeyHashSize); - } + _appendCredential(builder, stakeCredential); } { BUILDER_APPEND_CBOR(CBOR_TYPE_BYTES, poolKeyHashSize); @@ -854,17 +918,255 @@ void txHashBuilder_addCertificate_delegation( } } +void txHashBuilder_addCertificate_voteDelegation( + tx_hash_builder_t* builder, + const credential_t* stakeCredential, + const drep_t* drep +) +{ + _initNewCertificate(builder); + + // Array(3)[ + // Unsigned[9] + // Array(2)[ + // Unsigned[0] + // Bytes[stakingKeyHash] + // ] + // Array(1 or 2)[ + // Unsigned[drep_type] + // ?Bytes[key/script hash] // optional + // ] + // ] + { + BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 3); + { + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, CERTIFICATE_VOTE_DELEGATION); + } + { + _appendCredential(builder, stakeCredential); + } + { + // DRep + switch (drep->type) { + case DREP_KEY_HASH: { + BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 2); + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, DREP_KEY_HASH); + BUILDER_APPEND_CBOR(CBOR_TYPE_BYTES, SIZEOF(drep->keyHash)); + BUILDER_APPEND_DATA(drep->keyHash, SIZEOF(drep->keyHash)); + break; + } + case DREP_SCRIPT_HASH: { + BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 2); + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, DREP_SCRIPT_HASH); + BUILDER_APPEND_CBOR(CBOR_TYPE_BYTES, SIZEOF(drep->scriptHash)); + BUILDER_APPEND_DATA(drep->scriptHash, SIZEOF(drep->scriptHash)); + break; + } + case DREP_ALWAYS_ABSTAIN: + case DREP_ALWAYS_NO_CONFIDENCE: { + BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 1); + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, drep->type); + break; + } + default: + ASSERT(false); + } + } + } +} + +void txHashBuilder_addCertificate_committeeAuthHot( + tx_hash_builder_t* builder, + const credential_t* coldCredential, + const credential_t* hotCredential +) +{ + _initNewCertificate(builder); + + // Array(3)[ + // Unsigned[14] + // Array(2)[ + // Unsigned[0 or 1] + // Bytes[stakingKeyHash] + // ] + // Array(2)[ + // Unsigned[0 or 1] + // Bytes[stakingKeyHash] + // ] + // ] + { + BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 3); + { + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, CERTIFICATE_AUTHORIZE_COMMITTEE_HOT); + } + { + _appendCredential(builder, coldCredential); + } + { + _appendCredential(builder, hotCredential); + } + } +} + +static void _appendAnchor( + tx_hash_builder_t* builder, + const anchor_t* anchor +) +{ + if (anchor->isIncluded) { + // Array(2)[ + // Tstr[url] + // Bytes[32] + // ] + BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 2); + { + BUILDER_APPEND_CBOR(CBOR_TYPE_TEXT, anchor->urlLength); + BUILDER_APPEND_DATA(anchor->url, anchor->urlLength); + } + { + BUILDER_APPEND_CBOR(CBOR_TYPE_BYTES, SIZEOF(anchor->hash)); + BUILDER_APPEND_DATA(anchor->hash, SIZEOF(anchor->hash)); + } + } else { + // Null + BUILDER_APPEND_CBOR(CBOR_TYPE_NULL, 0); + } +} + +void txHashBuilder_addCertificate_committeeResign( + tx_hash_builder_t* builder, + const credential_t* coldCredential, + const anchor_t* anchor +) +{ + _initNewCertificate(builder); + + // Array(3)[ + // Unsigned[15] + // Array(2)[ + // Unsigned[0 or 1] + // Bytes[stakingKeyHash] + // ] + // Null / ...anchor + // ] + { + BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 3); + { + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, CERTIFICATE_RESIGN_COMMITTEE_COLD); + } + { + _appendCredential(builder, coldCredential); + } + { + _appendAnchor(builder, anchor); + } + } +} + +void txHashBuilder_addCertificate_dRepRegistration( + tx_hash_builder_t* builder, + const credential_t* dRepCredential, + uint64_t deposit, + const anchor_t* anchor +) +{ + _initNewCertificate(builder); + + // Array(4)[ + // Unsigned[16] + // Array(2)[ + // Unsigned[0/1] + // Bytes[key/script hash] + // ] + // Unsigned[deposit] + // Null / ...anchor + // ] + { + BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 4); + { + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, CERTIFICATE_DREP_REGISTRATION); + } + { + _appendCredential(builder, dRepCredential); + } + { + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, deposit); + } + { + _appendAnchor(builder, anchor); + } + } +} + +void txHashBuilder_addCertificate_dRepDeregistration( + tx_hash_builder_t* builder, + const credential_t* dRepCredential, + uint64_t deposit +) +{ + _initNewCertificate(builder); + + // Array(3)[ + // Unsigned[17] + // Array(2)[ + // Unsigned[0/1] + // Bytes[key/script hash] + // ] + // Unsigned[deposit] + // ] + { + BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 3); + { + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, CERTIFICATE_DREP_DEREGISTRATION); + } + { + _appendCredential(builder, dRepCredential); + } + { + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, deposit); + } + } +} + +void txHashBuilder_addCertificate_dRepUpdate( + tx_hash_builder_t* builder, + const credential_t* dRepCredential, + const anchor_t* anchor +) +{ + _initNewCertificate(builder); + + // Array(3)[ + // Unsigned[18] + // Array(2)[ + // Unsigned[0/1] + // Bytes[key/script hash] + // ] + // Null / ...anchor + // ] + { + BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 3); + { + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, CERTIFICATE_DREP_UPDATE); + } + { + _appendCredential(builder, dRepCredential); + } + { + _appendAnchor(builder, anchor); + } + } +} + +#ifdef APP_FEATURE_POOL_RETIREMENT + void txHashBuilder_addCertificate_poolRetirement( tx_hash_builder_t* builder, const uint8_t* poolKeyHash, size_t poolKeyHashSize, uint64_t epoch ) { - _TRACE("state = %d", builder->state); - - ASSERT(builder->state == TX_HASH_BUILDER_IN_CERTIFICATES); - ASSERT(builder->remainingCertificates > 0); - builder->remainingCertificates--; + _initNewCertificate(builder); ASSERT(poolKeyHashSize == POOL_KEY_HASH_LENGTH); @@ -876,7 +1178,7 @@ void txHashBuilder_addCertificate_poolRetirement( { BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 3); { - BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, 4); + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, CERTIFICATE_STAKE_POOL_RETIREMENT); } { BUILDER_APPEND_CBOR(CBOR_TYPE_BYTES, poolKeyHashSize); @@ -886,16 +1188,16 @@ void txHashBuilder_addCertificate_poolRetirement( } } +#endif // APP_FEATURE_POOL_RETIREMENT + +#ifdef APP_FEATURE_POOL_REGISTRATION + void txHashBuilder_poolRegistrationCertificate_enter( tx_hash_builder_t* builder, uint16_t numOwners, uint16_t numRelays ) { - _TRACE("state = %d, remainingCertificates = %u", builder->state, builder->remainingCertificates); - - ASSERT(builder->state == TX_HASH_BUILDER_IN_CERTIFICATES); - ASSERT(builder->remainingCertificates > 0); - builder->remainingCertificates--; + _initNewCertificate(builder); ASSERT(builder->poolCertificateData.remainingOwners == 0); builder->poolCertificateData.remainingOwners = numOwners; @@ -908,7 +1210,7 @@ void txHashBuilder_poolRegistrationCertificate_enter( { BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 10); { - BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, 3); + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, CERTIFICATE_STAKE_POOL_REGISTRATION); } } @@ -979,7 +1281,7 @@ void txHashBuilder_poolRegistrationCertificate_financials( BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, cost); } { - BUILDER_APPEND_CBOR(CBOR_TYPE_TAG, 30); + BUILDER_APPEND_CBOR(CBOR_TYPE_TAG, CBOR_TAG_UNIT_INTERVAL); BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 2); { BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, marginNumerator); @@ -1020,6 +1322,7 @@ void txHashBuilder_addPoolRegistrationCertificate_enterOwners(tx_hash_builder_t* ASSERT(builder->state == TX_HASH_BUILDER_IN_CERTIFICATES_POOL_REWARD_ACCOUNT); { + BUILDER_TAG_CBOR_SET(); BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, builder->poolCertificateData.remainingOwners); } @@ -1280,6 +1583,8 @@ void txHashBuilder_addPoolRegistrationCertificate_addPoolMetadata_null( builder->state = TX_HASH_BUILDER_IN_CERTIFICATES; } +#endif // APP_FEATURE_POOL_REGISTRATION + static void txHashBuilder_assertCanLeaveCertificates(tx_hash_builder_t* builder) { _TRACE("state = %d, remainingCertificates = %u", builder->state, builder->remainingCertificates); @@ -1434,6 +1739,8 @@ static void txHashBuilder_assertCanLeaveValidityIntervalStart(tx_hash_builder_t* // ============================== MINT ============================== +#ifdef APP_FEATURE_TOKEN_MINTING + void txHashBuilder_enterMint(tx_hash_builder_t* builder) { _TRACE("state = %d", builder->state); @@ -1464,7 +1771,7 @@ void txHashBuilder_addMint_topLevelData( // ] BUILDER_APPEND_CBOR(CBOR_TYPE_MAP, numAssetGroups); - builder->outputState = TX_OUTPUT_TOP_LEVEL_DATA; + builder->outputData.outputState = TX_OUTPUT_TOP_LEVEL_DATA; } void txHashBuilder_addMint_tokenGroup( @@ -1490,6 +1797,8 @@ void txHashBuilder_addMint_token( amount < 0 ? CBOR_TYPE_NEGATIVE : CBOR_TYPE_UNSIGNED); } +#endif // APP_FEATURE_TOKEN_MINTING + static void txHashBuilder_assertCanLeaveMint(tx_hash_builder_t* builder) { _TRACE("state = %u, remainingMintAssetGroups = %u, remainingMintTokens = %u", @@ -1497,7 +1806,7 @@ static void txHashBuilder_assertCanLeaveMint(tx_hash_builder_t* builder) switch (builder->state) { case TX_HASH_BUILDER_IN_MINT: - ASSERT(builder->outputState == TX_OUTPUT_ASSET_GROUP); + ASSERT(builder->outputData.outputState == TX_OUTPUT_ASSET_GROUP); ASSERT(builder->outputData.multiassetData.remainingAssetGroups == 0); ASSERT(builder->outputData.multiassetData.remainingTokens == 0); break; @@ -1563,6 +1872,7 @@ void txHashBuilder_enterCollateralInputs(tx_hash_builder_t* builder) { // Enter collateral inputs BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, TX_BODY_KEY_COLLATERAL_INPUTS); + BUILDER_TAG_CBOR_SET(); BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, builder->remainingCollateralInputs); } builder->state = TX_HASH_BUILDER_IN_COLLATERAL_INPUTS; @@ -1613,6 +1923,7 @@ void txHashBuilder_enterRequiredSigners(tx_hash_builder_t* builder) { // Enter required signers BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, TX_BODY_KEY_REQUIRED_SIGNERS); + BUILDER_TAG_CBOR_SET(); BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, builder->remainingRequiredSigners); } builder->state = TX_HASH_BUILDER_IN_REQUIRED_SIGNERS; @@ -1713,7 +2024,7 @@ void txHashBuilder_addCollateralOutput( } processOutputTopLevel(builder, output); - builder->outputState = TX_OUTPUT_TOP_LEVEL_DATA; + builder->outputData.outputState = TX_OUTPUT_TOP_LEVEL_DATA; builder->state = TX_HASH_BUILDER_IN_COLLATERAL_OUTPUT; } @@ -1805,6 +2116,7 @@ void txHashBuilder_enterReferenceInputs(tx_hash_builder_t* builder) { // Enter reference inputs BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, TX_BODY_KEY_REFERENCE_INPUTS); + BUILDER_TAG_CBOR_SET(); BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, builder->remainingReferenceInputs); } builder->state = TX_HASH_BUILDER_IN_REFERENCE_INPUTS; @@ -1847,11 +2159,194 @@ static void txHashBuilder_assertCanLeaveReferenceInputs(tx_hash_builder_t* build } } +// ========================= VOTING PROCEDURES ========================== + +void txHashBuilder_enterVotingProcedures(tx_hash_builder_t* builder) +{ + _TRACE("state = %d", builder->state); + + txHashBuilder_assertCanLeaveReferenceInputs(builder); + // we don't allow an empty map for an optional item + ASSERT(builder->remainingVotingProcedures > 0); + + { + // Enter voting procedures + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, TX_BODY_KEY_VOTING_PROCEDURES); + BUILDER_APPEND_CBOR(CBOR_TYPE_MAP, builder->remainingVotingProcedures); + } + builder->state = TX_HASH_BUILDER_IN_VOTING_PROCEDURES; +} + +// assumes a single voting procedure for the voter +void txHashBuilder_addVotingProcedure( + tx_hash_builder_t* builder, + voter_t* voter, + gov_action_id_t* govActionId, + voting_procedure_t* votingProcedure +) +{ + _TRACE("state = %d, remainingVotingProcedures = %u", builder->state, builder->remainingVotingProcedures); + + ASSERT(builder->state == TX_HASH_BUILDER_IN_VOTING_PROCEDURES); + ASSERT(builder->remainingVotingProcedures > 0); + builder->remainingVotingProcedures--; + + { + // voter + // Array(2)[ + // Unsigned[voter type] + // Bytes[key or script hash], + // ] + BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 2); + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, voter->type); + + switch (voter->type) { + case VOTER_COMMITTEE_HOT_KEY_HASH: + case VOTER_DREP_KEY_HASH: + case VOTER_STAKE_POOL_KEY_HASH: { + BUILDER_APPEND_CBOR(CBOR_TYPE_BYTES, SIZEOF(voter->keyHash)); + BUILDER_APPEND_DATA(voter->keyHash, SIZEOF(voter->keyHash)); + break; + } + case VOTER_COMMITTEE_HOT_SCRIPT_HASH: + case VOTER_DREP_SCRIPT_HASH: { + BUILDER_APPEND_CBOR(CBOR_TYPE_BYTES, SIZEOF(voter->scriptHash)); + BUILDER_APPEND_DATA(voter->scriptHash, SIZEOF(voter->scriptHash)); + break; + } + default: + ASSERT(false); + } + } + { + // only 1 vote for the voter is supported + BUILDER_APPEND_CBOR(CBOR_TYPE_MAP, 1); + { + // governance action id + // Array(2)[ + // Bytes[hash], + // Unsigned[index] + // ] + BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 2); + { + size_t size = SIZEOF(govActionId->txHashBuffer); + ASSERT(size == TX_HASH_LENGTH); + BUILDER_APPEND_CBOR(CBOR_TYPE_BYTES, size); + BUILDER_APPEND_DATA(govActionId->txHashBuffer, size); + } + { + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, govActionId->govActionIndex); + } + } + { + // voting procedure + // Array(2)[ + // Unsigned[vote] + // Null / ...anchor + // ] + BUILDER_APPEND_CBOR(CBOR_TYPE_ARRAY, 2); + { + // vote + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, votingProcedure->vote); + } + { + _appendAnchor(builder, &votingProcedure->anchor); + } + } + } +} + + +static void txHashBuilder_assertCanLeaveVotingProcedures(tx_hash_builder_t* builder) +{ + _TRACE("state = %d", builder->state); + + switch (builder->state) { + case TX_HASH_BUILDER_IN_VOTING_PROCEDURES: + // make sure there are no more voting procedures to process + ASSERT(builder->remainingVotingProcedures == 0); + break; + + default: + // make sure no voting procedures are expected + ASSERT(builder->remainingVotingProcedures == 0); + // assert we can leave the previous state + txHashBuilder_assertCanLeaveReferenceInputs(builder); + break; + } +} + +// ============================== TREASURY ============================== + +void txHashBuilder_addTreasury(tx_hash_builder_t* builder, uint64_t treasury) +{ + _TRACE("state = %d", builder->state); + + txHashBuilder_assertCanLeaveVotingProcedures(builder); + + // add treasury item into the main tx body map + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, TX_BODY_KEY_TREASURY); + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, treasury); + + builder->state = TX_HASH_BUILDER_IN_TREASURY; +} + +static void txHashBuilder_assertCanLeaveTreasury(tx_hash_builder_t* builder) +{ + _TRACE("state = %d", builder->state); + + switch (builder->state) { + case TX_HASH_BUILDER_IN_TREASURY: + // treasury item was added, we can move on + break; + + default: + // make sure treasury was not expected + ASSERT(!builder->includeTreasury); + // assert we can leave the previous state + txHashBuilder_assertCanLeaveVotingProcedures(builder); + break; + } +} + +// ============================== DONATION ============================== + +void txHashBuilder_addDonation(tx_hash_builder_t* builder, uint64_t donation) +{ + _TRACE("state = %d", builder->state); + + txHashBuilder_assertCanLeaveTreasury(builder); + + // add donation item into the main tx body map + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, TX_BODY_KEY_DONATION); + BUILDER_APPEND_CBOR(CBOR_TYPE_UNSIGNED, donation); + + builder->state = TX_HASH_BUILDER_IN_DONATION; +} + +static void txHashBuilder_assertCanLeaveDonation(tx_hash_builder_t* builder) +{ + _TRACE("state = %d", builder->state); + + switch (builder->state) { + case TX_HASH_BUILDER_IN_DONATION: + // donation item was added, we can move on + break; + + default: + // make sure donation was not expected + ASSERT(!builder->includeDonation); + // assert we can leave the previous state + txHashBuilder_assertCanLeaveTreasury(builder); + break; + } +} + // ========================= FINALIZE ========================== void txHashBuilder_finalize(tx_hash_builder_t* builder, uint8_t* outBuffer, size_t outSize) { - txHashBuilder_assertCanLeaveReferenceInputs(builder); + txHashBuilder_assertCanLeaveDonation(builder); ASSERT(outSize == TX_HASH_LENGTH); { diff --git a/src/txHashBuilder.h b/src/txHashBuilder.h index e633bca6..197ec9e1 100644 --- a/src/txHashBuilder.h +++ b/src/txHashBuilder.h @@ -5,6 +5,41 @@ #include "hash.h" #include "addressUtilsShelley.h" +typedef enum { + CREDENTIAL_KEY_HASH = 0, + CREDENTIAL_SCRIPT_HASH = 1, +} credential_type_t; + +typedef struct { + credential_type_t type; + union { + uint8_t keyHash[ADDRESS_KEY_HASH_LENGTH]; + uint8_t scriptHash[SCRIPT_HASH_LENGTH]; + }; +} credential_t; + +typedef enum { + DREP_KEY_HASH = 0, + DREP_SCRIPT_HASH = 1, + DREP_ALWAYS_ABSTAIN = 2, + DREP_ALWAYS_NO_CONFIDENCE = 3, +} drep_type_t; + +typedef struct { + drep_type_t type; + union { + uint8_t keyHash[ADDRESS_KEY_HASH_LENGTH]; + uint8_t scriptHash[SCRIPT_HASH_LENGTH]; + }; +} drep_t; + +typedef struct { + bool isIncluded; + uint8_t url[ANCHOR_URL_LENGTH_MAX]; + size_t urlLength; + uint8_t hash[ANCHOR_HASH_LENGTH]; +} anchor_t; + typedef enum { ARRAY_LEGACY = 0, // legacy_transaction_output MAP_BABBAGE = 1 // post_alonzo_transaction_output @@ -45,6 +80,39 @@ typedef struct { }; } tx_output_destination_t; +typedef enum { + VOTER_COMMITTEE_HOT_KEY_HASH = 0, + VOTER_COMMITTEE_HOT_SCRIPT_HASH = 1, + VOTER_DREP_KEY_HASH = 2, + VOTER_DREP_SCRIPT_HASH = 3, + VOTER_STAKE_POOL_KEY_HASH = 4, +} voter_type_t; + +typedef struct { + voter_type_t type; + union { + uint8_t keyHash[ADDRESS_KEY_HASH_LENGTH]; + uint8_t scriptHash[SCRIPT_HASH_LENGTH]; + }; +} voter_t; + +typedef struct { + uint8_t txHashBuffer[TX_HASH_LENGTH]; + uint32_t govActionIndex; +} gov_action_id_t; + +typedef enum { + VOTE_NO = 0, + VOTE_YES = 1, + VOTE_ABSTAIN = 2, +} vote_t; + +typedef struct { + vote_t vote; + anchor_t anchor; +} voting_procedure_t; + + enum { TX_BODY_KEY_INPUTS = 0, TX_BODY_KEY_OUTPUTS = 1, @@ -63,6 +131,10 @@ enum { TX_BODY_KEY_COLLATERAL_OUTPUT = 16, TX_BODY_KEY_TOTAL_COLLATERAL = 17, TX_BODY_KEY_REFERENCE_INPUTS = 18, + TX_BODY_KEY_VOTING_PROCEDURES = 19, + // TX_BODY_KEY_PROPOSAL_PROCEDURES = 20, // not used + TX_BODY_KEY_TREASURY = 21, + TX_BODY_KEY_DONATION = 22, }; enum { @@ -108,7 +180,10 @@ typedef enum { TX_HASH_BUILDER_IN_COLLATERAL_OUTPUT = 1500, TX_HASH_BUILDER_IN_TOTAL_COLLATERAL = 1600, TX_HASH_BUILDER_IN_REFERENCE_INPUTS = 1700, - TX_HASH_BUILDER_FINISHED = 1800, + TX_HASH_BUILDER_IN_VOTING_PROCEDURES = 1800, + TX_HASH_BUILDER_IN_TREASURY = 1900, + TX_HASH_BUILDER_IN_DONATION = 2000, + TX_HASH_BUILDER_FINISHED = 2100, } tx_hash_builder_state_t; typedef enum { @@ -121,6 +196,8 @@ typedef enum { } tx_hash_builder_output_state_t; typedef struct { + bool tagCborSets; + uint16_t remainingInputs; uint16_t remainingOutputs; uint16_t remainingWithdrawals; @@ -128,6 +205,7 @@ typedef struct { uint16_t remainingCollateralInputs; uint16_t remainingRequiredSigners; uint16_t remainingReferenceInputs; + uint16_t remainingVotingProcedures; bool includeTtl; bool includeAuxData; bool includeValidityIntervalStart; @@ -136,6 +214,8 @@ typedef struct { bool includeNetworkId; bool includeCollateralOutput; bool includeTotalCollateral; + bool includeTreasury; + bool includeDonation; union { struct { @@ -144,6 +224,7 @@ typedef struct { } poolCertificateData; struct { + tx_hash_builder_output_state_t outputState; tx_output_serialization_format_t serializationFormat; bool includeDatum; bool includeRefScript; @@ -164,9 +245,8 @@ typedef struct { size_t remainingBytes; } referenceScriptData; }; - } outputData; // TODO rename to output? + } outputData; }; - tx_hash_builder_output_state_t outputState; // TODO move to outputData above tx_hash_builder_state_t state; @@ -186,6 +266,7 @@ typedef struct { void txHashBuilder_init( tx_hash_builder_t* builder, + bool tagCborSets, uint16_t numInputs, uint16_t numOutputs, bool includeTtl, @@ -200,7 +281,10 @@ void txHashBuilder_init( bool includeNetworkId, bool includeCollateralOutput, bool includeTotalCollateral, - uint16_t numReferenceInputs + uint16_t numReferenceInputs, + uint16_t numVotingProcedures, + bool includeTreasury, + bool includeDonation ); void txHashBuilder_enterInputs(tx_hash_builder_t* builder); @@ -250,26 +334,73 @@ void txHashBuilder_addTtl(tx_hash_builder_t* builder, uint64_t ttl); void txHashBuilder_enterCertificates(tx_hash_builder_t* builder); -void txHashBuilder_addCertificate_stakingHash( +void txHashBuilder_addCertificate_stakingOld( + tx_hash_builder_t* builder, + const certificate_type_t certificateType, + const credential_t* stakingCredential +); +void txHashBuilder_addCertificate_staking( tx_hash_builder_t* builder, const certificate_type_t certificateType, - const stake_credential_type_t stakeCredentialType, - const uint8_t* stakingHash, size_t stakingHashSize + const credential_t* stakeCredential, + uint64_t deposit ); -void txHashBuilder_addCertificate_delegation( +void txHashBuilder_addCertificate_stakeDelegation( tx_hash_builder_t* builder, - const stake_credential_type_t stakeCredentialType, - const uint8_t* stakingKeyHash, size_t stakingKeyHashSize, + const credential_t* stakeCredential, const uint8_t* poolKeyHash, size_t poolKeyHashSize ); +void txHashBuilder_addCertificate_voteDelegation( + tx_hash_builder_t* builder, + const credential_t* stakeCredential, + const drep_t* drep +); + +void txHashBuilder_addCertificate_committeeAuthHot( + tx_hash_builder_t* builder, + const credential_t* coldCredential, + const credential_t* hotCredential +); + +void txHashBuilder_addCertificate_committeeResign( + tx_hash_builder_t* builder, + const credential_t* coldCredential, + const anchor_t* anchor +); + +void txHashBuilder_addCertificate_dRepRegistration( + tx_hash_builder_t* builder, + const credential_t* dRepCredential, + uint64_t deposit, + const anchor_t* anchor +); + +void txHashBuilder_addCertificate_dRepDeregistration( + tx_hash_builder_t* builder, + const credential_t* dRepCredential, + uint64_t deposit +); + +void txHashBuilder_addCertificate_dRepUpdate( + tx_hash_builder_t* builder, + const credential_t* dRepCredential, + const anchor_t* anchor +); + +#ifdef APP_FEATURE_POOL_RETIREMENT + void txHashBuilder_addCertificate_poolRetirement( tx_hash_builder_t* builder, const uint8_t* poolKeyHash, size_t poolKeyHashSize, uint64_t epoch ); +#endif // APP_FEATURE_POOL_RETIREMENT + +#ifdef APP_FEATURE_POOL_REGISTRATION + void txHashBuilder_poolRegistrationCertificate_enter( tx_hash_builder_t* builder, uint16_t numOwners, uint16_t numRelays @@ -320,6 +451,8 @@ void txHashBuilder_addPoolRegistrationCertificate_addPoolMetadata_null( tx_hash_builder_t* builder ); +#endif // APP_FEATURE_POOL_REGISTRATION + void txHashBuilder_enterWithdrawals(tx_hash_builder_t* builder); void txHashBuilder_addWithdrawal( @@ -338,6 +471,8 @@ void txHashBuilder_addValidityIntervalStart( uint64_t validityIntervalStart ); +#ifdef APP_FEATURE_TOKEN_MINTING + void txHashBuilder_enterMint(tx_hash_builder_t* builder); void txHashBuilder_addMint_topLevelData( @@ -356,6 +491,8 @@ void txHashBuilder_addMint_token( int64_t amount ); +#endif // APP_FEATURE_TOKEN_MINTING + void txHashBuilder_addScriptDataHash( tx_hash_builder_t* builder, const uint8_t* scriptHashData, size_t scriptHashDataSize @@ -400,14 +537,23 @@ void txHashBuilder_addReferenceInput( const tx_input_t* refInput ); +void txHashBuilder_enterVotingProcedures(tx_hash_builder_t* builder); + +void txHashBuilder_addVotingProcedure( + tx_hash_builder_t* builder, + voter_t* voter, + gov_action_id_t* govActionId, + voting_procedure_t* votingProcedure +); + +void txHashBuilder_addTreasury(tx_hash_builder_t* builder, uint64_t treasury); + +void txHashBuilder_addDonation(tx_hash_builder_t* builder, uint64_t donation); + void txHashBuilder_finalize( tx_hash_builder_t* builder, uint8_t* outBuffer, size_t outSize ); -#ifdef DEVEL -void run_txHashBuilder_test(); -#endif // DEVEL - #endif // H_CARDANO_APP_TX_HASH_BUILDER diff --git a/src/txHashBuilder_test.c b/src/txHashBuilder_test.c deleted file mode 100644 index 1a0f0a8a..00000000 --- a/src/txHashBuilder_test.c +++ /dev/null @@ -1,565 +0,0 @@ -#ifdef DEVEL - -#include "txHashBuilder.h" -#include "cardano.h" -#include "hexUtils.h" -#include "textUtils.h" -#include "testUtils.h" - - -static const struct { - const char* txHashHex; - int index; -} inputs[] = { - { - "0B40265111D8BB3C3C608D95B3A0BF83461ACE32D79336579A1939B3AAD1C0B7", - 0 - }, - { - "1B40265111D8BB3C3C608D95B3A0BF83461ACE32D79336579A1939B3AAD1C0B7", - 1 - }, - { - "2B40265111D8BB3C3C608D95B3A0BF83461ACE32D79336579A1939B3AAD1C0B7", - 2 - }, - { - "3B40265111D8BB3C3C608D95B3A0BF83461ACE32D79336579A1939B3AAD1C0B7", - 3 - }, -}; - -static const struct { - const char* rawAddressHex; - uint64_t amount; -} outputs[] = { - { - "82D818582183581C6EE5BB111C8771CE03278E624056A12C9CFB353EB112E8ABF21FA4FEA0001A74EEE408", - 100 - }, - { - "009493315CD92EB5D8C4304E67B7E16AE36D61D34502694657811A2C8E32C728D3861E164CAB28CB8F006448139C8F1740FFB8E7AA9E5232DC", - 200 - }, - { - "409493315CD92EB5D8C4304E67B7E16AE36D61D34502694657811A2C8E87688F509738", - 300 - }, - { - "609493315CD92EB5D8C4304E67B7E16AE36D61D34502694657811A2C8E", - 400 - }, - { - "609493315CD92EB5D8C4304E67B7E16AE36D61D34502694657811A2C8E", - 500 - }, -}; - -static struct { - const char* stakingKeyHash; -} registrationCertificates[] = { - { - "32C728D3861E164CAB28CB8F006448139C8F1740FFB8E7AA9E5232DC" - }, -}; - -static struct { - const char* stakingKeyHash; -} deregistrationCertificates[] = { - { - "32C728D3861E164CAB28CB8F006448139C8F1740FFB8E7AA9E5232DC" - }, - { - "337B62CFFF6403A06A3ACBC34F8C46003C69FE79A3628CEFA9C47251" - }, -}; - -static struct { - const char* stakingKeyHash; - const char* poolKeyHash; -} delegationCertificates[] = { - { - "32C728D3861E164CAB28CB8F006448139C8F1740FFB8E7AA9E5232DC", - "0D13015CDBCDBD0889CE276192A1601F2D4D20B8392D4EF4F9A754E2" - }, - { - "32C728D3861E164CAB28CB8F006448139C8F1740FFB8E7AA9E5232DC", - "1D13015CDBCDBD0889CE276192A1601F2D4D20B8392D4EF4F9A754E2" - }, - { - "32C728D3861E164CAB28CB8F006448139C8F1740FFB8E7AA9E5232DC", - "2D13015CDBCDBD0889CE276192A1601F2D4D20B8392D4EF4F9A754E2" - }, -}; - -static struct { - const char* rewardAddress; - uint64_t amount; -} withdrawals[] = { - { - "E032C728D3861E164CAB28CB8F006448139C8F1740FFB8E7AA9E5232DC", - 666 - } -}; - -static const char* expectedHex = "7d772be6f4bebee00b469ca2793b3636594a9b16267cf23ae40236065387b3f0"; -static const char* scriptDataHash = "853cbe68f7fccdeeeb0fd7b711ea147912190c35ac52d9d94080ae82809b2f84"; - -typedef void(*addTokenGroupFun)(tx_hash_builder_t* builder, - const uint8_t* policyIdBuffer, size_t policyIdSize, - uint16_t numTokens); -typedef void(*addTokenFun)(tx_hash_builder_t* builder, - const uint8_t* assetNameBuffer, size_t assetNameSize, - uint64_t amount); - -static void addTwoMultiassetTokenGroups(tx_hash_builder_t* builder, - addTokenGroupFun tokenGroupAdder, addTokenFun tokenAdder) -{ - // we reuse the buffers to avoid wasting stack - uint8_t policy[MINTING_POLICY_ID_SIZE] = {0}; - explicit_bzero(policy, SIZEOF(policy)); - - uint8_t assetNameBuffer[ASSET_NAME_SIZE_MAX] = {0}; - explicit_bzero(assetNameBuffer, SIZEOF(assetNameBuffer)); - - policy[0] = 1; - tokenGroupAdder(builder, policy, SIZEOF(policy), 2); - - assetNameBuffer[0] = 11; - tokenAdder(builder, assetNameBuffer, SIZEOF(assetNameBuffer), 110); - assetNameBuffer[0] = 12; - tokenAdder(builder, assetNameBuffer, SIZEOF(assetNameBuffer), 120); - - policy[0] = 2; - tokenGroupAdder(builder, policy, SIZEOF(policy), 2); - - assetNameBuffer[0] = 21; - tokenAdder(builder, assetNameBuffer, SIZEOF(assetNameBuffer), 210); - assetNameBuffer[0] = 22; - // use a short buffer on purpose - tokenAdder(builder, assetNameBuffer, 1, 220); -} - -static void mintTokenHandler(tx_hash_builder_t* builder, - const uint8_t* assetNameBuffer, size_t assetNameSize, - uint64_t amount) -{ - txHashBuilder_addMint_token(builder, assetNameBuffer, assetNameSize, (int64_t)amount); -} - -static void addMultiassetMint(tx_hash_builder_t* builder) -{ - txHashBuilder_addMint_topLevelData(builder, 2); - addTwoMultiassetTokenGroups(builder, &txHashBuilder_addMint_tokenGroup, &mintTokenHandler); -} - -static void addMint(tx_hash_builder_t* builder) -{ - txHashBuilder_enterMint(builder); - - addMultiassetMint(builder); -} - -static void outputTokenHandler( - tx_hash_builder_t* builder, - const uint8_t* assetNameBuffer, size_t assetNameSize, - uint64_t amount -) -{ - txHashBuilder_addOutput_token(builder, assetNameBuffer, assetNameSize, amount); -} - - -static void addMultiassetOutput(tx_hash_builder_t* builder, tx_output_serialization_format_t const* outputFormat) -{ - uint8_t tmp[70] = {0}; - size_t tmpSize = decode_hex(PTR_PIC(outputs[1].rawAddressHex), tmp, SIZEOF(tmp)); - tx_output_description_t output = { - .format = (*outputFormat), - .destination = { - .type = DESTINATION_THIRD_PARTY, - .address = { - .buffer = tmp, - .size = tmpSize, - }, - }, - .amount = outputs[1].amount, - .numAssetGroups = 2, - .includeDatum = false, - .includeRefScript = false - }; - txHashBuilder_addOutput_topLevelData(builder, &output); - - addTwoMultiassetTokenGroups(builder, &txHashBuilder_addOutput_tokenGroup, &outputTokenHandler); -} - -static void addOutputs(tx_hash_builder_t* builder) -{ - txHashBuilder_enterOutputs(builder); - - tx_output_serialization_format_t outputFormat = ARRAY_LEGACY; - addMultiassetOutput(builder, &outputFormat); - - ITERATE(it, outputs) { - uint8_t tmp[70] = {0}; - size_t tmpSize = decode_hex(PTR_PIC(it->rawAddressHex), tmp, SIZEOF(tmp)); - tx_output_description_t output = { - .format = outputFormat, - .destination = { - .type = DESTINATION_THIRD_PARTY, - .address = { - .size = tmpSize, - .buffer = tmp, - } - }, - .amount = it->amount, - .numAssetGroups = 0, - .includeDatum = false, - .includeRefScript = false - }; - txHashBuilder_addOutput_topLevelData( - builder, - &output - ); - } - - // added for the second time to more thoroughly check the state machine - addMultiassetOutput(builder, &outputFormat); - - //New output format - outputFormat = MAP_BABBAGE; - - addMultiassetOutput(builder, &outputFormat); - - ITERATE(it, outputs) { - uint8_t tmp[70] = {0}; - size_t tmpSize = decode_hex(PTR_PIC(it->rawAddressHex), tmp, SIZEOF(tmp)); - - tx_output_description_t output = { - .format = outputFormat, - .destination = { - .type = DESTINATION_THIRD_PARTY, - .address = { - .buffer = tmp, - .size = tmpSize, - }, - }, - .amount = it->amount, - .numAssetGroups = 0, - .includeDatum = false, - .includeRefScript = false - }; - txHashBuilder_addOutput_topLevelData(builder, &output); - } - - // added for the second time to more thoroughly check the state machine - addMultiassetOutput(builder, &outputFormat); -} - -static void collateralOutputTokenHandler(tx_hash_builder_t* builder, - const uint8_t* assetNameBuffer, size_t assetNameSize, - uint64_t amount) -{ - txHashBuilder_addCollateralOutput_token(builder, assetNameBuffer, assetNameSize, (int64_t)amount); -} -//TODO: more generic function to handle similar? or just merge to addCollRet? -static void addMultiassetCollateralOutput(tx_hash_builder_t* builder, tx_output_serialization_format_t outputFormat) -{ - uint8_t tmp[70] = {0}; - size_t tmpSize = decode_hex(PTR_PIC(outputs[1].rawAddressHex), tmp, SIZEOF(tmp)); - tx_output_description_t output = { - .format = outputFormat, - .destination = { - .type = DESTINATION_THIRD_PARTY, - .address = { - .buffer = tmp, - .size = tmpSize, - }, - }, - .amount = outputs[1].amount, - .numAssetGroups = 2, - .includeDatum = false, - .includeRefScript = false - }; - txHashBuilder_addCollateralOutput(builder, &output); - - addTwoMultiassetTokenGroups(builder, &txHashBuilder_addCollateralOutput_tokenGroup, &collateralOutputTokenHandler); -} - -static void addCollateralOutput(tx_hash_builder_t* builder) -{ - addMultiassetCollateralOutput(builder, MAP_BABBAGE); -} - -static void addPoolRegistrationCertificate(tx_hash_builder_t* builder) -{ - uint8_t poolKeyHash[POOL_KEY_HASH_LENGTH] = {0}; - uint8_t vrfKeyHash[VRF_KEY_HASH_LENGTH] = {0}; - uint64_t pledge = 500000000; - uint64_t cost = 340000000; - uint64_t marginNumerator = 1; - uint64_t marginDenominator = 1; - uint8_t rewardAccount[REWARD_ACCOUNT_SIZE] = {0}; - - size_t poolKeyHashSize = decode_hex( - "5631EDE662CFB10FD5FD69B4667101DD289568E12BCF5F64D1C406FC", - poolKeyHash, SIZEOF(poolKeyHash) - ); - ASSERT(poolKeyHashSize == SIZEOF(poolKeyHash)); - - size_t vrfKeyHashSize = decode_hex( - "198890AD6C92E80FBDAB554DDA02DA9FB49D001BBD96181F3E07F7A6AB0D0640", - vrfKeyHash, SIZEOF(vrfKeyHash) - ); - ASSERT(vrfKeyHashSize == SIZEOF(vrfKeyHash)); - - size_t rewardAccountSize = decode_hex( - "E03A7F09D3DF4CF66A7399C2B05BFA234D5A29560C311FC5DB4C490711", - rewardAccount, SIZEOF(rewardAccount) - ); - ASSERT(rewardAccountSize == SIZEOF(rewardAccount)); - - txHashBuilder_poolRegistrationCertificate_enter(builder, 1, 3); - txHashBuilder_poolRegistrationCertificate_poolKeyHash(builder, poolKeyHash, SIZEOF(poolKeyHash)); - txHashBuilder_poolRegistrationCertificate_vrfKeyHash(builder, vrfKeyHash, SIZEOF(vrfKeyHash)); - txHashBuilder_poolRegistrationCertificate_financials(builder, pledge, cost, marginNumerator, marginDenominator); - txHashBuilder_poolRegistrationCertificate_rewardAccount(builder, rewardAccount, SIZEOF(rewardAccount)); - - txHashBuilder_addPoolRegistrationCertificate_enterOwners(builder); - - uint8_t owner1[28] = {0}; - size_t owner1Size = decode_hex("3A7F09D3DF4CF66A7399C2B05BFA234D5A29560C311FC5DB4C490711", owner1, SIZEOF(owner1)); - ASSERT(owner1Size == SIZEOF(owner1)); - - txHashBuilder_addPoolRegistrationCertificate_addOwner(builder, owner1, owner1Size); - - txHashBuilder_addPoolRegistrationCertificate_enterRelays(builder); - - { - pool_relay_t relay0; - relay0.format = 0; - relay0.port.isNull = false; - relay0.port.number = 1234; - relay0.ipv4.isNull = false; - decode_hex("08080808", relay0.ipv4.ip, IPV4_SIZE); - relay0.ipv6.isNull = true; - txHashBuilder_addPoolRegistrationCertificate_addRelay(builder, &relay0); - } - { - pool_relay_t relay1; - relay1.format = 1; - relay1.port.isNull = true; - // a valid DNS AAAA record, since dnsName actually is supposed to be an A or AAAA record - const char* dnsName = "AAAA 2400:cb00:2049:1::a29f:1804"; - relay1.dnsNameSize = str_textToBuffer(dnsName, relay1.dnsName, SIZEOF(relay1.dnsName)); - txHashBuilder_addPoolRegistrationCertificate_addRelay(builder, &relay1); - } - { - pool_relay_t relay2; - relay2.format = 2; - // dnsName is not a valid DNS SRV record, but we don't validate it - const char* dnsName = "AAAA 2400:cb00:2049:1::a29f:1804"; - relay2.dnsNameSize = str_textToBuffer(dnsName, relay2.dnsName, SIZEOF(relay2.dnsName)); - txHashBuilder_addPoolRegistrationCertificate_addRelay(builder, &relay2); - } - - uint8_t metadataHash[32] = {0}; - size_t metadataHashSize = decode_hex("914C57C1F12BBF4A82B12D977D4F274674856A11ED4B9B95BD70F5D41C5064A6", metadataHash, SIZEOF(metadataHash)); - ASSERT(metadataHashSize == SIZEOF(metadataHash)); - - const char* metadataUrl = "https://teststakepool.com"; - uint8_t urlBuffer[DNS_NAME_SIZE_MAX] = {0}; - size_t urlSize = str_textToBuffer(metadataUrl, urlBuffer, SIZEOF(urlBuffer)); - ASSERT(urlSize <= DNS_NAME_SIZE_MAX); - - txHashBuilder_addPoolRegistrationCertificate_addPoolMetadata(builder, urlBuffer, urlSize, metadataHash, metadataHashSize); -} - -static void addPoolRetirementCertificate(tx_hash_builder_t* builder) -{ - uint8_t poolKeyHash[POOL_KEY_HASH_LENGTH] = {0}; - uint64_t epoch = 1000; - - size_t poolKeyHashSize = decode_hex( - "5631EDE662CFB10FD5FD69B4667101DD289568E12BCF5F64D1C406FC", - poolKeyHash, SIZEOF(poolKeyHash) - ); - ASSERT(poolKeyHashSize == SIZEOF(poolKeyHash)); - - txHashBuilder_addCertificate_poolRetirement( - builder, - poolKeyHash, SIZEOF(poolKeyHash), - epoch - ); -} - -static void addCertificates(tx_hash_builder_t* builder) -{ - txHashBuilder_enterCertificates(builder); - - ITERATE(it, registrationCertificates) { - uint8_t tmp[70] = {0}; - size_t tmpSize = decode_hex(PTR_PIC(it->stakingKeyHash), tmp, SIZEOF(tmp)); - txHashBuilder_addCertificate_stakingHash( - builder, - CERTIFICATE_TYPE_STAKE_REGISTRATION, - STAKE_CREDENTIAL_KEY_PATH, - tmp, tmpSize - ); - } - - ITERATE(it, deregistrationCertificates) { - uint8_t tmp[70] = {0}; - size_t tmpSize = decode_hex(PTR_PIC(it->stakingKeyHash), tmp, SIZEOF(tmp)); - txHashBuilder_addCertificate_stakingHash( - builder, - CERTIFICATE_TYPE_STAKE_DEREGISTRATION, - STAKE_CREDENTIAL_KEY_PATH, - tmp, tmpSize - ); - } - - addPoolRegistrationCertificate(builder); - - addPoolRetirementCertificate(builder); - - ITERATE(it, delegationCertificates) { - uint8_t tmp_credential[70] = {0}; - size_t tmpSize_credential = decode_hex( - PTR_PIC(it->stakingKeyHash), - tmp_credential, SIZEOF(tmp_credential) - ); - uint8_t tmp_pool[70] = {0}; - size_t tmpSize_pool = decode_hex(PTR_PIC(it->poolKeyHash), tmp_pool, SIZEOF(tmp_pool)); - txHashBuilder_addCertificate_delegation( - builder, STAKE_CREDENTIAL_KEY_PATH, - tmp_credential, tmpSize_credential, - tmp_pool, tmpSize_pool - ); - } -} - -void run_txHashBuilder_test() -{ - PRINTF("txHashBuilder test\n"); - tx_hash_builder_t builder; - - const size_t numCertificates = ARRAY_LEN(registrationCertificates) + - ARRAY_LEN(deregistrationCertificates) + - ARRAY_LEN(delegationCertificates) + - 1 + // stake pool retirement certificate - 1; // stake pool registration certificate - - txHashBuilder_init(&builder, - ARRAY_LEN(inputs), - (ARRAY_LEN(outputs) + 2) * 2, // +2 for multiasset outputs *2 for new format - true, // ttl - numCertificates, ARRAY_LEN(withdrawals), - true, // metadata - true, // validity interval start - true, // mint - true, // script hash data - 1, // collateral inputs - 1, // required - true, // network id - true, // collateral return output, - true, // total collateral, - ARRAY_LEN(inputs) // reference inputs - ); - - // 0 : set ; inputs - txHashBuilder_enterInputs(&builder); - ITERATE(it, inputs) { - uint8_t tmp[TX_HASH_LENGTH] = {0}; - size_t tmpSize = decode_hex(PTR_PIC(it->txHashHex), tmp, SIZEOF(tmp)); - tx_input_t input; - memmove(input.txHashBuffer, tmp, tmpSize); - input.index = it->index; - txHashBuilder_addInput(&builder, &input); - } - // 1 : [* transaction_output] - addOutputs(&builder); - // 2 : coin ; fee - txHashBuilder_addFee(&builder, 42); - // ? 3 : uint ; time to live - txHashBuilder_addTtl(&builder, 235000); - // ? 4 : [* certificate] - addCertificates(&builder); - // ? 5 : withdrawals - txHashBuilder_enterWithdrawals(&builder); - - ITERATE(it, withdrawals) { - uint8_t tmp[70] = {0}; - size_t tmpSize = decode_hex(PTR_PIC(it->rewardAddress), tmp, SIZEOF(tmp)); - txHashBuilder_addWithdrawal( - &builder, - tmp, tmpSize, - it->amount - ); - } - // ? 7 : auxiliary_data_hash - { - /* cspell:disable-next-line */ - const char auxDataHashHex[] = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; - uint8_t tmp[AUX_DATA_HASH_LENGTH] = {0}; - size_t tmpSize = decode_hex(auxDataHashHex, tmp, SIZEOF(tmp)); - ASSERT(tmpSize == AUX_DATA_HASH_LENGTH); - txHashBuilder_addAuxData(&builder, tmp, tmpSize); - } - // ? 8 : uint ; validity interval start - txHashBuilder_addValidityIntervalStart(&builder, 33); - // ? 9 : mint - addMint(&builder); - // ? 11 : script_data_hash - { - uint8_t scriptHashData[SCRIPT_DATA_HASH_LENGTH] = {0}; - size_t hashSize = decode_hex(scriptDataHash, scriptHashData, SIZEOF(scriptHashData)); - txHashBuilder_addScriptDataHash(&builder, scriptHashData, hashSize); - } - // ? 13 : set ; collateral inputs - { - txHashBuilder_enterCollateralInputs(&builder); - uint8_t tmp[TX_HASH_LENGTH] = {0}; - size_t tmpSize = decode_hex(PTR_PIC(inputs[0].txHashHex), tmp, SIZEOF(tmp)); - tx_input_t input; - memmove(input.txHashBuffer, tmp, tmpSize); - input.index = inputs[0].index; - txHashBuilder_addCollateralInput(&builder, &input); - } - // ? 14 : required_signers - { - uint8_t keyHash[ADDRESS_KEY_HASH_LENGTH] = {0}; - txHashBuilder_enterRequiredSigners(&builder); - txHashBuilder_addRequiredSigner(&builder, keyHash, SIZEOF(keyHash)); - } - // ? 15 : network_id - txHashBuilder_addNetworkId(&builder, 0); - // ? 16 : transaction_output ; collateral return - addCollateralOutput(&builder); - // ? 17 : coin ; total collateral - txHashBuilder_addTotalCollateral(&builder, 10); - // ? 18 : set ; reference inputs - txHashBuilder_enterReferenceInputs(&builder); - - ITERATE(it, inputs) { - uint8_t tmp[TX_HASH_LENGTH] = {0}; - size_t tmpSize = decode_hex(PTR_PIC(it->txHashHex), tmp, SIZEOF(tmp)); - tx_input_t input; - memmove(input.txHashBuffer, tmp, tmpSize); - input.index = it->index; - txHashBuilder_addReferenceInput(&builder, &input); - } - - uint8_t result[TX_HASH_LENGTH] = {0}; - txHashBuilder_finalize(&builder, result, SIZEOF(result)); - - uint8_t expected[TX_HASH_LENGTH] = {0}; - decode_hex(expectedHex, expected, SIZEOF(expected)); - - PRINTF("result\n"); - PRINTF("%.*h\n", 32, result); - - EXPECT_EQ_BYTES(result, expected, 32); -} - -#endif // DEVEL diff --git a/src/uiHelpers_nanos.c b/src/uiHelpers_nanos.c index 2472f138..93b6bc27 100644 --- a/src/uiHelpers_nanos.c +++ b/src/uiHelpers_nanos.c @@ -132,7 +132,7 @@ static const bagl_element_t ui_paginatedText[] = { UI_ICON_LEFT(ID_ICON_GO_LEFT, BAGL_GLYPH_ICON_LEFT), UI_ICON_RIGHT(ID_ICON_GO_RIGHT, BAGL_GLYPH_ICON_RIGHT), - // TODO(ppershing): what are the following magical numbers? + // Note(ppershing): what are the following magical numbers? UI_TEXT(ID_UNSPECIFIED, 0, 12, 128, &displayState.paginatedText.header), UI_TEXT(ID_UNSPECIFIED, 0, 26, 128, &displayState.paginatedText.currentText), diff --git a/src/uiScreens_bagl.c b/src/uiScreens_bagl.c index 1e158a0c..83bfb3ee 100644 --- a/src/uiScreens_bagl.c +++ b/src/uiScreens_bagl.c @@ -364,41 +364,41 @@ void ui_displayRewardAccountScreen( ); } -void ui_displaySpendingInfoScreen( +void ui_displayPaymentInfoScreen( const addressParams_t* addressParams, ui_callback_fn_t callback ) { - switch (determineSpendingChoice(addressParams->type)) { + switch (determinePaymentChoice(addressParams->type)) { - case SPENDING_PATH: { + case PAYMENT_PATH: { ui_displayPathScreen( - "Derivation path", - &addressParams->spendingKeyPath, + "Payment key path", + &addressParams->paymentKeyPath, callback ); return; } - case SPENDING_SCRIPT_HASH: { + case PAYMENT_SCRIPT_HASH: { ui_displayBech32Screen( - "Spending script hash", + "Payment script hash", "script", - addressParams->spendingScriptHash, - SIZEOF(addressParams->spendingScriptHash), + addressParams->paymentScriptHash, + SIZEOF(addressParams->paymentScriptHash), callback ); return; } default: { - // includes SPENDING_NONE + // includes PAYMENT_NONE ASSERT(false); } } } -static const char STAKING_HEADING_PATH[] = "Staking path"; +static const char STAKING_HEADING_PATH[] = "Stake key path"; static const char STAKING_HEADING_KEY_HASH[] = "Stake key hash"; static const char STAKING_HEADING_SCRIPT_HASH[] = "Stake script hash"; static const char STAKING_HEADING_POINTER[] = "Stake key pointer"; @@ -653,11 +653,23 @@ void ui_displayNetworkParamsScreen( STATIC_ASSERT(!IS_SIGNED(networkId), "signed type for %u"); STATIC_ASSERT(sizeof(protocolMagic) <= sizeof(unsigned), "oversized type for %u"); STATIC_ASSERT(!IS_SIGNED(protocolMagic), "signed type for %u"); + + #ifdef APP_FEATURE_BYRON_PROTOCOL_MAGIC_CHECK snprintf( networkParams, SIZEOF(networkParams), "network id %u / protocol magic %u", networkId, protocolMagic ); + #else + // if the protocol magic check is not enabled, + // displaying the protocol magic might be misleading, + // so we must not show it + snprintf( + networkParams, SIZEOF(networkParams), + "network id %u", + networkId + ); + #endif // APP_FEATURE_BYRON_PROTOCOL_MAGIC_CHECK ASSERT(strlen(networkParams) + 1 < SIZEOF(networkParams)); ui_displayPaginatedText( @@ -702,6 +714,8 @@ void ui_displayPoolMarginScreen( ); } +#ifdef APP_FEATURE_POOL_REGISTRATION + void ui_displayPoolOwnerScreen( const pool_owner_t* owner, uint32_t ownerIndex, @@ -859,6 +873,8 @@ void ui_displayIpPortScreen( ); } +#endif // APP_FEATURE_POOL_REGISTRATION + void ui_displayInputScreen( const sign_tx_transaction_input_t* input, ui_callback_fn_t callback) diff --git a/src/uiScreens_bagl.h b/src/uiScreens_bagl.h index f4073130..f0d78339 100644 --- a/src/uiScreens_bagl.h +++ b/src/uiScreens_bagl.h @@ -57,7 +57,7 @@ void ui_displayRewardAccountScreen( ); __noinline_due_to_stack__ -void ui_displaySpendingInfoScreen( +void ui_displayPaymentInfoScreen( const addressParams_t* addressParams, ui_callback_fn_t callback ); @@ -134,6 +134,8 @@ void ui_displayPoolMarginScreen( ui_callback_fn_t callback ); +#ifdef APP_FEATURE_POOL_REGISTRATION + __noinline_due_to_stack__ void ui_displayPoolOwnerScreen( const pool_owner_t* owner, @@ -167,6 +169,8 @@ void ui_displayIpPortScreen( ui_callback_fn_t callback ); +#endif // APP_FEATURE_POOL_REGISTRATION + __noinline_due_to_stack__ void ui_displayInputScreen( const sign_tx_transaction_input_t* input, diff --git a/src/uiScreens_nbgl.c b/src/uiScreens_nbgl.c index db1b7bf9..86a1034a 100644 --- a/src/uiScreens_nbgl.c +++ b/src/uiScreens_nbgl.c @@ -333,7 +333,7 @@ void ui_getRewardAccountScreen( ); } -void ui_getSpendingInfoScreen( +void ui_getPaymentInfoScreen( char* line1, const size_t line1Size, char* line2, @@ -341,32 +341,32 @@ void ui_getSpendingInfoScreen( const addressParams_t* addressParams ) { - switch (determineSpendingChoice(addressParams->type)) { + switch (determinePaymentChoice(addressParams->type)) { - case SPENDING_PATH: { - snprintf(line1, line1Size, "Derivation path"); + case PAYMENT_PATH: { + snprintf(line1, line1Size, "Payment key path"); ui_getPathScreen( line2, line2Size, - &addressParams->spendingKeyPath + &addressParams->paymentKeyPath ); return; } - case SPENDING_SCRIPT_HASH: { - snprintf(line1, line1Size, "Spending script hash"); + case PAYMENT_SCRIPT_HASH: { + snprintf(line1, line1Size, "Payment script hash"); ui_getBech32Screen( line2, line2Size, "script", - addressParams->spendingScriptHash, - SIZEOF(addressParams->spendingScriptHash) + addressParams->paymentScriptHash, + SIZEOF(addressParams->paymentScriptHash) ); return; } default: { - // includes SPENDING_NONE + // includes PAYMENT_NONE ASSERT(false); } } @@ -620,6 +620,8 @@ void ui_getPoolMarginScreen( TRACE("%s", line1); } +#ifdef APP_FEATURE_POOL_REGISTRATION + void ui_getPoolOwnerScreen( char* firstLine, const size_t firstLineSize, @@ -748,6 +750,8 @@ void ui_getIpPortScreen( ASSERT(strlen(portStr) + 1 < portStrSize); } +#endif // APP_FEATURE_POOL_REGISTRATION + void ui_getInputScreen( char* line, const size_t lineSize, diff --git a/src/uiScreens_nbgl.h b/src/uiScreens_nbgl.h index b7424145..664d83fb 100644 --- a/src/uiScreens_nbgl.h +++ b/src/uiScreens_nbgl.h @@ -74,7 +74,7 @@ void ui_getRewardAccountScreen( ); __noinline_due_to_stack__ -void ui_getSpendingInfoScreen( +void ui_getPaymentInfoScreen( char* line1, const size_t line1Size, char* line2, @@ -165,6 +165,8 @@ void ui_getPoolMarginScreen( uint64_t marginNumerator, uint64_t marginDenominator ); +#ifdef APP_FEATURE_POOL_REGISTRATION + __noinline_due_to_stack__ void ui_getPoolOwnerScreen( char* firstLine, @@ -200,6 +202,8 @@ void ui_getIpPortScreen( const ipport_t* port ); +#endif // APP_FEATURE_POOL_REGISTRATION + __noinline_due_to_stack__ void ui_getInputScreen( char* line, diff --git a/src/ui_nbgl.c b/src/ui_nbgl.c index 08adf0cc..bcb20563 100644 --- a/src/ui_nbgl.c +++ b/src/ui_nbgl.c @@ -214,6 +214,9 @@ static void _display_light_confirmation(void) { TRACE("_light_confirmation"); + // TODO "reject transaction" is used as a constant string in several places + // and then applied also for signing messages and operational certificates. + // We should change this as part of the upcoming larger UX rewrite. nbgl_useCaseChoice(&C_cardano_64, uiContext.pageText[0], "", "Confirm", "Reject Transaction", light_confirm_callback); diff --git a/src/utils.h b/src/utils.h index 7d54ec09..beb5f1f9 100644 --- a/src/utils.h +++ b/src/utils.h @@ -48,7 +48,6 @@ } while(0) // Helper functions for ranges -// TODO(ppershing): make more type safe? #define BEGIN(buf) buf // Note: SIZEOF would not work if buf is not uin8_t* #define END(buf) (buf + ARRAY_LEN(buf)) diff --git a/tokenRegistry/tokenList.json b/tokenRegistry/tokenList.json index 1fd16b5a..0d95ba2c 100644 --- a/tokenRegistry/tokenList.json +++ b/tokenRegistry/tokenList.json @@ -97,7 +97,7 @@ }, { "assetSubject": "7f376e3d1cf52e6c4350a1a91c8f8d0f0b63baedd443999ebe8fe57a424f52475a", - "name": "BORGZ", + "name": "Battle Borgz", "ticker": "BORGZ", "decimals": 0 }, @@ -275,12 +275,6 @@ "ticker": "RAT", "decimals": 6 }, - { - "assetSubject": "8fef2d34078659493ce161a6c7fba4b56afefa8535296a5743f6958741414441", - "name": "Aada DAO Token", - "ticker": "AADA", - "decimals": 6 - }, { "assetSubject": "0c78f619e54a5d00e143f66181a2c500d0c394b38a10e86cd1a23c5f41444158", "name": "ADAX", @@ -395,12 +389,6 @@ "ticker": "DOEX", "decimals": 0 }, - { - "assetSubject": "25f0fc240e91bd95dcdaebd2ba7713fc5168ac77234a3d79449fc20c534f4349455459", - "name": "SOCIETY", - "ticker": "SOCIETY", - "decimals": 6 - }, { "assetSubject": "c9f955eeffa84e42363b4992281d32dd2f9239153d6c66420a9acc154c4f47", "name": "Life Log Token", @@ -503,12 +491,6 @@ "ticker": "DGADA", "decimals": 0 }, - { - "assetSubject": "133fac9e153194428eb0919be39837b42b9e977fc7298f3ff1b76ef95055444759", - "name": "$PUDGY Token", - "ticker": "PUDGY", - "decimals": 0 - }, { "assetSubject": "f555c46bad0731d080c9381d7fff6f82839946a66bd070d185e1ea2f42444f4745", "name": "BlackDoge", @@ -610,5 +592,209 @@ "name": "iUSD", "ticker": "iUSD", "decimals": 6 + }, + { + "assetSubject": "279c909f348e533da5808898f87f9a14bb2c3dfbbacccd631d927a3f534e454b", + "name": "Snek", + "ticker": "SNEK", + "decimals": 0 + }, + { + "assetSubject": "95a427e384527065f2f8946f5e86320d0117839a5e98ea2c0b55fb0048554e54", + "name": "HUNT", + "ticker": "HUNT", + "decimals": 6 + }, + { + "assetSubject": "5d16cc1a177b5d9ba9cfa9793b07e60f1fb70fea1f8aef064415d114494147", + "name": "IAGON", + "ticker": "IAG", + "decimals": 6 + }, + { + "assetSubject": "749d101a169dbd15bcd488d3fca942665b7bc6f69512235bd301c5864652454e", + "name": "FREN", + "ticker": "FREN", + "decimals": 0 + }, + { + "assetSubject": "8fef2d34078659493ce161a6c7fba4b56afefa8535296a5743f6958741414441", + "name": "Lenfi DAO token", + "ticker": "LENFI", + "decimals": 6 + }, + { + "assetSubject": "f66d78b4a3cb3d37afa0ec36461e51ecbde00f26c8f0a68f94b6988069425443", + "name": "iBTC", + "ticker": "iBTC", + "decimals": 6 + }, + { + "assetSubject": "51a5e236c4de3af2b8020442e2a26f454fda3b04cb621c1294a0ef34424f4f4b", + "name": "BOOK", + "ticker": "BOOK", + "decimals": 6 + }, + { + "assetSubject": "edfd7a1d77bcb8b884c474bdc92a16002d1fb720e454fa6e993444794e5458", + "name": "NuNet Utility Token", + "ticker": "NTX", + "decimals": 6 + }, + { + "assetSubject": "a3931691f5c4e65d01c429e473d0dd24c51afdb6daf88e632a6c1e516f7263666178746f6b656e", + "name": "Orcfax Token", + "ticker": "FACT", + "decimals": 6 + }, + { + "assetSubject": "7914fae20eb2903ed6fd5021a415c1bd2626b64a2d86a304cb40ff5e4c494649", + "name": "LinkageFinanceToken", + "ticker": "LIFI", + "decimals": 6 + }, + { + "assetSubject": "e52964af4fffdb54504859875b1827b60ba679074996156461143dc14f5054494d", + "name": "OPTIM", + "ticker": "OPTIM", + "decimals": 6 + }, + { + "assetSubject": "1ddcb9c9de95361565392c5bdff64767492d61a96166cb16094e54be4f5054", + "name": "OptionFlowToken", + "ticker": "OPT", + "decimals": 6 + }, + { + "assetSubject": "2d587111358801114f04df83dc0015de0a740b462b75cce5170fc935434749", + "name": "CardanoGPT", + "ticker": "CGI", + "decimals": 6 + }, + { + "assetSubject": "8db269c3ec630e06ae29f74bc39edd1f87c819f1056206e879a1cd615368656e4d6963726f555344", + "name": "Shen USD", + "ticker": "SHEN", + "decimals": 6 + }, + { + "assetSubject": "cc8d1b026353022abbfcc2e1e71159f9e308d9c6e905ac1db24c7fb650617269627573", + "name": "Paribus", + "ticker": "PBX", + "decimals": 6 + }, + { + "assetSubject": "f66d78b4a3cb3d37afa0ec36461e51ecbde00f26c8f0a68f94b6988069455448", + "name": "iETH", + "ticker": "iETH", + "decimals": 6 + }, + { + "assetSubject": "b166a1047a8cd275bf0a50201ece3d4f0b4da300094ffcc668a6f4084b49545550", + "name": "KITUP", + "ticker": "KITUP", + "decimals": 0 + }, + { + "assetSubject": "dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb0014df1047454e53", + "name": "Genius Yield Token", + "ticker": "GENS", + "decimals": 6 + }, + { + "assetSubject": "b201928d6bdb21c2e39205a92e226653d6002b949eaaacde3d986c2f524f4e", + "name": "Ron", + "ticker": "RON", + "decimals": 0 + }, + { + "assetSubject": "ac015c38917f306a84748c2d646bed90bdd64421c592163e60702d735453555255", + "name": "TOOL", + "ticker": "TOOL", + "decimals": 0 + }, + { + "assetSubject": "681b5d0383ac3b457e1bcc453223c90ccef26b234328f45fa10fd2764a5047", + "name": "JPG TOKEN", + "ticker": "JPG", + "decimals": 6 + }, + { + "assetSubject": "25f0fc240e91bd95dcdaebd2ba7713fc5168ac77234a3d79449fc20c534f4349455459", + "name": "Society", + "ticker": "SOC", + "decimals": 6 + }, + { + "assetSubject": "caff93803e51c7b97bf79146790bfa3feb0d0b856ef16113b391b9975649504552", + "name": "VIPER", + "ticker": "VIPER", + "decimals": 0 + }, + { + "assetSubject": "9abf0afd2f236a19f2842d502d0450cbcd9c79f123a9708f96fd9b96454e4353", + "name": "ENCOINS utility token", + "ticker": "ENCS", + "decimals": 6 + }, + { + "assetSubject": "2b28c81dbba6d67e4b5a997c6be1212cba9d60d33f82444ab8b1f21842414e4b", + "name": "Bankercoin", + "ticker": "BANK", + "decimals": 0 + }, + { + "assetSubject": "a1c94e56e4b24945338f2779e33b02e52fdd30d5f279c6131cc43c7c42555a5a", + "name": "Buzz The Bellboy", + "ticker": "BUZZ", + "decimals": 2 + }, + { + "assetSubject": "04b95368393c821f180deee8229fbd941baaf9bd748ebcdbf7adbb147273455247", + "name": "rsERG", + "ticker": "rsERG", + "decimals": 9 + }, + { + "assetSubject": "c27600f3aff3d94043464a33786429b78e6ab9df5e1d23b774acb34c434e4354", + "name": "Coinecta", + "ticker": "CNCT", + "decimals": 4 + }, + { + "assetSubject": "420000029ad9527271b1b1e3c27ee065c18df70a4a4cfc3093a41a4441584f", + "name": "AXO", + "ticker": "AXO", + "decimals": 9 + }, + { + "assetSubject": "09f2d4e4a5c3662f4c1e6a7d9600e9605279dbdcedb22d4507cb6e75535046", + "name": "SPF", + "ticker": "SPF", + "decimals": 6 + }, + { + "assetSubject": "86abe45be4d8fb2e8f28e8047d17d0ba5592f2a6c8c452fc88c2c14358524159", + "name": "XRAY", + "ticker": "XRAY", + "decimals": 6 + }, + { + "assetSubject": "8daefa391220bd0d8d007f3748d870f7f3c106040314c8515ccc35a5464c4143", + "name": "Flac Finance", + "ticker": "FLAC", + "decimals": 6 + }, + { + "assetSubject": "75fcc276057db5fc48eae0e11453c773c8a54604c3086bf9d95ac1b743485259", + "name": "CherryLend", + "ticker": "CHRY", + "decimals": 6 + }, + { + "assetSubject": "c48cbb3d5e57ed56e276bc45f99ab39abe94e6cd7ac39fb402da47ad0014df105553444d", + "name": "USDM", + "ticker": "USDM", + "decimals": 6 } ] diff --git a/tokenRegistry/token_data.c b/tokenRegistry/token_data.c index 34e208fe..5ca0eea9 100644 --- a/tokenRegistry/token_data.c +++ b/tokenRegistry/token_data.c @@ -44,7 +44,6 @@ { { 0xc1, 0xb9, 0x94, 0x86, 0xca, 0xf1, 0x1a, 0xd8, 0x93, 0xb9, 0xcc, 0x59, 0x27, 0x0d, 0x22, 0x00, 0x71, 0xce, 0xd5, 0xeb }, 0, "SWEET" }, { { 0x0a, 0xe3, 0xb4, 0x86, 0xe8, 0x5a, 0xf1, 0x50, 0xb4, 0x78, 0x1a, 0x13, 0x17, 0x1f, 0x27, 0x91, 0x4b, 0x3c, 0xb6, 0x1b }, 0, "TOKEN" }, { { 0xbc, 0xca, 0x70, 0x17, 0x1f, 0xa6, 0x6f, 0x68, 0x0a, 0xe9, 0x3d, 0x42, 0x3b, 0xac, 0xac, 0x97, 0xcd, 0xa6, 0xd7, 0x91 }, 6, "RAT" }, -{ { 0xb5, 0xed, 0x5d, 0x2d, 0xa9, 0x85, 0xa6, 0x2c, 0x96, 0xc7, 0xaf, 0x72, 0xfc, 0xe5, 0xf6, 0x51, 0x8f, 0xbc, 0x3a, 0xe2 }, 6, "AADA" }, { { 0xb4, 0x45, 0x60, 0xb8, 0xff, 0xb6, 0x9e, 0xf2, 0x80, 0x74, 0x14, 0x2e, 0x6c, 0xf3, 0x2e, 0xcb, 0xe6, 0xfc, 0x15, 0xd9 }, 0, "ADAX" }, { { 0xfc, 0x09, 0x66, 0xfe, 0x97, 0x60, 0x10, 0xfe, 0x99, 0x4e, 0xb8, 0x5e, 0x92, 0x88, 0x31, 0x06, 0x23, 0x15, 0x10, 0x97 }, 6, "BLC" }, { { 0x33, 0xc2, 0xc5, 0x94, 0xcc, 0xf0, 0x2b, 0xeb, 0x0d, 0x45, 0xa8, 0xa2, 0xcc, 0x7e, 0x44, 0xa8, 0x15, 0x16, 0xe4, 0x66 }, 0, "Pina" }, @@ -64,7 +63,6 @@ { { 0xef, 0xb2, 0xfe, 0xf8, 0xf1, 0x41, 0xba, 0x2d, 0x36, 0x03, 0xc4, 0x99, 0xa5, 0xa7, 0xbb, 0x56, 0xe6, 0x3e, 0xd3, 0x6a }, 0, "TREES" }, { { 0x33, 0x4c, 0xa4, 0xeb, 0x94, 0xa1, 0x93, 0xe2, 0x14, 0x1b, 0x96, 0x84, 0x2d, 0x0d, 0x86, 0x30, 0xb7, 0x15, 0x43, 0x81 }, 0, "LOOKZ" }, { { 0x73, 0x39, 0x1f, 0xb2, 0xdd, 0xfe, 0xdb, 0xee, 0x6d, 0xe8, 0x66, 0xe4, 0xb7, 0x6d, 0x60, 0xf8, 0x52, 0x62, 0x35, 0x75 }, 0, "DOEX" }, -{ { 0x58, 0x61, 0x97, 0x35, 0x64, 0x6d, 0x94, 0xa8, 0xc9, 0x4b, 0xb1, 0x94, 0xeb, 0xbc, 0xa0, 0x0a, 0xf8, 0x44, 0x67, 0x51 }, 6, "SOCIETY" }, { { 0x1b, 0x4e, 0x44, 0x23, 0x7b, 0x3e, 0x88, 0x3f, 0x1d, 0x74, 0x06, 0x89, 0xcc, 0xe4, 0xd1, 0x5f, 0x63, 0xa1, 0x62, 0xf9 }, 6, "LOG" }, { { 0x93, 0xe9, 0x1a, 0xbb, 0x2f, 0x08, 0x0a, 0xe9, 0x5d, 0x73, 0xce, 0xfa, 0x2c, 0x33, 0xab, 0xc5, 0x1b, 0x05, 0xd3, 0x8c }, 0, "$CLAW" }, { { 0x09, 0xf5, 0xcd, 0x55, 0x6b, 0x56, 0x25, 0xfd, 0xaf, 0xee, 0xa1, 0x0f, 0x08, 0xac, 0x54, 0xbf, 0x72, 0xf3, 0xa3, 0x2d }, 0, "PIGY" }, @@ -82,7 +80,6 @@ { { 0x0f, 0x08, 0xfb, 0x00, 0x3c, 0xe8, 0xe0, 0x40, 0x84, 0xd6, 0x0f, 0xca, 0xe4, 0x92, 0x0c, 0xc9, 0xb4, 0xaf, 0xbb, 0x6c }, 0, "HIS" }, { { 0x6d, 0x88, 0xa7, 0x0d, 0xd7, 0x0a, 0x74, 0x0f, 0x89, 0x71, 0x8c, 0x6f, 0xf2, 0xdd, 0xef, 0x6a, 0xf1, 0xbf, 0x49, 0x32 }, 0, "CDFC6" }, { { 0x24, 0x6a, 0x12, 0xb6, 0x19, 0x32, 0x95, 0x54, 0xcb, 0xce, 0xa5, 0x2d, 0x6c, 0xd0, 0x88, 0x22, 0x80, 0x1d, 0x30, 0x97 }, 0, "DGADA" }, -{ { 0x0b, 0x02, 0x47, 0x51, 0x70, 0x2b, 0x12, 0x94, 0x44, 0xe2, 0x94, 0x1d, 0xf0, 0x7d, 0x45, 0x49, 0xf7, 0x16, 0x60, 0x42 }, 0, "PUDGY" }, { { 0x07, 0x1c, 0xe8, 0x95, 0xb6, 0xe5, 0xbc, 0x82, 0x48, 0xfd, 0x3c, 0x22, 0x23, 0x68, 0x6e, 0x42, 0x22, 0xa7, 0xd1, 0x2f }, 0, "BDOGE" }, { { 0xc8, 0x08, 0xd4, 0xc6, 0x12, 0x93, 0xdf, 0x37, 0x14, 0x00, 0x04, 0xca, 0x06, 0xfa, 0x81, 0xe0, 0xe4, 0x52, 0x28, 0xf4 }, 6, "MEOW" }, { { 0x20, 0xee, 0xdc, 0xc7, 0xe1, 0x87, 0xd5, 0xa6, 0x0f, 0x6b, 0x35, 0x57, 0x24, 0x3b, 0x61, 0xfa, 0xa4, 0xe3, 0x61, 0x91 }, 0, "C4" }, @@ -99,4 +96,38 @@ { { 0x73, 0x88, 0x43, 0x9d, 0x27, 0xe1, 0x63, 0xeb, 0x8b, 0xd6, 0xe4, 0xff, 0x68, 0x43, 0xc8, 0xe5, 0x6b, 0xa0, 0xeb, 0x2d }, 8, "AGIX" }, { { 0xe1, 0xf1, 0xde, 0x48, 0x36, 0xc3, 0xed, 0xba, 0xb3, 0xee, 0x34, 0xda, 0x74, 0x96, 0x95, 0xf9, 0x83, 0x71, 0xe0, 0xff }, 6, "INDY" }, { { 0xa2, 0x62, 0x4d, 0xda, 0x5d, 0x49, 0x8d, 0x9e, 0x4d, 0x94, 0x42, 0xdb, 0xfa, 0x55, 0x14, 0x04, 0x0e, 0xb2, 0x16, 0xc7 }, 6, "DJED" }, -{ { 0x1e, 0xe2, 0x7e, 0xdf, 0xf4, 0x99, 0xcf, 0x88, 0xf0, 0x3a, 0x1e, 0xbc, 0x4f, 0x22, 0x6f, 0x4a, 0x4c, 0x07, 0x71, 0xde }, 6, "iUSD" } +{ { 0x1e, 0xe2, 0x7e, 0xdf, 0xf4, 0x99, 0xcf, 0x88, 0xf0, 0x3a, 0x1e, 0xbc, 0x4f, 0x22, 0x6f, 0x4a, 0x4c, 0x07, 0x71, 0xde }, 6, "iUSD" }, +{ { 0x79, 0xcd, 0xc7, 0xab, 0x16, 0x70, 0xd3, 0x83, 0x82, 0x05, 0x69, 0x7d, 0xb4, 0x12, 0xf5, 0xb4, 0xce, 0x86, 0xf0, 0xae }, 0, "SNEK" }, +{ { 0x63, 0xdc, 0x06, 0x02, 0xea, 0xac, 0x4c, 0x17, 0x8e, 0xc9, 0x0b, 0xd2, 0x23, 0x8d, 0x94, 0xb6, 0xa9, 0x16, 0x6a, 0x76 }, 6, "HUNT" }, +{ { 0x16, 0x94, 0xeb, 0x43, 0x8c, 0xac, 0x25, 0x6f, 0xaa, 0xb6, 0x92, 0x06, 0xa8, 0x6c, 0xeb, 0x48, 0xea, 0x17, 0xe1, 0x25 }, 6, "IAG" }, +{ { 0x61, 0x71, 0xd3, 0x3e, 0x73, 0xb4, 0xbb, 0x18, 0x7c, 0xe4, 0x5f, 0xb8, 0x93, 0x2f, 0x73, 0xb0, 0x8a, 0x30, 0xca, 0xd0 }, 0, "FREN" }, +{ { 0xb5, 0xed, 0x5d, 0x2d, 0xa9, 0x85, 0xa6, 0x2c, 0x96, 0xc7, 0xaf, 0x72, 0xfc, 0xe5, 0xf6, 0x51, 0x8f, 0xbc, 0x3a, 0xe2 }, 6, "LENFI" }, +{ { 0xb2, 0x5d, 0xa0, 0xff, 0x7c, 0x12, 0x10, 0x0f, 0x71, 0x85, 0x7d, 0xee, 0xcb, 0x12, 0x81, 0xd1, 0x89, 0x8e, 0x9a, 0x3f }, 6, "iBTC" }, +{ { 0xf8, 0x77, 0x71, 0x02, 0x31, 0x64, 0x24, 0x59, 0x06, 0x4a, 0x74, 0x3f, 0xce, 0x3f, 0x90, 0x02, 0x7a, 0x9d, 0x5a, 0xc5 }, 6, "BOOK" }, +{ { 0x29, 0x39, 0x3c, 0x8d, 0x53, 0x12, 0xbc, 0x10, 0x14, 0x32, 0x6a, 0xfb, 0x47, 0x99, 0xc9, 0x23, 0x5e, 0xd5, 0x4c, 0x64 }, 6, "NTX" }, +{ { 0xb7, 0x1c, 0x96, 0xbf, 0x30, 0x49, 0x4d, 0x83, 0x70, 0x61, 0x51, 0xaa, 0x92, 0xbb, 0x54, 0xfb, 0x05, 0x55, 0xd9, 0x2e }, 6, "FACT" }, +{ { 0x23, 0x54, 0xee, 0x7a, 0xa2, 0x5d, 0xb0, 0xc5, 0x8a, 0x74, 0x94, 0x2c, 0x3f, 0xb9, 0xe7, 0xa1, 0xa5, 0x24, 0x6a, 0xf5 }, 6, "LIFI" }, +{ { 0x4b, 0x70, 0x07, 0xc8, 0xa0, 0xe3, 0x8b, 0xd1, 0xcf, 0x42, 0x49, 0xc6, 0x21, 0x0b, 0x80, 0xbe, 0x03, 0xf7, 0x9d, 0x8d }, 6, "OPTIM" }, +{ { 0x29, 0x35, 0x01, 0xe1, 0xd2, 0xea, 0x4a, 0x6b, 0x70, 0x47, 0x8a, 0xcf, 0x76, 0xb7, 0xab, 0x2d, 0x4e, 0xe2, 0x58, 0xa3 }, 6, "OPT" }, +{ { 0x27, 0x10, 0x3e, 0x8a, 0xc1, 0xf4, 0x52, 0x17, 0x2c, 0xd9, 0x4c, 0x32, 0x41, 0xe4, 0xd4, 0x3f, 0x67, 0xed, 0x06, 0xfd }, 6, "CGI" }, +{ { 0xf3, 0x0a, 0x25, 0x41, 0x3e, 0x63, 0xe8, 0x2d, 0x6e, 0x79, 0xa8, 0x2c, 0xfb, 0x0b, 0xfb, 0xe6, 0x69, 0xd0, 0x19, 0x69 }, 6, "SHEN" }, +{ { 0x25, 0xf8, 0xef, 0xb7, 0x66, 0xe5, 0x85, 0xc2, 0x9e, 0x85, 0x4f, 0x63, 0x46, 0xaa, 0x3e, 0x55, 0x74, 0x2c, 0x01, 0x89 }, 6, "PBX" }, +{ { 0x9a, 0x56, 0x95, 0x83, 0x63, 0x32, 0xd0, 0x6e, 0xdb, 0x97, 0x81, 0xd3, 0xbb, 0x60, 0xdf, 0xd4, 0x74, 0x6e, 0x2d, 0x12 }, 6, "iETH" }, +{ { 0x98, 0xb6, 0x0a, 0x33, 0xc5, 0x54, 0xe6, 0x9b, 0x90, 0xd9, 0x7d, 0x15, 0x7b, 0x5b, 0x3f, 0x2c, 0x9b, 0x85, 0x73, 0x12 }, 0, "KITUP" }, +{ { 0x56, 0xb4, 0x05, 0x65, 0xd7, 0x47, 0x3f, 0xe9, 0x5e, 0x26, 0x00, 0x59, 0x21, 0x03, 0xa3, 0x1e, 0x42, 0x99, 0x73, 0xf9 }, 6, "GENS" }, +{ { 0x1e, 0x57, 0xbc, 0x89, 0xed, 0x20, 0xc5, 0x2f, 0x2d, 0xc1, 0x00, 0x99, 0xf1, 0x8f, 0x25, 0x76, 0xe3, 0x8b, 0xd4, 0x22 }, 0, "RON" }, +{ { 0x19, 0x1c, 0xc9, 0xfe, 0x2b, 0x45, 0xa7, 0xa9, 0x0a, 0x71, 0x1e, 0x10, 0xb8, 0x5a, 0x9d, 0x8b, 0x0c, 0x29, 0xbc, 0x82 }, 0, "TOOL" }, +{ { 0x14, 0xba, 0x77, 0x29, 0x6f, 0x62, 0x73, 0xba, 0x60, 0x15, 0xbd, 0x7e, 0x75, 0xbf, 0x2d, 0x72, 0x4d, 0xb3, 0xcb, 0xa0 }, 6, "JPG" }, +{ { 0x58, 0x61, 0x97, 0x35, 0x64, 0x6d, 0x94, 0xa8, 0xc9, 0x4b, 0xb1, 0x94, 0xeb, 0xbc, 0xa0, 0x0a, 0xf8, 0x44, 0x67, 0x51 }, 6, "SOC" }, +{ { 0x1c, 0x77, 0x87, 0x8f, 0xb4, 0xe1, 0x40, 0x2e, 0xdf, 0xf5, 0x03, 0x40, 0x84, 0x0f, 0x80, 0x89, 0x00, 0x50, 0x68, 0x79 }, 0, "VIPER" }, +{ { 0x0f, 0x76, 0xf8, 0xdd, 0x1a, 0xba, 0xcf, 0xf4, 0xa4, 0xc3, 0x65, 0xa1, 0xfa, 0x64, 0x33, 0xb7, 0xac, 0x8d, 0x49, 0xc5 }, 6, "ENCS" }, +{ { 0xc6, 0x00, 0x7e, 0x0c, 0x42, 0xac, 0x1b, 0x3c, 0xf0, 0x0c, 0xa4, 0x94, 0xce, 0xae, 0x59, 0x90, 0x8f, 0x2a, 0x2f, 0x02 }, 0, "BANK" }, +{ { 0x2b, 0x60, 0x77, 0x00, 0xc1, 0xd6, 0x08, 0x71, 0x3d, 0x8f, 0x4f, 0xc5, 0xf6, 0xeb, 0x60, 0x65, 0x35, 0xd7, 0x01, 0x02 }, 2, "BUZZ" }, +{ { 0xef, 0xb3, 0x25, 0x1f, 0x0d, 0x4b, 0xfc, 0x75, 0xcd, 0xa2, 0xc3, 0x20, 0xed, 0xd1, 0xd7, 0xb2, 0xa4, 0x30, 0xb5, 0xe2 }, 9, "rsERG" }, +{ { 0xca, 0xbf, 0xde, 0x8b, 0xc1, 0xc7, 0xc8, 0x51, 0x9e, 0xbb, 0xf9, 0xf5, 0x6e, 0x48, 0xbd, 0x61, 0x57, 0x2e, 0xef, 0xc5 }, 4, "CNCT" }, +{ { 0xd2, 0x17, 0xf9, 0x26, 0x3c, 0x12, 0x2e, 0x62, 0x6f, 0xe5, 0xc4, 0xae, 0x5e, 0x31, 0xc8, 0xa4, 0xb7, 0x81, 0x2a, 0x88 }, 9, "AXO" }, +{ { 0x6d, 0x7a, 0xe0, 0x79, 0xfd, 0x4f, 0xfe, 0xe3, 0x94, 0xe5, 0xe1, 0x4a, 0x59, 0x9d, 0xac, 0x43, 0x3f, 0x47, 0x59, 0x64 }, 6, "SPF" }, +{ { 0x13, 0xbb, 0x5c, 0x5d, 0xc5, 0x36, 0xfc, 0x67, 0x5b, 0xc8, 0xae, 0xb2, 0x92, 0xd4, 0x10, 0x92, 0x8f, 0x7d, 0xcd, 0xbe }, 6, "XRAY" }, +{ { 0x0e, 0xc9, 0x9e, 0x17, 0x89, 0xa0, 0x23, 0xa9, 0xca, 0x55, 0xef, 0xce, 0xcf, 0xeb, 0x1e, 0xcf, 0x6e, 0x65, 0xfe, 0x6c }, 6, "FLAC" }, +{ { 0xcc, 0xcf, 0x11, 0xca, 0xe6, 0x9b, 0xe3, 0xd5, 0x3b, 0xae, 0x0b, 0x68, 0xb2, 0x2c, 0x65, 0x0a, 0xc8, 0x05, 0xa5, 0xa2 }, 6, "CHRY" }, +{ { 0x52, 0x52, 0xd9, 0x1e, 0xd6, 0x53, 0xaa, 0x7a, 0x40, 0x7e, 0xed, 0x33, 0x6d, 0xca, 0x4d, 0x26, 0x70, 0x23, 0x86, 0x0a }, 6, "USDM" }