Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/nova #60

Draft
wants to merge 8 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ include $(BOLOS_SDK)/Makefile.defines

APPVERSION_M = 0
APPVERSION_N = 8
APPVERSION_P = 6
APPVERSION_P = 7
APPVERSION = "$(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)"

APP_LOAD_PARAMS = --path "44'/1'" --curve ed25519 --appFlags 0x240 $(COMMON_LOAD_PARAMS)
Expand Down
123 changes: 115 additions & 8 deletions src/api.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@
#include "abstraction.h"

#include "iota/constants.h"
#include "iota/blindsigning_stardust.h"
#include "iota/blindsigning.h"
#include "iota/signing.h"

#include "ui/nano/flow_user_confirm_transaction.h"
#include "ui/nano/flow_user_confirm_new_address.h"
#include "ui/nano/flow_user_confirm_blindsigning.h"
#include "ui/nano/flow_generating_addresses.h"
#include "ui/nano/flow_generating_public_keys.h"
#include "ui/nano/flow_signing.h"

#pragma GCC diagnostic error "-Wall"
Expand All @@ -43,12 +44,16 @@ void api_initialize(APP_MODE_TYPE app_mode, uint32_t account_index)
// 0x80: unused (formerly IOTA + Chrysalis Testnet)
// 0x01: (107a) IOTA + Stardust
// 0x81: (1) IOTA + Stardust Testnet
// 0x04: (107a) IOTA + Nova
// 0x84: (1) IOTA + Nova Testnet

// Shimmer App
// 0x02: (107a) Shimmer Claiming (from IOTA)
// 0x82: (1) Shimmer Claiming (from IOTA) (Testnet)
// 0x03: (107b) Shimmer (default)
// 0x83: (1) Shimmer Testnet
// 0x05: (107a) Shimmer + Nova
// 0x85: (1) Shimmer + Nova Testnet

