diff --git a/app/src/apdu_handler.c b/app/src/apdu_handler.c index 5cbf67a0..dc1e7156 100644 --- a/app/src/apdu_handler.c +++ b/app/src/apdu_handler.c @@ -32,6 +32,7 @@ #include "zxformat.h" #include "hdpath.h" #include "parser_impl.h" +#include "message.h" __Z_INLINE void handleGetPubkey(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { hasPubkey = false; @@ -86,6 +87,21 @@ __Z_INLINE void handleSign(volatile uint32_t *flags, volatile uint32_t *tx, uint THROW(APDU_CODE_OK); } + if (callType == PROCESS_CHUNK_FINISHED_MESSAGE) { + zxerr_t err = message_parse(); + if (err != zxerr_ok) { + const char *error_msg = "Invalid message"; + int error_msg_length = strlen(error_msg); + MEMCPY(G_io_apdu_buffer, error_msg, error_msg_length); + *tx += (error_msg_length); + THROW(APDU_CODE_DATA_INVALID); + } + CHECK_APP_CANARY() + view_review_init(message_getItem, message_getNumItems, app_sign_message); + view_review_show(); + *flags |= IO_ASYNCH_REPLY; + } + const char *error_msg = tx_parse(callType); if (error_msg != NULL) { diff --git a/app/src/coin.h b/app/src/coin.h index a6d0c21f..9bbce45c 100644 --- a/app/src/coin.h +++ b/app/src/coin.h @@ -55,6 +55,8 @@ typedef struct { #define MAIN_SLOT 0 +#define DOMAIN_TAG_LENGTH 32 + #ifdef __cplusplus } #endif diff --git a/app/src/common/actions.c b/app/src/common/actions.c index 079a300d..93e04d9d 100644 --- a/app/src/common/actions.c +++ b/app/src/common/actions.c @@ -18,3 +18,19 @@ #include "actions.h" uint16_t action_addr_len; + +// UTF-8 encoding of "FLOW-V0.0-transaction" padded with zeros to 32 bytes +const uint8_t TX_DOMAIN_TAG_TRANSACTION[DOMAIN_TAG_LENGTH] = {\ + 0x46, 0x4C, 0x4F, 0x57, 0x2D, 0x56, 0x30, 0x2E, + 0x30, 0x2D, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6F, 0x6E, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +const uint8_t TX_DOMAIN_TAG_MESSAGE[DOMAIN_TAG_LENGTH] = {\ + 0x46, 0x4C, 0x4F, 0x57, 0x2D, 0x56, 0x30, 0x2E, + 0x30, 0x2D, 0x75, 0x73, 0x65, 0x72, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + diff --git a/app/src/common/actions.h b/app/src/common/actions.h index a012a96c..0e673135 100644 --- a/app/src/common/actions.h +++ b/app/src/common/actions.h @@ -25,12 +25,31 @@ #define GET_PUB_KEY_RESPONSE_LENGTH (3 * SECP256_PK_LEN) +extern const uint8_t TX_DOMAIN_TAG_TRANSACTION[DOMAIN_TAG_LENGTH]; +extern const uint8_t TX_DOMAIN_TAG_MESSAGE[DOMAIN_TAG_LENGTH]; + __Z_INLINE void app_sign() { - const uint8_t *message = get_signable(); - const uint16_t messageLength = get_signable_length(); + const uint8_t *message = tx_get_buffer(); + const uint16_t messageLength = tx_get_buffer_length(); + + uint16_t replyLen = 0; + zxerr_t err = crypto_sign(hdPath, cryptoOptions, message, messageLength, TX_DOMAIN_TAG_TRANSACTION, G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 3, &replyLen); + + if (err != zxerr_ok || replyLen == 0) { + set_code(G_io_apdu_buffer, 0, APDU_CODE_SIGN_VERIFY_ERROR); + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, 2); + } else { + set_code(G_io_apdu_buffer, replyLen, APDU_CODE_OK); + io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, replyLen + 2); + } +} + +__Z_INLINE void app_sign_message() { + const uint8_t *message = tx_get_buffer(); + const uint16_t messageLength = tx_get_buffer_length(); uint16_t replyLen = 0; - zxerr_t err = crypto_sign(hdPath, cryptoOptions, message, messageLength, G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 3, &replyLen); + zxerr_t err = crypto_sign(hdPath, cryptoOptions, message, messageLength, TX_DOMAIN_TAG_MESSAGE, G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 3, &replyLen); if (err != zxerr_ok || replyLen == 0) { set_code(G_io_apdu_buffer, 0, APDU_CODE_SIGN_VERIFY_ERROR); diff --git a/app/src/common/app_main.c b/app/src/common/app_main.c index a61c9c33..5174ce83 100644 --- a/app/src/common/app_main.c +++ b/app/src/common/app_main.c @@ -133,36 +133,42 @@ process_chunk_response_t process_chunk(__Z_UNUSED volatile uint32_t *tx, uint32_ extractHDPathAndCryptoOptions(rx, OFFSET_DATA); initStoredTxMetadata(); return PROCESS_CHUNK_NOT_FINISHED; - case 1: + case 0x01: added = tx_append(&(G_io_apdu_buffer[OFFSET_DATA]), rx - OFFSET_DATA); if (added != rx - OFFSET_DATA) { THROW(APDU_CODE_OUTPUT_BUFFER_TOO_SMALL); } return PROCESS_CHUNK_NOT_FINISHED; - case 2: + case 0x02: added = tx_append(&(G_io_apdu_buffer[OFFSET_DATA]), rx - OFFSET_DATA); if (added != rx - OFFSET_DATA) { THROW(APDU_CODE_OUTPUT_BUFFER_TOO_SMALL); } return PROCESS_CHUNK_FINISHED_NO_METADATA; - case 3: + case 0x03: if (storeTxMetadata(&(G_io_apdu_buffer[OFFSET_DATA]), rx - OFFSET_DATA) != PARSER_OK) { initStoredTxMetadata(); //delete merkle tree proof on error for redundant security THROW(APDU_CODE_DATA_INVALID); } return PROCESS_CHUNK_NOT_FINISHED; - case 4: + case 0x04: if (validateStoredTxMetadataMerkleTreeLevel(&(G_io_apdu_buffer[OFFSET_DATA]), rx - OFFSET_DATA) != PARSER_OK) { initStoredTxMetadata(); //delete merkle tree proof on error for redundant security THROW(APDU_CODE_DATA_INVALID); } return PROCESS_CHUNK_NOT_FINISHED; - case 5: + case 0x05: if (validateStoredTxMetadataMerkleTreeLevel(&(G_io_apdu_buffer[OFFSET_DATA]), rx - OFFSET_DATA) != PARSER_OK) { initStoredTxMetadata(); //delete merkle tree proof on error for redundant security THROW(APDU_CODE_DATA_INVALID); } return PROCESS_CHUNK_FINISHED_WITH_METADATA; + case 0x10: + added = tx_append(&(G_io_apdu_buffer[OFFSET_DATA]), rx - OFFSET_DATA); + if (added != rx - OFFSET_DATA) { + THROW(APDU_CODE_OUTPUT_BUFFER_TOO_SMALL); + } + return PROCESS_CHUNK_FINISHED_MESSAGE; } THROW(APDU_CODE_INVALIDP1P2); diff --git a/app/src/common/app_main.h b/app/src/common/app_main.h index 4a20680d..9f9fcbb0 100644 --- a/app/src/common/app_main.h +++ b/app/src/common/app_main.h @@ -49,6 +49,7 @@ typedef enum { PROCESS_CHUNK_NOT_FINISHED = 0, PROCESS_CHUNK_FINISHED_WITH_METADATA, PROCESS_CHUNK_FINISHED_NO_METADATA, + PROCESS_CHUNK_FINISHED_MESSAGE, } process_chunk_response_t; process_chunk_response_t process_chunk(volatile uint32_t *tx, uint32_t rx); diff --git a/app/src/common/tx.c b/app/src/common/tx.c index e2b0249e..9305c93f 100644 --- a/app/src/common/tx.c +++ b/app/src/common/tx.c @@ -46,14 +46,6 @@ storage_t NV_CONST N_appdata_impl __attribute__ ((aligned(64))); parser_context_t ctx_parsed_tx; -// UTF-8 encoding of "FLOW-V0.0-transaction" padded with zeros to 32 bytes -#define DOMAIN_TAG_LENGTH 32 -const uint8_t TX_DOMAIN_TAG[DOMAIN_TAG_LENGTH] = {\ - 0x46, 0x4C, 0x4F, 0x57, 0x2D, 0x56, 0x30, 0x2E, - 0x30, 0x2D, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6F, 0x6E, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, -}; #define TX_BUFFER_OFFSET DOMAIN_TAG_LENGTH @@ -68,7 +60,6 @@ void tx_initialize() { void tx_reset() { buffering_reset(); - buffering_append((uint8_t *)TX_DOMAIN_TAG, DOMAIN_TAG_LENGTH); } uint32_t tx_append(unsigned char *buffer, uint32_t length) { @@ -76,21 +67,10 @@ uint32_t tx_append(unsigned char *buffer, uint32_t length) { } uint32_t tx_get_buffer_length() { - if (buffering_get_buffer()->pos >= TX_BUFFER_OFFSET) { - return buffering_get_buffer()->pos - TX_BUFFER_OFFSET; - } - return 0; -} - -uint32_t get_signable_length() { return buffering_get_buffer()->pos; } uint8_t *tx_get_buffer() { - return buffering_get_buffer()->data + TX_BUFFER_OFFSET; -} - -uint8_t *get_signable() { return buffering_get_buffer()->data; } diff --git a/app/src/crypto.c b/app/src/crypto.c index 4e9b4f43..dbe20489 100644 --- a/app/src/crypto.c +++ b/app/src/crypto.c @@ -24,7 +24,6 @@ #if defined(TARGET_NANOS) || defined(TARGET_NANOX) || defined(TARGET_NANOS2) #include "cx.h" - __Z_INLINE digest_type_e get_hash_type(const uint16_t options) { const uint8_t hash_type = (uint8_t) (options & 0xFF); switch(hash_type) { @@ -118,7 +117,8 @@ void sha256(const uint8_t *message, uint16_t messageLen, uint8_t message_digest[ cx_hash_sha256(message, messageLen, message_digest, CX_SHA256_SIZE); } -zxerr_t digest_message(const uint8_t *message, uint16_t messageLen, digest_type_e hash_kind, uint8_t *digest, uint16_t digestMax, uint16_t* digest_size) { +zxerr_t digest_message(const uint8_t *message, uint16_t messageLen, const uint8_t domainTag[DOMAIN_TAG_LENGTH], + digest_type_e hash_kind, uint8_t *digest, uint16_t digestMax, uint16_t* digest_size) { switch(hash_kind) { case HASH_SHA2_256: { zemu_log_stack("sha2_256"); @@ -126,20 +126,23 @@ zxerr_t digest_message(const uint8_t *message, uint16_t messageLen, digest_type_ zemu_log_stack("digest_message: zxerr_buffer_too_small"); return zxerr_buffer_too_small; } - sha256(message, messageLen, digest); + cx_sha256_t sha2; + cx_sha256_init(&sha2); + cx_hash((cx_hash_t*) &sha2, 0, domainTag, DOMAIN_TAG_LENGTH, NULL, 0); + cx_hash((cx_hash_t*) &sha2, CX_LAST, message, messageLen, digest, CX_SHA256_SIZE); *digest_size = CX_SHA256_SIZE; return zxerr_ok; } case HASH_SHA3_256: { - if (digestMax < 32) { + zemu_log_stack("sha3_256"); + if (digestMax < CX_SHA256_SIZE) { return zxerr_buffer_too_small; } - zemu_log_stack("sha3_256"); cx_sha3_t sha3; cx_sha3_init(&sha3, 256); - cx_hash((cx_hash_t*)&sha3, CX_LAST, message, messageLen, digest, 32); - zemu_log_stack("sha3_256 ready"); - *digest_size = 32; + cx_hash((cx_hash_t*) &sha3, 0, domainTag, DOMAIN_TAG_LENGTH, NULL, 0); + cx_hash((cx_hash_t*) &sha3, CX_LAST, message, messageLen, digest, CX_SHA256_SIZE); + *digest_size = CX_SHA256_SIZE; return zxerr_ok; } default: { @@ -149,7 +152,9 @@ zxerr_t digest_message(const uint8_t *message, uint16_t messageLen, digest_type_ } } -zxerr_t crypto_sign(const hd_path_t path, const uint16_t options, const uint8_t *message, uint16_t messageLen, uint8_t *buffer, uint16_t bufferSize, uint16_t *sigSize) { +zxerr_t crypto_sign(const hd_path_t path, const uint16_t options, + const uint8_t *message, uint16_t messageLen, const uint8_t domainTag[DOMAIN_TAG_LENGTH], + uint8_t *buffer, uint16_t bufferSize, uint16_t *sigSize) { zemu_log_stack("crypto_sign"); cx_curve_t curve = get_cx_curve(options); @@ -163,7 +168,7 @@ zxerr_t crypto_sign(const hd_path_t path, const uint16_t options, const uint8_t uint8_t messageDigest[32]; uint16_t messageDigestSize = 0; - CHECK_ZXERR(digest_message(message, messageLen, cx_hash_kind, messageDigest, sizeof(messageDigest), &messageDigestSize)); + CHECK_ZXERR(digest_message(message, messageLen, domainTag, cx_hash_kind, messageDigest, sizeof(messageDigest), &messageDigestSize)); if (messageDigestSize != 32) { zemu_log_stack("crypto_sign: zxerr_out_of_bounds"); diff --git a/app/src/crypto.h b/app/src/crypto.h index c02c2ec7..917d5848 100644 --- a/app/src/crypto.h +++ b/app/src/crypto.h @@ -52,6 +52,7 @@ zxerr_t crypto_sign( const uint16_t options, const uint8_t *message, uint16_t messageLen, + const uint8_t domainTag[DOMAIN_TAG_LENGTH], uint8_t *signature, uint16_t signatureMaxlen, uint16_t *sigSize diff --git a/app/src/message.c b/app/src/message.c new file mode 100644 index 00000000..00a62397 --- /dev/null +++ b/app/src/message.c @@ -0,0 +1,107 @@ +/******************************************************************************* +* (c) 2022 Vacuumlabs +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ +#include "message.h" +#include "zxformat.h" +#include "app_mode.h" +#include "hdpath.h" +#include "crypto.h" +#include "buffering.h" + +#define MAX_MESSAGE_SHOW_LENGTH 34*5 +#define MAX_MESSAGE_LENGTH 0x7FFF + +struct { + uint8_t hash[CX_SHA256_SIZE]; + uint16_t length; + uint8_t *message; + bool canBeDisplayed; + +} messageData; + +zxerr_t message_parse() { + messageData.message = buffering_get_buffer()->data; + messageData.length = buffering_get_buffer()->pos; + + if (messageData.length > MAX_MESSAGE_LENGTH) { + return zxerr_out_of_bounds; + } + + for(size_t j=0; j127) { + return zxerr_out_of_bounds; + } + } + + sha256(messageData.message, messageData.length, messageData.hash); + messageData.canBeDisplayed = false; + + if (messageData.length <= MAX_MESSAGE_SHOW_LENGTH) { + messageData.canBeDisplayed = true; + } + + return zxerr_ok; +} + +zxerr_t message_getNumItems(uint8_t *num_items){ + *num_items = 2 + (!messageData.canBeDisplayed ? 1 : 0) + (app_mode_expert() ? 1 : 0); + return zxerr_ok; +} + +// retrieves a readable output for each field / page +zxerr_t message_getItem(int8_t displayIdx, + char *outKey, uint16_t outKeyLen, + char *outVal, uint16_t outValLen, + uint8_t pageIdx, uint8_t *pageCount) { + switch(displayIdx) { + case 0: + snprintf(outKey, outKeyLen, "Review"); + snprintf(outVal, outValLen, "the message to sign"); + return zxerr_ok; + case 1: + if (messageData.canBeDisplayed) { + snprintf(outKey, outKeyLen, "Message"); + pageStringExt(outVal, outValLen, (char *)messageData.message, messageData.length, pageIdx, pageCount); + return zxerr_ok; + } + else { + snprintf(outKey, outKeyLen, "Message too long,"); + snprintf(outVal, outValLen, "validate hash on a secure device."); + return zxerr_ok; + } + } + + displayIdx -= 2; + + if (displayIdx == 0 && !messageData.canBeDisplayed) { + snprintf(outKey, outKeyLen, "Message hash"); + pageHexString(outVal, outValLen, messageData.hash, sizeof(messageData.hash), pageIdx, pageCount); + return zxerr_ok; + } + + if (!messageData.canBeDisplayed) { + displayIdx -= 1; + } + + if (displayIdx == 0 && app_mode_expert()) { + snprintf(outKey, outKeyLen, "Your Path"); + char buffer[100]; + path_options_to_string(buffer, sizeof(buffer), hdPath.data, HDPATH_LEN_DEFAULT, cryptoOptions); + pageString(outVal, outValLen, buffer, pageIdx, pageCount); + return zxerr_ok; + } + + return zxerr_no_data; +} diff --git a/app/src/message.h b/app/src/message.h new file mode 100644 index 00000000..4587d912 --- /dev/null +++ b/app/src/message.h @@ -0,0 +1,29 @@ +/******************************************************************************* +* (c) 2022 Vacuumlabs +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +********************************************************************************/ +#pragma once + +#include "zxerror.h" + +zxerr_t message_parse(); + +zxerr_t message_getNumItems(uint8_t *num_items); + +zxerr_t message_getItem(int8_t displayIdx, + char *outKey, uint16_t outKeyLen, + char *outVal, uint16_t outValLen, + uint8_t pageIdx, uint8_t *pageCount); + + diff --git a/deps/ledger-zxlib/dockerized_build.mk b/deps/ledger-zxlib/dockerized_build.mk index 70f1d236..d3f9e939 100644 --- a/deps/ledger-zxlib/dockerized_build.mk +++ b/deps/ledger-zxlib/dockerized_build.mk @@ -346,6 +346,7 @@ speculos_port_5002_test_internal: $(call run_nodejs_test,5002,40002,test-slot-transaction-interaction.js) $(call run_nodejs_test,5002,40002,test-transaction-expert-mode.js) $(call run_nodejs_test,5002,40002,test-transaction-arbitrary.js) + $(call run_nodejs_test,5002,40002,test-messages.js) $(call run_nodejs_test,5002,40002,test-transactions.js) @echo "# ALL TESTS COMPLETED!" | tee -a $(TESTS_SPECULOS_DIR)/speculos-port-5002.log diff --git a/docs/APDUSPEC.md b/docs/APDUSPEC.md index 7dc3b166..413315d8 100644 --- a/docs/APDUSPEC.md +++ b/docs/APDUSPEC.md @@ -164,10 +164,11 @@ Each slot has the following structure The first packet/chunk includes only the derivation path -All other packets/chunks contain data chunks that are described below. There are two workflows as of now (typical sequences here, the app allows other combination of commands, too): +All other packets/chunks contain data chunks that are described below. There are three workflows as of now (typical sequences here, the app allows other combination of commands, too): Merkle tree workflow - Init packet, several add packets, metadata packet, four Merkle tree packets (3x 0x04 and finaly 0x05). -Arbitrary transaction signing - Init packer, several add packets, final packet. +Arbitrary transaction signing - Init packet, several add packets, final packet. +Message signing workflow - Init packet, several add packets, final message packet (P1=0x10). ##### Init Packet P1 = 0x00 @@ -180,7 +181,7 @@ Arbitrary transaction signing - Init packer, several add packets, final packet. | Path[4] | byte (4) | Derivation Path Data | ? | | Options | byte (2) | Crypto options (LE) | ? | -This clears tx data and sets detivation path and crypto options variable +This clears data and sets detivation path and crypto options variable ##### Add Packet P1 = 0x01 @@ -190,11 +191,11 @@ This clears tx data and sets detivation path and crypto options variable Data is defined as: -| Field | Type | Content | Expected | -| ------- | ------- | ---------------- | -------- | -| Message | bytes.. | RLP data to sign | | +| Field | Type | Content | Expected | +| ------- | ------- | ------------------------ | -------- | +| Message | bytes.. | RLP data/message to sign | | -Appends to transaction data +Appends to data (transaction or message) ##### Fimal Packet P1 = 0x02 @@ -208,7 +209,7 @@ Data is defined as: | ------- | ------- | ---------------- | -------- | | Message | bytes.. | RLP data to sign | | -Appends to transaction data and initiates signing without metadata (requires expert mode). +Appends to transaction data and initiates transaction signing without metadata (requires expert mode). ##### Metadata Packet P1 = 0x03 @@ -271,6 +272,20 @@ Four APDUs for four levels of internal merkle tree nodes. Each internal nerkle t Validates merkle tree node. Validates that previous hash (metadata hash or merkle tree node hash) is in the list of hashes. Computes new hash and increments merkle tree counter. Call with P1 = 0x05 starts the signing process with metadata. This requires that we are at the root of the merkle tree and that the hash value matches the one stored in the app. +##### Final message signing Packet P1 = 0x10 + +| Field | Type | Content | Expected | +| ----- | -------- | ------- | -------- | +| Data | bytes... | Message | | + +Data is defined as: + +| Field | Type | Content | Expected | +| ------- | ------- | ------------------- | -------- | +| Message | bytes.. | Mesage data to sign | | + +Appends to data to message and initiates message signing. + #### Response | Field | Type | Content | Note | diff --git a/js/src/index.js b/js/src/index.js index ba1f8ba5..5d3764a8 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -147,9 +147,17 @@ export default class FlowApp { } async sign(path, message, cryptoOptions, scriptHash) { + return this._signImplementation(path, message, cryptoOptions, scriptHash) + } + + async signMessage(path, message, cryptoOptions) { + return this._signImplementation(path, message, cryptoOptions, "Sign message") + } + + async _signImplementation(path, message, cryptoOptions, extraInfo) { validateCryptoOptions(cryptoOptions); const getVersionResponse = await this.getVersion(); - const chunks = signGetChunks(path, cryptoOptions, getVersionResponse, message, scriptHash) + const chunks = signGetChunks(path, cryptoOptions, getVersionResponse, message, extraInfo) if (typeof chunks === "string") { return { diff --git a/js/src/signTransaction.js b/js/src/signTransaction.js index 85b7aa8b..1fdd3e8a 100644 --- a/js/src/signTransaction.js +++ b/js/src/signTransaction.js @@ -9,10 +9,11 @@ const PAYLOAD_TYPE = { TX_METADATA: 0x03, MERKLE_TREE: 0x04, MERKLE_TREE_LAST: 0x05, + MESSAGE_LAST: 0x10, } export function signIsLastAPDU(type) { - return (type === PAYLOAD_TYPE.LAST || type === PAYLOAD_TYPE.MERKLE_TREE_LAST) + return (type === PAYLOAD_TYPE.LAST || type === PAYLOAD_TYPE.MERKLE_TREE_LAST || PAYLOAD_TYPE.MESSAGE_LAST) } /* @@ -45,11 +46,21 @@ function signGetChunksv1(path, options, getVersionResponse, message) { return chunks; } -function signGetChunksv2(path, options, getVersionResponse, message, scriptHash) { +//ExtraInfo is either +// - script hash from merkleIndex - initiates transaction signing with metadata +// - "Sign message" - initiates message signing +// - anything else - initiates transaction sining without metadata +function signGetChunksv2(path, options, getVersionResponse, message, extraInfo) { const serializedPath = serializePath(path, getVersionResponse, options); const basicChunks = prepareBasicChunks(serializedPath, message) + if (extraInfo == "Sign message") { + basicChunks[basicChunks.length-1].type = PAYLOAD_TYPE.MESSAGE_LAST + return basicChunks; + } + // We try to find hash in the merkle tree. If it is not there, we send the tx without metadata (arbitrary tx signing in expert mode) + const scriptHash = extraInfo const merkleI = merkleIndex[scriptHash.slice(0, 16)] if (merkleI === undefined) { basicChunks[basicChunks.length-1].type = PAYLOAD_TYPE.LAST diff --git a/tests_speculos/test-messages.js b/tests_speculos/test-messages.js new file mode 100644 index 00000000..3f2c00b8 --- /dev/null +++ b/tests_speculos/test-messages.js @@ -0,0 +1,119 @@ +'use strict'; + +import { testStart, testEnd, testStep, compareInAPDU, compareOutAPDU, noMoreAPDUs, compareGetVersionAPDUs, getScriptName, getSpeculosDefaultConf, humanTime } from "./speculos-common.js"; +import { getSpyTransport } from "./speculos-transport.js"; +import { getButtonsAndSnapshots } from "./speculos-buttons-and-snapshots.js"; +import { default as OnflowLedgerMod } from "@onflow/ledger"; +import { fileURLToPath, pathToFileURL } from 'url'; +import assert from 'assert/strict'; +import pkg from 'elliptic'; +const {ec: EC} = pkg; +import jsSHA from "jssha"; + +const scriptName = getScriptName(fileURLToPath(import.meta.url)); +testStart(scriptName); + +const speculosConf = getSpeculosDefaultConf(); +const transport = await getSpyTransport(speculosConf); +const FlowApp = OnflowLedgerMod.default; +const app = new FlowApp(transport); +const device = getButtonsAndSnapshots(scriptName, speculosConf); + +const ECDSA_SECP256K1 = { name: "secp256k1", code: FlowApp.Signature.SECP256K1, pathCode: 0x200 }; +const ECDSA_P256 = { name: "p256", code: FlowApp.Signature.P256, pathCode: 0x300}; + +const SHA2_256 = { name: "SHA-256", code: FlowApp.Hash.SHA2_256, pathCode: 0x01}; +const SHA3_256 = { name: "SHA3-256", code: FlowApp.Hash.SHA3_256, pathCode: 0x03}; + +const path = `m/44'/539'/0'/0/0`; +const options = ECDSA_P256.code | SHA3_256.code + +await device.makeStartingScreenshot(); + + +// We get pubkey so we can verify signature +testStep(" - - -", "await app.getAddressAndPubKey() // path=" + path); +const getPubkeyResponse = await app.getAddressAndPubKey(path, options); +assert.equal(getPubkeyResponse.returnCode, 0x9000); +assert.equal(getPubkeyResponse.errorMessage, "No errors"); +const pubkeyHex = getPubkeyResponse.publicKey.toString("hex") + + +{ + const message = Buffer.from("This is a nice message that has only displayable characters and is short enough to be displayed") + + testStep(" - - -", "Message with displayable characters"); + const signPromise = app.signMessage(path, message, options); + await device.review("Review message"); + const signResponse = await signPromise; + assert.equal(signResponse.returnCode, 0x9000); + assert.equal(signResponse.errorMessage, "No errors"); + + let tag = Buffer.alloc(32); + tag.write("FLOW-V0.0-user"); + const hasher = new jsSHA(SHA3_256.name, "UINT8ARRAY"); + hasher.update(tag); + hasher.update(message); + const digestHex = hasher.getHash("HEX"); + const ec = new EC(ECDSA_P256.name); + assert.ok(ec.verify(digestHex, signResponse.signatureDER.toString("hex"), pubkeyHex, 'hex')); +} + +{ + const message = Buffer.alloc(1000, 0x40); + + testStep(" - - -", "Message too long to display"); + const signPromise = app.signMessage(path, message, options); + await device.review("Review message"); + const signResponse = await signPromise; + assert.equal(signResponse.returnCode, 0x9000); + assert.equal(signResponse.errorMessage, "No errors"); + + let tag = Buffer.alloc(32); + tag.write("FLOW-V0.0-user"); + const hasher = new jsSHA(SHA3_256.name, "UINT8ARRAY"); + hasher.update(tag); + hasher.update(message); + const digestHex = hasher.getHash("HEX"); + const ec = new EC(ECDSA_P256.name); + assert.ok(ec.verify(digestHex, signResponse.signatureDER.toString("hex"), pubkeyHex, 'hex')); +} + +await device.toggleExpertMode("ON"); + +{ + const message = Buffer.from("This is a short message in expert mode"); + + testStep(" - - -", "A short message, expert mode"); + const signPromise = app.signMessage(path, message, options); + await device.review("Review message"); + const signResponse = await signPromise; + assert.equal(signResponse.returnCode, 0x9000); + assert.equal(signResponse.errorMessage, "No errors"); + + let tag = Buffer.alloc(32); + tag.write("FLOW-V0.0-user"); + const hasher = new jsSHA(SHA3_256.name, "UINT8ARRAY"); + hasher.update(tag); + hasher.update(message); + const digestHex = hasher.getHash("HEX"); + const ec = new EC(ECDSA_P256.name); + assert.ok(ec.verify(digestHex, signResponse.signatureDER.toString("hex"), pubkeyHex, 'hex')); +} + +await device.toggleExpertMode("OFF"); + +{ + const message = Buffer.concat([Buffer.from("This is a short message in expert mode"), Buffer.from("ee", "hex")]); + + testStep(" - - -", "A short message, non displayable character"); + const signPromise = app.signMessage(path, message, options); + const signResponse = await signPromise; + console.log(signResponse) + assert.equal(signResponse.returnCode, 0x6984); + assert.equal(signResponse.errorMessage, "Data is invalid : Invalid message"); +} + +await transport.close() +testEnd(scriptName); +process.stdin.pause() diff --git a/tests_speculos/test-messages/nanos.01.png b/tests_speculos/test-messages/nanos.01.png new file mode 100644 index 00000000..7f6ab5e6 Binary files /dev/null and b/tests_speculos/test-messages/nanos.01.png differ diff --git a/tests_speculos/test-messages/nanos.02.png b/tests_speculos/test-messages/nanos.02.png new file mode 100644 index 00000000..ca19a0c3 Binary files /dev/null and b/tests_speculos/test-messages/nanos.02.png differ diff --git a/tests_speculos/test-messages/nanos.03.png b/tests_speculos/test-messages/nanos.03.png new file mode 100644 index 00000000..57c1d8cc Binary files /dev/null and b/tests_speculos/test-messages/nanos.03.png differ diff --git a/tests_speculos/test-messages/nanos.04.png b/tests_speculos/test-messages/nanos.04.png new file mode 100644 index 00000000..8782b4de Binary files /dev/null and b/tests_speculos/test-messages/nanos.04.png differ diff --git a/tests_speculos/test-messages/nanos.05.png b/tests_speculos/test-messages/nanos.05.png new file mode 100644 index 00000000..f9c1e1e8 Binary files /dev/null and b/tests_speculos/test-messages/nanos.05.png differ diff --git a/tests_speculos/test-messages/nanos.06.png b/tests_speculos/test-messages/nanos.06.png new file mode 100644 index 00000000..006c26ab Binary files /dev/null and b/tests_speculos/test-messages/nanos.06.png differ diff --git a/tests_speculos/test-messages/nanos.07.png b/tests_speculos/test-messages/nanos.07.png new file mode 100644 index 00000000..7f6ab5e6 Binary files /dev/null and b/tests_speculos/test-messages/nanos.07.png differ diff --git a/tests_speculos/test-messages/nanos.08.png b/tests_speculos/test-messages/nanos.08.png new file mode 100644 index 00000000..ca19a0c3 Binary files /dev/null and b/tests_speculos/test-messages/nanos.08.png differ diff --git a/tests_speculos/test-messages/nanos.09.png b/tests_speculos/test-messages/nanos.09.png new file mode 100644 index 00000000..72882952 Binary files /dev/null and b/tests_speculos/test-messages/nanos.09.png differ diff --git a/tests_speculos/test-messages/nanos.10.png b/tests_speculos/test-messages/nanos.10.png new file mode 100644 index 00000000..7e7b1fa6 Binary files /dev/null and b/tests_speculos/test-messages/nanos.10.png differ diff --git a/tests_speculos/test-messages/nanos.11.png b/tests_speculos/test-messages/nanos.11.png new file mode 100644 index 00000000..547d2fb7 Binary files /dev/null and b/tests_speculos/test-messages/nanos.11.png differ diff --git a/tests_speculos/test-messages/nanos.12.png b/tests_speculos/test-messages/nanos.12.png new file mode 100644 index 00000000..006c26ab Binary files /dev/null and b/tests_speculos/test-messages/nanos.12.png differ diff --git a/tests_speculos/test-messages/nanos.13.png b/tests_speculos/test-messages/nanos.13.png new file mode 100644 index 00000000..7f6ab5e6 Binary files /dev/null and b/tests_speculos/test-messages/nanos.13.png differ diff --git a/tests_speculos/test-messages/nanos.14.png b/tests_speculos/test-messages/nanos.14.png new file mode 100644 index 00000000..85d9725b Binary files /dev/null and b/tests_speculos/test-messages/nanos.14.png differ diff --git a/tests_speculos/test-messages/nanos.15.png b/tests_speculos/test-messages/nanos.15.png new file mode 100644 index 00000000..50bcea8e Binary files /dev/null and b/tests_speculos/test-messages/nanos.15.png differ diff --git a/tests_speculos/test-messages/nanos.16.png b/tests_speculos/test-messages/nanos.16.png new file mode 100644 index 00000000..7f6ab5e6 Binary files /dev/null and b/tests_speculos/test-messages/nanos.16.png differ diff --git a/tests_speculos/test-messages/nanos.17.png b/tests_speculos/test-messages/nanos.17.png new file mode 100644 index 00000000..ca19a0c3 Binary files /dev/null and b/tests_speculos/test-messages/nanos.17.png differ diff --git a/tests_speculos/test-messages/nanos.18.png b/tests_speculos/test-messages/nanos.18.png new file mode 100644 index 00000000..10a806b1 Binary files /dev/null and b/tests_speculos/test-messages/nanos.18.png differ diff --git a/tests_speculos/test-messages/nanos.19.png b/tests_speculos/test-messages/nanos.19.png new file mode 100644 index 00000000..e8678de6 Binary files /dev/null and b/tests_speculos/test-messages/nanos.19.png differ diff --git a/tests_speculos/test-messages/nanos.20.png b/tests_speculos/test-messages/nanos.20.png new file mode 100644 index 00000000..d4fe3ec9 Binary files /dev/null and b/tests_speculos/test-messages/nanos.20.png differ diff --git a/tests_speculos/test-messages/nanos.21.png b/tests_speculos/test-messages/nanos.21.png new file mode 100644 index 00000000..006c26ab Binary files /dev/null and b/tests_speculos/test-messages/nanos.21.png differ diff --git a/tests_speculos/test-messages/nanos.22.png b/tests_speculos/test-messages/nanos.22.png new file mode 100644 index 00000000..7f6ab5e6 Binary files /dev/null and b/tests_speculos/test-messages/nanos.22.png differ diff --git a/tests_speculos/test-messages/nanos.23.png b/tests_speculos/test-messages/nanos.23.png new file mode 100644 index 00000000..50bcea8e Binary files /dev/null and b/tests_speculos/test-messages/nanos.23.png differ diff --git a/tests_speculos/test-messages/nanos.24.png b/tests_speculos/test-messages/nanos.24.png new file mode 100644 index 00000000..85d9725b Binary files /dev/null and b/tests_speculos/test-messages/nanos.24.png differ diff --git a/tests_speculos/test-messages/nanos.25.png b/tests_speculos/test-messages/nanos.25.png new file mode 100644 index 00000000..7f6ab5e6 Binary files /dev/null and b/tests_speculos/test-messages/nanos.25.png differ diff --git a/tests_speculos/test-messages/nanox.01.png b/tests_speculos/test-messages/nanox.01.png new file mode 100644 index 00000000..dbbdb276 Binary files /dev/null and b/tests_speculos/test-messages/nanox.01.png differ diff --git a/tests_speculos/test-messages/nanox.02.png b/tests_speculos/test-messages/nanox.02.png new file mode 100644 index 00000000..2689ddb2 Binary files /dev/null and b/tests_speculos/test-messages/nanox.02.png differ diff --git a/tests_speculos/test-messages/nanox.03.png b/tests_speculos/test-messages/nanox.03.png new file mode 100644 index 00000000..d3179deb Binary files /dev/null and b/tests_speculos/test-messages/nanox.03.png differ diff --git a/tests_speculos/test-messages/nanox.04.png b/tests_speculos/test-messages/nanox.04.png new file mode 100644 index 00000000..9a276d55 Binary files /dev/null and b/tests_speculos/test-messages/nanox.04.png differ diff --git a/tests_speculos/test-messages/nanox.05.png b/tests_speculos/test-messages/nanox.05.png new file mode 100644 index 00000000..7e49f56f Binary files /dev/null and b/tests_speculos/test-messages/nanox.05.png differ diff --git a/tests_speculos/test-messages/nanox.06.png b/tests_speculos/test-messages/nanox.06.png new file mode 100644 index 00000000..1e4be699 Binary files /dev/null and b/tests_speculos/test-messages/nanox.06.png differ diff --git a/tests_speculos/test-messages/nanox.07.png b/tests_speculos/test-messages/nanox.07.png new file mode 100644 index 00000000..dbbdb276 Binary files /dev/null and b/tests_speculos/test-messages/nanox.07.png differ diff --git a/tests_speculos/test-messages/nanox.08.png b/tests_speculos/test-messages/nanox.08.png new file mode 100644 index 00000000..2689ddb2 Binary files /dev/null and b/tests_speculos/test-messages/nanox.08.png differ diff --git a/tests_speculos/test-messages/nanox.09.png b/tests_speculos/test-messages/nanox.09.png new file mode 100644 index 00000000..d3179deb Binary files /dev/null and b/tests_speculos/test-messages/nanox.09.png differ diff --git a/tests_speculos/test-messages/nanox.10.png b/tests_speculos/test-messages/nanox.10.png new file mode 100644 index 00000000..cf795648 Binary files /dev/null and b/tests_speculos/test-messages/nanox.10.png differ diff --git a/tests_speculos/test-messages/nanox.11.png b/tests_speculos/test-messages/nanox.11.png new file mode 100644 index 00000000..b6b0217c Binary files /dev/null and b/tests_speculos/test-messages/nanox.11.png differ diff --git a/tests_speculos/test-messages/nanox.12.png b/tests_speculos/test-messages/nanox.12.png new file mode 100644 index 00000000..8b16b4d9 Binary files /dev/null and b/tests_speculos/test-messages/nanox.12.png differ diff --git a/tests_speculos/test-messages/nanox.13.png b/tests_speculos/test-messages/nanox.13.png new file mode 100644 index 00000000..1e4be699 Binary files /dev/null and b/tests_speculos/test-messages/nanox.13.png differ diff --git a/tests_speculos/test-messages/nanox.14.png b/tests_speculos/test-messages/nanox.14.png new file mode 100644 index 00000000..dbbdb276 Binary files /dev/null and b/tests_speculos/test-messages/nanox.14.png differ diff --git a/tests_speculos/test-messages/nanox.15.png b/tests_speculos/test-messages/nanox.15.png new file mode 100644 index 00000000..e10e0049 Binary files /dev/null and b/tests_speculos/test-messages/nanox.15.png differ diff --git a/tests_speculos/test-messages/nanox.16.png b/tests_speculos/test-messages/nanox.16.png new file mode 100644 index 00000000..7e236da6 Binary files /dev/null and b/tests_speculos/test-messages/nanox.16.png differ diff --git a/tests_speculos/test-messages/nanox.17.png b/tests_speculos/test-messages/nanox.17.png new file mode 100644 index 00000000..dbbdb276 Binary files /dev/null and b/tests_speculos/test-messages/nanox.17.png differ diff --git a/tests_speculos/test-messages/nanox.18.png b/tests_speculos/test-messages/nanox.18.png new file mode 100644 index 00000000..2689ddb2 Binary files /dev/null and b/tests_speculos/test-messages/nanox.18.png differ diff --git a/tests_speculos/test-messages/nanox.19.png b/tests_speculos/test-messages/nanox.19.png new file mode 100644 index 00000000..d3179deb Binary files /dev/null and b/tests_speculos/test-messages/nanox.19.png differ diff --git a/tests_speculos/test-messages/nanox.20.png b/tests_speculos/test-messages/nanox.20.png new file mode 100644 index 00000000..4face5fb Binary files /dev/null and b/tests_speculos/test-messages/nanox.20.png differ diff --git a/tests_speculos/test-messages/nanox.21.png b/tests_speculos/test-messages/nanox.21.png new file mode 100644 index 00000000..94fa1038 Binary files /dev/null and b/tests_speculos/test-messages/nanox.21.png differ diff --git a/tests_speculos/test-messages/nanox.22.png b/tests_speculos/test-messages/nanox.22.png new file mode 100644 index 00000000..1e4be699 Binary files /dev/null and b/tests_speculos/test-messages/nanox.22.png differ diff --git a/tests_speculos/test-messages/nanox.23.png b/tests_speculos/test-messages/nanox.23.png new file mode 100644 index 00000000..dbbdb276 Binary files /dev/null and b/tests_speculos/test-messages/nanox.23.png differ diff --git a/tests_speculos/test-messages/nanox.24.png b/tests_speculos/test-messages/nanox.24.png new file mode 100644 index 00000000..7e236da6 Binary files /dev/null and b/tests_speculos/test-messages/nanox.24.png differ diff --git a/tests_speculos/test-messages/nanox.25.png b/tests_speculos/test-messages/nanox.25.png new file mode 100644 index 00000000..e10e0049 Binary files /dev/null and b/tests_speculos/test-messages/nanox.25.png differ diff --git a/tests_speculos/test-messages/nanox.26.png b/tests_speculos/test-messages/nanox.26.png new file mode 100644 index 00000000..dbbdb276 Binary files /dev/null and b/tests_speculos/test-messages/nanox.26.png differ