diff --git a/app/Makefile b/app/Makefile index 087ed35b..a56498c9 100755 --- a/app/Makefile +++ b/app/Makefile @@ -54,8 +54,8 @@ ifndef COIN COIN=ICP endif -APPVERSION_M=1 -APPVERSION_N=2 +APPVERSION_M=2 +APPVERSION_N=0 APPVERSION_P=0 $(info COIN = [$(COIN)]) diff --git a/app/src/apdu_handler.c b/app/src/apdu_handler.c index e846a27e..13a27360 100644 --- a/app/src/apdu_handler.c +++ b/app/src/apdu_handler.c @@ -75,6 +75,29 @@ __Z_INLINE void handleSign(volatile uint32_t *flags, volatile uint32_t *tx, uint *flags |= IO_ASYNCH_REPLY; } +__Z_INLINE void handleSignCombined(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { + if (!process_chunk(tx, rx)) { + THROW(APDU_CODE_OK); + } + + CHECK_APP_CANARY() + + const char *error_msg = tx_parse_combined(); + CHECK_APP_CANARY() + + if (error_msg != NULL) { + 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(tx_getItem, tx_getNumItems, app_sign_combined); + view_review_show(); + *flags |= IO_ASYNCH_REPLY; +} + void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { uint16_t sw = 0; @@ -112,6 +135,14 @@ void handleApdu(volatile uint32_t *flags, volatile uint32_t *tx, uint32_t rx) { break; } + case INS_SIGN_COMBINED: { + if (os_global_pin_is_validated() != BOLOS_UX_OK) { + THROW(APDU_CODE_COMMAND_NOT_ALLOWED); + } + handleSignCombined(flags, tx, rx); + break; + } + default: THROW(APDU_CODE_INS_NOT_SUPPORTED); } diff --git a/app/src/common/actions.h b/app/src/common/actions.h index 5eb28ccb..70c35cea 100644 --- a/app/src/common/actions.h +++ b/app/src/common/actions.h @@ -40,6 +40,20 @@ __Z_INLINE void app_sign() { } } +__Z_INLINE void app_sign_combined() { + uint16_t replyLen = 0; + + zxerr_t err = crypto_sign_combined(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE - 3,&G_io_apdu_buffer[0], &G_io_apdu_buffer[32], &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 zxerr_t app_fill_address() { // Put data directly in the apdu buffer MEMZERO(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE); diff --git a/app/src/common/app_main.c b/app/src/common/app_main.c index 536f712c..f3af334d 100644 --- a/app/src/common/app_main.c +++ b/app/src/common/app_main.c @@ -136,7 +136,7 @@ bool process_chunk(volatile uint32_t *tx, uint32_t rx) { THROW(APDU_CODE_DATA_INVALID); } - bool is_stake_tx = parser_tx_obj.tx_fields.call.special_transfer_type == neuron_stake_transaction; + bool is_stake_tx = parser_tx_obj.special_transfer_type == neuron_stake_transaction; uint32_t added; switch (payloadType) { @@ -146,7 +146,9 @@ bool process_chunk(volatile uint32_t *tx, uint32_t rx) { extractHDPath(rx, OFFSET_DATA); MEMZERO(&parser_tx_obj, sizeof(parser_tx_t)); if(G_io_apdu_buffer[OFFSET_P2] == 1){ - parser_tx_obj.tx_fields.call.special_transfer_type = neuron_stake_transaction; + parser_tx_obj.special_transfer_type = neuron_stake_transaction; + }else{ + parser_tx_obj.special_transfer_type = normal_transaction; } tx_initialized = true; return false; diff --git a/app/src/common/app_main.h b/app/src/common/app_main.h index f84439b4..c40bd619 100644 --- a/app/src/common/app_main.h +++ b/app/src/common/app_main.h @@ -33,6 +33,7 @@ #define INS_GET_VERSION 0x00 #define INS_GET_ADDR 0x01 #define INS_SIGN 0x02 +#define INS_SIGN_COMBINED 0x03 void app_init(); diff --git a/app/src/common/parser.h b/app/src/common/parser.h index 002159e2..7aec97f3 100644 --- a/app/src/common/parser.h +++ b/app/src/common/parser.h @@ -27,6 +27,9 @@ const char *parser_getErrorDescription(parser_error_t err); //// parses a tx buffer parser_error_t parser_parse(parser_context_t *ctx, const uint8_t *data, size_t dataLen); +/// parses a tx buffer of a combined transaction +parser_error_t parser_parse_combined(parser_context_t *ctx, const uint8_t *data, size_t dataLen); + //// verifies tx fields parser_error_t parser_validate(const parser_context_t *ctx); diff --git a/app/src/common/tx.c b/app/src/common/tx.c index 4a53da23..306403f5 100644 --- a/app/src/common/tx.c +++ b/app/src/common/tx.c @@ -89,6 +89,26 @@ const char *tx_parse() { return NULL; } +const char *tx_parse_combined() { + uint8_t err = parser_parse_combined( + &ctx_parsed_tx, + tx_get_buffer(), + tx_get_buffer_length()); + + if (err != parser_ok) { + return parser_getErrorDescription(err); + } + + err = parser_validate(&ctx_parsed_tx); + CHECK_APP_CANARY() + + if (err != parser_ok) { + return parser_getErrorDescription(err); + } + + return NULL; +} + zxerr_t tx_getNumItems(uint8_t *num_items) { parser_error_t err = parser_getNumItems(&ctx_parsed_tx, num_items); diff --git a/app/src/common/tx.h b/app/src/common/tx.h index b84b9151..82710da2 100644 --- a/app/src/common/tx.h +++ b/app/src/common/tx.h @@ -44,6 +44,9 @@ uint8_t *tx_get_buffer(); /// \return It returns NULL if data is valid or error message otherwise. const char *tx_parse(); +///Parses combined transaction message in transaction buffer +const char *tx_parse_combined(); + /// Return the number of items in the transaction zxerr_t tx_getNumItems(uint8_t *num_items); diff --git a/app/src/crypto.c b/app/src/crypto.c index e976f905..30d334c7 100644 --- a/app/src/crypto.c +++ b/app/src/crypto.c @@ -40,6 +40,10 @@ uint8_t const DER_PREFIX[] = {0x30, 0x56, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x #define STAKEACCOUNT_PREFIX_SIZE 12u #define STAKEACCOUNT_PRINCIPAL_SIZE 10u +#define SIGNATURE_SIZE_R 32 +#define SIGNATURE_SIZE_S 32 +#define SIGNATURE_SIZE_RS 64 + #if defined(TARGET_NANOS) || defined(TARGET_NANOX) #include "cx.h" @@ -262,12 +266,127 @@ zxerr_t crypto_sign(uint8_t *signatureBuffer, return err; } +//Start: +//PREDIGEST_REQUEST || PREDIGEST_STATEREAD +//END: +//DIGEST_REQUEST || SIGNATURE_REQUEST || DIGEST_STATEREAD || SIGNATURE_STATEREAD + +zxerr_t crypto_sign_combined(uint8_t *signatureBuffer, + uint16_t signatureMaxlen, + uint8_t *predigest_request, + uint8_t *predigest_stateread, + uint16_t *sigSize) { + if (signatureMaxlen < 2*(CX_SHA256_SIZE + SIGNATURE_SIZE_RS)){ + return zxerr_buffer_too_small; + } + + uint8_t message_buffer[SIGN_PREFIX_SIZE + CX_SHA256_SIZE]; + MEMZERO(message_buffer, sizeof(message_buffer)); + + uint8_t message_digest[CX_SHA256_SIZE]; + MEMZERO(message_digest,sizeof(message_digest)); + + message_buffer[0] = 0x0a; + MEMCPY(&message_buffer[1], (uint8_t *)"ic-request",SIGN_PREFIX_SIZE - 1); + + MEMCPY(message_buffer + SIGN_PREFIX_SIZE, predigest_stateread, CX_SHA256_SIZE); + + CHECK_APP_CANARY() + + cx_hash_sha256(message_buffer, SIGN_PREHASH_SIZE, message_digest, CX_SHA256_SIZE); + MEMCPY(signatureBuffer + CX_SHA256_SIZE + SIGNATURE_SIZE_RS, message_digest, CX_SHA256_SIZE); + + + MEMCPY(message_buffer + SIGN_PREFIX_SIZE, predigest_request, CX_SHA256_SIZE); + cx_hash_sha256(message_buffer, SIGN_PREHASH_SIZE, message_digest, CX_SHA256_SIZE); + MEMCPY(signatureBuffer, message_digest, CX_SHA256_SIZE); + + CHECK_APP_CANARY() + + cx_ecfp_private_key_t cx_privateKey; + uint8_t privateKeyData[32]; + unsigned int info = 0; + int signatureLength = 0; + + signature_t sigma; + MEMZERO(&sigma, sizeof(signature_t)); + + zxerr_t err = zxerr_ok; + BEGIN_TRY + { + TRY + { + // Generate keys + os_perso_derive_node_bip32(CX_CURVE_SECP256K1, + hdPath, + HDPATH_LEN_DEFAULT, + privateKeyData, NULL); + + cx_ecfp_init_private_key(CX_CURVE_SECP256K1, privateKeyData, 32, &cx_privateKey); + + // Sign request + signatureLength = cx_ecdsa_sign(&cx_privateKey, + CX_RND_RFC6979 | CX_LAST, + CX_SHA256, + signatureBuffer, + CX_SHA256_SIZE, + sigma.der_signature, + sizeof_field(signature_t, der_signature), + &info); + + err_convert_e err_c = convertDERtoRSV(sigma.der_signature, info, sigma.r, sigma.s, &sigma.v); + if (err_c != no_error) { + MEMZERO(signatureBuffer, signatureMaxlen); + err = zxerr_unknown; + }else{ + MEMCPY(signatureBuffer + CX_SHA256_SIZE, sigma.r, SIGNATURE_SIZE_R); + MEMCPY(signatureBuffer + CX_SHA256_SIZE + SIGNATURE_SIZE_R, sigma.s, SIGNATURE_SIZE_S); + + MEMZERO(&sigma, sizeof(signature_t)); + // Sign stateread + signatureLength = cx_ecdsa_sign(&cx_privateKey, + CX_RND_RFC6979 | CX_LAST, + CX_SHA256, + signatureBuffer + CX_SHA256_SIZE + SIGNATURE_SIZE_RS, + CX_SHA256_SIZE, + sigma.der_signature, + sizeof_field(signature_t, der_signature), + &info); + + err_convert_e err_c = convertDERtoRSV(sigma.der_signature, info, sigma.r, sigma.s, &sigma.v); + if (err_c != no_error) { + MEMZERO(signatureBuffer, signatureMaxlen); + err = zxerr_unknown; + }else{ + MEMCPY(signatureBuffer + 2*CX_SHA256_SIZE + SIGNATURE_SIZE_RS, sigma.r, SIGNATURE_SIZE_R); + MEMCPY(signatureBuffer + 2*CX_SHA256_SIZE + SIGNATURE_SIZE_RS + SIGNATURE_SIZE_R, sigma.s, SIGNATURE_SIZE_S); + *sigSize = 2*(CX_SHA256_SIZE + SIGNATURE_SIZE_RS); + } + } + } + CATCH_ALL { + err = zxerr_ledger_api_error; + } + FINALLY { + MEMZERO(&cx_privateKey, sizeof(cx_privateKey)); + MEMZERO(privateKeyData, 32); + } + } + END_TRY; + + return err; +} + #else #include #include "picohash.h" +zxerr_t crypto_getDigest(uint8_t *digest, txtype_e txtype){ + return zxerr_ok; +} + zxerr_t cx_hash_sha256(uint8_t *input, uint16_t inputLen, uint8_t *output, uint16_t outputLen) { if (outputLen < 32) { return zxerr_invalid_crypto_settings; diff --git a/app/src/crypto.h b/app/src/crypto.h index 6bed8e7d..048abe83 100644 --- a/app/src/crypto.h +++ b/app/src/crypto.h @@ -26,6 +26,8 @@ extern "C" { #include #include +#include "parser_txdef.h" + extern uint32_t hdPath[HDPATH_LEN_DEFAULT]; bool isTestnet(); @@ -54,6 +56,14 @@ zxerr_t crypto_sign(uint8_t *signature, uint16_t signatureMaxlen, uint16_t *sigSize); +zxerr_t crypto_sign_combined(uint8_t *signatureBuffer, + uint16_t signatureMaxlen, + uint8_t *predigest_request, + uint8_t *predigest_stateread, + uint16_t *sigSize); + +zxerr_t crypto_getDigest(uint8_t *digest, txtype_e txtype); + zxerr_t crypto_principalToStakeAccount(const uint8_t *principal, uint16_t principalLen, const uint64_t neuron_creation_memo, uint8_t *address, uint16_t maxoutLen); diff --git a/app/src/parser.c b/app/src/parser.c index ac193650..763fbd33 100644 --- a/app/src/parser.c +++ b/app/src/parser.c @@ -50,6 +50,62 @@ GEN_DEC_READFIX_UNSIGNED(32); GEN_DEC_READFIX_UNSIGNED(64); +parser_error_t parser_parse_combined(parser_context_t *ctx, const uint8_t *data, size_t dataLen) { + if (dataLen < 1) { + return parser_no_data; + } + zemu_log_stack("parser parse combined"); + //if combined_tx: + //split data in two transactions + //should start with checking status + //add one more check in validate + //define txtype + const uint8_t *start_state_read_data = data; + CHECK_PARSER_ERR(parser_init(ctx, start_state_read_data, dataLen)) + uint32_t dataLen_state_read = 0; + CHECK_PARSER_ERR(_readUInt32(ctx, &dataLen_state_read)) + ctx->bufferLen = 4 + dataLen_state_read; + + CHECK_PARSER_ERR(_readEnvelope(ctx, &parser_tx_obj)) + PARSER_ASSERT_OR_ERROR(parser_tx_obj.txtype == state_transaction_read, parser_unexpected_type) + CHECK_PARSER_ERR(_validateTx(ctx, &parser_tx_obj)) + uint8_t state_hash[32]; + MEMZERO(state_hash, sizeof(state_hash)); + PARSER_ASSERT_OR_ERROR(zxerr_ok == crypto_getDigest(state_hash, state_transaction_read), parser_unexepected_error) + + uint8_t request_id_stateread[32]; + MEMZERO(request_id_stateread, 32); + PARSER_ASSERT_OR_ERROR(32 == parser_tx_obj.tx_fields.stateRead.paths.paths[1].len, parser_unexepected_error) + + MEMCPY(request_id_stateread, parser_tx_obj.tx_fields.stateRead.paths.paths[1].data, 32); + + data += 4 + dataLen_state_read; + const uint8_t *start_request_data = data; + CHECK_PARSER_ERR(parser_init(ctx, start_request_data, dataLen - 4 - dataLen_state_read)) + uint32_t dataLen_request = 0; + CHECK_PARSER_ERR(_readUInt32(ctx, &dataLen_request)) + ctx->bufferLen = 4 + dataLen_request; + + PARSER_ASSERT_OR_ERROR(dataLen == dataLen_request + dataLen_state_read + 8, parser_context_unexpected_size) + + CHECK_PARSER_ERR(_readEnvelope(ctx, &parser_tx_obj)) + PARSER_ASSERT_OR_ERROR(parser_tx_obj.txtype == call, parser_unexpected_type) + CHECK_PARSER_ERR(_validateTx(ctx, &parser_tx_obj)) + + uint8_t request_hash[32]; + MEMZERO(request_hash, sizeof(request_hash)); + PARSER_ASSERT_OR_ERROR(zxerr_ok == crypto_getDigest(request_hash, call), parser_unexepected_error) + +#if defined(TARGET_NANOS) || defined(TARGET_NANOX) + MEMZERO(G_io_apdu_buffer, IO_APDU_BUFFER_SIZE); + PARSER_ASSERT_OR_ERROR(memcmp(request_hash, request_id_stateread, 32) == 0, parser_context_invalid_chars) + MEMCPY(G_io_apdu_buffer, request_hash, 32); + MEMCPY(G_io_apdu_buffer + 32, state_hash, 32); +#endif + + + return parser_ok; +} parser_error_t parser_parse(parser_context_t *ctx, const uint8_t *data, size_t dataLen) { if (dataLen < 1) { @@ -330,7 +386,7 @@ parser_error_t parser_getItemTokenTransfer(const parser_context_t *ctx, return parser_no_data; } - const bool is_stake_tx = parser_tx_obj.tx_fields.call.special_transfer_type == neuron_stake_transaction; + const bool is_stake_tx = parser_tx_obj.special_transfer_type == neuron_stake_transaction; if (is_stake_tx) { return parser_unexepected_error; } @@ -432,7 +488,7 @@ parser_error_t parser_getItemStakeNeuron(const parser_context_t *ctx, return parser_no_data; } - const bool is_stake_tx = parser_tx_obj.tx_fields.call.special_transfer_type == neuron_stake_transaction; + const bool is_stake_tx = parser_tx_obj.special_transfer_type == neuron_stake_transaction; if (!is_stake_tx) { return parser_unexepected_error; } @@ -994,7 +1050,7 @@ parser_error_t parser_getItem(const parser_context_t *ctx, case call: { switch(parser_tx_obj.tx_fields.call.pbtype) { case pb_sendrequest : { - const bool is_stake_tx = parser_tx_obj.tx_fields.call.special_transfer_type == neuron_stake_transaction; + const bool is_stake_tx = parser_tx_obj.special_transfer_type == neuron_stake_transaction; if (is_stake_tx) { return parser_getItemStakeNeuron(ctx, displayIdx, diff --git a/app/src/parser_impl.c b/app/src/parser_impl.c index b0d58da4..68fd2d06 100644 --- a/app/src/parser_impl.c +++ b/app/src/parser_impl.c @@ -303,7 +303,7 @@ parser_error_t getManageNeuronType(parser_tx_t *v){ parser_error_t readProtobuf(parser_tx_t *v, uint8_t *buffer, size_t bufferLen) { char *method = v->tx_fields.call.method_name.data; - if (strcmp(method, "send_pb") == 0 || v->tx_fields.call.special_transfer_type == neuron_stake_transaction) { + if (strcmp(method, "send_pb") == 0 ||v->special_transfer_type == neuron_stake_transaction) { v->tx_fields.call.pbtype = pb_sendrequest; return _parser_pb_SendRequest(v, buffer, bufferLen); } @@ -330,7 +330,7 @@ parser_error_t readProtobuf(parser_tx_t *v, uint8_t *buffer, size_t bufferLen) { parser_error_t readContent(CborValue *content_map, parser_tx_t *v) { CborValue content_it; - + zemu_log_stack("read content"); PARSER_ASSERT_OR_ERROR(cbor_value_is_container(content_map), parser_unexpected_type) CHECK_CBOR_MAP_ERR(cbor_value_enter_container(content_map, &content_it)) CHECK_CBOR_TYPE(cbor_value_get_type(content_map), CborMapType) @@ -389,14 +389,16 @@ parser_error_t readContent(CborValue *content_map, parser_tx_t *v) { parser_error_t _readEnvelope(const parser_context_t *c, parser_tx_t *v) { zemu_log_stack("read envelope"); CborValue it; + CHECK_APP_CANARY() INIT_CBOR_PARSER(c, it) + CHECK_APP_CANARY() PARSER_ASSERT_OR_ERROR(!cbor_value_at_end(&it), parser_unexpected_buffer_end) - // Verify tag CHECK_CBOR_TYPE(cbor_value_get_type(&it), CborTagType) CborTag tag; CHECK_CBOR_MAP_ERR(cbor_value_get_tag(&it, &tag)) if (tag != 55799) { + zemu_log_stack("wrong tag"); return parser_unexpected_value; } cbor_value_advance(&it); @@ -414,7 +416,6 @@ parser_error_t _readEnvelope(const parser_context_t *c, parser_tx_t *v) { } CborValue envelope; CHECK_CBOR_MAP_ERR(cbor_value_enter_container(&it, &envelope)) - { // Enter content CborValue content_item; @@ -479,6 +480,11 @@ parser_error_t _validateTx(const parser_context_t *c, const parser_tx_t *v) { return parser_unexpected_value; } + if (v->special_transfer_type == invalid){ + zemu_log_stack("invalid transfer type"); + return parser_unexpected_value; + } + if (v->tx_fields.call.pbtype == pb_manageneuron){ ic_nns_governance_pb_v1_ManageNeuron *fields = &parser_tx_obj.tx_fields.call.pb_fields.ic_nns_governance_pb_v1_ManageNeuron; PARSER_ASSERT_OR_ERROR(fields->has_id ^ (fields->neuron_id_or_subaccount.neuron_id.id != 0), parser_unexepected_error); @@ -532,7 +538,7 @@ parser_error_t _validateTx(const parser_context_t *c, const parser_tx_t *v) { #endif - bool is_stake_tx = parser_tx_obj.tx_fields.call.special_transfer_type == neuron_stake_transaction; + bool is_stake_tx = parser_tx_obj.special_transfer_type == neuron_stake_transaction; if(is_stake_tx){ uint8_t to_hash[32]; PARSER_ASSERT_OR_ERROR(zxerr_ok == crypto_principalToStakeAccount(sender, DFINITY_PRINCIPAL_LEN, @@ -555,7 +561,7 @@ uint8_t _getNumItems(const parser_context_t *c, const parser_tx_t *v) { case call: { switch(v->tx_fields.call.pbtype) { case pb_sendrequest: { - const bool is_stake_tx = v->tx_fields.call.special_transfer_type == neuron_stake_transaction; + const bool is_stake_tx =v->special_transfer_type == neuron_stake_transaction; if (is_stake_tx) { return app_mode_expert() ? 7 : 5; diff --git a/app/src/parser_txdef.h b/app/src/parser_txdef.h index 91289468..77272272 100644 --- a/app/src/parser_txdef.h +++ b/app/src/parser_txdef.h @@ -76,7 +76,8 @@ typedef enum { typedef enum { invalid = 0x00, - neuron_stake_transaction = 0x01, + normal_transaction = 0x01, + neuron_stake_transaction = 0x02, } special_transfer_e; typedef struct { @@ -126,8 +127,6 @@ typedef struct { uint64_t ingress_expiry; uint64_t neuron_creation_memo; - special_transfer_e special_transfer_type; - canister_t canister_id; sender_t sender; @@ -160,6 +159,8 @@ typedef struct { call_t call; state_read_t stateRead; } tx_fields; + + special_transfer_e special_transfer_type; } parser_tx_t; #ifdef __cplusplus diff --git a/js/src/common.ts b/js/src/common.ts index 7d49163a..6a7487e7 100644 --- a/js/src/common.ts +++ b/js/src/common.ts @@ -8,6 +8,7 @@ export const INS = { GET_VERSION: 0x00, GET_ADDR_SECP256K1: 0x01, SIGN_SECP256K1: 0x02, + SIGN_COMBINED: 0x03, }; export const PAYLOAD_TYPE = { diff --git a/js/src/index.ts b/js/src/index.ts index eeb6b3b0..d27c99a0 100644 --- a/js/src/index.ts +++ b/js/src/index.ts @@ -15,7 +15,14 @@ * limitations under the License. ******************************************************************************* */ import Transport from '@ledgerhq/hw-transport'; -import {ResponseAddress, ResponseAppInfo, ResponseDeviceInfo, ResponseSign, ResponseVersion} from './types'; +import { + ResponseAddress, + ResponseAppInfo, + ResponseDeviceInfo, + ResponseSign, + ResponseSignUpdateCall, + ResponseVersion +} from './types'; import { ADDRLEN, CHUNK_SIZE, @@ -293,4 +300,91 @@ export default class InternetComputerApp { }, processErrorResponse); }, processErrorResponse); } + + async signSendChunkUpdateCall(chunkIdx: number, chunkNum: number, chunk: Buffer, txtype: number): Promise { + let payloadType = PAYLOAD_TYPE.ADD; + if (chunkIdx === 1) { + payloadType = PAYLOAD_TYPE.INIT; + } + if (chunkIdx === chunkNum) { + payloadType = PAYLOAD_TYPE.LAST; + } + + return this.transport + .send(CLA, INS.SIGN_COMBINED, payloadType, txtype, chunk, [ + LedgerError.NoErrors, + LedgerError.DataIsInvalid, + LedgerError.BadKeyHandle, + LedgerError.SignVerifyError + ]) + .then((response: Buffer) => { + const errorCodeData = response.slice(-2); + const returnCode = errorCodeData[0] * 256 + errorCodeData[1]; + let errorMessage = errorCodeToString(returnCode); + + let RequestHash = Buffer.alloc(0); + let RequestSignatureRS = Buffer.alloc(0); + let StatusReadHash = Buffer.alloc(0); + let StatusReadSignatureRS = Buffer.alloc(0); + + if (returnCode === LedgerError.BadKeyHandle || + returnCode === LedgerError.DataIsInvalid || + returnCode === LedgerError.SignVerifyError) { + errorMessage = `${errorMessage} : ${response + .slice(0, response.length - 2) + .toString('ascii')}`; + } + + if (returnCode === LedgerError.NoErrors && response.length > 2) { + RequestHash = response.slice(0, 32); + RequestSignatureRS = response.slice(32, 96); + StatusReadHash = response.slice(96, 128); + StatusReadSignatureRS = response.slice(128, 192); + return { + RequestHash, + RequestSignatureRS, + StatusReadHash, + StatusReadSignatureRS, + returnCode: returnCode, + errorMessage: errorMessage, + }; + } + + return { + returnCode: returnCode, + errorMessage: errorMessage, + } as ResponseSignUpdateCall; + + }, processErrorResponse); + } + + async signUpdateCall(path: string, request: Buffer, checkStatus: Buffer, txtype: number) { + const message = Buffer.alloc(8 + request.byteLength + checkStatus.byteLength); + message.writeUInt32LE(checkStatus.byteLength, 0); + checkStatus.copy(message, 4); + message.writeUInt32LE(request.byteLength, 4 + checkStatus.byteLength); + request.copy(message, 8 + checkStatus.byteLength); + console.log(message.toString('hex')) + return this.signGetChunks(path, message).then(chunks => { + return this.signSendChunk(1, chunks.length, chunks[0], txtype % 256).then(async response => { + let result = { + returnCode: response.returnCode, + errorMessage: response.errorMessage, + RequestHash: null as null | Buffer, + RequestSignatureRS: null as null | Buffer, + StatusReadHash: null as null | Buffer, + StatusReadSignatureRS: null as null | Buffer, + } as ResponseSignUpdateCall; + + for (let i = 1; i < chunks.length; i += 1) { + // eslint-disable-next-line no-await-in-loop + result = await this.signSendChunkUpdateCall(1 + i, chunks.length, chunks[i], txtype % 256); + if (result.returnCode !== LedgerError.NoErrors) { + break; + } + } + return result; + }, processErrorResponse); + }, processErrorResponse); + } } diff --git a/js/src/types.ts b/js/src/types.ts index b06ff8d5..79907c51 100644 --- a/js/src/types.ts +++ b/js/src/types.ts @@ -44,3 +44,10 @@ export interface ResponseSign extends ResponseBase { signatureRS: Buffer, signatureDER: Buffer, } + +export interface ResponseSignUpdateCall extends ResponseBase { + RequestHash: Buffer, + RequestSignatureRS: Buffer, + StatusReadHash: Buffer, + StatusReadSignatureRS: Buffer, +} diff --git a/tests/cbor_parser.cpp b/tests/cbor_parser.cpp index 4ba6bf6d..a3f8da33 100644 --- a/tests/cbor_parser.cpp +++ b/tests/cbor_parser.cpp @@ -33,6 +33,7 @@ namespace { TEST(TxTest, one_byte_accountid) { + parser_tx_obj.special_transfer_type = normal_transaction; uint8_t inBuffer[1000]; const char *tmp = "d9d9f7a167636f6e74656e74a6636172674c620210011a0612040a0211116b63616e69737465725f69644a000000000000000101016e696e67726573735f6578706972791b16a2cd02c5b2d1006b6d6574686f645f6e616d65706d616e6167655f6e6575726f6e5f70626c726571756573745f747970656463616c6c6673656e646572581d8a4aa4ffc7bc5ccdcd5a7a3d10c9bb06741063b02c7e908a624f721d02"; @@ -490,6 +491,7 @@ namespace { } TEST(CBORParserTest, TokenTransfer) { + parser_tx_obj.special_transfer_type = normal_transaction; uint8_t inBuffer[1000]; const char *tmp = "d9d9f7a367636f6e74656e74a76c726571756573745f747970656463616c6c656e6f6e636550f5390d960c6e52f489155a4309da03da6e696e67726573735f6578706972791b1674c5e29ec9c2106673656e646572581d19aa3d42c048dd7d14f0cfa0df69a1c1381780f6e9a137abaa6a82e3026b63616e69737465725f69644a000000000000000201016b6d6574686f645f6e616d656773656e645f70626361726758560a0012050a0308e8071a0308890122220a2001010101010101010101010101010101010101010101010101010101010101012a220a2035548ec29e9d85305850e87a2d2642fe7214ff4bb36334070deafc3345c3b1276d73656e6465725f7075626b657958583056301006072a8648ce3d020106052b8104000a03420004e1142e1fbc940344d9161709196bb8bd151f94379c48dd507ab99a0776109128b94b5303cf2b2d28e25a779da175b62f8a975599b20c63d5193202640576ec5e6a73656e6465725f7369675840de5bccbb0a0173c432cd58ea4495d4d1e122d6ce04e31dcf63217f3d3a9b73130dc9bbf3b10e61c8db8bf8800bb4649e27786e5bc9418838c95864be28487a6a"; auto inBufferLen = parseHexString(inBuffer, sizeof(inBuffer), tmp); @@ -504,6 +506,7 @@ namespace { } TEST(CBORParserTest, IncreaseNeuronTimer) { + parser_tx_obj.special_transfer_type = normal_transaction; uint8_t inBuffer[1000]; const char *tmp = "d9d9f7a167636f6e74656e74a6636172675839620a10a7d18aaad3a2a2c6131a2b0a0508959aef3a12220a2068d518e2fd2be6566e62c36611b9794dfcbc04eb4227eefb73ab3c7a2d0ae5776b63616e69737465725f69644a000000000000000101016e696e67726573735f6578706972791b169bc8985c330d006b6d6574686f645f6e616d65706d616e6167655f6e6575726f6e5f70626c726571756573745f747970656463616c6c6673656e646572581d8a4aa4ffc7bc5ccdcd5a7a3d10c9bb06741063b02c7e908a624f721d02"; @@ -518,7 +521,7 @@ namespace { } TEST(CBORParserTest, StakeTx) { - parser_tx_obj.tx_fields.call.special_transfer_type = neuron_stake_transaction; + parser_tx_obj.special_transfer_type = neuron_stake_transaction; uint8_t inBuffer[1000]; const char *tmp = "d9d9f7a167636f6e74656e74a663617267583e0a0a08f2d4a0eca697869f0812070a050880c2d72f1a0308904e2a220a20a8a1abecdb66f57eb6eba44c3b5f11a6c433fe932680a9519b064b80ca8794e16b63616e69737465725f69644a000000000000000201016e696e67726573735f6578706972791b16985a582755f1806b6d6574686f645f6e616d656773656e645f70626c726571756573745f747970656463616c6c6673656e646572581d19aa3d42c048dd7d14f0cfa0df69a1c1381780f6e9a137abaa6a82e302"; @@ -530,10 +533,11 @@ namespace { err = parser_validate(&ctx); EXPECT_EQ(err, parser_ok); - parser_tx_obj.tx_fields.call.special_transfer_type = invalid; + parser_tx_obj.special_transfer_type = invalid; } TEST(CBORParserTest, ClaimNeuron) { + parser_tx_obj.special_transfer_type = normal_transaction; uint8_t inBuffer[1000]; const char *tmp = "d9d9f7a167636f6e74656e74a76c726571756573745f747970656463616c6c656e6f6e6365505833a6590c6d2b601e3a24557cfbb4336e696e67726573735f6578706972791b16bad506bb4ca0f06673656e646572581d2594dccb73ca0226c58299d4e21badbcee00d153deccb38fa20cd46e026b63616e69737465725f69644a000000000000000601016b6d6574686f645f6e616d656d636c61696d5f6e6575726f6e7363617267588b4449444c000171820130343139623066656363356639613164353162393033643262363234346430356531326134386661386233353731396538313262623635643966393035613365613965356137323362363537616665393136313236396431663134633164383034376530323230616461633434653731313630323531656364616662613064636535"; @@ -548,6 +552,7 @@ namespace { } TEST(CBORParserTest, JoinCommunityFund) { + parser_tx_obj.special_transfer_type = normal_transaction; uint8_t inBuffer[1000]; const char *tmp = "d9d9f7a167636f6e74656e74a663617267486202107b12023a006b63616e69737465725f69644a000000000000000101016e696e67726573735f6578706972791b16ba67d2b864bf406b6d6574686f645f6e616d65706d616e6167655f6e6575726f6e5f70626c726571756573745f747970656463616c6c6673656e646572581dd899978f029508f4fa5fce3d2539de5aade6d229efcc458233deee7502"; @@ -583,4 +588,30 @@ namespace { EXPECT_EQ(request.neuron_id_or_subaccount.neuron_id.id, 123); } + + TEST(CBORParserTest, CombinedTX) { + uint8_t inBuffer[1000]; + + const char *tmp = "d9d9f7a167636f6e74656e74a46e696e67726573735f6578706972791b16bc685267142b8065706174687381824e726571756573745f737461747573582038b344ba26f15444b4f989078c952ce99b559d3eb59e829c5a463a33812e32546c726571756573745f747970656a726561645f73746174656673656e646572581dd899978f029508f4fa5fce3d2539de5aade6d229efcc458233deee7502"; + uint32_t inBufferLen = parseHexString(inBuffer + 4, sizeof(inBuffer) - 4, tmp); + EXPECT_EQ(inBufferLen, 156); + MEMCPY(&inBuffer[0], &inBufferLen, 4); + + const char *tmp2 = "d9d9f7a167636f6e74656e74a66361726758320a0012050a0308904e1a0308904e2a220a20a2a794c66495083317e4be5197eb655b1e63015469d769e2338af3d3e3f3aa866b63616e69737465725f69644a000000000000000201016e696e67726573735f6578706972791b16bc685084d14ec06b6d6574686f645f6e616d656773656e645f70626c726571756573745f747970656463616c6c6673656e646572581dd899978f029508f4fa5fce3d2539de5aade6d229efcc458233deee7502"; + uint32_t inBufferLen2 = parseHexString(inBuffer + 8 + inBufferLen, sizeof(inBuffer) - 4 - inBufferLen, tmp2); + EXPECT_EQ(inBufferLen2, 192); + MEMCPY(&inBuffer[4 + inBufferLen], &inBufferLen2, 4); +// char array[2000]; +// MEMZERO(array,2000); +// uint16_t total_len = 156 + 192 + 8; +// uint32_t strLen = array_to_hexstr(array, sizeof(array), inBuffer, 160); +// strLen += array_to_hexstr(array + strLen, sizeof(array) - strLen, inBuffer + 160, 200); +// EXPECT_EQ(strLen, 2*(156+192 + 8)); +// std::cout << array << std::endl; + parser_context_t ctx; + parser_tx_obj.special_transfer_type = normal_transaction; + auto err = parser_parse_combined(&ctx, inBuffer, inBufferLen + inBufferLen2 + 8); + EXPECT_EQ(err, parser_ok); + } + } diff --git a/tests/ui_tests.cpp b/tests/ui_tests.cpp index 07d2527d..60d049a0 100644 --- a/tests/ui_tests.cpp +++ b/tests/ui_tests.cpp @@ -122,7 +122,7 @@ void check_testcase(const testcase_t &tc, bool expert_mode) { ASSERT_NE(err, parser_ok) << parser_getErrorDescription(err); return; } - + parser_tx_obj.special_transfer_type = normal_transaction; err = parser_validate(&ctx); ASSERT_EQ(err, parser_ok) << parser_getErrorDescription(err); diff --git a/tests_zemu/snapshots/s-mainmenu/00004.png b/tests_zemu/snapshots/s-mainmenu/00004.png index 77d41e46..64fbc39e 100644 Binary files a/tests_zemu/snapshots/s-mainmenu/00004.png and b/tests_zemu/snapshots/s-mainmenu/00004.png differ diff --git a/tests_zemu/snapshots/s-mainmenu/00011.png b/tests_zemu/snapshots/s-mainmenu/00011.png index 77d41e46..64fbc39e 100644 Binary files a/tests_zemu/snapshots/s-mainmenu/00011.png and b/tests_zemu/snapshots/s-mainmenu/00011.png differ diff --git a/tests_zemu/snapshots/s-sign_updateCall/00000.png b/tests_zemu/snapshots/s-sign_updateCall/00000.png new file mode 100644 index 00000000..6d8cc453 Binary files /dev/null and b/tests_zemu/snapshots/s-sign_updateCall/00000.png differ diff --git a/tests_zemu/snapshots/s-sign_updateCall/00001.png b/tests_zemu/snapshots/s-sign_updateCall/00001.png new file mode 100644 index 00000000..30a0ddce Binary files /dev/null and b/tests_zemu/snapshots/s-sign_updateCall/00001.png differ diff --git a/tests_zemu/snapshots/s-sign_updateCall/00002.png b/tests_zemu/snapshots/s-sign_updateCall/00002.png new file mode 100644 index 00000000..3d2dcf03 Binary files /dev/null and b/tests_zemu/snapshots/s-sign_updateCall/00002.png differ diff --git a/tests_zemu/snapshots/s-sign_updateCall/00003.png b/tests_zemu/snapshots/s-sign_updateCall/00003.png new file mode 100644 index 00000000..a29bdef7 Binary files /dev/null and b/tests_zemu/snapshots/s-sign_updateCall/00003.png differ diff --git a/tests_zemu/snapshots/s-sign_updateCall/00004.png b/tests_zemu/snapshots/s-sign_updateCall/00004.png new file mode 100644 index 00000000..d5b7b976 Binary files /dev/null and b/tests_zemu/snapshots/s-sign_updateCall/00004.png differ diff --git a/tests_zemu/snapshots/s-sign_updateCall/00005.png b/tests_zemu/snapshots/s-sign_updateCall/00005.png new file mode 100644 index 00000000..86a97f75 Binary files /dev/null and b/tests_zemu/snapshots/s-sign_updateCall/00005.png differ diff --git a/tests_zemu/snapshots/s-sign_updateCall/00006.png b/tests_zemu/snapshots/s-sign_updateCall/00006.png new file mode 100644 index 00000000..a6d9fc54 Binary files /dev/null and b/tests_zemu/snapshots/s-sign_updateCall/00006.png differ diff --git a/tests_zemu/snapshots/s-sign_updateCall/00007.png b/tests_zemu/snapshots/s-sign_updateCall/00007.png new file mode 100644 index 00000000..b00a17e6 Binary files /dev/null and b/tests_zemu/snapshots/s-sign_updateCall/00007.png differ diff --git a/tests_zemu/snapshots/s-sign_updateCall/00008.png b/tests_zemu/snapshots/s-sign_updateCall/00008.png new file mode 100644 index 00000000..06ec16e6 Binary files /dev/null and b/tests_zemu/snapshots/s-sign_updateCall/00008.png differ diff --git a/tests_zemu/snapshots/s-sign_updateCall/00009.png b/tests_zemu/snapshots/s-sign_updateCall/00009.png new file mode 100644 index 00000000..6c609bb9 Binary files /dev/null and b/tests_zemu/snapshots/s-sign_updateCall/00009.png differ diff --git a/tests_zemu/snapshots/x-mainmenu/00004.png b/tests_zemu/snapshots/x-mainmenu/00004.png index 96eb5277..c69a8f43 100644 Binary files a/tests_zemu/snapshots/x-mainmenu/00004.png and b/tests_zemu/snapshots/x-mainmenu/00004.png differ diff --git a/tests_zemu/snapshots/x-mainmenu/00011.png b/tests_zemu/snapshots/x-mainmenu/00011.png index 96eb5277..c69a8f43 100644 Binary files a/tests_zemu/snapshots/x-mainmenu/00011.png and b/tests_zemu/snapshots/x-mainmenu/00011.png differ diff --git a/tests_zemu/snapshots/x-sign_updateCall/00000.png b/tests_zemu/snapshots/x-sign_updateCall/00000.png new file mode 100644 index 00000000..a4621c24 Binary files /dev/null and b/tests_zemu/snapshots/x-sign_updateCall/00000.png differ diff --git a/tests_zemu/snapshots/x-sign_updateCall/00001.png b/tests_zemu/snapshots/x-sign_updateCall/00001.png new file mode 100644 index 00000000..4bda06fa Binary files /dev/null and b/tests_zemu/snapshots/x-sign_updateCall/00001.png differ diff --git a/tests_zemu/snapshots/x-sign_updateCall/00002.png b/tests_zemu/snapshots/x-sign_updateCall/00002.png new file mode 100644 index 00000000..180cf55f Binary files /dev/null and b/tests_zemu/snapshots/x-sign_updateCall/00002.png differ diff --git a/tests_zemu/snapshots/x-sign_updateCall/00003.png b/tests_zemu/snapshots/x-sign_updateCall/00003.png new file mode 100644 index 00000000..0c60d6f9 Binary files /dev/null and b/tests_zemu/snapshots/x-sign_updateCall/00003.png differ diff --git a/tests_zemu/snapshots/x-sign_updateCall/00004.png b/tests_zemu/snapshots/x-sign_updateCall/00004.png new file mode 100644 index 00000000..982ca30f Binary files /dev/null and b/tests_zemu/snapshots/x-sign_updateCall/00004.png differ diff --git a/tests_zemu/snapshots/x-sign_updateCall/00005.png b/tests_zemu/snapshots/x-sign_updateCall/00005.png new file mode 100644 index 00000000..68a0ffae Binary files /dev/null and b/tests_zemu/snapshots/x-sign_updateCall/00005.png differ diff --git a/tests_zemu/snapshots/x-sign_updateCall/00006.png b/tests_zemu/snapshots/x-sign_updateCall/00006.png new file mode 100644 index 00000000..a5dfd8cd Binary files /dev/null and b/tests_zemu/snapshots/x-sign_updateCall/00006.png differ diff --git a/tests_zemu/snapshots/x-sign_updateCall/00007.png b/tests_zemu/snapshots/x-sign_updateCall/00007.png new file mode 100644 index 00000000..2f99be64 Binary files /dev/null and b/tests_zemu/snapshots/x-sign_updateCall/00007.png differ diff --git a/tests_zemu/snapshots/x-sign_updateCall/00008.png b/tests_zemu/snapshots/x-sign_updateCall/00008.png new file mode 100644 index 00000000..e8be6f70 Binary files /dev/null and b/tests_zemu/snapshots/x-sign_updateCall/00008.png differ diff --git a/tests_zemu/snapshots/x-sign_updateCall/00009.png b/tests_zemu/snapshots/x-sign_updateCall/00009.png new file mode 100644 index 00000000..03398bcd Binary files /dev/null and b/tests_zemu/snapshots/x-sign_updateCall/00009.png differ diff --git a/tests_zemu/snapshots/x-sign_updateCall/00010.png b/tests_zemu/snapshots/x-sign_updateCall/00010.png new file mode 100644 index 00000000..cefc39ac Binary files /dev/null and b/tests_zemu/snapshots/x-sign_updateCall/00010.png differ diff --git a/tests_zemu/tests/phase2.test.ts b/tests_zemu/tests/phase2.test.ts index 4a9f449b..a150b027 100644 --- a/tests_zemu/tests/phase2.test.ts +++ b/tests_zemu/tests/phase2.test.ts @@ -470,4 +470,51 @@ describe('Phase2', function () { } }) + test.each(models)('sign normal -- combined_tx', async function (m) { + const sim = new Zemu(m.path) + try { + await sim.start({ ...defaultOptions, model: m.name }) + const app = new InternetComputerApp(sim.getTransport()) + + const txBlobStr_read = + 'd9d9f7a167636f6e74656e74a46e696e67726573735f6578706972791b16bc685267142b8065706174687381824e726571756573745f73746174757358208d304d294d3f611f992b3f2b184d32b9b3c058d918d7a7ab1946614b13ba0a496c726571756573745f747970656a726561645f73746174656673656e646572581d19AA3D42C048DD7D14F0CFA0DF69A1C1381780F6E9A137ABAA6A82E302' + const txBlob_read = Buffer.from(txBlobStr_read, 'hex') + + const txBlobStr_request = + 'd9d9f7a167636f6e74656e74a66361726758320a0012050a0308904e1a0308904e2a220a20a2a794c66495083317e4be5197eb655b1e63015469d769e2338af3d3e3f3aa866b63616e69737465725f69644a000000000000000201016e696e67726573735f6578706972791b16bc685084d14ec06b6d6574686f645f6e616d656773656e645f70626c726571756573745f747970656463616c6c6673656e646572581d19AA3D42C048DD7D14F0CFA0DF69A1C1381780F6E9A137ABAA6A82E302' + const txBlob_request = Buffer.from(txBlobStr_request, 'hex') + + const respRequest = app.signUpdateCall("m/44'/223'/0'/0/0", txBlob_request, txBlob_read, SIGN_VALUES_P2.DEFAULT) + + // Wait until we are not in the main menu + await sim.waitUntilScreenIsNot(sim.getMainMenuSnapshot()) + + await sim.compareSnapshotsAndAccept('.', `${m.prefix.toLowerCase()}-sign_updateCall`, m.name === 'nanos' ? 8 : 9) + + const signatureResponse = await respRequest + console.log(signatureResponse) + + expect(signatureResponse.returnCode).toEqual(0x9000) + expect(signatureResponse.errorMessage).toEqual('No errors') + + const pk = Uint8Array.from(Buffer.from("0410d34980a51af89d3331ad5fa80fe30d8868ad87526460b3b3e15596ee58e812422987d8589ba61098264df5bb9c2d3ff6fe061746b4b31a44ec26636632b835", 'hex')) + + const digest_request = Uint8Array.from(signatureResponse.RequestHash) + const signature_request = Uint8Array.from(signatureResponse.RequestSignatureRS) + expect(signature_request.byteLength).toEqual(64) + + const signatureOk = secp256k1.ecdsaVerify(signature_request, digest_request, pk) + expect(signatureOk).toEqual(true) + + const digest_statusread = Uint8Array.from(signatureResponse.StatusReadHash) + const signature_statusread = Uint8Array.from(signatureResponse.StatusReadSignatureRS) + expect(signature_request.byteLength).toEqual(64) + + const signatureOk_statusread = secp256k1.ecdsaVerify(signature_statusread, digest_statusread, pk) + expect(signatureOk_statusread).toEqual(true) + + } finally { + await sim.close() + } + }) }) \ No newline at end of file