switch (app_mode & 0x7f) {
#if defined(APP_IOTA)
Expand All @@ -58,6 +63,12 @@ void api_initialize(APP_MODE_TYPE app_mode, uint32_t account_index)
api.protocol = PROTOCOL_STARDUST;
api.coin = COIN_IOTA;
break;
case APP_MODE_IOTA_NOVA:
// iota
api.bip32_path[BIP32_COIN_INDEX] = BIP32_COIN_IOTA;
api.protocol = PROTOCOL_NOVA;
api.coin = COIN_IOTA;
break;
#elif defined(APP_SHIMMER)
case APP_MODE_SHIMMER_CLAIMING:
// iota
Expand All @@ -71,6 +82,12 @@ void api_initialize(APP_MODE_TYPE app_mode, uint32_t account_index)
api.protocol = PROTOCOL_STARDUST;
api.coin = COIN_SHIMMER;
break;
case APP_MODE_SHIMMER_NOVA:
// shimmer
api.bip32_path[BIP32_COIN_INDEX] = BIP32_COIN_SHIMMER;
api.protocol = PROTOCOL_NOVA;
api.coin = COIN_SHIMMER;
break;
#else
#error unknown app
#endif
Expand Down Expand Up @@ -147,7 +164,8 @@ uint32_t api_write_data_block(uint8_t block_number, const uint8_t *input_data,

uint32_t api_read_data_block(uint8_t block_number)
{
if (api.data.type != GENERATED_ADDRESSES && api.data.type != SIGNATURES) {
if (api.data.type != GENERATED_ADDRESSES &&
api.data.type != GENERATED_PUBLIC_KEYS && api.data.type != SIGNATURES) {
THROW(SW_COMMAND_NOT_ALLOWED);
}

Expand Down Expand Up @@ -238,7 +256,7 @@ uint32_t api_set_account(uint8_t app_mode, const uint8_t *data, uint32_t len)
THROW(SW_INCORRECT_LENGTH);
}

if ((app_mode & 0x7f) > APP_MODE_SHIMMER) {
if ((app_mode & 0x7f) > APP_MODE_SHIMMER_NOVA) {
THROW(SW_INCORRECT_P1P2);
}

Expand Down Expand Up @@ -381,6 +399,92 @@ uint32_t api_generate_address(uint8_t show_on_screen, const uint8_t *data,
return IO_ASYNCH_REPLY;
}

uint32_t api_generate_public_key(uint8_t show_on_screen, const uint8_t *data,
uint32_t len)
{
// maybe we need it ...
UNUSED(show_on_screen);

// only allow in nova protocol
if (api.protocol != PROTOCOL_NOVA) {
THROW(SW_COMMAND_NOT_ALLOWED);
}

// don't allow command if an interactive flow already is running
if (api.flow_locked) {
THROW(SW_COMMAND_NOT_ALLOWED);
}

// was account selected?
if (!(api.bip32_path[BIP32_ACCOUNT_INDEX] & 0x80000000)) {
THROW(SW_ACCOUNT_NOT_VALID);
}

// if buffer contains data, throw exception
if (api.data.type != EMPTY) {
THROW(SW_COMMAND_NOT_ALLOWED);
}

// disable external read and write access before doing anything else
api.data.type = LOCKED;

if (len != sizeof(API_GENERATE_PUBLIC_KEYS_REQUEST)) {
THROW(SW_INCORRECT_LENGTH);
}

API_GENERATE_PUBLIC_KEYS_REQUEST req;
memcpy(&req, data, sizeof(req));

// check if too many public keys to generate
if (req.count > API_GENERATE_PUBLIC_KEYS_MAX_COUNT) {
THROW(SW_COMMAND_INVALID_DATA);
}

// check if MSBs set
if (!(req.bip32_index & 0x80000000) || !(req.bip32_change & 0x80000000)) {
THROW(SW_COMMAND_INVALID_DATA);
}

// bip32 change can be 0x80000000 or 0x80000001
if (req.bip32_change & 0x7ffffffe) {
THROW(SW_COMMAND_INVALID_DATA);
}

// check if there would be an overflow when generating public keys
if (!((req.bip32_index + req.count) & 0x80000000)) {
THROW(SW_COMMAND_INVALID_DATA);
}

// show "generating public keys ..."
if (!show_on_screen) {
flow_generating_public_keys();
}

api.bip32_path[BIP32_ADDRESS_INDEX] = req.bip32_index;
api.bip32_path[BIP32_CHANGE_INDEX] = req.bip32_change;

// wipe all data before buffer is used again
memset(api.data.buffer, 0, API_BUFFER_SIZE_BYTES);
for (uint32_t i = 0; i < req.count; i++) {
// with address_type
uint8_t ret =
public_key_generate(api.bip32_path, BIP32_PATH_LEN,
&api.data.buffer[i * PUBKEY_SIZE_BYTES]);

if (!ret) {
THROW(SW_UNKNOWN);
}
// generate next address
api.bip32_path[BIP32_ADDRESS_INDEX]++;
}

api.data.length = req.count * PUBKEY_SIZE_BYTES;

api.data.type = GENERATED_PUBLIC_KEYS;
io_send(NULL, 0, SW_OK);
return 0;
}

uint32_t api_prepare_signing(uint8_t has_remainder, const uint8_t *data,
uint32_t len)
{
Expand Down Expand Up @@ -443,16 +547,15 @@ uint32_t api_prepare_signing(uint8_t has_remainder, const uint8_t *data,
return 0;
}

uint32_t api_prepare_blindsigning()
uint32_t api_prepare_blindsigning(uint8_t num_hashes)
{
// when calling validation the buffer still is marked as empty
if (api.data.type != EMPTY) {
THROW(SW_COMMAND_NOT_ALLOWED);
}

// blindsigning only allowed with stardust protocol but not SMR claiming
if (api.protocol != PROTOCOL_STARDUST ||
api.app_mode == APP_MODE_SHIMMER_CLAIMING) {
// blindsigning not allowed on shimmer claiming
if (api.app_mode == APP_MODE_SHIMMER_CLAIMING) {
THROW(SW_COMMAND_NOT_ALLOWED);
}

Expand All @@ -467,13 +570,17 @@ uint32_t api_prepare_blindsigning()
// set flag for blindsigning
api.essence.blindsigning = 1;

// multiple of 32byte chunks
// value of 0 is the same as 1 for compatibility
uint16_t signing_input_len = (uint16_t) (!num_hashes ? 1 : num_hashes) << 5;

// we allow to prepare without blindsigning enabled but the user will only
// get an error message that blindsigning is not enabled on the Nano when
// trying to sign what is the most consistent behaviour because the outcome
// is the same as rejecting the signing (the flow only has a reject button
// in this case and accepting is not possible) and we don't have to cope
// with additional errors.
if (!parse_and_validate_blindsigning(&api)) {
if (!parse_and_validate_blindsigning(&api, signing_input_len)) {
THROW(SW_COMMAND_INVALID_DATA);
}

Expand Down
26 changes: 21 additions & 5 deletions src/api.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "iota_io.h"

#include "iota/address.h"
#include "iota/public_key.h"

#define IO_STRUCT struct __attribute__((packed, may_alias))

Expand All @@ -19,15 +20,18 @@ typedef enum {
USER_CONFIRMED_ESSENCE = 3,
SIGNATURES = 4,
LOCKED = 5,
GENERATED_PUBLIC_KEYS = 6,
} DATA_TYPE;

typedef enum {
APP_MODE_IOTA_STARDUST = 1,
APP_MODE_SHIMMER_CLAIMING = 2,
APP_MODE_SHIMMER = 3
APP_MODE_SHIMMER = 3,
APP_MODE_IOTA_NOVA = 4,
APP_MODE_SHIMMER_NOVA = 5,
} APP_MODE_TYPE;

typedef enum { PROTOCOL_STARDUST = 1 } PROTOCOL_TYPE;
typedef enum { PROTOCOL_STARDUST = 1, PROTOCOL_NOVA = 2 } PROTOCOL_TYPE;

typedef enum { COIN_IOTA = 0, COIN_SHIMMER = 1 } COIN_TYPE;

Expand Down Expand Up @@ -119,6 +123,14 @@ typedef IO_STRUCT
}
API_GENERATE_ADDRESS_REQUEST;

typedef IO_STRUCT
{
uint32_t bip32_index;
uint32_t bip32_change;
uint32_t count;
}
API_GENERATE_PUBLIC_KEYS_REQUEST;


typedef IO_STRUCT
{
Expand Down Expand Up @@ -193,8 +205,9 @@ typedef struct {
// flag that signals that it's a sweeping transaction
uint8_t is_internal_transfer;

// hash of the essence
uint8_t hash[BLAKE2B_SIZE_BYTES];
// signing input (hash(s))
uint8_t signing_input[SIGNING_INPUT_MAX_BYTES];
uint16_t signing_input_len;
} ESSENCE;

typedef struct {
Expand Down Expand Up @@ -275,7 +288,7 @@ uint32_t api_clear_data_buffer(void);
uint32_t api_prepare_signing(uint8_t has_remainder, const uint8_t *data,
uint32_t len);

uint32_t api_prepare_blindsigning(void);
uint32_t api_prepare_blindsigning(uint8_t num_hashes);

uint32_t api_user_confirm_essence(void);

Expand All @@ -284,6 +297,9 @@ uint32_t api_sign(uint8_t p1);
uint32_t api_generate_address(uint8_t show_on_screen, const uint8_t *data,
uint32_t len);

uint32_t api_generate_public_key(uint8_t show_on_screen, const uint8_t *data,
uint32_t len);

#ifdef APP_DEBUG
uint32_t api_dump_memory(uint8_t pagenr);
uint32_t api_set_non_interactive_mode(uint8_t mode);
Expand Down
47 changes: 24 additions & 23 deletions src/iota/abstraction.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ const uint8_t *get_output_address_ptr(const API_CTX *api, uint8_t index)
ret = &tmp[index].address_type;
break;
}
// not supported yet
case PROTOCOL_NOVA: {
THROW(SW_UNKNOWN);
}
default:
THROW(SW_UNKNOWN);
break;
Expand All @@ -51,6 +55,10 @@ uint64_t get_output_amount(const API_CTX *api, uint8_t index)
memcpy(&amount, &tmp[index].amount, sizeof(uint64_t));
break;
}
// not supported yet
case PROTOCOL_NOVA: {
THROW(SW_UNKNOWN);
}
default:
THROW(SW_UNKNOWN);
break;
Expand All @@ -61,28 +69,26 @@ uint64_t get_output_amount(const API_CTX *api, uint8_t index)
uint8_t address_encode_bech32(const API_CTX *api, const uint8_t *addr_with_type,
char *bech32, uint32_t bech32_max_length)
{
const char *hrp;

switch (api->coin) {
case COIN_IOTA: {
MUST(address_encode_bech32_hrp(
addr_with_type, bech32, bech32_max_length,
(api->app_mode & 0x80) ? COIN_HRP_IOTA_TESTNET : COIN_HRP_IOTA,
strlen(COIN_HRP_IOTA))); // strlen valid because HRP has the same
// length in testnet
hrp = (api->app_mode & 0x80) ? COIN_HRP_IOTA_TESTNET : COIN_HRP_IOTA;
break;
}
case COIN_SHIMMER: {
MUST(address_encode_bech32_hrp(
addr_with_type, bech32, bech32_max_length,
(api->app_mode & 0x80) ? COIN_HRP_SHIMMER_TESTNET
: COIN_HRP_SHIMMER,
strlen(COIN_HRP_SHIMMER))); // strlen valid because HRP has the same
// length in testnet
hrp = (api->app_mode & 0x80) ? COIN_HRP_SHIMMER_TESTNET
: COIN_HRP_SHIMMER;
break;
}
default:
THROW(SW_UNKNOWN);
break;
}

MUST(address_encode_bech32_hrp(addr_with_type, bech32, bech32_max_length,
hrp, strlen(hrp)));

return 1;
}

Expand All @@ -94,6 +100,10 @@ uint8_t essence_parse_and_validate(API_CTX *api)
MUST(essence_parse_and_validate_stardust(api));
break;
}
// not supported yet
case PROTOCOL_NOVA: {
THROW(SW_UNKNOWN);
}
default:
THROW(SW_UNKNOWN);
break;
Expand All @@ -108,17 +118,8 @@ uint8_t get_amount(const API_CTX *api, int index, char *dst, size_t dst_len)
// amount > 0 enforced by validation
MUST(amount = get_output_amount(api, index));

switch (api->coin) {
case COIN_IOTA: {
format_value_full_decimals(dst, dst_len, amount);
break;
}
case COIN_SHIMMER: {
format_value_full_decimals(dst, dst_len, amount);
break;
}
default:
THROW(SW_UNKNOWN);
}
format_value_full_decimals(dst, dst_len, amount);

return 1;
}

Loading
Loading