diff --git a/Makefile b/Makefile index 9a7a3bd5..b84f57f2 100644 --- a/Makefile +++ b/Makefile @@ -173,5 +173,5 @@ listvariants: # Generate delegates from baker list src/delegates.h: tools/gen-delegates.sh tools/BakersRegistryCoreUnfilteredData.json - ./tools/gen-delegates.sh ./tools/BakersRegistryCoreUnfilteredData.json + bash ./tools/gen-delegates.sh ./tools/BakersRegistryCoreUnfilteredData.json dep/to_string.d: src/delegates.h diff --git a/default.nix b/default.nix index c2f023e0..cf7f8d5c 100644 --- a/default.nix +++ b/default.nix @@ -49,6 +49,7 @@ let ''; nativeBuildInputs = [ (pkgs.python3.withPackages (ps: [ps.pillow ps.ledgerblue])) + pkgs.jq ]; TARGET = bolos.target; GIT_DESCRIBE = gitDescribe; diff --git a/src/apdu.c b/src/apdu.c index 0d0b5fd7..88491705 100644 --- a/src/apdu.c +++ b/src/apdu.c @@ -79,12 +79,14 @@ void main_loop(apdu_handler const *const handlers, size_t const handlers_size) { clear_apdu_globals(); // IMPORTANT: Application state must not persist through errors uint16_t sw = e; + PRINTF("Error caught at top level, number: %x\n", sw); switch (sw) { default: sw = 0x6800 | (e & 0x7FF); // FALL THROUGH case 0x6000 ... 0x6FFF: case 0x9000 ... 0x9FFF: { + PRINTF("Line number: %d", sw & 0x0FFF); size_t tx = 0; G_io_apdu_buffer[tx++] = sw >> 8; G_io_apdu_buffer[tx++] = sw; diff --git a/src/apdu_pubkey.c b/src/apdu_pubkey.c index 28b8b59e..0ddbeeb5 100644 --- a/src/apdu_pubkey.c +++ b/src/apdu_pubkey.c @@ -7,6 +7,9 @@ #include "protocol.h" #include "to_string.h" #include "ui.h" +#ifdef BAKING_APP +#include "baking_auth.h" +#endif // BAKING_APP #include diff --git a/src/apdu_sign.c b/src/apdu_sign.c index 771a9573..e63af86e 100644 --- a/src/apdu_sign.c +++ b/src/apdu_sign.c @@ -106,6 +106,7 @@ static bool is_operation_allowed(enum operation_tag tag) { } } +#ifdef BAKING_APP static bool parse_allowed_operations( struct parsed_operation_group *const out, uint8_t const *const in, @@ -115,6 +116,18 @@ static bool parse_allowed_operations( return parse_operations(out, in, in_size, key->derivation_type, &key->bip32_path, &is_operation_allowed); } +#else + +static bool parse_allowed_operation_packet( + struct parsed_operation_group *const out, + uint8_t const *const in, + size_t const in_size +) { + return parse_operations_packet(out, in, in_size, &is_operation_allowed); +} + +#endif + #ifdef BAKING_APP // ---------------------------------------------------------- __attribute__((noreturn)) static void prompt_register_delegate( @@ -556,12 +569,14 @@ static size_t handle_apdu(bool const enable_hashing, bool const enable_parsing, if (!parse_baking_data(&G.parsed_baking_data, buff, buff_size)) PARSE_ERROR(); } # else - if (G.packet_index == 1) { + if (G.packet_index == 1) { + G.maybe_ops.is_valid = false; G.magic_byte = get_magic_byte_or_throw(buff, buff_size); - G.maybe_ops.is_valid = parse_allowed_operations(&G.maybe_ops.v, buff, buff_size, &G.key); - } else { - G.maybe_ops.is_valid = false; // Force multiple packets to be treated as unparsed - } + parse_operations_init(&G.maybe_ops.v, G.key.derivation_type, &G.key.bip32_path, &G.parse_state); + } + + parse_allowed_operation_packet(&G.maybe_ops.v, buff, buff_size); + # endif } @@ -592,6 +607,8 @@ static size_t handle_apdu(bool const enable_hashing, bool const enable_parsing, &G.hash_state); } + G.maybe_ops.is_valid = parse_operations_final(&G.parse_state, &G.maybe_ops.v); + return # ifdef BAKING_APP baking_sign_complete(instruction == INS_SIGN_WITH_HASH); diff --git a/src/globals.h b/src/globals.h index 2106ab14..e2c1ab81 100644 --- a/src/globals.h +++ b/src/globals.h @@ -4,6 +4,8 @@ #include "bolos_target.h" +#include "operations.h" + // Zeros out all globals that can keep track of APDU instruction state. // Notably this does *not* include UI state. void clear_apdu_globals(void); @@ -62,6 +64,7 @@ typedef struct { uint8_t magic_byte; bool hash_only; + struct parse_state parse_state; } apdu_sign_state_t; typedef struct { diff --git a/src/operations.c b/src/operations.c index b87c4d7d..2e1ff860 100644 --- a/src/operations.c +++ b/src/operations.c @@ -10,52 +10,7 @@ #include #include -// Wire format that gets parsed into `signature_type`. -typedef struct { - uint8_t v; -} __attribute__((packed)) raw_tezos_header_signature_type_t; - -struct operation_group_header { - uint8_t magic_byte; - uint8_t hash[32]; -} __attribute__((packed)); - -struct implicit_contract { - raw_tezos_header_signature_type_t signature_type; - uint8_t pkh[HASH_SIZE]; -} __attribute__((packed)); - -struct contract { - uint8_t originated; - union { - struct implicit_contract implicit; - struct { - uint8_t pkh[HASH_SIZE]; - uint8_t padding; - } originated; - } u; -} __attribute__((packed)); - -struct delegation_contents { - raw_tezos_header_signature_type_t signature_type; - uint8_t hash[HASH_SIZE]; -} __attribute__((packed)); - -struct proposal_contents { - int32_t period; - size_t num_bytes; - uint8_t hash[PROTOCOL_HASH_SIZE]; -} __attribute__((packed)); - -struct ballot_contents { - int32_t period; - uint8_t proposal[PROTOCOL_HASH_SIZE]; - int8_t ballot; -} __attribute__((packed)); - -typedef struct { - uint8_t v[HASH_SIZE]; -} __attribute__((packed)) hash_t; +#define STEP_HARD_FAIL -2 // Argument is to distinguish between different parse errors for debugging purposes only __attribute__((noreturn)) @@ -64,6 +19,8 @@ static void parse_error( __attribute__((unused)) #endif uint32_t lineno) { + + global.apdu.u.sign.parse_state.op_step=STEP_HARD_FAIL; #ifdef TEZOS_DEBUG THROW(0x9000 + lineno); #else @@ -73,67 +30,7 @@ static void parse_error( #define PARSE_ERROR() parse_error(__LINE__) -static inline void advance_ix(size_t *ix, size_t length, size_t amount) { - if (*ix + amount > length) PARSE_ERROR(); - - *ix += amount; -} - -static inline uint8_t next_byte(void const *data, size_t *ix, size_t length, uint32_t lineno) { - if (*ix >= length) parse_error(lineno); - uint8_t res = ((const char *)data)[*ix]; - (*ix)++; - return res; -} - -#define NEXT_BYTE(data, ix, length) next_byte(data, ix, length, __LINE__) - -static inline uint64_t parse_z(void const *data, size_t *ix, size_t length, uint32_t lineno) { - uint64_t acc = 0; - for (uint8_t shift = 0; ; ) { - const uint8_t byte = next_byte(data, ix, length, lineno); - acc |= ((uint64_t)byte & 0x7F) << shift; - if (!(byte & 0x80)) { - break; - } - shift += 7; - } - return acc; -} - -#define PARSE_Z(data, ix, length) parse_z(data, ix, length, __LINE__) - -static inline uint64_t parse_z_michelson(void const *data, size_t *ix, size_t length, uint32_t lineno) { - uint64_t acc = 0; - for (uint8_t shift = 0; ; ) { - const uint8_t byte = next_byte(data, ix, length, lineno); - acc |= ((uint64_t)byte & 0x7F) << shift; - if (!(byte & 0x80)) { - break; - } - // For some reason we are getting numbers shifted 1 bit to the - // left. TODO: figure out why this happens - if (shift == 0) { - shift += 6; - } else { - shift += 7; - } - } - return acc; -} - -#define PARSE_Z_MICHELSON(data, ix, length) parse_z_michelson(data, ix, length, __LINE__) - -// This macro assumes: -// * Beginning of data: const void *data -// * Total length of data: size_t length -// * Current index of data: size_t ix -// Any function that uses these macros should have these as local variables -#define NEXT_TYPE(type) ({ \ - const type *val = data + ix; \ - advance_ix(&ix, length, sizeof(type)); \ - val; \ -}) +// Conversion/check functions static inline signature_type_t parse_raw_tezos_header_signature_type( raw_tezos_header_signature_type_t const *const raw_signature_type @@ -188,62 +85,169 @@ static inline void parse_contract(parsed_contract_t *const out, struct contract } } -static inline uint32_t michelson_read_length(void const *data, size_t *ix, size_t length, uint32_t lineno) { - if (*ix >= length) parse_error(lineno); - const uint32_t res = READ_UNALIGNED_BIG_ENDIAN(uint32_t, &data[*ix]); - (*ix) += sizeof(uint32_t); - return res; + +#define CALL_SUBPARSER_LN(func, line, ...) if(func( __VA_ARGS__, line)) return true +#define CALL_SUBPARSER(func, ...) CALL_SUBPARSER_LN(func, __LINE__, __VA_ARGS__) + +// Subparsers: no function here should be called anywhere in this file without using the CALL_SUBPARSER macro above. +// Verify this with +// /\s*( +// the only result should be the functiond definition. + +#define NEXT_BYTE (byte) + +static inline bool parse_z(uint8_t current_byte, struct int_subparser_state *state, uint32_t lineno) { + if(state->lineno != lineno) { + // New call; initialize. + state->lineno = lineno; + state->value = 0; + state->shift = 0; + } + state->value |= ((uint64_t)current_byte & 0x7F) << state->shift; + state->shift += 7; + return current_byte & 0x80; // Return true if we need more bytes. +} + +#define PARSE_Z ({CALL_SUBPARSER(parse_z, (byte), &(state)->subparser_state.integer); (state)->subparser_state.integer.value;}) + +// Only used through the macro +static inline bool parse_z_michelson(uint8_t current_byte, struct int_subparser_state *state, uint32_t lineno) { + if(state->lineno != lineno) { + // New call; initialize. + state->lineno = lineno; + state->value = 0; + state->shift = 0; + } + state->value |= ((uint64_t)current_byte & 0x7F) << state->shift; + // For some reason we are getting numbers shifted 1 bit to the + // left. TODO: figure out why this happens + if (state->shift == 0) { + state->shift += 6; + } else { + state->shift += 7; + } + return current_byte & 0x80; // Return true if we need more bytes. +} + +#define PARSE_Z_MICHELSON ({CALL_SUBPARSER(parse_z_michelson, (byte), (&state->subparser_state.integer)); state->subparser_state.integer.value;}) + +static inline bool parse_next_type(uint8_t current_byte, struct nexttype_subparser_state *state, uint32_t sizeof_type, uint32_t lineno) { + #ifdef DEBUG + if(sizeof_type > sizeof(state->body)) PARSE_ERROR(); // Shouldn't happen, but error if it does and we're debugging. Neither side is dynamic. + #endif + + if(state->lineno != lineno) { + state->lineno = lineno; + state->fill_idx = 0; + } + + state->body.raw[state->fill_idx]=current_byte; + state->fill_idx++; + + return state->fill_idx < sizeof_type; // Return true if we need more bytes. } -#define MICHELSON_READ_LENGTH(data, ix, length) michelson_read_length(data, ix, length, __LINE__) +// do _NOT_ keep pointers to this data around. +#define NEXT_TYPE(type) ({CALL_SUBPARSER(parse_next_type, byte, &(state->subparser_state.nexttype), sizeof(type)); (const type *) &(state->subparser_state.nexttype.body);}) -static inline uint16_t michelson_read_short(void const *data, size_t *ix, size_t length, uint32_t lineno) { - if (*ix >= length) parse_error(lineno); - const uint16_t res = READ_UNALIGNED_BIG_ENDIAN(uint16_t, &data[*ix]); - (*ix) += sizeof(uint16_t); - return res; + +static inline bool michelson_read_length(uint8_t current_byte, struct nexttype_subparser_state *state, uint32_t lineno) { + CALL_SUBPARSER_LN(parse_next_type, lineno, current_byte, state, sizeof(uint32_t)); // Using the line number we were called with. + uint32_t res = READ_UNALIGNED_BIG_ENDIAN(uint32_t, &state->body.raw); + state->body.i32 = res; + return false; } -#define MICHELSON_READ_SHORT(data, ix, length) michelson_read_short(data, ix, length, __LINE__) +#define MICHELSON_READ_LENGTH ({CALL_SUBPARSER(michelson_read_length, byte, &state->subparser_state.nexttype); state->subparser_state.nexttype.body.i32;}) -static inline void michelson_read_address(parsed_contract_t *const out, const void *data, size_t *ix, size_t length) { - switch (NEXT_BYTE(data, ix, length)) { - case MICHELSON_TYPE_BYTE_SEQUENCE: { - // Need 1 byte for signature, plus the rest of the hash. - if (MICHELSON_READ_LENGTH(data, ix, length) != HASH_SIZE + 1) { - PARSE_ERROR(); - } - const raw_tezos_header_signature_type_t* signature_type = data + *ix; - advance_ix(ix, length, sizeof(raw_tezos_header_signature_type_t)); - const hash_t *key_hash = data + *ix; - advance_ix(ix, length, sizeof(hash_t)); - parse_implicit(out, signature_type, (const uint8_t *)key_hash); - break; - } - case MICHELSON_TYPE_STRING: { - if (MICHELSON_READ_LENGTH(data, ix, length) != HASH_SIZE_B58) { - PARSE_ERROR(); +static inline bool michelson_read_short(uint8_t current_byte, struct nexttype_subparser_state *state, uint32_t lineno) { + CALL_SUBPARSER_LN(parse_next_type, lineno, current_byte, state, sizeof(uint16_t)); + uint32_t res = READ_UNALIGNED_BIG_ENDIAN(uint16_t, &state->body.raw); + state->body.i16 = res; + return false; +} + +#define MICHELSON_READ_SHORT ({CALL_SUBPARSER(michelson_read_short, byte, &state->subparser_state.nexttype); state->subparser_state.nexttype.body.i16;}) + +static inline bool michelson_read_address( + uint8_t byte, + parsed_contract_t *const out, + char* base58_address_buffer, + struct michelson_address_subparser_state *state, + uint32_t lineno) { + + if(state->lineno != lineno) { // We won't have to use CALL_SUBPARSER_LN because we're initializing ourselves here. + memset(state, 0, sizeof(*state)); + state->lineno=lineno; + } + switch (state->address_step) { + case 0: + state->micheline_type=byte; + state->address_step=1; + return true; + + case 1: + + CALL_SUBPARSER(michelson_read_length, byte, &state->subsub_state); + state->addr_length = state->subsub_state.body.i32; + + state->address_step=2; + return true; + default: { + switch (state->micheline_type) { + case MICHELSON_TYPE_BYTE_SEQUENCE: { + switch (state->address_step) { + case 2: + + // Need 1 byte for signature, plus the rest of the hash. + if (state->addr_length != HASH_SIZE + 1) { + PARSE_ERROR(); + } + + CALL_SUBPARSER(parse_next_type, byte, &(state->subsub_state), sizeof(state->signature_type)); + memcpy(&(state->signature_type), &(state->subsub_state.body), sizeof(state->signature_type)); + + case 3: + + CALL_SUBPARSER(parse_next_type, byte, &(state->subsub_state), sizeof(state->key_hash)); + memcpy(&(state->key_hash), &(state->subsub_state.body), sizeof(state->key_hash)); + + parse_implicit(out, &state->signature_type, (const uint8_t *)&state->key_hash); + + return false; + } + case MICHELSON_TYPE_STRING: { + if (state->addr_length != HASH_SIZE_B58) { + PARSE_ERROR(); + } + + CALL_SUBPARSER(parse_next_type, byte, &(state->subsub_state), sizeof(state->subsub_state.body.text_pkh)); + + memcpy(base58_address_buffer, state->subsub_state.body.text_pkh, sizeof(state->subsub_state.body.text_pkh)); + out->hash_ptr = base58_address_buffer; + out->originated = false; + out->signature_type = SIGNATURE_TYPE_UNSET; + return false; + } + default: PARSE_ERROR(); + } } - out->hash_ptr = (char*)data + *ix; - (*ix) += HASH_SIZE_B58; - out->originated = false; - out->signature_type = SIGNATURE_TYPE_UNSET; - break; } - default: PARSE_ERROR(); } } -static void parse_operations_throws_parse_error( +#define MICHELSON_READ_ADDRESS(out, buffer) CALL_SUBPARSER(michelson_read_address, byte, (out), buffer, &state->subparser_state.michelson_address) + +// End of subparsers. + +void parse_operations_init( struct parsed_operation_group *const out, - void const *const data, - size_t length, derivation_type_t derivation_type, bip32_path_t const *const bip32_path, - is_operation_allowed_t is_operation_allowed -) { + struct parse_state *const state + ) { + check_null(out); - check_null(data); check_null(bip32_path); memset(out, 0, sizeof(*out)); @@ -251,75 +255,168 @@ static void parse_operations_throws_parse_error( compute_pkh(&out->public_key, &out->signing, derivation_type, bip32_path); - size_t ix = 0; - - // Verify magic byte, ignore block hash - const struct operation_group_header *ogh = NEXT_TYPE(struct operation_group_header); - if (ogh->magic_byte != MAGIC_BYTE_UNSAFE_OP) PARSE_ERROR(); - // Start out with source = signing, for reveals // TODO: This is slightly hackish memcpy(&out->operation.source, &out->signing, sizeof(out->signing)); - while (ix < length) { - const enum operation_tag tag = NEXT_BYTE(data, &ix, length); // 1 byte is always aligned + state->op_step=0; + state->subparser_state.integer.lineno=-1; + state->tag=OPERATION_TAG_NONE; // This and the rest shouldn't be required. + state->argument_length=0; + state->michelson_op=-1; +} - if (!is_operation_allowed(tag)) PARSE_ERROR(); +// Named steps in the top-level state machine +#define STEP_END_OF_MESSAGE -1 +#define STEP_OP_TYPE_DISPATCH 10001 +#define STEP_AFTER_MANAGER_FIELDS 10002 +#define STEP_HAS_DELEGATE 10003 +#define STEP_MICHELSON_FIRST_IS_PUSH 10010 +#define STEP_MICHELSON_FIRST_IS_NONE 10011 +#define STEP_MICHELSON_SECOND_IS_KEY_HASH 10012 +#define STEP_MICHELSON_CONTRACT_TO_CONTRACT 10013 +#define STEP_MICHELSON_SET_DELEGATE_CHAIN 10014 +#define STEP_MICHELSON_CONTRACT_TO_IMPLICIT_CHAIN 10015 +#define STEP_MICHELSON_CONTRACT_END 10016 +#define STEP_MICHELSON_CHECKING_CONTRACT_ENTRYPOINT 10017 +#define STEP_MICHELSON_CONTRACT_TO_CONTRACT_CHAIN_2 10018 + +bool parse_operations_final(struct parse_state *const state, struct parsed_operation_group *const out) { + if (out->operation.tag == OPERATION_TAG_NONE && !out->has_reveal) { + return false; + } + return state->op_step == STEP_END_OF_MESSAGE || state->op_step == 1; +} - // Parse 'source' - switch (tag) { - // Tags that don't have "originated" byte only support tz accounts, not KT or tz. - case OPERATION_TAG_PROPOSAL: - case OPERATION_TAG_BALLOT: - case OPERATION_TAG_BABYLON_DELEGATION: - case OPERATION_TAG_BABYLON_ORIGINATION: - case OPERATION_TAG_BABYLON_REVEAL: - case OPERATION_TAG_BABYLON_TRANSACTION: { - struct implicit_contract const *const implicit_source = NEXT_TYPE(struct implicit_contract); - out->operation.source.originated = 0; - out->operation.source.signature_type = parse_raw_tezos_header_signature_type(&implicit_source->signature_type); - memcpy(out->operation.source.hash, implicit_source->pkh, sizeof(out->operation.source.hash)); - break; +static inline bool parse_byte( + uint8_t byte, + struct parse_state *const state, + struct parsed_operation_group *const out, + is_operation_allowed_t is_operation_allowed + ){ + +// OP_STEP finishes the current state transition, setting the state, and introduces the next state. For linear chains of states, this keeps the code structurally similar to equivalent imperative parsing code. +#define OP_STEP state->op_step=__LINE__; return true; case __LINE__: + +// The same as OP_STEP, but with a particular name, such that we could jump to this state. +#define OP_NAMED_STEP(name) state->op_step=name; return true; case name: + +// "jump" to specific state: (set state to foo and return.) +#define JMP(step) state->op_step=step; return true + +// Set the next state to end-of-message +#define JMP_EOM JMP(-1) + +// Set the next state to start-of-payload; used after reveal. +#define JMP_TO_TOP JMP(1) + +// Conditionally set the next state. +#define OP_JMPIF(step, cond) if(cond) { state->op_step=step; return true; } + +// Shortcuts for defining literal-matching states; mostly used for contract-call boilerplate. +#define OP_STEP_REQUIRE_SHORT(constant) { uint16_t val = MICHELSON_READ_SHORT; if(val != constant) PARSE_ERROR(); } OP_STEP +#define OP_STEP_REQUIRE_BYTE(constant) { if(byte != constant) PARSE_ERROR(); } OP_STEP +#define OP_STEP_REQUIRE_LENGTH(constant) { uint32_t val = MICHELSON_READ_LENGTH; if(val != constant) PARSE_ERROR(); } OP_STEP + + switch(state->op_step) { + + case STEP_HARD_FAIL: + PARSE_ERROR(); + + case STEP_END_OF_MESSAGE: + PARSE_ERROR(); // We already hit a hard end of message; fail. + + + case 0: + { + // Verify magic byte, ignore block hash + const struct operation_group_header *ogh = NEXT_TYPE(struct operation_group_header); + if (ogh->magic_byte != MAGIC_BYTE_UNSAFE_OP) PARSE_ERROR(); } - case OPERATION_TAG_ATHENS_DELEGATION: - case OPERATION_TAG_ATHENS_ORIGINATION: - case OPERATION_TAG_ATHENS_REVEAL: - case OPERATION_TAG_ATHENS_TRANSACTION: { - struct contract const *const source = NEXT_TYPE(struct contract); - parse_contract(&out->operation.source, source); - break; + OP_NAMED_STEP(1) + + state->tag = NEXT_BYTE; + + if (!is_operation_allowed(state->tag)) PARSE_ERROR(); + + OP_STEP + + // Parse 'source' + switch (state->tag) { + // Tags that don't have "originated" byte only support tz accounts, not KT or tz. + case OPERATION_TAG_PROPOSAL: + case OPERATION_TAG_BALLOT: + case OPERATION_TAG_BABYLON_DELEGATION: + case OPERATION_TAG_BABYLON_ORIGINATION: + case OPERATION_TAG_BABYLON_REVEAL: + case OPERATION_TAG_BABYLON_TRANSACTION: { + struct implicit_contract const *const implicit_source = NEXT_TYPE(struct implicit_contract); + out->operation.source.originated = 0; + out->operation.source.signature_type = parse_raw_tezos_header_signature_type(&implicit_source->signature_type); + memcpy(out->operation.source.hash, implicit_source->pkh, sizeof(out->operation.source.hash)); + break; + } + + case OPERATION_TAG_ATHENS_DELEGATION: + case OPERATION_TAG_ATHENS_ORIGINATION: + case OPERATION_TAG_ATHENS_REVEAL: + case OPERATION_TAG_ATHENS_TRANSACTION: { + struct contract const *const source = NEXT_TYPE(struct contract); + parse_contract(&out->operation.source, source); + break; + } + + default: PARSE_ERROR(); } - default: PARSE_ERROR(); - } + + OP_JMPIF(STEP_AFTER_MANAGER_FIELDS, (state->tag == OPERATION_TAG_PROPOSAL || state->tag == OPERATION_TAG_BALLOT)); + + OP_STEP // out->operation.source IS NORMALIZED AT THIS POINT // Parse common fields for non-governance related operations. - if (tag != OPERATION_TAG_PROPOSAL && tag != OPERATION_TAG_BALLOT) { - out->total_fee += PARSE_Z(data, &ix, length); // fee - PARSE_Z(data, &ix, length); // counter - PARSE_Z(data, &ix, length); // gas limit - out->total_storage_limit += PARSE_Z(data, &ix, length); // storage limit - } - if (tag == OPERATION_TAG_ATHENS_REVEAL || tag == OPERATION_TAG_BABYLON_REVEAL) { + out->total_fee += PARSE_Z; // fee + OP_STEP + PARSE_Z; // counter + OP_STEP + PARSE_Z; // gas limit + OP_STEP + out->total_storage_limit += PARSE_Z; // storage limit + + OP_JMPIF(STEP_AFTER_MANAGER_FIELDS, (state->tag != OPERATION_TAG_ATHENS_REVEAL && state->tag != OPERATION_TAG_BABYLON_REVEAL)) + OP_STEP + + // We know this is a reveal + // Public key up next! Ensure it matches signing key. // Ignore source :-) and do not parse it from hdr. // We don't much care about reveals, they have very little in the way of bad security // implications and any fees have already been accounted for + { raw_tezos_header_signature_type_t const *const sig_type = NEXT_TYPE(raw_tezos_header_signature_type_t); if (parse_raw_tezos_header_signature_type(sig_type) != out->signing.signature_type) PARSE_ERROR(); + } + + OP_STEP + { size_t klen = out->public_key.W_len; - advance_ix(&ix, length, klen); - if (memcmp(out->public_key.W, data + ix - klen, klen) != 0) PARSE_ERROR(); + + CALL_SUBPARSER(parse_next_type, byte, &(state->subparser_state.nexttype), klen); + + if(memcmp(out->public_key.W, &(state->subparser_state.nexttype.body.raw), klen) != 0) PARSE_ERROR(); out->has_reveal = true; - continue; + + JMP_TO_TOP; } + case STEP_AFTER_MANAGER_FIELDS: // Anything but a reveal + if (out->operation.tag != OPERATION_TAG_NONE) { // We are only currently allowing one non-reveal operation PARSE_ERROR(); @@ -327,7 +424,7 @@ static void parse_operations_throws_parse_error( // This is the one allowable non-reveal operation per set - out->operation.tag = (uint8_t)tag; + out->operation.tag = (uint8_t)state->tag; // If the source is an implicit contract,... if (out->operation.source.originated == 0) { @@ -340,321 +437,389 @@ static void parse_operations_throws_parse_error( out->operation.delegate.signature_type = SIGNATURE_TYPE_UNSET; out->operation.delegate.originated = 0; - switch (tag) { + + // Deliberate epsilon-transition. + state->op_step=STEP_OP_TYPE_DISPATCH; + default: + + switch (state->tag) { case OPERATION_TAG_PROPOSAL: - { - const struct proposal_contents *proposal_data = NEXT_TYPE(struct proposal_contents); - if (ix != length) PARSE_ERROR(); + switch(state->op_step) { + case STEP_OP_TYPE_DISPATCH: { + const struct proposal_contents *proposal_data = NEXT_TYPE(struct proposal_contents); + + const size_t payload_size = READ_UNALIGNED_BIG_ENDIAN(int32_t, &proposal_data->num_bytes); + if (payload_size != PROTOCOL_HASH_SIZE) PARSE_ERROR(); // We only accept exactly 1 proposal hash. - const size_t payload_size = READ_UNALIGNED_BIG_ENDIAN(int32_t, &proposal_data->num_bytes); - if (payload_size != PROTOCOL_HASH_SIZE) PARSE_ERROR(); // We only accept exactly 1 proposal hash. + out->operation.proposal.voting_period = READ_UNALIGNED_BIG_ENDIAN(int32_t, &proposal_data->period); - out->operation.proposal.voting_period = READ_UNALIGNED_BIG_ENDIAN(int32_t, &proposal_data->period); - memcpy(out->operation.proposal.protocol_hash, proposal_data->hash, sizeof(out->operation.proposal.protocol_hash)); + memcpy(out->operation.proposal.protocol_hash, proposal_data->hash, sizeof(out->operation.proposal.protocol_hash)); + } + + JMP_EOM; } break; case OPERATION_TAG_BALLOT: - { - const struct ballot_contents *ballot_data = NEXT_TYPE(struct ballot_contents); - if (ix != length) PARSE_ERROR(); - - out->operation.ballot.voting_period = READ_UNALIGNED_BIG_ENDIAN(int32_t, &ballot_data->period); - memcpy(out->operation.ballot.protocol_hash, ballot_data->proposal, sizeof(out->operation.ballot.protocol_hash)); - - const int8_t ballot_vote = READ_UNALIGNED_BIG_ENDIAN(int8_t, &ballot_data->ballot); - switch (ballot_vote) { - case 0: - out->operation.ballot.vote = BALLOT_VOTE_YEA; - break; - case 1: - out->operation.ballot.vote = BALLOT_VOTE_NAY; - break; - case 2: - out->operation.ballot.vote = BALLOT_VOTE_PASS; - break; - default: - PARSE_ERROR(); + switch(state->op_step) { + case STEP_OP_TYPE_DISPATCH: { + const struct ballot_contents *ballot_data = NEXT_TYPE(struct ballot_contents); + + out->operation.ballot.voting_period = READ_UNALIGNED_BIG_ENDIAN(int32_t, &ballot_data->period); + memcpy(out->operation.ballot.protocol_hash, ballot_data->proposal, sizeof(out->operation.ballot.protocol_hash)); + + const int8_t ballot_vote = READ_UNALIGNED_BIG_ENDIAN(int8_t, &ballot_data->ballot); + switch (ballot_vote) { + case 0: + out->operation.ballot.vote = BALLOT_VOTE_YEA; + break; + case 1: + out->operation.ballot.vote = BALLOT_VOTE_NAY; + break; + case 2: + out->operation.ballot.vote = BALLOT_VOTE_PASS; + break; + default: + PARSE_ERROR(); + } + JMP_EOM; } } - break; + case OPERATION_TAG_ATHENS_DELEGATION: case OPERATION_TAG_BABYLON_DELEGATION: - { - uint8_t delegate_present = NEXT_BYTE(data, &ix, length); - if (delegate_present) { + switch(state->op_step) { + case STEP_OP_TYPE_DISPATCH: { + uint8_t delegate_present = NEXT_BYTE; + + OP_JMPIF(STEP_HAS_DELEGATE, delegate_present) + } + // Else branch: Encode "not present" + out->operation.destination.originated = 0; + out->operation.destination.signature_type = SIGNATURE_TYPE_UNSET; + + JMP_TO_TOP; // These go back to the top to catch any reveals. + + case STEP_HAS_DELEGATE: { const struct delegation_contents *dlg = NEXT_TYPE(struct delegation_contents); parse_implicit(&out->operation.destination, &dlg->signature_type, dlg->hash); - } else { - // Encode "not present" - out->operation.destination.originated = 0; - out->operation.destination.signature_type = SIGNATURE_TYPE_UNSET; } + JMP_TO_TOP; // These go back to the top to catch any reveals. } - break; case OPERATION_TAG_ATHENS_ORIGINATION: case OPERATION_TAG_BABYLON_ORIGINATION: - { - struct origination_header { - raw_tezos_header_signature_type_t signature_type; - uint8_t hash[HASH_SIZE]; - } __attribute__((packed)); - struct origination_header const *const hdr = NEXT_TYPE(struct origination_header); - - parse_implicit(&out->operation.destination, &hdr->signature_type, hdr->hash); - out->operation.amount = PARSE_Z(data, &ix, length); - if (NEXT_BYTE(data, &ix, length) != 0) { - out->operation.flags |= ORIGINATION_FLAG_SPENDABLE; + PARSE_ERROR(); // We can't parse the script yet, and all babylon originations have a script; we have to just reject originations. + + case OPERATION_TAG_ATHENS_TRANSACTION: + case OPERATION_TAG_BABYLON_TRANSACTION: + switch(state->op_step) { + case STEP_OP_TYPE_DISPATCH: + + out->operation.amount = PARSE_Z; + + OP_STEP { + const struct contract *destination = NEXT_TYPE(struct contract); + parse_contract(&out->operation.destination, destination); + } + + OP_STEP { + char has_params = NEXT_BYTE; + + if(has_params == MICHELSON_PARAMS_NONE) { + JMP_TO_TOP; + } + + if(has_params != MICHELSON_PARAMS_SOME) { + PARSE_ERROR(); + } + + // From this point on we are _only_ parsing manager.tz operatinos, so we show the outer destination (the KT1) as the source of the transaction. + out->operation.is_manager_tz_operation = true; + memcpy(&out->operation.implicit_account, &out->operation.source, sizeof(parsed_contract_t)); + memcpy(&out->operation.source, &out->operation.destination, sizeof(parsed_contract_t)); + + // manager.tz operations cannot actually transfer any amount. + if (out->operation.amount > 0) { + PARSE_ERROR(); + } } - if (NEXT_BYTE(data, &ix, length) != 0) { - out->operation.flags |= ORIGINATION_FLAG_DELEGATABLE; + + + OP_STEP { + const enum entrypoint_tag entrypoint = NEXT_BYTE; + + // Don't bother parsing the name, we'll reject if it's there either way. + + // Anything that’s not “do” is not a + // manager.tz contract. + if (entrypoint != ENTRYPOINT_DO) { + PARSE_ERROR(); + } } - if (NEXT_BYTE(data, &ix, length) != 0) { - // Has delegate - const struct delegation_contents *dlg = NEXT_TYPE(struct delegation_contents); - parse_implicit(&out->operation.delegate, &dlg->signature_type, dlg->hash); + + OP_STEP { + state->argument_length = MICHELSON_READ_LENGTH; } - if (NEXT_BYTE(data, &ix, length) != 0) PARSE_ERROR(); // Has script - } - break; - case OPERATION_TAG_ATHENS_TRANSACTION: - case OPERATION_TAG_BABYLON_TRANSACTION: - { - out->operation.amount = PARSE_Z(data, &ix, length); - - const struct contract *destination = NEXT_TYPE(struct contract); - parse_contract(&out->operation.destination, destination); - - switch (NEXT_BYTE(data, &ix, length)) { - // We cannot parse all parameters, but a subset of - // manager.tz operations is accepted. - case MICHELSON_PARAMS_SOME: { - // Destination is now source - out->operation.is_manager_tz_operation = true; - memcpy(&out->operation.implicit_account, &out->operation.source, sizeof(parsed_contract_t)); - memcpy(&out->operation.source, &out->operation.destination, sizeof(parsed_contract_t)); - - // Operations cannot actually transfer any amount. - if (out->operation.amount > 0) { - PARSE_ERROR(); - } - const enum entrypoint_tag entrypoint = NEXT_BYTE(data, &ix, length); - - // Named entrypoints have up to 31 bytes. - if (entrypoint == ENTRYPOINT_NAMED) { - const uint8_t entrypoint_length = NEXT_BYTE(data, &ix, length); - if (entrypoint_length > MAX_ENTRYPOINT_LENGTH) { - PARSE_ERROR(); - } - for (int n = 0; n < entrypoint_length; n++) { - NEXT_BYTE(data, &ix, length); - } - } + // Error on anything but a michelson sequence. + OP_STEP_REQUIRE_BYTE(MICHELSON_TYPE_SEQUENCE); - // Anything that’s not “do” is not a - // manager.tz contract. - if (entrypoint != ENTRYPOINT_DO) { - PARSE_ERROR(); - } + OP_STEP { + const uint32_t sequence_length = MICHELSON_READ_LENGTH; - const uint32_t argument_length = MICHELSON_READ_LENGTH(data, &ix, length); + // Only allow single sequence (5 is needed + // in argument length for above two + // bytes). Also bail out on really big + // Michelson that we don’t support. + if ( sequence_length + sizeof(uint8_t) + sizeof(uint32_t) != state->argument_length + || state->argument_length > MAX_MICHELSON_SEQUENCE_LENGTH) { + PARSE_ERROR(); + } + } - // Error on anything but a michelson - // sequence. - if (NEXT_BYTE(data, &ix, length) != MICHELSON_TYPE_SEQUENCE) { - PARSE_ERROR(); - } + OP_STEP - const uint32_t sequence_length = MICHELSON_READ_LENGTH(data, &ix, length); + // All manager.tz operations should begin + // with this. Otherwise, bail out. + OP_STEP_REQUIRE_SHORT(MICHELSON_DROP) + OP_STEP_REQUIRE_SHORT(MICHELSON_NIL) + OP_STEP_REQUIRE_SHORT(MICHELSON_OPERATION) - // Only allow single sequence (5 is needed - // in argument length for above two - // bytes). Also bail out on really big - // Michelson that we don’t support. - if ( sequence_length + sizeof(uint8_t) + sizeof(uint32_t) != argument_length - || argument_length > MAX_MICHELSON_SEQUENCE_LENGTH) { - PARSE_ERROR(); - } + { + state->michelson_op = MICHELSON_READ_SHORT; - // All manager.tz operations should begin - // with this. Otherwise, bail out. - if ( MICHELSON_READ_SHORT(data, &ix, length) != MICHELSON_DROP - || MICHELSON_READ_SHORT(data, &ix, length) != MICHELSON_NIL - || MICHELSON_READ_SHORT(data, &ix, length) != MICHELSON_OPERATION) { + // First real michelson op. + switch (state->michelson_op) { + case MICHELSON_PUSH: + JMP(STEP_MICHELSON_FIRST_IS_PUSH); + case MICHELSON_NONE: // withdraw delegate + JMP(STEP_MICHELSON_FIRST_IS_NONE); + default: PARSE_ERROR(); - } + } + } - // First real michelson op. - switch (MICHELSON_READ_SHORT(data, &ix, length)) { - case MICHELSON_PUSH: { - switch (MICHELSON_READ_SHORT(data, &ix, length)) { - case MICHELSON_KEY_HASH: { - michelson_read_address(&out->operation.destination, data, &ix, length); - - switch (MICHELSON_READ_SHORT(data, &ix, length)) { - case MICHELSON_SOME: { // Set delegate - // Matching: PUSH key_hash ; SOME ; SET_DELEGATE - if (MICHELSON_READ_SHORT(data, &ix, length) != MICHELSON_SET_DELEGATE) { - PARSE_ERROR(); - } - out->operation.tag = OPERATION_TAG_BABYLON_DELEGATION; - out->operation.destination.originated = true; - break; - } - case MICHELSON_IMPLICIT_ACCOUNT: { // transfer contract to implicit - // Matching: PUSH key_hash ; IMPLICIT_ACCOUNT ; PUSH mutez ; UNIT ; TRANSFER_TOKENS - if ( MICHELSON_READ_SHORT(data, &ix, length) != MICHELSON_PUSH - || MICHELSON_READ_SHORT(data, &ix, length) != MICHELSON_MUTEZ) { - PARSE_ERROR(); - } - - if (NEXT_BYTE(data, &ix, length) != 0) { - PARSE_ERROR(); - }; - - out->operation.amount = PARSE_Z_MICHELSON(data, &ix, length); - - if ( MICHELSON_READ_SHORT(data, &ix, length) != MICHELSON_UNIT - || MICHELSON_READ_SHORT(data, &ix, length) != MICHELSON_TRANSFER_TOKENS) { - PARSE_ERROR(); - } - out->operation.tag = OPERATION_TAG_BABYLON_TRANSACTION; - break; - } - default: PARSE_ERROR(); - } - break; - } - case MICHELSON_ADDRESS: { // transfer contract to contract - // Matching: PUSH address ; CONTRACT ; ASSERT_SOME ; PUSH mutez ; UNIT ; TRANSFER_TOKENS - michelson_read_address(&out->operation.destination, data, &ix, length); - const enum michelson_code contract_code = MICHELSON_READ_SHORT(data, &ix, length); - const enum michelson_code type = MICHELSON_READ_SHORT(data, &ix, length); - switch (contract_code) { - case MICHELSON_CONTRACT_WITH_ENTRYPOINT: { - // No way to display - // entrypoint now, so need to - // bail out on anything but - // default. - // TODO: display entrypoints - if (NEXT_BYTE(data, &ix, length) != ENTRYPOINT_DEFAULT) { - PARSE_ERROR(); - } - - break; - } - case MICHELSON_CONTRACT: { - break; - } - default: PARSE_ERROR(); - } - - // Can’t display any parameters, need - // to throw anything but unit out for now. - // TODO: display michelson arguments - if (type != MICHELSON_CONTRACT_UNIT) { - PARSE_ERROR(); - } - - // Matching: ASSERT_SOME (unfolded) - if (NEXT_BYTE(data, &ix, length) != MICHELSON_TYPE_SEQUENCE) { - PARSE_ERROR(); - } - if (MICHELSON_READ_LENGTH(data, &ix, length) != 0x15) { - PARSE_ERROR(); - } - if (MICHELSON_READ_SHORT(data, &ix, length) != MICHELSON_IF_NONE) { - PARSE_ERROR(); - } - if (NEXT_BYTE(data, &ix, length) != MICHELSON_TYPE_SEQUENCE) { - PARSE_ERROR(); - } - if (MICHELSON_READ_LENGTH(data, &ix, length) != 9) { - PARSE_ERROR(); - } - if (NEXT_BYTE(data, &ix, length) != MICHELSON_TYPE_SEQUENCE) { - PARSE_ERROR(); - } - if (MICHELSON_READ_LENGTH(data, &ix, length) != 4) { - PARSE_ERROR(); - } - if (MICHELSON_READ_SHORT(data, &ix, length) != MICHELSON_UNIT) { - PARSE_ERROR(); - } - if (MICHELSON_READ_SHORT(data, &ix, length) != MICHELSON_FAILWITH) { - PARSE_ERROR(); - } - if (NEXT_BYTE(data, &ix, length) != MICHELSON_TYPE_SEQUENCE) { - PARSE_ERROR(); - } - if (MICHELSON_READ_LENGTH(data, &ix, length) != 0) { - PARSE_ERROR(); - } - - if ( MICHELSON_READ_SHORT(data, &ix, length) != MICHELSON_PUSH - || MICHELSON_READ_SHORT(data, &ix, length) != MICHELSON_MUTEZ) { - PARSE_ERROR(); - } - - if (NEXT_BYTE(data, &ix, length) != 0) { - PARSE_ERROR(); - }; - - out->operation.amount = PARSE_Z_MICHELSON(data, &ix, length); - - if ( MICHELSON_READ_SHORT(data, &ix, length) != MICHELSON_UNIT - || MICHELSON_READ_SHORT(data, &ix, length) != MICHELSON_TRANSFER_TOKENS) { - PARSE_ERROR(); - } - out->operation.tag = OPERATION_TAG_BABYLON_TRANSACTION; - break; - } - default: PARSE_ERROR(); - } - break; - } - case MICHELSON_NONE: { // withdraw delegate - // Matching: NONE key_hash ; SET_DELEGATE - if ( MICHELSON_READ_SHORT(data, &ix, length) != MICHELSON_KEY_HASH - || MICHELSON_READ_SHORT(data, &ix, length) != MICHELSON_SET_DELEGATE) { - PARSE_ERROR(); - } - out->operation.tag = OPERATION_TAG_BABYLON_DELEGATION; - out->operation.destination.originated = 0; - out->operation.destination.signature_type = SIGNATURE_TYPE_UNSET; - break; - } - default: PARSE_ERROR(); - } + case STEP_MICHELSON_FIRST_IS_PUSH: { + + state->michelson_op = MICHELSON_READ_SHORT; - // All michelson contracts end with a cons. - if (MICHELSON_READ_SHORT(data, &ix, length) != MICHELSON_CONS) { + // First real michelson op. + switch (state->michelson_op) { + case MICHELSON_KEY_HASH: + JMP(STEP_MICHELSON_SECOND_IS_KEY_HASH); + case MICHELSON_ADDRESS: // transfer contract to contract + JMP(STEP_MICHELSON_CONTRACT_TO_CONTRACT); + default: PARSE_ERROR(); - } + } + } + + case STEP_MICHELSON_SECOND_IS_KEY_HASH: + + MICHELSON_READ_ADDRESS(&out->operation.destination, state->base58_pkh1); + + OP_STEP { + + state->michelson_op = MICHELSON_READ_SHORT; - // This should be the exact end of the - // APDU. Any more data can’t be parsed. - if (ix != length) { + switch (state->michelson_op) { + case MICHELSON_SOME: // Set delegate + JMP(STEP_MICHELSON_SET_DELEGATE_CHAIN); + case MICHELSON_IMPLICIT_ACCOUNT: // transfer contract to implicit + JMP(STEP_MICHELSON_CONTRACT_TO_IMPLICIT_CHAIN); + default: PARSE_ERROR(); - } + } + } + + case STEP_MICHELSON_SET_DELEGATE_CHAIN: + + OP_STEP_REQUIRE_SHORT(MICHELSON_SOME) - break; + { + uint16_t val = MICHELSON_READ_SHORT; + if(val != MICHELSON_SET_DELEGATE) PARSE_ERROR(); + + out->operation.tag = OPERATION_TAG_BABYLON_DELEGATION; + out->operation.destination.originated = true; + JMP(STEP_MICHELSON_CONTRACT_END); + + } + + case STEP_MICHELSON_CONTRACT_TO_IMPLICIT_CHAIN: + // Matching: PUSH key_hash ; IMPLICIT_ACCOUNT ; PUSH mutez ; UNIT ; TRANSFER_TOKENS + + OP_STEP_REQUIRE_SHORT(MICHELSON_PUSH) + OP_STEP_REQUIRE_SHORT(MICHELSON_MUTEZ) + + { + + uint8_t contractOrImplicit = NEXT_BYTE; + if(contractOrImplicit != 0) PARSE_ERROR(); // what is this? + + } + + OP_STEP + + out->operation.amount = PARSE_Z_MICHELSON; + + OP_STEP + + OP_STEP_REQUIRE_SHORT(MICHELSON_UNIT) + + { + + uint16_t val = MICHELSON_READ_SHORT; + if(val != MICHELSON_TRANSFER_TOKENS) PARSE_ERROR(); + out->operation.tag = OPERATION_TAG_BABYLON_TRANSACTION; + + JMP(STEP_MICHELSON_CONTRACT_END); + } + + case STEP_MICHELSON_CONTRACT_TO_CONTRACT: + + { + // Matching: PUSH address ; CONTRACT ; ASSERT_SOME ; PUSH mutez ; UNIT ; TRANSFER_TOKENS + MICHELSON_READ_ADDRESS(&out->operation.destination, state->base58_pkh2); + } + + OP_STEP + + state->contract_code = MICHELSON_READ_SHORT; + + OP_STEP + + { + + const enum michelson_code type = MICHELSON_READ_SHORT; + + // Can’t display any parameters, need + // to throw anything but unit out for now. + // TODO: display michelson arguments + if (type != MICHELSON_CONTRACT_UNIT) { + PARSE_ERROR(); } - case MICHELSON_PARAMS_NONE: { - break; + + + switch (state->contract_code) { + case MICHELSON_CONTRACT_WITH_ENTRYPOINT: { + JMP(STEP_MICHELSON_CHECKING_CONTRACT_ENTRYPOINT); + } + case MICHELSON_CONTRACT: { + JMP(STEP_MICHELSON_CONTRACT_TO_CONTRACT_CHAIN_2); + } + default: PARSE_ERROR(); + } + } + + case STEP_MICHELSON_CHECKING_CONTRACT_ENTRYPOINT: + { + // No way to display + // entrypoint now, so need to + // bail out on anything but + // default. + // TODO: display entrypoints + uint8_t entrypoint_byte = NEXT_BYTE; + if(entrypoint_byte != ENTRYPOINT_DEFAULT) { + PARSE_ERROR(); } - default: PARSE_ERROR(); } + JMP(STEP_MICHELSON_CONTRACT_TO_CONTRACT_CHAIN_2); + + case STEP_MICHELSON_CONTRACT_TO_CONTRACT_CHAIN_2: + + OP_STEP_REQUIRE_BYTE(MICHELSON_TYPE_SEQUENCE); + + OP_STEP_REQUIRE_LENGTH(0x15); + OP_STEP_REQUIRE_SHORT(MICHELSON_IF_NONE); + OP_STEP_REQUIRE_BYTE(MICHELSON_TYPE_SEQUENCE); + OP_STEP_REQUIRE_LENGTH(9); + OP_STEP_REQUIRE_BYTE(MICHELSON_TYPE_SEQUENCE); + OP_STEP_REQUIRE_LENGTH(4); + OP_STEP_REQUIRE_SHORT(MICHELSON_UNIT); + OP_STEP_REQUIRE_SHORT(MICHELSON_FAILWITH); + OP_STEP_REQUIRE_BYTE(MICHELSON_TYPE_SEQUENCE); + OP_STEP_REQUIRE_LENGTH(0); + OP_STEP_REQUIRE_SHORT(MICHELSON_PUSH); + OP_STEP_REQUIRE_SHORT(MICHELSON_MUTEZ); + OP_STEP_REQUIRE_BYTE(0); + + { + out->operation.amount = PARSE_Z_MICHELSON; + } + + OP_STEP + + OP_STEP_REQUIRE_SHORT(MICHELSON_UNIT); + + { + uint16_t val = MICHELSON_READ_SHORT; + if(val != MICHELSON_TRANSFER_TOKENS) PARSE_ERROR(); + out->operation.tag = OPERATION_TAG_BABYLON_TRANSACTION; + JMP(STEP_MICHELSON_CONTRACT_END); + } + + case STEP_MICHELSON_FIRST_IS_NONE: // withdraw delegate + + OP_STEP_REQUIRE_SHORT(MICHELSON_KEY_HASH); + + { + + uint16_t val = MICHELSON_READ_SHORT; + if(val != MICHELSON_SET_DELEGATE) PARSE_ERROR(); + + out->operation.tag = OPERATION_TAG_BABYLON_DELEGATION; + out->operation.destination.originated = 0; + out->operation.destination.signature_type = SIGNATURE_TYPE_UNSET; + + } + + JMP(STEP_MICHELSON_CONTRACT_END); + + case STEP_MICHELSON_CONTRACT_END: + + { + uint16_t val = MICHELSON_READ_SHORT; + if(val != MICHELSON_CONS) PARSE_ERROR(); + } + + JMP_EOM; + + default: + PARSE_ERROR(); } - break; - default: + default: // Any other tag; probably not possible here. PARSE_ERROR(); } } - if (out->operation.tag == OPERATION_TAG_NONE && !out->has_reveal) { - PARSE_ERROR(); // Must have at least one op + PARSE_ERROR(); // Probably not reachable, but removes a warning. +} + +#define G global.apdu.u.sign +#ifdef BAKING_APP + +static void parse_operations_throws_parse_error( + struct parsed_operation_group *const out, + void const *const data, + size_t length, + derivation_type_t derivation_type, + bip32_path_t const *const bip32_path, + is_operation_allowed_t is_operation_allowed +) { + + size_t ix = 0; + + parse_operations_init(out, derivation_type, bip32_path, &G.parse_state); + + while (ix < length) { + uint8_t byte = ((uint8_t*)data)[ix]; + parse_byte(byte, &G.parse_state, out, is_operation_allowed); + PRINTF("Byte: %x - Next op_step state: %d\n", byte, G.parse_state.op_step); + ix++; } + + if(! parse_operations_final(&G.parse_state, out)) PARSE_ERROR(); + } bool parse_operations( @@ -680,3 +845,35 @@ bool parse_operations( END_TRY; return true; } + +#else + +bool parse_operations_packet( + struct parsed_operation_group *const out, + uint8_t const *const data, + size_t length, + is_operation_allowed_t is_operation_allowed +) { + BEGIN_TRY { + TRY { + size_t ix = 0; + while (ix < length) { + uint8_t byte = ((uint8_t*)data)[ix]; + parse_byte(byte, &G.parse_state, out, is_operation_allowed); + PRINTF("Byte: %x - Next op_step state: %d\n", byte, G.parse_state.op_step); + ix++; + } + } + CATCH(EXC_PARSE_ERROR) { + return false; + } + CATCH_OTHER(e) { + THROW(e); + } + FINALLY { } + } + END_TRY; + return true; +} + +#endif diff --git a/src/operations.h b/src/operations.h index 40552a80..39d6fd19 100644 --- a/src/operations.h +++ b/src/operations.h @@ -12,6 +12,118 @@ typedef bool (*is_operation_allowed_t)(enum operation_tag); + +// Wire format that gets parsed into `signature_type`. +typedef struct { + uint8_t v; +} __attribute__((packed)) raw_tezos_header_signature_type_t; + +struct operation_group_header { + uint8_t magic_byte; + uint8_t hash[32]; +} __attribute__((packed)); + +struct implicit_contract { + raw_tezos_header_signature_type_t signature_type; + uint8_t pkh[HASH_SIZE]; +} __attribute__((packed)); + +struct contract { + uint8_t originated; + union { + struct implicit_contract implicit; + struct { + uint8_t pkh[HASH_SIZE]; + uint8_t padding; + } originated; + } u; +} __attribute__((packed)); + +struct delegation_contents { + raw_tezos_header_signature_type_t signature_type; + uint8_t hash[HASH_SIZE]; +} __attribute__((packed)); + +struct proposal_contents { + int32_t period; + size_t num_bytes; + uint8_t hash[PROTOCOL_HASH_SIZE]; +} __attribute__((packed)); + +struct ballot_contents { + int32_t period; + uint8_t proposal[PROTOCOL_HASH_SIZE]; + int8_t ballot; +} __attribute__((packed)); + +typedef struct { + uint8_t v[HASH_SIZE]; +} __attribute__((packed)) hash_t; + +struct int_subparser_state { + uint32_t lineno; // Has to be in _all_ members of the subparser union. + uint64_t value; // Still need to fix this. + uint8_t shift; +}; + +struct nexttype_subparser_state { + uint32_t lineno; + union { + raw_tezos_header_signature_type_t sigtype; + + struct operation_group_header ogh; + + struct implicit_contract ic; + struct contract c; + + struct delegation_contents dc; + struct proposal_contents pc; + struct ballot_contents bc; + + hash_t ht; + + uint16_t i16; + uint32_t i32; + uint64_t i64; + + uint8_t raw[1]; + uint8_t key[64]; // FIXME: check key length for non-tz1. + uint8_t text_pkh[HASH_SIZE_B58]; + } body; + uint32_t fill_idx; +}; + +struct michelson_address_subparser_state { + uint32_t lineno; + uint8_t address_step; + uint8_t micheline_type; + uint32_t addr_length; + hash_t key_hash; + parsed_contract_t result; // Not neccessarily optimal, but easy. + struct nexttype_subparser_state subsub_state; + raw_tezos_header_signature_type_t signature_type; + +}; + +union subparser_state { + struct int_subparser_state integer; + struct nexttype_subparser_state nexttype; + struct michelson_address_subparser_state michelson_address; +}; + +struct parse_state { + int16_t op_step; + union subparser_state subparser_state; + enum operation_tag tag; + uint32_t argument_length; + uint16_t michelson_op; + uint16_t contract_code; + + // Places to stash textual base58-encoded PKHes. + char base58_pkh1[HASH_SIZE_B58]; + char base58_pkh2[HASH_SIZE_B58]; +}; + // Allows arbitrarily many "REVEAL" operations but only one operation of any other type, // which is the one it puts into the group. bool parse_operations( @@ -22,3 +134,19 @@ bool parse_operations( bip32_path_t const *const bip32_path, is_operation_allowed_t is_operation_allowed ); + +void parse_operations_init( + struct parsed_operation_group *const out, + derivation_type_t derivation_type, + bip32_path_t const *const bip32_path, + struct parse_state *const state + ); + +bool parse_operations_final(struct parse_state *const state, struct parsed_operation_group *const out); + +bool parse_operations_packet( + struct parsed_operation_group *const out, + uint8_t const *const data, + size_t length, + is_operation_allowed_t is_operation_allowed +); diff --git a/src/ui_nano_s.c b/src/ui_nano_s.c index 3cab0fcd..7e4e01cb 100644 --- a/src/ui_nano_s.c +++ b/src/ui_nano_s.c @@ -38,9 +38,10 @@ static void switch_screen(uint32_t which); // This is called by internal UI code to prevent callbacks from sticking around static void clear_ui_callbacks(void); +#ifndef BAKING_APP // ------------------------------- ui_meno static void main_menu(void); - +#endif static unsigned button_handler(unsigned button_mask, unsigned button_mask_counter); @@ -52,86 +53,38 @@ static const bagl_element_t ui_idle_screen[] = { // fill fg bg fid iid txt touchparams... ] {{BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, - NULL, - 0, - 0, - 0, - NULL, - NULL, - NULL}, + NULL }, {{BAGL_ICON, 0x00, 3, 12, 7, 7, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CROSS}, - NULL, - 0, - 0, - 0, - NULL, - NULL, - NULL}, + NULL }, //{{BAGL_ICON , 0x01, 21, 9, 14, 14, 0, 0, 0 //, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_TRANSACTION_BADGE }, NULL, 0, 0, //0, NULL, NULL, NULL }, {{BAGL_LABELINE, 0x01, 0, 12, 128, 12, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, - "Last Block Level", - 0, - 0, - 0, - NULL, - NULL, - NULL}, + "Last Block Level" }, {{BAGL_LABELINE, 0x01, 0, 26, 128, 12, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, - G.baking_idle_screens.hwm, - 0, - 0, - 0, - NULL, - NULL, - NULL}, + G.baking_idle_screens.hwm }, {{BAGL_LABELINE, 0x02, 0, 12, 128, 12, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, - "Baking Key", - 0, - 0, - 0, - NULL, - NULL, - NULL}, + "Baking Key" }, {{BAGL_LABELINE, 0x02, 23, 26, 82, 12, 0x80 | 10, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 26}, - G.baking_idle_screens.pkh, - 0, - 0, - 0, - NULL, - NULL, - NULL}, + G.baking_idle_screens.pkh }, {{BAGL_LABELINE, 0x03, 0, 12, 128, 12, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, - "Chain", - 0, - 0, - 0, - NULL, - NULL, - NULL}, + "Chain" }, {{BAGL_LABELINE, 0x03, 23, 26, 82, 12, 0x80 | 10, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 26}, - G.baking_idle_screens.chain, - 0, - 0, - 0, - NULL, - NULL, - NULL}, + G.baking_idle_screens.chain }, }; @@ -308,53 +261,23 @@ unsigned char io_event(__attribute__((unused)) unsigned char channel) { static const bagl_element_t ui_multi_screen[] = { {{BAGL_RECTANGLE, BAGL_STATIC_ELEMENT, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000, 0xFFFFFF, 0, 0}, - NULL, - 0, - 0, - 0, - NULL, - NULL, - NULL}, + NULL }, {{BAGL_ICON, BAGL_STATIC_ELEMENT, 3, 12, 7, 7, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CROSS}, - NULL, - 0, - 0, - 0, - NULL, - NULL, - NULL}, + NULL }, {{BAGL_ICON, BAGL_STATIC_ELEMENT, 117, 13, 8, 6, 0, 0, 0, 0xFFFFFF, 0x000000, 0, BAGL_GLYPH_ICON_CHECK}, - NULL, - 0, - 0, - 0, - NULL, - NULL, - NULL}, + NULL }, {{BAGL_LABELINE, BAGL_STATIC_ELEMENT, 0, 12, 128, 12, 0, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 0}, - global.ui.prompt.active_prompt, - 0, - 0, - 0, - NULL, - NULL, - NULL}, + global.ui.prompt.active_prompt }, {{BAGL_LABELINE, BAGL_SCROLLING_ELEMENT, 23, 26, 82, 12, 0x80 | 10, 0, 0, 0xFFFFFF, 0x000000, BAGL_FONT_OPEN_SANS_EXTRABOLD_11px | BAGL_FONT_ALIGNMENT_CENTER, 26}, - global.ui.prompt.active_value, - 0, - 0, - 0, - NULL, - NULL, - NULL}, + global.ui.prompt.active_value }, }; void switch_screen(uint32_t which) { @@ -388,7 +311,12 @@ void ui_prompt(const char *const *labels, ui_callback_t ok_c, ui_callback_t cxl_ ui_display(ui_multi_screen, NUM_ELEMENTS(ui_multi_screen), ok_c, cxl_c, screen_count); +#ifdef DEBUG + // In debug mode, the THROW below produces a PRINTF statement in an invalid position and causes the screen to blank, so instead we just directly call the equivalent longjmp for debug only. + longjmp(try_context_get()->jmp_buf, ASYNC_EXCEPTION); +#else THROW(ASYNC_EXCEPTION); +#endif } diff --git a/tools/gen-delegates.sh b/tools/gen-delegates.sh index 6aa96dd2..f827b9df 100755 --- a/tools/gen-delegates.sh +++ b/tools/gen-delegates.sh @@ -1,7 +1,6 @@ -#! /usr/bin/env nix-shell -#! nix-shell -i bash -p jq -root="$(cd "$(dirname "${BASH_SOURCE[0]}")" && git rev-parse --show-toplevel)" +# root="$(cd "$(dirname "${BASH_SOURCE[0]}")" && git rev-parse --show-toplevel)" +root="." registry_json=$root/tools/BakersRegistryCoreUnfilteredData.json if [ $# -eq 1 ]; then