diff --git a/Makefile b/Makefile index 52f0235fe..6bad1a054 100644 --- a/Makefile +++ b/Makefile @@ -40,11 +40,11 @@ APP_STACK_SIZE = 3072 # Setting to allow building variant applications VARIANT_PARAM = COIN -VARIANT_VALUES = bitcoin_testnet bitcoin +VARIANT_VALUES = qtum_testnet qtum # simplify for tests ifndef COIN -COIN=bitcoin_testnet +COIN=qtum_testnet endif ######################################## @@ -55,35 +55,35 @@ HAVE_APPLICATION_FLAG_GLOBAL_PIN = 1 HAVE_APPLICATION_FLAG_BOLOS_SETTINGS = 1 HAVE_APPLICATION_FLAG_LIBRARY = 1 -ifeq ($(COIN),bitcoin_testnet) +ifeq ($(COIN),qtum_testnet) -# Bitcoin testnet, no legacy support +# Qtum testnet, no legacy support DEFINES += BIP32_PUBKEY_VERSION=0x043587CF DEFINES += BIP44_COIN_TYPE=1 DEFINES += BIP44_COIN_TYPE_2=1 -DEFINES += COIN_P2PKH_VERSION=111 -DEFINES += COIN_P2SH_VERSION=196 -DEFINES += COIN_NATIVE_SEGWIT_PREFIX=\"tb\" +DEFINES += COIN_P2PKH_VERSION=120 +DEFINES += COIN_P2SH_VERSION=110 +DEFINES += COIN_NATIVE_SEGWIT_PREFIX=\"tq\" DEFINES += COIN_COINID_SHORT=\"TEST\" -APPNAME = "Bitcoin Test" +APPNAME = "Qtum Test" -else ifeq ($(COIN),bitcoin) +else ifeq ($(COIN),qtum) -# Bitcoin mainnet, no legacy support +# Qtum mainnet, no legacy support DEFINES += BIP32_PUBKEY_VERSION=0x0488B21E -DEFINES += BIP44_COIN_TYPE=0 -DEFINES += BIP44_COIN_TYPE_2=0 -DEFINES += COIN_P2PKH_VERSION=0 -DEFINES += COIN_P2SH_VERSION=5 -DEFINES += COIN_NATIVE_SEGWIT_PREFIX=\"bc\" -DEFINES += COIN_COINID_SHORT=\"BTC\" +DEFINES += BIP44_COIN_TYPE=88 +DEFINES += BIP44_COIN_TYPE_2=88 +DEFINES += COIN_P2PKH_VERSION=58 +DEFINES += COIN_P2SH_VERSION=50 +DEFINES += COIN_NATIVE_SEGWIT_PREFIX=\"qc\" +DEFINES += COIN_COINID_SHORT=\"QTUM\" -APPNAME = "Bitcoin" +APPNAME = "Qtum" else ifeq ($(filter clean,$(MAKECMDGOALS)),) -$(error Unsupported COIN - use bitcoin_testnet, bitcoin) +$(error Unsupported COIN - use qtum_testnet, qtum) endif endif diff --git a/README.md b/README.md index 07b3b3564..2969123dd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Ledger Bitcoin Application +# Ledger Qtum Application ## Prerequisite @@ -26,7 +26,7 @@ make load # load the app on the Nano using ledgerblue ## Documentation High level documentation on the architecture and interface of the app: -- [bitcoin.md](doc/bitcoin.md): specifications of application commands. +- [qtum.md](doc/qtum.md): specifications of application commands. - [wallet.md](doc/wallet.md): supported wallet signing policies. - [merkle.md](doc/merkle.md): rationale and specifications for the usage of Merkle trees. @@ -55,6 +55,42 @@ The flow processed in [GitHub Actions](https://github.com/features/actions) is t It outputs 4 artifacts: -- `bitcoin-app-debug` within output files of the compilation process in debug mode +- `qtum-app-debug` within output files of the compilation process in debug mode - `code-coverage` within HTML details of code coverage - `documentation` within HTML auto-generated documentation + +## Develop on Ubuntu + +This is a quick start script for developing app-qtum on Ubuntu. + + # Install docker + sudo apt update + sudo apt install snapd + sudo snap refresh snapd + sudo snap install docker + sudo addgroup --system docker + sudo adduser $USER docker + newgrp docker + sudo snap disable docker + sudo snap enable docker + + # Pull the latest dev tool + sudo docker pull ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest + + # Clone app-qtum + sudo apt install git -y + git clone https://github.com/qtumproject/app-qtum + + # Add rules for the supported devices + sudo app-qtum/script/add_udev_rules.sh + + # Add ledger_env with command line to ~/.bashrc + app-qtum/script/ledger_ubuntu_env.sh + source ~/.bashrc + + # Build the ledger app + cd app-qtum + ledger_env + make + make load + exit diff --git a/doc/merkle.md b/doc/merkle.md index 85e16a37a..61fb25a58 100644 --- a/doc/merkle.md +++ b/doc/merkle.md @@ -62,7 +62,7 @@ and ### Serialization A Merklelized Map commitment is serialized as a string of bytes containing, in sequence: -- the number of key-value pairs, encoded as a Bitcoin-style varint; +- the number of key-value pairs, encoded as a Qtum-style varint; - the 32 bytes `keys_root` - the 32 bytes `values_root` diff --git a/doc/bitcoin.md b/doc/qtum.md similarity index 94% rename from doc/bitcoin.md rename to doc/qtum.md index cd19cb308..10781d318 100644 --- a/doc/bitcoin.md +++ b/doc/qtum.md @@ -1,4 +1,4 @@ -# Bitcoin application: Technical Specifications +# Qtum application: Technical Specifications This page details the protocol implemented since version 2.1.0 of the app. @@ -10,7 +10,7 @@ The protocol documentation for version from 2.0.0 and before 2.1.0 is [here](./v The messaging format of the app is compatible with the [APDU protocol](https://developers.ledger.com/docs/nano-app/application-structure/#apdu-interpretation-loop). The `P1` field is reserved for future use and must be set to `0` in all messages. The `P2` field is used as a protocol version identifier; the current version is `1`, while version `0` is still supported. No other value must be used. -The main commands use `CLA = 0xE1`, unlike the legacy Bitcoin application that used `CLA = 0xE0`. +The main commands use `CLA = 0xE1`, unlike the legacy Qtum application that used `CLA = 0xE0`. | CLA | INS | COMMAND NAME | DESCRIPTION | |-----|-----|------------------------|-------------| @@ -19,7 +19,8 @@ The main commands use `CLA = 0xE1`, unlike the legacy Bitcoin application that u | E1 | 03 | GET_WALLET_ADDRESS | Return and show on screen an address for a registered or default wallet | | E1 | 04 | SIGN_PSBT | Sign a PSBT with a registered or default wallet | | E1 | 05 | GET_MASTER_FINGERPRINT | Return the fingerprint of the master public key | -| E1 | 10 | SIGN_MESSAGE | Sign a message with a key from a BIP32 path (Bitcoin Message Signing) | +| E1 | 10 | SIGN_MESSAGE | Sign a message with a key from a BIP32 path (Qtum Message Signing) | +| E1 | 81 | SIGN_SENDER_PSBT | Sign an op_sender output | The `CLA = 0xF8` is used for framework-specific (rather than app-specific) APDUs; at this time, only one command is present. @@ -37,7 +38,7 @@ The specs for the client commands are detailed below. ## Descriptors and wallet policies -The Bitcoin app uses a language similar to [output script descriptors](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md) in order to represent the wallets that can be used to sign transactions. +The Qtum app uses a language similar to [output script descriptors](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md) in order to represent the wallets that can be used to sign transactions. Wallet policies need to be registered on the device, with an interactive process that requires user's approval. See [here](wallet.md) for detailed information on the wallet policy language. @@ -65,7 +66,7 @@ Once the user approves, the `REGISTER_WALLET` returns to the client a 32-byte HM | 0xE000 | `SW_INTERRUPTED_EXECUTION` | The command is interrupted, and requires the client's response | | 0x9000 | `SW_OK` | Success | - + ## Commands @@ -230,7 +231,7 @@ No output data; the signature are returned using the YIELD client command. Using the information in the PSBT and the wallet description, this command verifies what inputs are internal and what outputs match the pattern for a change address. After validating all the external outputs and the transaction fee with the user, it signs each of the internal inputs; each signature is sent to the client using the YIELD command, in the format described below. If multiple key placeholders of the wallet policy are internal, the process is repeated for each of them. The results yielded via the YIELD command respect the following format: ` `, where: -- `input_index` is a Bitcoin style varint, the index input of the input being signed (starting from 0); +- `input_index` is a Qtum style varint, the index input of the input being signed (starting from 0); - `pubkey_augm_len` is an unsigned byte equal to the length of `pubkey_augm`; - `pubkey_augm` is the `pubkey` used for signing for legacy, segwit or taproot script path spends (a compressed pubkey if non-taproot, a 32-byte x-only pubkey if taproot); for taproot script path spends, it is the concatenation of the `x-only` pubkey and the 32-byte *tapleaf hash* as defined in [BIP-0341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki); - `signature` is the returned signature, possibly concatenated with the sighash byte (as it would be pushed on the stack). @@ -282,7 +283,7 @@ User interaction is not required for this command. ### SIGN_MESSAGE -Signs a message, according to the standard Bitcoin Message Signing. +Signs a message, according to the standard Qtum Message Signing. The device shows on its secure screen the BIP-32 path used for signing, and the SHA256 hash of the message; the hash should be verified by the user using an external tool if the client is untrusted. @@ -303,7 +304,7 @@ The device shows on its secure screen the BIP-32 path used for signing, and the | `4` | `bip32_path[1]` | Second derivation step (big endian) | | | ... | | | `4` | `bip32_path[n-1]` | `n`-th derivation step (big endian) | -| `` | `msg_length` | The byte length of the message to sign (Bitcoin-style varint) | +| `` | `msg_length` | The byte length of the message to sign (Qtum-style varint) | | `32` | `msg_merkle_root` | The Merkle root of the message, split in 64-byte chunks | The message to be signed is split into `ceil(msg_length/64)` chunks of 64 bytes (except the last chunk that could be smaller); `msg_merkle_root` is the root of the Merkle tree of the corresponding list of chunks. @@ -314,7 +315,7 @@ The theoretical maximum valid length of the message is 232-1 = 4  | Length | Description | |--------|-------------| -| `65` | The returned signature, encoded in the standard Bitcoin message signing format | +| `65` | The returned signature, encoded in the standard Qtum message signing format | The signature is returned as a 65-byte binary string (1 byte equal to 32 or 33, followed by `r` and `s`, each of them represented as a 32-byte big-endian integer). @@ -322,8 +323,8 @@ The signature is returned as a 65-byte binary string (1 byte equal to 32 or 33, The digest being signed is the double-SHA256 of the message, after prefixing the message with: -- the magic string `"\x18Bitcoin Signed Message:\n"` (equal to `18426974636f696e205369676e6564204d6573736167653a0a` in hexadecimal) -- the length of the message, encoded as a Bitcoin-style variable length integer. +- the magic string `"\x18Qtum Signed Message:\n"` (equal to `185174756D205369676e6564204d6573736167653a0a` in hexadecimal) +- the length of the message, encoded as a Qtum-style variable length integer. #### Client commands @@ -360,7 +361,7 @@ The request contains: - `32` bytes: a sha-256 hash. The response must contain: -- ``: the length of the preimage, encoded as a Bitcoin-style varint; +- ``: the length of the preimage, encoded as a Qtum-style varint; - `1` byte: a 1-byte unsigned integer `b`, the length of the prefix of the pre-image that is part of the response; - `b` bytes: corresponding to the first `b` bytes of the preimage. @@ -374,8 +375,8 @@ The `GET_MERKLE_LEAF_PROOF` command requests the hash of a given leaf of a Merkl The request contains: - `32` bytes: the Merkle root hash; -- `` bytes: the tree size `n`, encoded as a Bitcoin-style varint; -- `` bytes: the leaf index `i`, encoded as a Bitcoin-style varint. +- `` bytes: the tree size `n`, encoded as a Qtum-style varint; +- `` bytes: the leaf index `i`, encoded as a Qtum-style varint. The client must respond with: - `32` bytes: the hash of the leaf with index `i` in the requested Merkle tree; @@ -397,7 +398,7 @@ The request contains: The response contains: - `1` byte: `1` if the leaf is found, `0` if matching leaf exists; -- ``: the index of the leaf, encoded as a Bitcoin-style varint. +- ``: the index of the leaf, encoded as a Qtum-style varint. ### GET_MORE_ELEMENTS diff --git a/doc/wallet.md b/doc/wallet.md index 85b0c2920..e47072f71 100644 --- a/doc/wallet.md +++ b/doc/wallet.md @@ -140,9 +140,9 @@ The wallet policy is serialized as the concatenation of: - `1 byte`: a byte equal to `0x02`, the version of the wallet policy language - `1 byte`: the length of the wallet name (0 for standard wallet) - ``: the wallet name (empty for standard wallets) -- ``: the length of the wallet descriptor template, encoded as a Bitcoin-style variable-length integer +- ``: the length of the wallet descriptor template, encoded as a Qtum-style variable-length integer - `32 bytes`: the sha256 hash of the wallet descriptor template -- ``: the number of keys in the list of keys, encoded as a Bitcoin-style variable-length integer +- ``: the number of keys in the list of keys, encoded as a Qtum-style variable-length integer - `<32 bytes>`: the root of the canonical Merkle tree of the list of keys See [merkle](merkle.md) for information on Merkle trees. diff --git a/glyphs/Bitcoin_64px.bmp b/glyphs/Bitcoin_64px.bmp index 142aba154..c0983e3e3 100755 Binary files a/glyphs/Bitcoin_64px.bmp and b/glyphs/Bitcoin_64px.bmp differ diff --git a/glyphs/bitcoin_logo.gif b/glyphs/bitcoin_logo.gif index d43e12f59..dd24326ff 100644 Binary files a/glyphs/bitcoin_logo.gif and b/glyphs/bitcoin_logo.gif differ diff --git a/glyphs/nanos_badge_bitcoin.gif b/glyphs/nanos_badge_bitcoin.gif index d43e12f59..dd24326ff 100644 Binary files a/glyphs/nanos_badge_bitcoin.gif and b/glyphs/nanos_badge_bitcoin.gif differ diff --git a/glyphs/nanos_badge_bitcoin_testnet.gif b/glyphs/nanos_badge_bitcoin_testnet.gif index d43e12f59..dd24326ff 100644 Binary files a/glyphs/nanos_badge_bitcoin_testnet.gif and b/glyphs/nanos_badge_bitcoin_testnet.gif differ diff --git a/icons/bitcoin.png b/icons/bitcoin.png index 32778bcd6..242128c36 100644 Binary files a/icons/bitcoin.png and b/icons/bitcoin.png differ diff --git a/icons/nanos_app_bitcoin.gif b/icons/nanos_app_bitcoin.gif index 9fa736b19..62782dcb9 100644 Binary files a/icons/nanos_app_bitcoin.gif and b/icons/nanos_app_bitcoin.gif differ diff --git a/icons/nanox_app_bitcoin.gif b/icons/nanox_app_bitcoin.gif index 7a92ac33e..65eeeb742 100644 Binary files a/icons/nanox_app_bitcoin.gif and b/icons/nanox_app_bitcoin.gif differ diff --git a/icons/stax_app_bitcoin.gif b/icons/stax_app_bitcoin.gif index 1630a5dd5..b48f6c6b6 100755 Binary files a/icons/stax_app_bitcoin.gif and b/icons/stax_app_bitcoin.gif differ diff --git a/script/add_udev_rules.sh b/script/add_udev_rules.sh new file mode 100755 index 000000000..de307f02f --- /dev/null +++ b/script/add_udev_rules.sh @@ -0,0 +1,24 @@ +#!/bin/bash +cat < /etc/udev/rules.d/20-hw1.rules +# HW.1 / Nano +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="1b7c|2b7c|3b7c|4b7c", TAG+="uaccess", TAG+="udev-acl" +# Blue +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0000|0000|0001|0002|0003|0004|0005|0006|0007|0008|0009|000a|000b|000c|000d|000e|000f|0010|0011|0012|0013|0014|0015|0016|0017|0018|0019|001a|001b|001c|001d|001e|001f", TAG+="uaccess", TAG+="udev-acl" +# Nano S +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0001|1000|1001|1002|1003|1004|1005|1006|1007|1008|1009|100a|100b|100c|100d|100e|100f|1010|1011|1012|1013|1014|1015|1016|1017|1018|1019|101a|101b|101c|101d|101e|101f", TAG+="uaccess", TAG+="udev-acl" +# Aramis +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0002|2000|2001|2002|2003|2004|2005|2006|2007|2008|2009|200a|200b|200c|200d|200e|200f|2010|2011|2012|2013|2014|2015|2016|2017|2018|2019|201a|201b|201c|201d|201e|201f", TAG+="uaccess", TAG+="udev-acl" +# HW2 +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0003|3000|3001|3002|3003|3004|3005|3006|3007|3008|3009|300a|300b|300c|300d|300e|300f|3010|3011|3012|3013|3014|3015|3016|3017|3018|3019|301a|301b|301c|301d|301e|301f", TAG+="uaccess", TAG+="udev-acl" +# Nano X +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0004|4000|4001|4002|4003|4004|4005|4006|4007|4008|4009|400a|400b|400c|400d|400e|400f|4010|4011|4012|4013|4014|4015|4016|4017|4018|4019|401a|401b|401c|401d|401e|401f", TAG+="uaccess", TAG+="udev-acl" +# Nano SP +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0005|5000|5001|5002|5003|5004|5005|5006|5007|5008|5009|500a|500b|500c|500d|500e|500f|5010|5011|5012|5013|5014|5015|5016|5017|5018|5019|501a|501b|501c|501d|501e|501f", TAG+="uaccess", TAG+="udev-acl" +# Ledger Stax +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="6011", TAG+="uaccess", TAG+="udev-acl" +EOF + +udevadm trigger +udevadm control --reload-rules +groupadd plugdev +usermod -aG plugdev -aG plugdev `whoami` diff --git a/script/ledger_ubuntu_env.sh b/script/ledger_ubuntu_env.sh new file mode 100755 index 000000000..174a1cd08 --- /dev/null +++ b/script/ledger_ubuntu_env.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +#alias ledger_env='sudo docker run --rm -ti --user "$(id -u):$(id -g)" --privileged -v "/dev/bus/usb:/dev/bus/usb" -v "$(realpath .):/app" ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest' +# For manual update of ~/.bashrc with ledger_env, just append the above line without the leading # character + +# Add ledger_env to ~/.bashrc, alias for the command that is needed to connect to docker +DEVTOOLSET="alias ledger_env='sudo docker run --rm -ti --user \"\$(id -u):\$(id -g)\" --privileged -v \"/dev/bus/usb:/dev/bus/usb\" -v \"\$(realpath .):/app\" ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest'" +FILENAME="$HOME/.bashrc" +# Search file +if grep "$DEVTOOLSET" $FILENAME > /dev/null +then + echo "ledger_env present" +else + # Add ledger_env ~/.bashrc if not present into the file + echo $DEVTOOLSET >> $FILENAME +fi diff --git a/src/commands.h b/src/commands.h index 63b3b4d10..0e75800d1 100644 --- a/src/commands.h +++ b/src/commands.h @@ -10,4 +10,5 @@ typedef enum { SIGN_PSBT = 0x04, GET_MASTER_FINGERPRINT = 0x05, SIGN_MESSAGE = 0x10, + SIGN_SENDER_PSBT = 0x81, } command_e; diff --git a/src/common/script.c b/src/common/script.c index aeb35144c..d4950090c 100644 --- a/src/common/script.c +++ b/src/common/script.c @@ -2,6 +2,7 @@ #include #include #include +#include // strncpy, memmove #include "../common/bip32.h" #include "../common/buffer.h" @@ -60,6 +61,18 @@ int get_script_type(const uint8_t script[], size_t script_len) { } } + bool isOpSender = is_opsender(script, script_len); + bool isOpCreate = is_opcreate(script, script_len); + bool isOpCall = is_opcall(script, script_len); + + if(isOpCreate) { + return isOpSender ? SCRIPT_TYPE_CREATE_SENDER : SCRIPT_TYPE_CREATE; + } + + if(isOpCall) { + return isOpSender ? SCRIPT_TYPE_CALL_SENDER : SCRIPT_TYPE_CALL; + } + // unknown/invalid, or doesn't have an address return -1; } @@ -105,6 +118,32 @@ int get_script_address(const uint8_t script[], size_t script_len, char *out, siz addr_len = strlen(out); break; } + case SCRIPT_TYPE_CREATE_SENDER: { + strcpy(out, "OP_SENDER_CREATE"); + addr_len = strlen(out); + break; + } + case SCRIPT_TYPE_CALL_SENDER: { + if (!opcall_addr_encode(script, script_len, out, out_len, 1)) { + return -1; + } + + addr_len = strlen(out); + break; + } + case SCRIPT_TYPE_CREATE: { + strcpy(out, "OP_CREATE"); + addr_len = strlen(out); + break; + } + case SCRIPT_TYPE_CALL: { + if (!opcall_addr_encode(script, script_len, out, out_len, 0)) { + return -1; + } + + addr_len = strlen(out); + break; + } default: return -1; } @@ -192,4 +231,290 @@ int format_opscript_script(const uint8_t script[], out[out_ctr++] = '\0'; return out_ctr; -} \ No newline at end of file +} + +int format_opscript_script_short(const uint8_t script[], + size_t script_len, + char out[static MAX_OPRETURN_OUTPUT_DESC_SIZE_SHORT]) { + if (script_len == 0 || script[0] != OP_RETURN) { + return -1; + } + + strcpy(out, "OP_RETURN"); + int out_ctr = strlen(out); + out[out_ctr++] = '\0'; + return out_ctr; +} + +bool get_script_op(uint8_t ** pc, const uint8_t * end, uint8_t* opcodeRet, uint8_t **pvchRet, unsigned int *pvchSize) +{ + *opcodeRet = OP_INVALIDOPCODE; + if (*pc >= end) + return 0; + + if(pvchRet) + *pvchRet = 0; + if(pvchSize) + *pvchSize = 0; + + // Read instruction + if (end - *pc < 1) + return 0; + uint8_t opcode = *(*pc)++; + + // Immediate operand + if (opcode <= OP_PUSHDATA4) + { + unsigned int nSize = 0; + if (opcode < OP_PUSHDATA1) + { + nSize = opcode; + } + else if (opcode == OP_PUSHDATA1) + { + if (end - *pc < 1) + return 0; + nSize = *(*pc)++; + } + else if (opcode == OP_PUSHDATA2) + { + if (end - *pc < 2) + return 0; + + nSize = read_u16_le(*pc, 0); + *pc += 2; + } + else if (opcode == OP_PUSHDATA4) + { + if (end - *pc < 4) + return 0; + nSize = read_u32_le(*pc, 0); + *pc += 4; + } + if (end - *pc < 0 || (unsigned int)(end - *pc) < nSize) + return 0; + if(pvchRet) + *pvchRet = *pc; + if(pvchSize) + *pvchSize = nSize; + *pc += nSize; + } + + *opcodeRet = opcode; + return 1; +} + +bool get_script_size(uint8_t *buffer, size_t maxSize, unsigned int *scriptSize, unsigned int *discardSize) +{ + *scriptSize = 0; + *discardSize = 0; + if (maxSize > 0 && buffer[0] < 0xFD) { + *scriptSize = buffer[0]; + *discardSize = 1; + } else if (maxSize > 2 && buffer[0] == 0xFD) { + *scriptSize = read_u16_le(buffer + 1, 0); + *discardSize = 3; + } else { + return 0; + } + + size_t bifferSize = *scriptSize + *discardSize; + if(bifferSize <= maxSize) { + return 1; + } + + return 0; +} + +// Have script size inside the script +#define HAVE_SCRIPT_SIZE 0 + +int find_script_op(uint8_t *buffer, size_t size, uint8_t op, bool haveSize) +{ + int nFound = 0; + unsigned int scriptSize = size; + unsigned int discardSize = 0; + if(haveSize) + get_script_size(buffer, size, &scriptSize, &discardSize); + uint8_t opcode = OP_INVALIDOPCODE; + const uint8_t* end = buffer + scriptSize + discardSize; + uint8_t *begin = buffer + discardSize; + for (uint8_t * pc = begin; pc != end && get_script_op(&pc, end, &opcode, 0, 0);) + if (opcode == op) + ++nFound; + return nFound; +} + +bool find_script_data(uint8_t *buffer, size_t size, int index, bool haveSize, uint8_t **pvchRet, unsigned int *pvchSize) +{ + unsigned int scriptSize = size; + unsigned int discardSize = 0; + if(haveSize) + get_script_size(buffer, size, &scriptSize, &discardSize); + uint8_t opcode = OP_INVALIDOPCODE; + const uint8_t* end = buffer + scriptSize + discardSize; + uint8_t *begin = buffer + discardSize; + int i = 0; + for (uint8_t * pc = begin; i < index && pc != end && get_script_op(&pc, end, &opcode, pvchRet, pvchSize); i++); + return i == index; +} + +void get_script_p2pkh(const uint8_t *pkh, uint8_t *script, uint8_t haveSize) +{ + uint8_t offset = haveSize ? 1 : 0; + if(haveSize) script[0] = 0x19; + script[0 + offset] = OP_DUP; + script[1 + offset] = OP_HASH160; + script[2 + offset] = 0x14; + memcpy(script + 3 + offset, pkh, 20); + script[23 + offset] = OP_EQUALVERIFY; + script[24 + offset] = OP_CHECKSIG; +} + +bool is_opcontract(uint8_t script[], size_t script_len, uint8_t value) { + return (!is_p2wpkh(script, script_len) && + !is_p2wsh(script, script_len) && + !is_opreturn(script, script_len) && + find_script_op(script, script_len, value, HAVE_SCRIPT_SIZE) == 1); +} + +bool is_opcreate(const uint8_t script[], size_t script_len) { + return is_opcontract((uint8_t *)script, script_len, OP_CREATE); +} + +bool is_opcall(const uint8_t script[], size_t script_len) { + return is_opcontract((uint8_t *)script, script_len, OP_CALL); +} + +bool is_opsender(const uint8_t script[], size_t script_len) { + return is_opcontract((uint8_t *)script, script_len, OP_SENDER); +} + +bool get_script_sender_address(uint8_t *buffer, size_t size, uint8_t *script) { + uint8_t *pkh = 0; + unsigned int pkhSize = 0; + bool ret = find_script_data(buffer, size, 2, HAVE_SCRIPT_SIZE, &pkh, &pkhSize) == 1 && pkh != 0 && pkhSize == 20; + if(ret) get_script_p2pkh(pkh, script, 1); + return ret; +} + +bool get_sender_sig(uint8_t *buffer, size_t size, uint8_t **sig, unsigned int *sigSize) { + if(sig == 0 || sigSize == 0) + return 0; + return find_script_data(buffer, size, 3, HAVE_SCRIPT_SIZE, sig, sigSize) && *sig != 0 && *sigSize > 0; +} + +#define DELEGATIONS_ADDRESS "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x86" +#define ADD_DELEGATION_HASH "\x4c\x0e\x96\x8c" +#define REMOVE_DELEGATION_HASH "\x3d\x66\x6e\x8b" + +uint16_t public_key_to_encoded_base58( + uint8_t *in, uint16_t inlen, char *out, + size_t outlen, uint16_t version, + uint8_t alreadyHashed) { + uint8_t tmpBuffer[34]; + + uint8_t versionSize = (version > 255 ? 2 : 1); + int outputLen = outlen; + + if (!alreadyHashed) { + PRINTF("To hash\n%.*H\n",inlen,in); + crypto_hash160(in, inlen, tmpBuffer + versionSize); + PRINTF("Hash160\n%.*H\n",20,(tmpBuffer + versionSize)); + if (version > 255) { + tmpBuffer[0] = (version >> 8); + tmpBuffer[1] = version; + } else { + tmpBuffer[0] = version; + } + } else { + memmove(tmpBuffer, in, 20 + versionSize); + } + + crypto_get_checksum(tmpBuffer, 20 + versionSize, tmpBuffer + 20 + versionSize); + + outputLen = base58_encode_address(tmpBuffer, 24 + versionSize, out, outlen); + if (outputLen < 0) { + THROW(EXCEPTION); + } + return (uint16_t) outputLen; +} + +bool opcall_addr_encode(const uint8_t script[], size_t script_len, char *out, size_t out_len, bool isOpSender) +{ + char contractaddress[20]; + size_t i; + int pos = 0; + for (i = 0; i < sizeof(contractaddress); i++) { + contractaddress[i] = script[script_len - 21 + i]; + } + if (strncmp(contractaddress, DELEGATIONS_ADDRESS, + sizeof(contractaddress)) == 0) { + char functionhash[4]; + if (!isOpSender) { + pos += script[pos]; // version + pos += script[pos] + 1; // gas limit + pos += script[pos] + 1; // gas price + } else { + pos += script[pos]; // address version + pos += script[pos]; // address + pos += script[pos] + 1; // gas price + if (script[pos] == 0x4c) { // check for OP_PUSHDATA1 + pos += script[pos + 1] + 2; + } else if (script[pos] == 0x00) { + pos += 1; + } + pos += 1; // OP_SENDER + pos += script[pos] + 1; // // version + pos += script[pos] + 1; // gas limit + pos += script[pos] + 1; // gas price + } + if (script[pos] == 0x4c) + pos++; // check for OP_PUSHDATA1 + + for (i = 0; i < sizeof(functionhash); i++) { + functionhash[i] = script[pos + 1 + i]; + } + if (strncmp(functionhash, ADD_DELEGATION_HASH, sizeof(functionhash)) + == 0) { + uint8_t stakeraddress[21]; + char stakerbase58[80]; + uint16_t stakerbase58size; + uint8_t delegationfee; + stakeraddress[0] = COIN_P2PKH_VERSION; + + for (i = 0; i < sizeof(stakeraddress); i++) { + stakeraddress[i + 1] = script[pos + + 17 + i]; + } + stakerbase58size = public_key_to_encoded_base58( + stakeraddress, sizeof(stakeraddress), + stakerbase58, sizeof(stakerbase58), + COIN_P2PKH_VERSION, 1); + stakerbase58[stakerbase58size] = '\0'; + + delegationfee = script[pos + 17 + 20 + + 31]; + snprintf(out, out_len, + "Delegate to %s (fee %d %%)", stakerbase58, + delegationfee); + } else if (strncmp(functionhash, REMOVE_DELEGATION_HASH, + sizeof(functionhash)) == 0) { + strcpy(out, "Undelegate"); + } + } else { + uint8_t contractaddressstring[41]; + const char *hex = "0123456789ABCDEF"; + for (i = 0; i < sizeof(contractaddressstring); i = i + 2) { + contractaddressstring[i] = hex[(contractaddress[i / 2] >> 4) + & 0xF]; + contractaddressstring[i + 1] = + hex[contractaddress[i / 2] & 0xF]; + } + contractaddressstring[40] = '\0'; + snprintf(out, out_len, + "Call contract %s", contractaddressstring); + } + + return 1; +} diff --git a/src/common/script.h b/src/common/script.h index ab3065f94..995b00956 100644 --- a/src/common/script.h +++ b/src/common/script.h @@ -142,6 +142,12 @@ enum opcodetype { // Opcode added by BIP 342 (Tapscript) OP_CHECKSIGADD = 0xba, + // Execute EXT byte code. + OP_CREATE = 0xc1, + OP_CALL = 0xc2, + OP_SPEND = 0xc3, + OP_SENDER = 0xc4, + OP_INVALIDOPCODE = 0xff, }; @@ -151,7 +157,11 @@ typedef enum { SCRIPT_TYPE_P2WPKH = 0x02, SCRIPT_TYPE_P2WSH = 0x03, SCRIPT_TYPE_P2TR = 0x04, - SCRIPT_TYPE_UNKNOWN_SEGWIT = 0xFF // a valid but undefined segwit script + SCRIPT_TYPE_UNKNOWN_SEGWIT = 0xFF, // a valid but undefined segwit script + SCRIPT_TYPE_CREATE_SENDER, + SCRIPT_TYPE_CALL_SENDER, + SCRIPT_TYPE_CREATE, + SCRIPT_TYPE_CALL, } script_type_e; static inline bool is_p2wpkh(const uint8_t script[], size_t script_len) { @@ -166,6 +176,12 @@ static inline bool is_opreturn(const uint8_t script[], size_t script_len) { return script_len > 0 && script_len <= 83 && script[0] == OP_RETURN; } +bool is_opcreate(const uint8_t script[], size_t script_len); + +bool is_opcall(const uint8_t script[], size_t script_len); + +bool is_opsender(const uint8_t script[], size_t script_len); + /** * Returns the size in bytes of the minimal push opcode for , where n a uint32_t. */ @@ -201,6 +217,7 @@ int get_script_address(const uint8_t script[], size_t script_len, char *out, siz // the longest OP_RETURN description "OP_RETURN 0x" followed by 160 hexadecimal characters #define MAX_OPRETURN_OUTPUT_DESC_SIZE (12 + 80 * 2 + 1) +#define MAX_OPRETURN_OUTPUT_DESC_SIZE_SHORT (9 + 1) /** * Formats a valid OP_RETURN script for user verification. The resulting string is "OP_RETURN @@ -222,4 +239,13 @@ int get_script_address(const uint8_t script[], size_t script_len, char *out, siz */ int format_opscript_script(const uint8_t script[], size_t script_len, - char out[static MAX_OPRETURN_OUTPUT_DESC_SIZE]); \ No newline at end of file + char out[static MAX_OPRETURN_OUTPUT_DESC_SIZE]); + +int format_opscript_script_short(const uint8_t script[], + size_t script_len, + char out[static MAX_OPRETURN_OUTPUT_DESC_SIZE_SHORT]); + +bool get_script_size(uint8_t *buffer, size_t maxSize, unsigned int *scriptSize, unsigned int *discardSize); +bool get_script_sender_address(uint8_t *buffer, size_t size, uint8_t *script); +bool get_sender_sig(uint8_t *buffer, size_t size, uint8_t **sig, unsigned int *sigSize); +bool opcall_addr_encode(const uint8_t script[], size_t script_len, char *out, size_t out_len, bool isOpSender); diff --git a/src/constants.h b/src/constants.h index 4a83dac61..d849e3581 100644 --- a/src/constants.h +++ b/src/constants.h @@ -1,5 +1,8 @@ #pragma once +#define BIP32_PUBKEY_VERSION_MAINNET 0x0488B21E +#define BIP32_PUBKEY_VERSION_TESTNET 0x043587CF + /** * Instruction class of the Bitcoin application. */ @@ -24,12 +27,13 @@ /** * Maximum scriptPubKey length for an input that we can sign. */ -#define MAX_PREVOUT_SCRIPTPUBKEY_LEN 34 // P2WSH's scriptPubKeys are the longest supported +#define MAX_PREVOUT_SCRIPTPUBKEY_LEN 35 // P2PK's scriptPubKeys are the longest supported /** * Maximum scriptPubKey length for an output that we can recognize. */ -#define MAX_OUTPUT_SCRIPTPUBKEY_LEN 83 // max 83 for OP_RETURN; other scripts are shorter +#define MAX_OUTPUT_SCRIPTPUBKEY_LEN 400 // max 393 for contracts; other scripts are shorter +#define MAX_INPUT_SCRIPTPUBKEY_LEN 83 // max 83 for OP_RETURN; other scripts are shorter /** * Maximum length of a wallet registered into the device (characters), excluding terminating NULL. @@ -58,4 +62,4 @@ #define MAX_STANDARD_P2WSH_STACK_ITEMS 100U #define MAX_STANDARD_P2WSH_SCRIPT_SIZE 3600U -#define MAX_OPS_PER_SCRIPT 201U \ No newline at end of file +#define MAX_OPS_PER_SCRIPT 201U diff --git a/src/crypto.c b/src/crypto.c index 37c0a194a..25e2202db 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -266,10 +266,27 @@ uint32_t crypto_get_key_fingerprint(const uint8_t pub_key[static 33]) { return read_u32_be(key_rip, 0); } +void crypto_get_master_fingerprint_path(bip32_path_t* path) +{ + path->length = 0; + if (BIP32_PUBKEY_VERSION == BIP32_PUBKEY_VERSION_MAINNET) { // mainnet + // Mainnet fingerprint bip32 path m/44'/88' in HWI + path->path[0] = 0x8000002c; + path->path[1] = 0x80000058; + path->length = 2; + } else if (BIP32_PUBKEY_VERSION == BIP32_PUBKEY_VERSION_TESTNET) { // testnet + // Testnet fingerprint bip32 path m/0'/45342' in HWI + path->path[0] = 0x80000000; + path->path[1] = 0x8000b11e; + path->length = 2; + } +} + uint32_t crypto_get_master_key_fingerprint() { uint8_t master_pub_key[33]; - uint32_t bip32_path[] = {}; - crypto_get_compressed_pubkey_at_path(bip32_path, 0, master_pub_key, NULL); + bip32_path_t path; + crypto_get_master_fingerprint_path(&path); + crypto_get_compressed_pubkey_at_path(path.path, path.length, master_pub_key, NULL); return crypto_get_key_fingerprint(master_pub_key); } diff --git a/src/crypto.h b/src/crypto.h index 89ac3adfd..dc72a30b7 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -11,6 +11,7 @@ #include "./common/bip32.h" #include "./common/varint.h" #include "./common/write.h" +#include "./swap/bip32_path.h" /** * A serialized extended pubkey according to BIP32 specifications. @@ -244,6 +245,14 @@ bool crypto_get_compressed_pubkey_at_path(const uint32_t bip32_path[], */ uint32_t crypto_get_key_fingerprint(const uint8_t pub_key[static 33]); +/** + * Computes the master fingerprint path as per BIP32. + * + * @param[out] path + * Master fingerprint path. + */ +void crypto_get_master_fingerprint_path(bip32_path_t* path); + /** * Computes the fingerprint of the master key as per BIP32. * diff --git a/src/handler/get_master_fingerprint.c b/src/handler/get_master_fingerprint.c index 4f46917c2..08eb42691 100644 --- a/src/handler/get_master_fingerprint.c +++ b/src/handler/get_master_fingerprint.c @@ -34,7 +34,9 @@ void handler_get_master_fingerprint(dispatcher_context_t *dc, uint8_t protocol_v } uint8_t master_pubkey[33]; - if (!crypto_get_compressed_pubkey_at_path((uint32_t[]){}, 0, master_pubkey, NULL)) { + bip32_path_t path; + crypto_get_master_fingerprint_path(&path); + if (!crypto_get_compressed_pubkey_at_path(path.path, path.length, master_pubkey, NULL)) { SEND_SW(dc, SW_BAD_STATE); // should never happen return; } diff --git a/src/handler/handlers.h b/src/handler/handlers.h index ad3d28d2b..c9b5a7ca3 100644 --- a/src/handler/handlers.h +++ b/src/handler/handlers.h @@ -8,3 +8,4 @@ void handler_get_wallet_address(dispatcher_context_t *dispatcher_context, uint8_ void handler_register_wallet(dispatcher_context_t *dispatcher_context, uint8_t p2); void handler_sign_message(dispatcher_context_t *dispatcher_context, uint8_t p2); void handler_sign_psbt(dispatcher_context_t *dispatcher_context, uint8_t p2); +void handler_sign_sender_psbt(dispatcher_context_t *dispatcher_context, uint8_t p2); diff --git a/src/handler/lib/get_merkle_preimage.c b/src/handler/lib/get_merkle_preimage.c index 5afe1bd37..c5ceaaafc 100644 --- a/src/handler/lib/get_merkle_preimage.c +++ b/src/handler/lib/get_merkle_preimage.c @@ -113,6 +113,8 @@ int call_get_merkle_preimage(dispatcher_context_t *dispatcher_context, n_bytes); // write bytes to output + data_ptr = + dispatcher_context->read_buffer.ptr + dispatcher_context->read_buffer.offset; buffer_write_bytes(&out_buffer, data_ptr, n_bytes); bytes_remaining -= n_bytes; @@ -128,4 +130,4 @@ int call_get_merkle_preimage(dispatcher_context_t *dispatcher_context, } return (int) (preimage_len - 1); -} \ No newline at end of file +} diff --git a/src/handler/lib/policy.c b/src/handler/lib/policy.c index 053ada479..78d7fd8dd 100644 --- a/src/handler/lib/policy.c +++ b/src/handler/lib/policy.c @@ -941,7 +941,7 @@ int __attribute__((noinline)) compute_taptree_hash(dispatcher_context_t *dc, int get_wallet_script(dispatcher_context_t *dispatcher_context, const policy_node_t *policy, const wallet_derivation_info_t *wdi, - uint8_t out[static 34]) { + uint8_t out[static 35]) { int script_type = -1; cx_sha256_t hash_context; @@ -966,6 +966,21 @@ int get_wallet_script(dispatcher_context_t *dispatcher_context, out[23] = OP_EQUALVERIFY; out[24] = OP_CHECKSIG; return 25; + } else if (policy->type == TOKEN_PK) { + uint8_t compressed_pubkey[33]; + policy_node_with_key_t *pkh_policy = (policy_node_with_key_t *) policy; + if (0 > get_derived_pubkey(dispatcher_context, + wdi, + pkh_policy->key_placeholder, + compressed_pubkey)) { + return -1; + } + out[0] = 33; // PUSH 33 bytes + + memcpy(out + 1, compressed_pubkey, 33); + + out[34] = OP_CHECKSIG; + return 35; } else if (policy->type == TOKEN_WPKH) { uint8_t compressed_pubkey[33]; policy_node_with_key_t *wpkh_policy = (policy_node_with_key_t *) policy; diff --git a/src/handler/sign_message.c b/src/handler/sign_message.c index a264d7634..78c8f2602 100644 --- a/src/handler/sign_message.c +++ b/src/handler/sign_message.c @@ -30,7 +30,7 @@ #include "handlers.h" -static unsigned char const BSM_SIGN_MAGIC[] = {'\x18', 'B', 'i', 't', 'c', 'o', 'i', 'n', ' ', +static unsigned char const BSM_SIGN_MAGIC[] = {'\x15', 'Q', 't', 'u', 'm', ' ', 'S', 'i', 'g', 'n', 'e', 'd', ' ', 'M', 'e', 's', 's', 'a', 'g', 'e', ':', '\n'}; diff --git a/src/handler/sign_psbt.c b/src/handler/sign_psbt.c index 90d42f2b1..e77340a8e 100644 --- a/src/handler/sign_psbt.c +++ b/src/handler/sign_psbt.c @@ -73,10 +73,30 @@ typedef struct { uint8_t scriptPubKey[MAX_OUTPUT_SCRIPTPUBKEY_LEN]; size_t scriptPubKey_len; -} in_out_info_t; +} out_info_t; typedef struct { - in_out_info_t in_out; + merkleized_map_commitment_t map; + + bool unexpected_pubkey_error; // Set to true if the pubkey in the keydata of + // PSBT_{IN,OUT}_BIP32_DERIVATION or + // PSBT_{IN,OUT}_TAP_BIP32_DERIVATION is not the correct length. + + bool placeholder_found; // Set to true if a matching placeholder is found in the input info + + bool is_change; + int address_index; + + // For an output, its scriptPubKey + // for an input, the prevout's scriptPubKey (either from the non-witness-utxo, or from the + // witness-utxo) + + uint8_t scriptPubKey[MAX_INPUT_SCRIPTPUBKEY_LEN]; + size_t scriptPubKey_len; +} in_info_t; + +typedef struct { + in_info_t in_out; bool has_witnessUtxo; bool has_nonWitnessUtxo; bool has_redeemScript; @@ -98,7 +118,7 @@ typedef struct { } input_info_t; typedef struct { - in_out_info_t in_out; + out_info_t in_out; uint64_t value; } output_info_t; @@ -122,6 +142,12 @@ typedef struct { uint8_t sha_outputs[32]; } segwit_hashes_t; +typedef struct { + uint8_t sha_prevouts[32]; + uint8_t sha_sequences[32]; + uint8_t sha_outputs[32]; +} output_hashes_t; + typedef struct { uint32_t master_key_fingerprint; uint32_t tx_version; @@ -178,6 +204,10 @@ It would be possible to generalize to more complex scripts, but it makes it more the right paths to identify internal inputs/outputs. */ +bool compute_op_sender_hashes(dispatcher_context_t *dc, sign_psbt_state_t *st, uint8_t* sha_prevouts, uint8_t* sha_sequences, uint8_t* sha_outputs); +bool hash_sender_start(cx_sha256_t* sighash_context, uint8_t* tx_version, uint8_t* sha_prevouts, uint8_t* sha_sequences, uint8_t* sender_script, size_t sender_script_len, uint8_t* output_value); +void hash_sender_finalize(cx_sha256_t* sighash_context, uint8_t* data_buffer, uint8_t* sha_outputs); + // HELPER FUNCTIONS // Updates the hash_context with the output of given index // returns -1 on error. 0 on success. @@ -219,7 +249,7 @@ static int hash_output_n(dispatcher_context_t *dc, 1, out_script, sizeof(out_script)); - if (out_script_len == -1) { + if (out_script_len < 0) { return -1; } @@ -365,10 +395,95 @@ static int get_amount_scriptpubkey_from_psbt( // Convenience function to share common logic when processing all the // PSBT_{IN|OUT}_{TAP}?_BIP32_DERIVATION fields. -static int read_change_and_index_from_psbt_bip32_derivation( +static int read_change_and_index_from_psbt_bip32_derivation_in( + dispatcher_context_t *dc, + placeholder_info_t *placeholder_info, + in_info_t *in_out, + int psbt_key_type, + buffer_t *data, + const merkleized_map_commitment_t *map_commitment, + int index) { + uint8_t bip32_derivation_pubkey[33]; + + bool is_tap = psbt_key_type == PSBT_IN_TAP_BIP32_DERIVATION || + psbt_key_type == PSBT_OUT_TAP_BIP32_DERIVATION; + int key_len = is_tap ? 32 : 33; + + if (!buffer_read_bytes(data, + bip32_derivation_pubkey, + key_len) // read compressed pubkey or x-only pubkey + || buffer_can_read(data, 1) // ...but should not be able to read more + ) { + PRINTF("Unexpected pubkey length\n"); + in_out->unexpected_pubkey_error = true; + return -1; + } + + // get the corresponding value in the values Merkle tree, + // then fetch the bip32 path from the field + uint32_t fpt_der[1 + MAX_BIP32_PATH_STEPS]; + + int der_len = extract_bip32_derivation(dc, + psbt_key_type, + map_commitment->values_root, + map_commitment->size, + index, + fpt_der); + if (der_len < 0) { + PRINTF("Failed to read BIP32_DERIVATION\n"); + return -1; + } + + if (der_len < 2 || der_len > MAX_BIP32_PATH_STEPS) { + PRINTF("BIP32_DERIVATION path too long\n"); + return -1; + } + + // if this derivation path matches the internal placeholder, + // we use it to detect whether the current input is change or not, + // and store its address index + if (fpt_der[0] == placeholder_info->fingerprint && + der_len == placeholder_info->key_derivation_length + 2) { + for (int i = 0; i < placeholder_info->key_derivation_length; i++) { + if (placeholder_info->key_derivation[i] != fpt_der[1 + i]) { + return 0; + } + } + + uint32_t change = fpt_der[1 + der_len - 2]; + uint32_t addr_index = fpt_der[1 + der_len - 1]; + + // check that we can indeed derive the same key from the current placeholder + serialized_extended_pubkey_t pubkey; + if (0 > bip32_CKDpub(&placeholder_info->pubkey, change, &pubkey)) return -1; + if (0 > bip32_CKDpub(&pubkey, addr_index, &pubkey)) return -1; + + int pk_offset = is_tap ? 1 : 0; + if (memcmp(pubkey.compressed_pubkey + pk_offset, bip32_derivation_pubkey, key_len) != 0) { + return 0; + } + + // check if the 'change' derivation step is indeed coherent with placeholder + if (change == placeholder_info->placeholder.num_first) { + in_out->is_change = false; + in_out->address_index = addr_index; + } else if (change == placeholder_info->placeholder.num_second) { + in_out->is_change = true; + in_out->address_index = addr_index; + } else { + return 0; + } + + in_out->placeholder_found = true; + return 1; + } + return 0; +} + +static int read_change_and_index_from_psbt_bip32_derivation_out( dispatcher_context_t *dc, placeholder_info_t *placeholder_info, - in_out_info_t *in_out, + out_info_t *in_out, int psbt_key_type, buffer_t *data, const merkleized_map_commitment_t *map_commitment, @@ -457,9 +572,35 @@ static int read_change_and_index_from_psbt_bip32_derivation( * * @return 1 if the given input/output is internal; 0 if external; -1 on error. */ -static int is_in_out_internal(dispatcher_context_t *dispatcher_context, +static int is_in_out_internal_in(dispatcher_context_t *dispatcher_context, + const sign_psbt_state_t *state, + const in_info_t *in_out_info, + bool is_input) { + // If we did not find any info about the pubkey associated to the placeholder we're considering, + // then it's external + if (!in_out_info->placeholder_found) { + return 0; + } + + if (!is_input && in_out_info->is_change != 1) { + // unlike for inputs, we only consider outputs internal if they are on the change path + return 0; + } + + return compare_wallet_script_at_path(dispatcher_context, + in_out_info->is_change, + in_out_info->address_index, + &state->wallet_policy_map, + state->wallet_header_version, + state->wallet_header_keys_info_merkle_root, + state->wallet_header_n_keys, + in_out_info->scriptPubKey, + in_out_info->scriptPubKey_len); +} + +static int is_in_out_internal_out(dispatcher_context_t *dispatcher_context, const sign_psbt_state_t *state, - const in_out_info_t *in_out_info, + const out_info_t *in_out_info, bool is_input) { // If we did not find any info about the pubkey associated to the placeholder we're considering, // then it's external @@ -566,7 +707,7 @@ init_global_state(dispatcher_context_t *dc, sign_psbt_state_t *st) { 1, raw_result, sizeof(raw_result)); - if (result_len == -1) { + if (result_len < 0) { st->locktime = 0; } else if (result_len != 4) { SEND_SW(dc, SW_INCORRECT_DATA); @@ -844,7 +985,7 @@ static void input_keys_callback(dispatcher_context_t *dc, key_type == PSBT_IN_TAP_BIP32_DERIVATION) && !callback_data->input->in_out.placeholder_found) { if (0 > - read_change_and_index_from_psbt_bip32_derivation(dc, + read_change_and_index_from_psbt_bip32_derivation_in(dc, callback_data->placeholder_info, &callback_data->input->in_out, key_type, @@ -974,7 +1115,7 @@ preprocess_inputs(dispatcher_context_t *dc, // check if the input is internal; if not, continue - int is_internal = is_in_out_internal(dc, st, &input.in_out, true); + int is_internal = is_in_out_internal_in(dc, st, &input.in_out, true); if (is_internal < 0) { PRINTF("Error checking if input %d is internal\n", cur_input_index); SEND_SW(dc, SW_INCORRECT_DATA); @@ -1131,7 +1272,7 @@ static void output_keys_callback(dispatcher_context_t *dc, if ((key_type == PSBT_OUT_BIP32_DERIVATION || key_type == PSBT_OUT_TAP_BIP32_DERIVATION) && !callback_data->output->in_out.placeholder_found) { if (0 > - read_change_and_index_from_psbt_bip32_derivation(dc, + read_change_and_index_from_psbt_bip32_derivation_out(dc, callback_data->placeholder_info, &callback_data->output->in_out, key_type, @@ -1152,7 +1293,7 @@ static bool __attribute__((noinline)) display_output(dispatcher_context_t *dc, (void) cur_output_index; // show this output's address - char output_address[MAX(MAX_ADDRESS_LENGTH_STR + 1, MAX_OPRETURN_OUTPUT_DESC_SIZE)]; + char output_address[MAX(MAX_ADDRESS_LENGTH_STR + 1, MAX_OPRETURN_OUTPUT_DESC_SIZE_SHORT)]; int address_len = get_script_address(output->in_out.scriptPubKey, output->in_out.scriptPubKey_len, output_address, @@ -1160,7 +1301,7 @@ static bool __attribute__((noinline)) display_output(dispatcher_context_t *dc, if (address_len < 0) { // script does not have an address; check if OP_RETURN if (is_opreturn(output->in_out.scriptPubKey, output->in_out.scriptPubKey_len)) { - int res = format_opscript_script(output->in_out.scriptPubKey, + int res = format_opscript_script_short(output->in_out.scriptPubKey, output->in_out.scriptPubKey_len, output_address); if (res == -1) { @@ -1205,7 +1346,7 @@ static bool __attribute__((noinline)) display_output(dispatcher_context_t *dc, static bool read_outputs(dispatcher_context_t *dc, sign_psbt_state_t *st, placeholder_info_t *placeholder_info, - bool dry_run) { + bool dry_run, output_hashes_t *hashes, uint8_t* hash, int* output_index) { // the counter used when showing outputs to the user, which ignores change outputs // (0-indexed here, although the UX starts with 1) int external_outputs_count = 0; @@ -1213,6 +1354,7 @@ static bool read_outputs(dispatcher_context_t *dc, for (unsigned int cur_output_index = 0; cur_output_index < st->n_outputs; cur_output_index++) { output_info_t output; memset(&output, 0, sizeof(output)); + bool isOpSender = false; output_keys_callback_data_t callback_data = {.output = &output, .placeholder_info = placeholder_info}; @@ -1236,9 +1378,9 @@ static bool read_outputs(dispatcher_context_t *dc, return false; } + // Read output amount + uint8_t raw_result[8]; if (!dry_run) { - // Read output amount - uint8_t raw_result[8]; // Read the output's amount int result_len = call_get_merkleized_map_value(dc, @@ -1265,14 +1407,14 @@ static bool read_outputs(dispatcher_context_t *dc, output.in_out.scriptPubKey, sizeof(output.in_out.scriptPubKey)); - if (result_len == -1 || result_len > (int) sizeof(output.in_out.scriptPubKey)) { + if (result_len < 0 || result_len > (int) sizeof(output.in_out.scriptPubKey)) { SEND_SW(dc, SW_INCORRECT_DATA); return false; } output.in_out.scriptPubKey_len = result_len; - int is_internal = is_in_out_internal(dc, st, &output.in_out, false); + int is_internal = is_in_out_internal_out(dc, st, &output.in_out, false); if (is_internal < 0) { PRINTF("Error checking if output %d is internal\n", cur_output_index); @@ -1285,12 +1427,39 @@ static bool read_outputs(dispatcher_context_t *dc, if (!dry_run && !display_output(dc, st, cur_output_index, external_outputs_count, &output)) return false; + if(hash) + { + isOpSender = is_opsender(output.in_out.scriptPubKey, output.in_out.scriptPubKey_len); + } } else if (!dry_run) { // valid change address, nothing to show to the user st->outputs.change_total_amount += output.value; ++st->outputs.n_change; } + + if(isOpSender) + { + { + cx_sha256_t sighash_context; + + uint8_t tx_version[4]; + write_u32_le(tx_version, 0, st->tx_version); + if(!hash_sender_start(&sighash_context, tx_version, hashes->sha_prevouts, hashes->sha_sequences, output.in_out.scriptPubKey, output.in_out.scriptPubKey_len, raw_result)) + return false; + + uint8_t data_buffer[8]; + write_u32_le(data_buffer, 0, st->locktime); + write_u32_le(data_buffer, 4, 0x01); + hash_sender_finalize(&sighash_context, data_buffer, hashes->sha_outputs); + + crypto_hash_digest(&sighash_context.header, hash, 32); + } + + // Rehash + cx_hash_sha256(hash, 32, hash, 32); + *output_index = cur_output_index; + } } st->outputs.n_external = external_outputs_count; @@ -1299,7 +1468,7 @@ static bool read_outputs(dispatcher_context_t *dc, } static bool __attribute__((noinline)) -process_outputs(dispatcher_context_t *dc, sign_psbt_state_t *st) { +process_outputs(dispatcher_context_t *dc, sign_psbt_state_t *st, output_hashes_t *hashes, uint8_t* hash, int* output_index) { /** OUTPUTS VERIFICATION FLOW * * For each output, check if it's a change address. @@ -1329,13 +1498,13 @@ process_outputs(dispatcher_context_t *dc, sign_psbt_state_t *st) { } #endif - if (!read_outputs(dc, st, &placeholder_info, false)) return false; + if (!read_outputs(dc, st, &placeholder_info, false, hashes, hash, output_index)) return false; return true; } static bool __attribute__((noinline)) -confirm_transaction(dispatcher_context_t *dc, sign_psbt_state_t *st) { +confirm_transaction(dispatcher_context_t *dc, sign_psbt_state_t *st, bool sign_sender) { LOG_PROCESSOR(__FILE__, __LINE__, __func__); if (st->inputs_total_amount < st->outputs.total_amount) { @@ -1379,7 +1548,7 @@ confirm_transaction(dispatcher_context_t *dc, sign_psbt_state_t *st) { } else { // Show final user validation UI bool is_self_transfer = st->outputs.n_external == 0; - if (!ui_validate_transaction(dc, COIN_COINID_SHORT, fee, is_self_transfer)) { + if (!ui_validate_transaction(dc, COIN_COINID_SHORT, fee, is_self_transfer, sign_sender)) { SEND_SW(dc, SW_DENY); ui_post_processing_confirm_transaction(dc, false); return false; @@ -2490,6 +2659,39 @@ sign_transaction(dispatcher_context_t *dc, return true; } +static bool __attribute__((noinline)) sign_transaction_output(dispatcher_context_t *dc, + sign_psbt_state_t *st, + uint32_t *sign_path, + uint8_t sign_path_len, + uint8_t* sighash, + unsigned int output_index) { + LOG_PROCESSOR(__FILE__, __LINE__, __func__); + + uint8_t sig[MAX_DER_SIG_LEN + 1]; // extra byte for the appended sighash-type + + uint8_t pubkey[33]; + + int sig_len = crypto_ecdsa_sign_sha256_hash_with_key(sign_path, + sign_path_len, + sighash, + pubkey, + sig, + NULL); + if (sig_len < 0) { + // unexpected error when signing + SEND_SW(dc, SW_BAD_STATE); + return false; + } + + // append the sighash type byte + uint8_t sighash_byte = 0x01; + sig[sig_len++] = sighash_byte; + + if (!yield_signature(dc, st, output_index, pubkey, 33, NULL, sig, sig_len)) return false; + + return true; +} + void handler_sign_psbt(dispatcher_context_t *dc, uint8_t protocol_version) { LOG_PROCESSOR(__FILE__, __LINE__, __func__); @@ -2535,13 +2737,13 @@ void handler_sign_psbt(dispatcher_context_t *dc, uint8_t protocol_version) { * For each output, check if it's a change address. * Show each output that is not a change address to the user for verification. */ - if (!process_outputs(dc, &st)) return; + if (!process_outputs(dc, &st, 0, 0, 0)) return; /** TANSACTION CONFIRMATION * * Show summary info to the user (transaction fees), ask for final confirmation */ - if (!confirm_transaction(dc, &st)) return; + if (!confirm_transaction(dc, &st, false)) return; /** SIGNING FLOW * @@ -2557,3 +2759,205 @@ void handler_sign_psbt(dispatcher_context_t *dc, uint8_t protocol_version) { SEND_SW(dc, SW_OK); } + +bool hash_sender_start(cx_sha256_t* sighash_context, uint8_t* tx_version, uint8_t* sha_prevouts, uint8_t* sha_sequences, uint8_t* sender_script, size_t sender_script_len, uint8_t* output_value) +{ + cx_sha256_init(sighash_context); + + // Use cache data generated from Segwit + PRINTF("--- ADD TO HASH SENDER:\n%.*H\n", 4, tx_version); + crypto_hash_update(&sighash_context->header, tx_version, 4); + + PRINTF("--- ADD TO HASH SENDER:\n%.*H\n", 32, sha_prevouts); + crypto_hash_update(&sighash_context->header, sha_prevouts, 32); + + PRINTF("--- ADD TO HASH SENDER:\n%.*H\n", 32, sha_sequences); + crypto_hash_update(&sighash_context->header, sha_sequences, 32); + + // Op sender specific data + PRINTF("--- ADD TO HASH SENDER:\n%.*H\n", sender_script_len, sender_script); + crypto_hash_update(&sighash_context->header, output_value, 8); + + uint8_t output_script_size[3]; + size_t output_script_size_len = 1; + if(sender_script_len < 0xFD) + { + output_script_size[0] = sender_script_len; + } + else + { + output_script_size[0] = 0xFD; + output_script_size_len = 3; + write_u16_le(output_script_size, 1, sender_script_len); + } + + crypto_hash_update(&sighash_context->header, output_script_size, output_script_size_len); + + crypto_hash_update(&sighash_context->header, sender_script, sender_script_len); + + uint8_t script_code[26]; + if(!get_script_sender_address(sender_script, sender_script_len, script_code)) + return 0; + + PRINTF("--- ADD TO HASH SENDER:\n%.*H\n", sizeof(script_code), script_code); + crypto_hash_update(&sighash_context->header, script_code, 26); + + PRINTF("--- ADD TO HASH SENDER:\n%.*H\n", 8, output_value); + crypto_hash_update(&sighash_context->header, output_value, 8); + + return 1; +} + +void hash_sender_finalize(cx_sha256_t* sighash_context, uint8_t* data_buffer, uint8_t* sha_outputs) +{ + PRINTF("--- ADD TO HASH SENDER:\n%.*H\n", 32, sha_outputs); + crypto_hash_update(&sighash_context->header, sha_outputs, 32); + PRINTF("--- ADD TO HASH SENDER:\n%.*H\n", 8, data_buffer); + crypto_hash_update(&sighash_context->header, data_buffer, 8); +} + +bool compute_op_sender_hashes(dispatcher_context_t *dc, sign_psbt_state_t *st, uint8_t* sha_prevouts, uint8_t* sha_sequences, uint8_t* sha_outputs) { + if(sha_prevouts && sha_sequences) + { + // compute sha_prevouts and sha_sequences + cx_sha256_t sha_prevouts_context, sha_sequences_context; + + // compute hashPrevouts and hashSequence + cx_sha256_init(&sha_prevouts_context); + cx_sha256_init(&sha_sequences_context); + + for (unsigned int i = 0; i < st->n_inputs; i++) { + // get this input's map + merkleized_map_commitment_t ith_map; + + int res = call_get_merkleized_map(dc, st->inputs_root, st->n_inputs, i, &ith_map); + if (res < 0) { + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } + + // get prevout hash and output index for the i-th input + uint8_t ith_prevout_hash[32]; + if (32 != call_get_merkleized_map_value(dc, + &ith_map, + (uint8_t[]){PSBT_IN_PREVIOUS_TXID}, + 1, + ith_prevout_hash, + 32)) { + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } + + crypto_hash_update(&sha_prevouts_context.header, ith_prevout_hash, 32); + + uint8_t ith_prevout_n_raw[4]; + if (4 != call_get_merkleized_map_value(dc, + &ith_map, + (uint8_t[]){PSBT_IN_OUTPUT_INDEX}, + 1, + ith_prevout_n_raw, + 4)) { + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } + + crypto_hash_update(&sha_prevouts_context.header, ith_prevout_n_raw, 4); + + uint8_t ith_nSequence_raw[4]; + if (4 != call_get_merkleized_map_value(dc, + &ith_map, + (uint8_t[]){PSBT_IN_SEQUENCE}, + 1, + ith_nSequence_raw, + 4)) { + // if no PSBT_IN_SEQUENCE is present, we must assume nSequence 0xFFFFFFFF + memset(ith_nSequence_raw, 0xFF, 4); + } + + crypto_hash_update(&sha_sequences_context.header, ith_nSequence_raw, 4); + } + + crypto_hash_digest(&sha_prevouts_context.header, sha_prevouts, 32); + cx_hash_sha256(sha_prevouts, 32, sha_prevouts, 32); + crypto_hash_digest(&sha_sequences_context.header, sha_sequences, 32); + cx_hash_sha256(sha_sequences, 32, sha_sequences, 32); + } + + if(sha_outputs) + { + // compute sha_outputs + cx_sha256_t sha_outputs_context; + cx_sha256_init(&sha_outputs_context); + + if (hash_outputs(dc, st, &sha_outputs_context.header) == -1) { + SEND_SW(dc, SW_INCORRECT_DATA); + return false; + } + + crypto_hash_digest(&sha_outputs_context.header, sha_outputs, 32); + cx_hash_sha256(sha_outputs, 32, sha_outputs, 32); + } + + return true; +} + +void handler_sign_sender_psbt(dispatcher_context_t *dc, uint8_t protocol_version) { + LOG_PROCESSOR(__FILE__, __LINE__, __func__); + + uint8_t bip32_path_len = 0; + uint32_t bip32_path[MAX_BIP32_PATH_STEPS]; + sign_psbt_state_t st; + memset(&st, 0, sizeof(st)); + + // Device must be unlocked + if (os_global_pin_is_validated() != BOLOS_UX_OK) { + SEND_SW(dc, SW_SECURITY_STATUS_NOT_SATISFIED); + return; + } + + st.protocol_version = protocol_version; + + if (!buffer_read_u8(&dc->read_buffer, &bip32_path_len) || + !buffer_read_bip32_path(&dc->read_buffer, bip32_path, bip32_path_len)) { + SEND_SW(dc, SW_WRONG_DATA_LENGTH); + return; + } + + if (bip32_path_len > MAX_BIP32_PATH_STEPS) { + SEND_SW(dc, SW_INCORRECT_DATA); + return; + } + + // Read APDU inputs, intialize global state and read global PSBT map + if (!init_global_state(dc, &st)) return; + + { + // Bitmap to keep track of which inputs are internal + uint8_t internal_inputs[BITVECTOR_REAL_SIZE(MAX_N_INPUTS_CAN_SIGN)]; + + // Inputs verification flow + if (!preprocess_inputs(dc, &st, internal_inputs)) return; + + // Inputs verification alert + if (!show_alerts(dc, &st, internal_inputs)) return; + } + + // Process sender outputs + { + uint8_t hash[32]; + int output_index = -1; + { + output_hashes_t hashes; + if (!compute_op_sender_hashes(dc, &st, hashes.sha_prevouts, hashes.sha_sequences, hashes.sha_outputs)) return; + if (!process_outputs(dc, &st, &hashes, hash, &output_index)) return; + } + if (output_index == -1) { + SEND_SW(dc, SW_SIGNATURE_FAIL); + return; + } + if (!confirm_transaction(dc, &st, true)) return; + if (!sign_transaction_output(dc, &st, bip32_path, bip32_path_len, hash, output_index)) return; + } + + SEND_SW(dc, SW_OK); +} diff --git a/src/handler/sign_psbt/compare_wallet_script_at_path.c b/src/handler/sign_psbt/compare_wallet_script_at_path.c index 08f2c2f8d..5e5c1320b 100644 --- a/src/handler/sign_psbt/compare_wallet_script_at_path.c +++ b/src/handler/sign_psbt/compare_wallet_script_at_path.c @@ -21,6 +21,11 @@ int compare_wallet_script_at_path(dispatcher_context_t *dispatcher_context, // derive wallet's scriptPubKey, check if it matches the expected one uint8_t wallet_script[MAX_PREVOUT_SCRIPTPUBKEY_LEN]; + bool need_p2pk_type = expected_script_len == 35 && policy->type == TOKEN_PKH; + if(need_p2pk_type) { + // Overwrite PKH to PK for P2PK inputs + ((policy_node_t*)policy)->type = TOKEN_PK; + } int wallet_script_len = get_wallet_script(dispatcher_context, policy, @@ -30,6 +35,9 @@ int compare_wallet_script_at_path(dispatcher_context_t *dispatcher_context, .change = change, .address_index = address_index}, wallet_script); + if(need_p2pk_type) { + ((policy_node_t*)policy)->type = TOKEN_PKH; + } if (wallet_script_len < 0) { PRINTF("Failed to get wallet script\n"); return -1; // shouldn't happen diff --git a/src/main.c b/src/main.c index 7cf5c1c2a..9a40a9a21 100644 --- a/src/main.c +++ b/src/main.c @@ -99,6 +99,11 @@ const command_descriptor_t COMMAND_DESCRIPTORS[] = { .ins = SIGN_MESSAGE, .handler = (command_handler_t)handler_sign_message }, + { + .cla = CLA_APP, + .ins = SIGN_SENDER_PSBT, + .handler = (command_handler_t)handler_sign_sender_psbt + }, }; // clang-format on @@ -147,9 +152,9 @@ void app_main() { continue; } if (cmd.ins != GET_EXTENDED_PUBKEY && cmd.ins != GET_WALLET_ADDRESS && - cmd.ins != SIGN_PSBT && cmd.ins != GET_MASTER_FINGERPRINT) { + cmd.ins != SIGN_PSBT && cmd.ins != GET_MASTER_FINGERPRINT && cmd.ins != SIGN_SENDER_PSBT) { PRINTF( - "Only GET_EXTENDED_PUBKEY, GET_WALLET_ADDRESS, SIGN_PSBT and " + "Only GET_EXTENDED_PUBKEY, GET_WALLET_ADDRESS, SIGN_PSBT, SIGN_SENDER_PSBT and " "GET_MASTER_FINGERPRINT can be called during swap\n"); io_send_sw(SW_INS_NOT_SUPPORTED); continue; diff --git a/src/ui/display.c b/src/ui/display.c index 57db7a691..f8c923090 100644 --- a/src/ui/display.c +++ b/src/ui/display.c @@ -202,12 +202,12 @@ bool ui_validate_output(dispatcher_context_t *context, bool ui_validate_transaction(dispatcher_context_t *context, const char *coin_name, uint64_t fee, - bool is_self_transfer) { + bool is_self_transfer, bool sign_sender) { ui_validate_transaction_state_t *state = (ui_validate_transaction_state_t *) &g_ui_state; format_sats_amount(coin_name, fee, state->fee); - ui_accept_transaction_flow(is_self_transfer); + ui_accept_transaction_flow(is_self_transfer, sign_sender); return io_ui_process(context, true); } diff --git a/src/ui/display.h b/src/ui/display.h index 21eb450cc..f9d86a784 100644 --- a/src/ui/display.h +++ b/src/ui/display.h @@ -54,7 +54,7 @@ typedef struct { typedef struct { char index[sizeof("output #999")]; - char address_or_description[MAX(MAX_ADDRESS_LENGTH_STR + 1, MAX_OPRETURN_OUTPUT_DESC_SIZE)]; + char address_or_description[MAX(MAX_ADDRESS_LENGTH_STR + 1, MAX_OPRETURN_OUTPUT_DESC_SIZE_SHORT)]; char amount[MAX_AMOUNT_LENGTH + 1]; } ui_validate_output_state_t; @@ -135,7 +135,7 @@ bool ui_validate_output(dispatcher_context_t *context, bool ui_validate_transaction(dispatcher_context_t *context, const char *coin_name, uint64_t fee, - bool is_self_transfer); + bool is_self_transfer, bool sign_sender); void set_ux_flow_response(bool approved); @@ -165,7 +165,7 @@ void ui_display_output_address_amount_flow(int index); void ui_display_output_address_amount_no_index_flow(int index); -void ui_accept_transaction_flow(bool is_self_transfer); +void ui_accept_transaction_flow(bool is_self_transfer, bool sign_sender); void ui_display_transaction_prompt(const int external_outputs_total_count); diff --git a/src/ui/display_bagl.c b/src/ui/display_bagl.c index 5bbfd709c..1b58ccec0 100644 --- a/src/ui/display_bagl.c +++ b/src/ui/display_bagl.c @@ -180,6 +180,11 @@ UX_STEP_CB(ux_accept_and_send_step, set_ux_flow_response(true), {&C_icon_validate_14, "Accept", "and send"}); +UX_STEP_CB(ux_sign_sender_step, + pbb, + set_ux_flow_response(true), + {&C_icon_validate_14, "Sign", "OP_SENDER"}); + // Step with wallet icon and "Register wallet" UX_STEP_NOCB(ux_display_register_wallet_step, pb, @@ -408,6 +413,17 @@ UX_FLOW(ux_accept_selftransfer_flow, &ux_accept_and_send_step, &ux_display_reject_step); +// Finalize see the transaction fees and finally sign sender +// #1 screen: eye icon + "Confirm Transaction" +// #2 screen: fee amount +// #3 screen: "Sign OP_SENDER", with approve button +// #4 screen: reject button +UX_FLOW(ux_sign_sender_transaction_flow, + &ux_confirm_transaction_step, + &ux_confirm_transaction_fees_step, + &ux_sign_sender_step, + &ux_display_reject_step); + void ui_display_pubkey_flow(void) { ux_flow_init(0, ux_display_pubkey_flow, NULL); } @@ -466,10 +482,15 @@ void ui_display_output_address_amount_no_index_flow(int index) { ui_display_output_address_amount_flow(index); } -void ui_accept_transaction_flow(bool is_self_transfer) { - ux_flow_init(0, +void ui_accept_transaction_flow(bool is_self_transfer, bool sign_sender) { + if(sign_sender) { + ux_flow_init(0, ux_sign_sender_transaction_flow, NULL); + } + else { + ux_flow_init(0, is_self_transfer ? ux_accept_selftransfer_flow : ux_accept_transaction_flow, NULL); + } } #endif // HAVE_BAGL diff --git a/src/ui/menu.c b/src/ui/menu.c index 88925e5e0..b1b52987c 100644 --- a/src/ui/menu.c +++ b/src/ui/menu.c @@ -21,9 +21,6 @@ #include "../globals.h" #include "menu.h" -#define BIP32_PUBKEY_VERSION_MAINNET 0x0488B21E -#define BIP32_PUBKEY_VERSION_TESTNET 0x043587CF - void ui_menu_main() { if (BIP32_PUBKEY_VERSION == BIP32_PUBKEY_VERSION_MAINNET) { // mainnet ui_menu_main_flow_bitcoin(); diff --git a/src/ui/menu_bagl.c b/src/ui/menu_bagl.c index b0f1b835e..0e698359c 100644 --- a/src/ui/menu_bagl.c +++ b/src/ui/menu_bagl.c @@ -24,10 +24,10 @@ // We have a screen with the icon and "Bitcoin is ready" for Bitcoin, // "Bitcoin Testnet is ready" for Bitcoin Testnet. -UX_STEP_NOCB(ux_menu_ready_step_bitcoin, pnn, {&C_bitcoin_logo, "Bitcoin", "is ready"}); +UX_STEP_NOCB(ux_menu_ready_step_bitcoin, pnn, {&C_bitcoin_logo, "Qtum", "is ready"}); UX_STEP_NOCB(ux_menu_ready_step_bitcoin_testnet, pnn, - {&C_bitcoin_logo, "Bitcoin Testnet", "is ready"}); + {&C_bitcoin_logo, "Qtum Testnet", "is ready"}); UX_STEP_NOCB(ux_menu_version_step, bn, {"Version", APPVERSION}); UX_STEP_CB(ux_menu_about_step, pb, ui_menu_about(), {&C_icon_certificate, "About"}); @@ -57,7 +57,7 @@ UX_FLOW(ux_menu_main_flow_bitcoin_testnet, &ux_menu_exit_step, FLOW_LOOP); -UX_STEP_NOCB(ux_menu_info_step, bn, {"Bitcoin App", "(c) 2023 Ledger"}); +UX_STEP_NOCB(ux_menu_info_step, bn, {"Qtum App", "(c) 2023 Ledger"}); UX_STEP_CB(ux_menu_back_step, pb, ui_menu_main(), {&C_icon_back, "Back"}); // FLOW for the about submenu: diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index cbfb9a12e..70f43d868 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -23,5 +23,5 @@ def test_dashboard(comm: SpeculosClient, is_speculos: bool, app_version: str, mo comm.wait_for_text_event("Quit") comm.press_and_release("right") - comm.wait_for_text_event("Bitcoin Testnet") + comm.wait_for_text_event("Qtum Testnet") comm.wait_for_text_event("is ready")