mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-22 07:28:10 +00:00
cardano: add Cardano currency support
This commit is contained in:
parent
8cd8be9bd1
commit
09ddcc7ac9
@ -27,6 +27,7 @@ CPPDEFINES_MOD += [
|
|||||||
'RAND_PLATFORM_INDEPENDENT',
|
'RAND_PLATFORM_INDEPENDENT',
|
||||||
('USE_KECCAK', '1'),
|
('USE_KECCAK', '1'),
|
||||||
('USE_ETHEREUM', '1'),
|
('USE_ETHEREUM', '1'),
|
||||||
|
('USE_CARDANO', '1'),
|
||||||
('USE_NEM', '1'),
|
('USE_NEM', '1'),
|
||||||
]
|
]
|
||||||
SOURCE_MOD += [
|
SOURCE_MOD += [
|
||||||
|
@ -25,6 +25,7 @@ CPPDEFINES_MOD += [
|
|||||||
'AES_192',
|
'AES_192',
|
||||||
('USE_KECCAK', '1'),
|
('USE_KECCAK', '1'),
|
||||||
('USE_ETHEREUM', '1'),
|
('USE_ETHEREUM', '1'),
|
||||||
|
('USE_CARDANO', '1'),
|
||||||
('USE_NEM', '1'),
|
('USE_NEM', '1'),
|
||||||
]
|
]
|
||||||
SOURCE_MOD += [
|
SOURCE_MOD += [
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include "embed/extmod/trezorobj.h"
|
#include "embed/extmod/trezorobj.h"
|
||||||
|
|
||||||
#include "bip32.h"
|
#include "bip32.h"
|
||||||
|
#include "bip39.h"
|
||||||
#include "curves.h"
|
#include "curves.h"
|
||||||
#include "memzero.h"
|
#include "memzero.h"
|
||||||
#include "nem.h"
|
#include "nem.h"
|
||||||
@ -156,6 +157,32 @@ STATIC mp_obj_t mod_trezorcrypto_HDNode_derive(size_t n_args, const mp_obj_t *ar
|
|||||||
}
|
}
|
||||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_HDNode_derive_obj, 2, 3, mod_trezorcrypto_HDNode_derive);
|
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_HDNode_derive_obj, 2, 3, mod_trezorcrypto_HDNode_derive);
|
||||||
|
|
||||||
|
/// def derive_cardano(self, index: int) -> None:
|
||||||
|
/// '''
|
||||||
|
/// Derive a BIP0032 child node in place using Cardano algorithm.
|
||||||
|
/// '''
|
||||||
|
STATIC mp_obj_t mod_trezorcrypto_HDNode_derive_cardano(mp_obj_t self, mp_obj_t index) {
|
||||||
|
mp_obj_HDNode_t *o = MP_OBJ_TO_PTR(self);
|
||||||
|
uint32_t i = mp_obj_get_int_truncated(index);
|
||||||
|
uint32_t fp = hdnode_fingerprint(&o->hdnode);
|
||||||
|
int res;
|
||||||
|
// same as in derive
|
||||||
|
if (0 == memcmp(o->hdnode.private_key, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 32)) {
|
||||||
|
memzero(&o->hdnode, sizeof(o->hdnode));
|
||||||
|
mp_raise_ValueError("Failed to derive, private key not set");
|
||||||
|
}
|
||||||
|
// special for cardano
|
||||||
|
res = hdnode_private_ckd_cardano(&o->hdnode, i);
|
||||||
|
if (!res) {
|
||||||
|
memzero(&o->hdnode, sizeof(o->hdnode));
|
||||||
|
mp_raise_ValueError("Failed to derive");
|
||||||
|
}
|
||||||
|
o->fingerprint = fp;
|
||||||
|
|
||||||
|
return mp_const_none;
|
||||||
|
}
|
||||||
|
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_HDNode_derive_cardano_obj, mod_trezorcrypto_HDNode_derive_cardano);
|
||||||
|
|
||||||
/// def derive_path(self, path: List[int]) -> None:
|
/// def derive_path(self, path: List[int]) -> None:
|
||||||
/// '''
|
/// '''
|
||||||
/// Go through a list of indexes and iteratively derive a child node in place.
|
/// Go through a list of indexes and iteratively derive a child node in place.
|
||||||
@ -289,6 +316,16 @@ STATIC mp_obj_t mod_trezorcrypto_HDNode_private_key(mp_obj_t self) {
|
|||||||
}
|
}
|
||||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_HDNode_private_key_obj, mod_trezorcrypto_HDNode_private_key);
|
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_HDNode_private_key_obj, mod_trezorcrypto_HDNode_private_key);
|
||||||
|
|
||||||
|
/// def private_key_ext(self) -> bytes:
|
||||||
|
/// '''
|
||||||
|
/// Returns a private key extension of the HD node.
|
||||||
|
/// '''
|
||||||
|
STATIC mp_obj_t mod_trezorcrypto_HDNode_private_key_ext(mp_obj_t self) {
|
||||||
|
mp_obj_HDNode_t *o = MP_OBJ_TO_PTR(self);
|
||||||
|
return mp_obj_new_bytes(o->hdnode.private_key_extension, sizeof(o->hdnode.private_key_extension));
|
||||||
|
}
|
||||||
|
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_HDNode_private_key_ext_obj, mod_trezorcrypto_HDNode_private_key_ext);
|
||||||
|
|
||||||
/// def public_key(self) -> bytes:
|
/// def public_key(self) -> bytes:
|
||||||
/// '''
|
/// '''
|
||||||
/// Returns a public key of the HD node.
|
/// Returns a public key of the HD node.
|
||||||
@ -385,6 +422,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_HDNode_ethereum_pubkeyhash_obj
|
|||||||
|
|
||||||
STATIC const mp_rom_map_elem_t mod_trezorcrypto_HDNode_locals_dict_table[] = {
|
STATIC const mp_rom_map_elem_t mod_trezorcrypto_HDNode_locals_dict_table[] = {
|
||||||
{ MP_ROM_QSTR(MP_QSTR_derive), MP_ROM_PTR(&mod_trezorcrypto_HDNode_derive_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_derive), MP_ROM_PTR(&mod_trezorcrypto_HDNode_derive_obj) },
|
||||||
|
{ MP_ROM_QSTR(MP_QSTR_derive_cardano), MP_ROM_PTR(&mod_trezorcrypto_HDNode_derive_cardano_obj) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_derive_path), MP_ROM_PTR(&mod_trezorcrypto_HDNode_derive_path_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_derive_path), MP_ROM_PTR(&mod_trezorcrypto_HDNode_derive_path_obj) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_serialize_private), MP_ROM_PTR(&mod_trezorcrypto_HDNode_serialize_private_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_serialize_private), MP_ROM_PTR(&mod_trezorcrypto_HDNode_serialize_private_obj) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_serialize_public), MP_ROM_PTR(&mod_trezorcrypto_HDNode_serialize_public_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_serialize_public), MP_ROM_PTR(&mod_trezorcrypto_HDNode_serialize_public_obj) },
|
||||||
@ -395,6 +433,7 @@ STATIC const mp_rom_map_elem_t mod_trezorcrypto_HDNode_locals_dict_table[] = {
|
|||||||
{ MP_ROM_QSTR(MP_QSTR_child_num), MP_ROM_PTR(&mod_trezorcrypto_HDNode_child_num_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_child_num), MP_ROM_PTR(&mod_trezorcrypto_HDNode_child_num_obj) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_chain_code), MP_ROM_PTR(&mod_trezorcrypto_HDNode_chain_code_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_chain_code), MP_ROM_PTR(&mod_trezorcrypto_HDNode_chain_code_obj) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_private_key), MP_ROM_PTR(&mod_trezorcrypto_HDNode_private_key_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_private_key), MP_ROM_PTR(&mod_trezorcrypto_HDNode_private_key_obj) },
|
||||||
|
{ MP_ROM_QSTR(MP_QSTR_private_key_ext), MP_ROM_PTR(&mod_trezorcrypto_HDNode_private_key_ext_obj) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_public_key), MP_ROM_PTR(&mod_trezorcrypto_HDNode_public_key_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_public_key), MP_ROM_PTR(&mod_trezorcrypto_HDNode_public_key_obj) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_address), MP_ROM_PTR(&mod_trezorcrypto_HDNode_address_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_address), MP_ROM_PTR(&mod_trezorcrypto_HDNode_address_obj) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_nem_address), MP_ROM_PTR(&mod_trezorcrypto_HDNode_nem_address_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_nem_address), MP_ROM_PTR(&mod_trezorcrypto_HDNode_nem_address_obj) },
|
||||||
@ -462,11 +501,45 @@ STATIC mp_obj_t mod_trezorcrypto_bip32_from_seed(mp_obj_t seed, mp_obj_t curve_n
|
|||||||
}
|
}
|
||||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_bip32_from_seed_obj, mod_trezorcrypto_bip32_from_seed);
|
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_bip32_from_seed_obj, mod_trezorcrypto_bip32_from_seed);
|
||||||
|
|
||||||
|
/// def from_mnemonic_cardano(mnemonic: str) -> bytes:
|
||||||
|
/// '''
|
||||||
|
/// Convert mnemonic to hdnode
|
||||||
|
// '''
|
||||||
|
STATIC mp_obj_t mod_trezorcrypto_bip32_from_mnemonic_cardano(mp_obj_t mnemonic) {
|
||||||
|
mp_buffer_info_t mnemo;
|
||||||
|
mp_get_buffer_raise(mnemonic, &mnemo, MP_BUFFER_READ);
|
||||||
|
HDNode hdnode;
|
||||||
|
const char *pmnemonic = mnemo.len > 0 ? mnemo.buf : "";
|
||||||
|
uint8_t entropy[66];
|
||||||
|
int entropy_len = mnemonic_to_entropy(pmnemonic, entropy + 2);
|
||||||
|
|
||||||
|
if (entropy_len == 0) {
|
||||||
|
mp_raise_ValueError("Invalid mnemonic");
|
||||||
|
}
|
||||||
|
|
||||||
|
const int res = hdnode_from_seed_cardano(entropy, entropy_len / 8, &hdnode);
|
||||||
|
if (!res) {
|
||||||
|
mp_raise_ValueError("Secret key generation from mnemonic is looping forever");
|
||||||
|
}else if(res == -1){
|
||||||
|
mp_raise_ValueError("Invalid mnemonic");
|
||||||
|
}
|
||||||
|
|
||||||
|
mp_obj_HDNode_t *o = m_new_obj(mp_obj_HDNode_t);
|
||||||
|
o->base.type = &mod_trezorcrypto_HDNode_type;
|
||||||
|
o->hdnode = hdnode;
|
||||||
|
return MP_OBJ_FROM_PTR(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorcrypto_bip32_from_mnemonic_cardano_obj,
|
||||||
|
mod_trezorcrypto_bip32_from_mnemonic_cardano);
|
||||||
|
|
||||||
|
|
||||||
STATIC const mp_rom_map_elem_t mod_trezorcrypto_bip32_globals_table[] = {
|
STATIC const mp_rom_map_elem_t mod_trezorcrypto_bip32_globals_table[] = {
|
||||||
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_bip32) },
|
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_bip32) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_HDNode), MP_ROM_PTR(&mod_trezorcrypto_HDNode_type) },
|
{ MP_ROM_QSTR(MP_QSTR_HDNode), MP_ROM_PTR(&mod_trezorcrypto_HDNode_type) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_deserialize), MP_ROM_PTR(&mod_trezorcrypto_bip32_deserialize_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_deserialize), MP_ROM_PTR(&mod_trezorcrypto_bip32_deserialize_obj) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_from_seed), MP_ROM_PTR(&mod_trezorcrypto_bip32_from_seed_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_from_seed), MP_ROM_PTR(&mod_trezorcrypto_bip32_from_seed_obj) },
|
||||||
|
{ MP_ROM_QSTR(MP_QSTR_from_mnemonic_cardano), MP_ROM_PTR(&mod_trezorcrypto_bip32_from_mnemonic_cardano_obj) },
|
||||||
};
|
};
|
||||||
STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_bip32_globals, mod_trezorcrypto_bip32_globals_table);
|
STATIC MP_DEFINE_CONST_DICT(mod_trezorcrypto_bip32_globals, mod_trezorcrypto_bip32_globals_table);
|
||||||
|
|
||||||
|
@ -91,6 +91,33 @@ STATIC mp_obj_t mod_trezorcrypto_ed25519_sign(size_t n_args, const mp_obj_t *arg
|
|||||||
}
|
}
|
||||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_ed25519_sign_obj, 2, 3, mod_trezorcrypto_ed25519_sign);
|
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorcrypto_ed25519_sign_obj, 2, 3, mod_trezorcrypto_ed25519_sign);
|
||||||
|
|
||||||
|
/// def sign_ext(secret_key: bytes, secret_extension: bytes, message: bytes) -> bytes:
|
||||||
|
/// '''
|
||||||
|
/// Uses secret key to produce the cardano signature of message.
|
||||||
|
/// '''
|
||||||
|
STATIC mp_obj_t mod_trezorcrypto_ed25519_sign_ext(mp_obj_t secret_key, mp_obj_t secret_extension, mp_obj_t message) {
|
||||||
|
mp_buffer_info_t sk, skext, msg;
|
||||||
|
mp_get_buffer_raise(secret_key, &sk, MP_BUFFER_READ);
|
||||||
|
mp_get_buffer_raise(secret_extension, &skext, MP_BUFFER_READ);
|
||||||
|
mp_get_buffer_raise(message, &msg, MP_BUFFER_READ);
|
||||||
|
if (sk.len != 32) {
|
||||||
|
mp_raise_ValueError("Invalid length of secret key");
|
||||||
|
}
|
||||||
|
if (skext.len != 32) {
|
||||||
|
mp_raise_ValueError("Invalid length of secret key extension");
|
||||||
|
}
|
||||||
|
if (msg.len == 0) {
|
||||||
|
mp_raise_ValueError("Empty data to sign");
|
||||||
|
}
|
||||||
|
ed25519_public_key pk;
|
||||||
|
|
||||||
|
ed25519_publickey_ext(*(const ed25519_secret_key *)sk.buf, *(const ed25519_secret_key *)skext.buf, pk);
|
||||||
|
uint8_t out[64];
|
||||||
|
ed25519_sign_ext(msg.buf, msg.len, *(const ed25519_secret_key *)sk.buf, *(const ed25519_secret_key *)skext.buf, pk, *(ed25519_signature *)out);
|
||||||
|
return mp_obj_new_bytes(out, sizeof(out));
|
||||||
|
}
|
||||||
|
STATIC MP_DEFINE_CONST_FUN_OBJ_3(mod_trezorcrypto_ed25519_sign_ext_obj, mod_trezorcrypto_ed25519_sign_ext);
|
||||||
|
|
||||||
/// def verify(public_key: bytes, signature: bytes, message: bytes) -> bool:
|
/// def verify(public_key: bytes, signature: bytes, message: bytes) -> bool:
|
||||||
/// '''
|
/// '''
|
||||||
/// Uses public key to verify the signature of the message.
|
/// Uses public key to verify the signature of the message.
|
||||||
@ -207,6 +234,7 @@ STATIC const mp_rom_map_elem_t mod_trezorcrypto_ed25519_globals_table[] = {
|
|||||||
{ MP_ROM_QSTR(MP_QSTR_generate_secret), MP_ROM_PTR(&mod_trezorcrypto_ed25519_generate_secret_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_generate_secret), MP_ROM_PTR(&mod_trezorcrypto_ed25519_generate_secret_obj) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_publickey), MP_ROM_PTR(&mod_trezorcrypto_ed25519_publickey_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_publickey), MP_ROM_PTR(&mod_trezorcrypto_ed25519_publickey_obj) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_sign), MP_ROM_PTR(&mod_trezorcrypto_ed25519_sign_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_sign), MP_ROM_PTR(&mod_trezorcrypto_ed25519_sign_obj) },
|
||||||
|
{ MP_ROM_QSTR(MP_QSTR_sign_ext), MP_ROM_PTR(&mod_trezorcrypto_ed25519_sign_ext_obj) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_verify), MP_ROM_PTR(&mod_trezorcrypto_ed25519_verify_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_verify), MP_ROM_PTR(&mod_trezorcrypto_ed25519_verify_obj) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_cosi_combine_publickeys), MP_ROM_PTR(&mod_trezorcrypto_ed25519_cosi_combine_publickeys_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_cosi_combine_publickeys), MP_ROM_PTR(&mod_trezorcrypto_ed25519_cosi_combine_publickeys_obj) },
|
||||||
{ MP_ROM_QSTR(MP_QSTR_cosi_combine_signatures), MP_ROM_PTR(&mod_trezorcrypto_ed25519_cosi_combine_signatures_obj) },
|
{ MP_ROM_QSTR(MP_QSTR_cosi_combine_signatures), MP_ROM_PTR(&mod_trezorcrypto_ed25519_cosi_combine_signatures_obj) },
|
||||||
|
46
src/apps/cardano/__init__.py
Normal file
46
src/apps/cardano/__init__.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
from trezor.messages.MessageType import (
|
||||||
|
CardanoGetAddress,
|
||||||
|
CardanoGetPublicKey,
|
||||||
|
CardanoSignMessage,
|
||||||
|
CardanoSignTransaction,
|
||||||
|
CardanoVerifyMessage,
|
||||||
|
)
|
||||||
|
from trezor.wire import protobuf_workflow, register
|
||||||
|
|
||||||
|
|
||||||
|
def dispatch_CardanoGetAddress(*args, **kwargs):
|
||||||
|
from .get_address import cardano_get_address
|
||||||
|
|
||||||
|
return cardano_get_address(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def dispatch_CardanoGetPublicKey(*args, **kwargs):
|
||||||
|
from .get_public_key import cardano_get_public_key
|
||||||
|
|
||||||
|
return cardano_get_public_key(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def dispatch_CardanoSignMessage(*args, **kwargs):
|
||||||
|
from .sign_message import cardano_sign_message
|
||||||
|
|
||||||
|
return cardano_sign_message(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def dispatch_CardanoSignTransaction(*args, **kwargs):
|
||||||
|
from .sign_transaction import cardano_sign_transaction
|
||||||
|
|
||||||
|
return cardano_sign_transaction(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def dispatch_CardanoVerifyMessage(*args, **kwargs):
|
||||||
|
from .verify_message import cardano_verify_message
|
||||||
|
|
||||||
|
return cardano_verify_message(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def boot():
|
||||||
|
register(CardanoGetAddress, protobuf_workflow, dispatch_CardanoGetAddress)
|
||||||
|
register(CardanoGetPublicKey, protobuf_workflow, dispatch_CardanoGetPublicKey)
|
||||||
|
register(CardanoSignMessage, protobuf_workflow, dispatch_CardanoSignMessage)
|
||||||
|
register(CardanoVerifyMessage, protobuf_workflow, dispatch_CardanoVerifyMessage)
|
||||||
|
register(CardanoSignTransaction, protobuf_workflow, dispatch_CardanoSignTransaction)
|
104
src/apps/cardano/address.py
Normal file
104
src/apps/cardano/address.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
from micropython import const
|
||||||
|
|
||||||
|
from trezor import wire
|
||||||
|
from trezor.crypto import base58, chacha20poly1305, crc, hashlib, pbkdf2
|
||||||
|
|
||||||
|
from . import cbor
|
||||||
|
|
||||||
|
from apps.common import HARDENED, seed
|
||||||
|
|
||||||
|
|
||||||
|
def validate_derivation_path(path: list):
|
||||||
|
if len(path) < 2 or len(path) > 5:
|
||||||
|
raise wire.ProcessError("Derivation path must be composed from 2-5 indices")
|
||||||
|
|
||||||
|
if path[0] != HARDENED | 44 or path[1] != HARDENED | 1815:
|
||||||
|
raise wire.ProcessError("This is not cardano derivation path")
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def _derive_hd_passphrase(node) -> bytes:
|
||||||
|
iterations = const(500)
|
||||||
|
length = const(32)
|
||||||
|
passwd = seed.remove_ed25519_prefix(node.public_key()) + node.chain_code()
|
||||||
|
x = pbkdf2("hmac-sha512", passwd, b"address-hashing", iterations)
|
||||||
|
return x.key()[:length]
|
||||||
|
|
||||||
|
|
||||||
|
def _address_hash(data) -> bytes:
|
||||||
|
data = cbor.encode(data)
|
||||||
|
data = hashlib.sha3_256(data).digest()
|
||||||
|
res = hashlib.blake2b(data=data, outlen=28).digest()
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def _get_address_root(node, payload):
|
||||||
|
extpubkey = seed.remove_ed25519_prefix(node.public_key()) + node.chain_code()
|
||||||
|
if payload:
|
||||||
|
payload = {1: cbor.encode(payload)}
|
||||||
|
else:
|
||||||
|
payload = {}
|
||||||
|
return _address_hash([0, [0, extpubkey], payload])
|
||||||
|
|
||||||
|
|
||||||
|
def _encrypt_derivation_path(path: list, hd_passphrase: bytes) -> bytes:
|
||||||
|
serialized = cbor.encode(cbor.IndefiniteLengthArray(path))
|
||||||
|
ctx = chacha20poly1305(hd_passphrase, b"serokellfore")
|
||||||
|
data = ctx.encrypt(serialized)
|
||||||
|
tag = ctx.finish()
|
||||||
|
return data + tag
|
||||||
|
|
||||||
|
|
||||||
|
def derive_address_and_node(root_node, path: list):
|
||||||
|
validate_derivation_path(path)
|
||||||
|
|
||||||
|
derived_node = root_node.clone()
|
||||||
|
|
||||||
|
# this means empty derivation path m/44'/1815'
|
||||||
|
if len(path) == 2:
|
||||||
|
address_payload = None
|
||||||
|
address_attributes = {}
|
||||||
|
else:
|
||||||
|
if len(path) == 5:
|
||||||
|
p = [path[2], path[4]]
|
||||||
|
else:
|
||||||
|
p = [path[2]]
|
||||||
|
for indice in p:
|
||||||
|
derived_node.derive_cardano(indice)
|
||||||
|
|
||||||
|
hd_passphrase = _derive_hd_passphrase(root_node)
|
||||||
|
address_payload = _encrypt_derivation_path(p, hd_passphrase)
|
||||||
|
address_attributes = {1: cbor.encode(address_payload)}
|
||||||
|
|
||||||
|
address_root = _get_address_root(derived_node, address_payload)
|
||||||
|
address_type = 0
|
||||||
|
address_data = [address_root, address_attributes, address_type]
|
||||||
|
address_data_encoded = cbor.encode(address_data)
|
||||||
|
|
||||||
|
address = base58.encode(
|
||||||
|
cbor.encode(
|
||||||
|
[cbor.Tagged(24, address_data_encoded), crc.crc32(address_data_encoded)]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return (address, derived_node)
|
||||||
|
|
||||||
|
|
||||||
|
def _break_address_n_to_lines(address_n: list) -> list:
|
||||||
|
def path_item(i: int):
|
||||||
|
if i & HARDENED:
|
||||||
|
return str(i ^ HARDENED) + "'"
|
||||||
|
else:
|
||||||
|
return str(i)
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
path_str = "m/" + "/".join([path_item(i) for i in address_n])
|
||||||
|
|
||||||
|
per_line = const(17)
|
||||||
|
while len(path_str) > per_line:
|
||||||
|
i = path_str[:per_line].rfind("/")
|
||||||
|
lines.append(path_str[:i])
|
||||||
|
path_str = path_str[i:]
|
||||||
|
lines.append(path_str)
|
||||||
|
|
||||||
|
return lines
|
182
src/apps/cardano/cbor.py
Normal file
182
src/apps/cardano/cbor.py
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
"""
|
||||||
|
Minimalistic CBOR implementation, supports only what we need in cardano.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import ustruct as struct
|
||||||
|
from micropython import const
|
||||||
|
|
||||||
|
from trezor import log
|
||||||
|
|
||||||
|
_CBOR_TYPE_MASK = const(0xE0)
|
||||||
|
_CBOR_INFO_BITS = const(0x1F)
|
||||||
|
|
||||||
|
_CBOR_UNSIGNED_INT = const(0b000 << 5)
|
||||||
|
_CBOR_BYTE_STRING = const(0b010 << 5)
|
||||||
|
_CBOR_ARRAY = const(0b100 << 5)
|
||||||
|
_CBOR_MAP = const(0b101 << 5)
|
||||||
|
_CBOR_TAG = const(0b110 << 5)
|
||||||
|
_CBOR_PRIMITIVE = const(0b111 << 5)
|
||||||
|
|
||||||
|
_CBOR_UINT8_FOLLOWS = const(0x18)
|
||||||
|
_CBOR_UINT16_FOLLOWS = const(0x19)
|
||||||
|
_CBOR_UINT32_FOLLOWS = const(0x1a)
|
||||||
|
_CBOR_UINT64_FOLLOWS = const(0x1b)
|
||||||
|
_CBOR_VAR_FOLLOWS = const(0x1f)
|
||||||
|
|
||||||
|
_CBOR_BREAK = const(0x1f)
|
||||||
|
_CBOR_RAW_TAG = const(0x18)
|
||||||
|
|
||||||
|
|
||||||
|
def _header(typ, l: int):
|
||||||
|
if l < 24:
|
||||||
|
return struct.pack(">B", typ + l)
|
||||||
|
elif l < 2 ** 8:
|
||||||
|
return struct.pack(">BB", typ + 24, l)
|
||||||
|
elif l < 2 ** 16:
|
||||||
|
return struct.pack(">BH", typ + 25, l)
|
||||||
|
elif l < 2 ** 32:
|
||||||
|
return struct.pack(">BI", typ + 26, l)
|
||||||
|
elif l < 2 ** 64:
|
||||||
|
return struct.pack(">BQ", typ + 27, l)
|
||||||
|
else:
|
||||||
|
raise NotImplementedError("Length %d not suppported" % l)
|
||||||
|
|
||||||
|
|
||||||
|
def _cbor_encode(value):
|
||||||
|
if isinstance(value, int):
|
||||||
|
yield _header(_CBOR_UNSIGNED_INT, value)
|
||||||
|
elif isinstance(value, bytes):
|
||||||
|
yield _header(_CBOR_BYTE_STRING, len(value))
|
||||||
|
yield value
|
||||||
|
elif isinstance(value, bytearray):
|
||||||
|
yield _header(_CBOR_BYTE_STRING, len(value))
|
||||||
|
yield bytes(value)
|
||||||
|
elif isinstance(value, list):
|
||||||
|
# definite-length valued list
|
||||||
|
yield _header(_CBOR_ARRAY, len(value))
|
||||||
|
for x in value:
|
||||||
|
yield from _cbor_encode(x)
|
||||||
|
elif isinstance(value, dict):
|
||||||
|
yield _header(_CBOR_MAP, len(value))
|
||||||
|
for k, v in value.items():
|
||||||
|
yield from _cbor_encode(k)
|
||||||
|
yield from _cbor_encode(v)
|
||||||
|
elif isinstance(value, Tagged):
|
||||||
|
yield _header(_CBOR_TAG, value.tag)
|
||||||
|
yield from _cbor_encode(value.value)
|
||||||
|
elif isinstance(value, IndefiniteLengthArray):
|
||||||
|
yield bytes([_CBOR_ARRAY + 31])
|
||||||
|
for x in value.array:
|
||||||
|
yield from _cbor_encode(x)
|
||||||
|
yield bytes([_CBOR_PRIMITIVE + 31])
|
||||||
|
elif isinstance(value, Raw):
|
||||||
|
yield value.value
|
||||||
|
else:
|
||||||
|
if __debug__:
|
||||||
|
log.debug(__name__, "not implemented (encode): %s", type(value))
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
def _read_length(cbor, aux):
|
||||||
|
if aux == _CBOR_UINT8_FOLLOWS:
|
||||||
|
return (cbor[0], cbor[1:])
|
||||||
|
elif aux == _CBOR_UINT16_FOLLOWS:
|
||||||
|
res = cbor[1]
|
||||||
|
res += cbor[0] << 8
|
||||||
|
return (res, cbor[2:])
|
||||||
|
elif aux == _CBOR_UINT32_FOLLOWS:
|
||||||
|
res = cbor[3]
|
||||||
|
res += cbor[2] << 8
|
||||||
|
res += cbor[1] << 16
|
||||||
|
res += cbor[0] << 24
|
||||||
|
return (res, cbor[4:])
|
||||||
|
elif aux == _CBOR_UINT64_FOLLOWS:
|
||||||
|
res = cbor[7]
|
||||||
|
res += cbor[6] << 8
|
||||||
|
res += cbor[5] << 16
|
||||||
|
res += cbor[4] << 24
|
||||||
|
res += cbor[3] << 32
|
||||||
|
res += cbor[2] << 40
|
||||||
|
res += cbor[1] << 48
|
||||||
|
res += cbor[0] << 56
|
||||||
|
return (res, cbor[8:])
|
||||||
|
else:
|
||||||
|
raise NotImplementedError("Length %d not suppported" % aux)
|
||||||
|
|
||||||
|
|
||||||
|
def _cbor_decode(cbor):
|
||||||
|
fb = cbor[0]
|
||||||
|
data = b""
|
||||||
|
fb_type = fb & _CBOR_TYPE_MASK
|
||||||
|
fb_aux = fb & _CBOR_INFO_BITS
|
||||||
|
if fb_type == _CBOR_UNSIGNED_INT:
|
||||||
|
if fb_aux < 0x18:
|
||||||
|
return (fb_aux, cbor[1:])
|
||||||
|
else:
|
||||||
|
val, data = _read_length(cbor[1:], fb_aux)
|
||||||
|
return (int(val), data)
|
||||||
|
elif fb_type == _CBOR_BYTE_STRING:
|
||||||
|
ln, data = _read_length(cbor[1:], fb_aux)
|
||||||
|
return (data[0:ln], data[ln:])
|
||||||
|
elif fb_type == _CBOR_ARRAY:
|
||||||
|
if fb_aux == _CBOR_VAR_FOLLOWS:
|
||||||
|
res = []
|
||||||
|
data = cbor[1:]
|
||||||
|
while True:
|
||||||
|
item, data = _cbor_decode(data)
|
||||||
|
if item == _CBOR_PRIMITIVE + _CBOR_BREAK:
|
||||||
|
break
|
||||||
|
res.append(item)
|
||||||
|
return (res, data)
|
||||||
|
else:
|
||||||
|
if fb_aux < _CBOR_UINT8_FOLLOWS:
|
||||||
|
ln = fb_aux
|
||||||
|
data = cbor[1:]
|
||||||
|
else:
|
||||||
|
ln, data = _read_length(cbor[1:], fb_aux)
|
||||||
|
res = []
|
||||||
|
for i in range(ln):
|
||||||
|
item, data = _cbor_decode(data)
|
||||||
|
res.append(item)
|
||||||
|
return (res, data)
|
||||||
|
elif fb_type == _CBOR_MAP:
|
||||||
|
return ({}, cbor[1:])
|
||||||
|
elif fb_type == _CBOR_TAG:
|
||||||
|
if cbor[1] == _CBOR_RAW_TAG: # only tag 24 (0x18) is supported
|
||||||
|
return _cbor_decode(cbor[2:])
|
||||||
|
else:
|
||||||
|
raise NotImplementedError()
|
||||||
|
elif fb_type == _CBOR_PRIMITIVE: # only break code is supported
|
||||||
|
return (cbor[0], cbor[1:])
|
||||||
|
else:
|
||||||
|
if __debug__:
|
||||||
|
log.debug(__name__, "not implemented (decode): %s", cbor[0])
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class Tagged:
|
||||||
|
def __init__(self, tag, value):
|
||||||
|
self.tag = tag
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
|
||||||
|
class Raw:
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
|
||||||
|
class IndefiniteLengthArray:
|
||||||
|
def __init__(self, array):
|
||||||
|
assert isinstance(array, list)
|
||||||
|
self.array = array
|
||||||
|
|
||||||
|
|
||||||
|
def encode(value):
|
||||||
|
return b"".join(_cbor_encode(value))
|
||||||
|
|
||||||
|
|
||||||
|
def decode(cbor: bytes):
|
||||||
|
res, check = _cbor_decode(cbor)
|
||||||
|
if not (check == b""):
|
||||||
|
raise ValueError()
|
||||||
|
return res
|
36
src/apps/cardano/get_address.py
Normal file
36
src/apps/cardano/get_address.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
from trezor import log, ui, wire
|
||||||
|
from trezor.crypto import bip32
|
||||||
|
from trezor.messages.CardanoAddress import CardanoAddress
|
||||||
|
|
||||||
|
from .address import _break_address_n_to_lines, derive_address_and_node
|
||||||
|
from .ui import show_swipable_with_confirmation
|
||||||
|
|
||||||
|
from apps.common import storage
|
||||||
|
|
||||||
|
|
||||||
|
async def cardano_get_address(ctx, msg):
|
||||||
|
mnemonic = storage.get_mnemonic()
|
||||||
|
root_node = bip32.from_mnemonic_cardano(mnemonic)
|
||||||
|
|
||||||
|
try:
|
||||||
|
address, _ = derive_address_and_node(root_node, msg.address_n)
|
||||||
|
except ValueError as e:
|
||||||
|
if __debug__:
|
||||||
|
log.exception(__name__, e)
|
||||||
|
raise wire.ProcessError("Deriving address failed")
|
||||||
|
mnemonic = None
|
||||||
|
root_node = None
|
||||||
|
|
||||||
|
if msg.show_display:
|
||||||
|
if not await show_swipable_with_confirmation(
|
||||||
|
ctx, address, "Export address", icon=ui.ICON_SEND, icon_color=ui.GREEN
|
||||||
|
):
|
||||||
|
raise wire.ActionCancelled("Exporting cancelled")
|
||||||
|
else:
|
||||||
|
lines = _break_address_n_to_lines(msg.address_n)
|
||||||
|
if not await show_swipable_with_confirmation(
|
||||||
|
ctx, lines, "For BIP32 path", icon=ui.ICON_SEND, icon_color=ui.GREEN
|
||||||
|
):
|
||||||
|
raise wire.ActionCancelled("Exporting cancelled")
|
||||||
|
|
||||||
|
return CardanoAddress(address=address)
|
57
src/apps/cardano/get_public_key.py
Normal file
57
src/apps/cardano/get_public_key.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
from ubinascii import hexlify
|
||||||
|
|
||||||
|
from trezor import log, wire
|
||||||
|
from trezor.crypto import bip32
|
||||||
|
from trezor.messages.CardanoPublicKey import CardanoPublicKey
|
||||||
|
from trezor.messages.HDNodeType import HDNodeType
|
||||||
|
|
||||||
|
from .address import (
|
||||||
|
_break_address_n_to_lines,
|
||||||
|
_derive_hd_passphrase,
|
||||||
|
derive_address_and_node,
|
||||||
|
)
|
||||||
|
from .ui import show_swipable_with_confirmation
|
||||||
|
|
||||||
|
from apps.common import seed, storage
|
||||||
|
|
||||||
|
|
||||||
|
async def cardano_get_public_key(ctx, msg):
|
||||||
|
mnemonic = storage.get_mnemonic()
|
||||||
|
root_node = bip32.from_mnemonic_cardano(mnemonic)
|
||||||
|
|
||||||
|
try:
|
||||||
|
key = _get_public_key(root_node, msg.address_n)
|
||||||
|
except ValueError as e:
|
||||||
|
if __debug__:
|
||||||
|
log.exception(__name__, e)
|
||||||
|
raise wire.ProcessError("Deriving public key failed")
|
||||||
|
mnemonic = None
|
||||||
|
root_node = None
|
||||||
|
|
||||||
|
lines = ["For BIP32 path: ", ""]
|
||||||
|
lines.extend(_break_address_n_to_lines(msg.address_n))
|
||||||
|
if not await show_swipable_with_confirmation(ctx, lines, "Export xpub key"):
|
||||||
|
raise wire.ActionCancelled("Exporting cancelled")
|
||||||
|
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
def _get_public_key(root_node, derivation_path: list):
|
||||||
|
_, node = derive_address_and_node(root_node, derivation_path)
|
||||||
|
|
||||||
|
public_key = hexlify(seed.remove_ed25519_prefix(node.public_key())).decode("utf8")
|
||||||
|
chain_code = hexlify(node.chain_code()).decode("utf8")
|
||||||
|
xpub_key = public_key + chain_code
|
||||||
|
root_hd_passphrase = hexlify(_derive_hd_passphrase(root_node)).decode("utf8")
|
||||||
|
|
||||||
|
node_type = HDNodeType(
|
||||||
|
depth=node.depth(),
|
||||||
|
child_num=node.child_num(),
|
||||||
|
fingerprint=node.fingerprint(),
|
||||||
|
chain_code=node.chain_code(),
|
||||||
|
public_key=seed.remove_ed25519_prefix(node.public_key()),
|
||||||
|
)
|
||||||
|
|
||||||
|
return CardanoPublicKey(
|
||||||
|
node=node_type, xpub=xpub_key, root_hd_passphrase=root_hd_passphrase
|
||||||
|
)
|
51
src/apps/cardano/sign_message.py
Normal file
51
src/apps/cardano/sign_message.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
from trezor import log, ui, wire
|
||||||
|
from trezor.crypto import bip32
|
||||||
|
from trezor.crypto.curve import ed25519
|
||||||
|
from trezor.messages.CardanoMessageSignature import CardanoMessageSignature
|
||||||
|
|
||||||
|
from .address import _break_address_n_to_lines, derive_address_and_node
|
||||||
|
from .ui import show_swipable_with_confirmation
|
||||||
|
|
||||||
|
from apps.common import seed, storage
|
||||||
|
|
||||||
|
|
||||||
|
async def cardano_sign_message(ctx, msg):
|
||||||
|
mnemonic = storage.get_mnemonic()
|
||||||
|
root_node = bip32.from_mnemonic_cardano(mnemonic)
|
||||||
|
|
||||||
|
try:
|
||||||
|
signature = _sign_message(root_node, msg.message, msg.address_n)
|
||||||
|
except ValueError as e:
|
||||||
|
if __debug__:
|
||||||
|
log.exception(__name__, e)
|
||||||
|
raise wire.ProcessError("Signing failed")
|
||||||
|
mnemonic = None
|
||||||
|
root_node = None
|
||||||
|
|
||||||
|
if not await show_swipable_with_confirmation(
|
||||||
|
ctx, msg.message, "Signing message", ui.ICON_RECEIVE, ui.GREEN
|
||||||
|
):
|
||||||
|
raise wire.ActionCancelled("Signing cancelled")
|
||||||
|
|
||||||
|
if not await show_swipable_with_confirmation(
|
||||||
|
ctx,
|
||||||
|
_break_address_n_to_lines(msg.address_n),
|
||||||
|
"With address",
|
||||||
|
ui.ICON_RECEIVE,
|
||||||
|
ui.GREEN,
|
||||||
|
):
|
||||||
|
raise wire.ActionCancelled("Signing cancelled")
|
||||||
|
|
||||||
|
return signature
|
||||||
|
|
||||||
|
|
||||||
|
def _sign_message(root_node, message: str, derivation_path: list):
|
||||||
|
address, node = derive_address_and_node(root_node, derivation_path)
|
||||||
|
|
||||||
|
signature = ed25519.sign_ext(node.private_key(), node.private_key_ext(), message)
|
||||||
|
|
||||||
|
sig = CardanoMessageSignature()
|
||||||
|
sig.public_key = seed.remove_ed25519_prefix(node.public_key())
|
||||||
|
sig.signature = signature
|
||||||
|
|
||||||
|
return sig
|
263
src/apps/cardano/sign_transaction.py
Normal file
263
src/apps/cardano/sign_transaction.py
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
from trezor import log, ui, wire
|
||||||
|
from trezor.crypto import base58, bip32, hashlib
|
||||||
|
from trezor.crypto.curve import ed25519
|
||||||
|
from trezor.messages.CardanoTxRequest import CardanoTxRequest
|
||||||
|
from trezor.messages.MessageType import CardanoTxAck
|
||||||
|
from trezor.ui.text import BR
|
||||||
|
|
||||||
|
from .address import _break_address_n_to_lines, derive_address_and_node
|
||||||
|
from .ui import progress, show_swipable_with_confirmation
|
||||||
|
|
||||||
|
from apps.cardano import cbor
|
||||||
|
from apps.common import seed, storage
|
||||||
|
from apps.homescreen.homescreen import display_homescreen
|
||||||
|
|
||||||
|
|
||||||
|
async def show_tx(
|
||||||
|
ctx,
|
||||||
|
outputs: list,
|
||||||
|
outcoins: list,
|
||||||
|
change_derivation_paths: list,
|
||||||
|
change_coins: list,
|
||||||
|
fee: float,
|
||||||
|
tx_size: float,
|
||||||
|
) -> bool:
|
||||||
|
lines = ("%s ADA" % _micro_ada_to_ada(fee), BR, "Tx size:", "%s bytes" % tx_size)
|
||||||
|
if not await show_swipable_with_confirmation(
|
||||||
|
ctx, lines, "Confirm fee", ui.ICON_SEND, ui.GREEN
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
for index, output in enumerate(outputs):
|
||||||
|
if not await show_swipable_with_confirmation(
|
||||||
|
ctx, output, "Confirm output", ui.ICON_SEND, ui.GREEN
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not await show_swipable_with_confirmation(
|
||||||
|
ctx,
|
||||||
|
"%s ADA" % _micro_ada_to_ada(outcoins[index]),
|
||||||
|
"Confirm amount",
|
||||||
|
ui.ICON_SEND,
|
||||||
|
ui.GREEN,
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
for index, change in enumerate(change_derivation_paths):
|
||||||
|
if not await show_swipable_with_confirmation(
|
||||||
|
ctx,
|
||||||
|
_break_address_n_to_lines(change),
|
||||||
|
"Confirm change",
|
||||||
|
ui.ICON_SEND,
|
||||||
|
ui.GREEN,
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not await show_swipable_with_confirmation(
|
||||||
|
ctx,
|
||||||
|
"%s ADA" % _micro_ada_to_ada(change_coins[index]),
|
||||||
|
"Confirm amount",
|
||||||
|
ui.ICON_SEND,
|
||||||
|
ui.GREEN,
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def request_transaction(ctx, tx_req: CardanoTxRequest, index: int):
|
||||||
|
tx_req.tx_index = index
|
||||||
|
return await ctx.call(tx_req, CardanoTxAck)
|
||||||
|
|
||||||
|
|
||||||
|
async def cardano_sign_transaction(ctx, msg):
|
||||||
|
mnemonic = storage.get_mnemonic()
|
||||||
|
root_node = bip32.from_mnemonic_cardano(mnemonic)
|
||||||
|
|
||||||
|
progress.init(msg.transactions_count, "Loading data")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# request transactions
|
||||||
|
transactions = []
|
||||||
|
tx_req = CardanoTxRequest()
|
||||||
|
for index in range(msg.transactions_count):
|
||||||
|
progress.advance()
|
||||||
|
tx_ack = await request_transaction(ctx, tx_req, index)
|
||||||
|
transactions.append(tx_ack.transaction)
|
||||||
|
|
||||||
|
# clear progress bar
|
||||||
|
display_homescreen()
|
||||||
|
|
||||||
|
# sign the transaction bundle and prepare the result
|
||||||
|
transaction = Transaction(msg.inputs, msg.outputs, transactions, root_node)
|
||||||
|
tx_body, tx_hash = transaction.serialise_tx()
|
||||||
|
tx = CardanoTxRequest(tx_body=tx_body, tx_hash=tx_hash)
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
if __debug__:
|
||||||
|
log.exception(__name__, e)
|
||||||
|
raise wire.ProcessError("Signing failed")
|
||||||
|
|
||||||
|
# display the transaction in UI
|
||||||
|
if not await show_tx(
|
||||||
|
ctx,
|
||||||
|
transaction.output_addresses,
|
||||||
|
transaction.outgoing_coins,
|
||||||
|
transaction.change_derivation_paths,
|
||||||
|
transaction.change_coins,
|
||||||
|
transaction.fee,
|
||||||
|
len(tx_body),
|
||||||
|
):
|
||||||
|
raise wire.ActionCancelled("Signing cancelled")
|
||||||
|
|
||||||
|
return tx
|
||||||
|
|
||||||
|
|
||||||
|
def _micro_ada_to_ada(amount: float) -> float:
|
||||||
|
return amount / 1000000
|
||||||
|
|
||||||
|
|
||||||
|
class Transaction:
|
||||||
|
CARDANO_WITNESS_MAGIC_PREFIX = b"\x01\x1a\x2d\x96\x4a\x09\x58\x20"
|
||||||
|
|
||||||
|
def __init__(self, inputs: list, outputs: list, transactions: list, root_node):
|
||||||
|
self.inputs = inputs
|
||||||
|
self.outputs = outputs
|
||||||
|
self.transactions = transactions
|
||||||
|
self.root_node = root_node
|
||||||
|
|
||||||
|
# attributes have to be always empty in current Cardano
|
||||||
|
self.attributes = {}
|
||||||
|
|
||||||
|
def _process_inputs(self):
|
||||||
|
input_coins = []
|
||||||
|
input_hashes = []
|
||||||
|
output_indexes = []
|
||||||
|
types = []
|
||||||
|
tx_data = {}
|
||||||
|
|
||||||
|
for raw_transaction in self.transactions:
|
||||||
|
tx_hash = hashlib.blake2b(data=bytes(raw_transaction), outlen=32).digest()
|
||||||
|
tx_data[tx_hash] = cbor.decode(raw_transaction)
|
||||||
|
|
||||||
|
for input in self.inputs:
|
||||||
|
input_hashes.append(input.prev_hash)
|
||||||
|
output_indexes.append(input.prev_index)
|
||||||
|
types.append(input.type or 0)
|
||||||
|
|
||||||
|
nodes = []
|
||||||
|
for input in self.inputs:
|
||||||
|
_, node = derive_address_and_node(self.root_node, input.address_n)
|
||||||
|
nodes.append(node)
|
||||||
|
|
||||||
|
for index, output_index in enumerate(output_indexes):
|
||||||
|
tx_hash = bytes(input_hashes[index])
|
||||||
|
if tx_hash in tx_data:
|
||||||
|
tx = tx_data[tx_hash]
|
||||||
|
outputs = tx[1]
|
||||||
|
amount = outputs[output_index][1]
|
||||||
|
input_coins.append(amount)
|
||||||
|
else:
|
||||||
|
raise wire.ProcessError("No tx data sent for input " + str(index))
|
||||||
|
|
||||||
|
self.input_coins = input_coins
|
||||||
|
self.nodes = nodes
|
||||||
|
self.types = types
|
||||||
|
self.input_hashes = input_hashes
|
||||||
|
self.output_indexes = output_indexes
|
||||||
|
|
||||||
|
def _process_outputs(self):
|
||||||
|
change_addresses = []
|
||||||
|
change_derivation_paths = []
|
||||||
|
output_addresses = []
|
||||||
|
outgoing_coins = []
|
||||||
|
change_coins = []
|
||||||
|
|
||||||
|
for output in self.outputs:
|
||||||
|
if output.address_n:
|
||||||
|
address, _ = derive_address_and_node(self.root_node, output.address_n)
|
||||||
|
change_addresses.append(address)
|
||||||
|
change_derivation_paths.append(output.address_n)
|
||||||
|
change_coins.append(output.amount)
|
||||||
|
else:
|
||||||
|
if output.address is None:
|
||||||
|
raise wire.ProcessError(
|
||||||
|
"Each output must have address or address_n field!"
|
||||||
|
)
|
||||||
|
|
||||||
|
outgoing_coins.append(output.amount)
|
||||||
|
output_addresses.append(output.address)
|
||||||
|
|
||||||
|
self.change_addresses = change_addresses
|
||||||
|
self.output_addresses = output_addresses
|
||||||
|
self.outgoing_coins = outgoing_coins
|
||||||
|
self.change_coins = change_coins
|
||||||
|
self.change_derivation_paths = change_derivation_paths
|
||||||
|
|
||||||
|
def _build_witnesses(self, tx_aux_hash: str):
|
||||||
|
witnesses = []
|
||||||
|
for index, node in enumerate(self.nodes):
|
||||||
|
message = self.CARDANO_WITNESS_MAGIC_PREFIX + tx_aux_hash
|
||||||
|
signature = ed25519.sign_ext(
|
||||||
|
node.private_key(), node.private_key_ext(), message
|
||||||
|
)
|
||||||
|
extended_public_key = (
|
||||||
|
seed.remove_ed25519_prefix(node.public_key()) + node.chain_code()
|
||||||
|
)
|
||||||
|
witnesses.append(
|
||||||
|
[
|
||||||
|
self.types[index],
|
||||||
|
cbor.Tagged(24, cbor.encode([extended_public_key, signature])),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
return witnesses
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def compute_fee(input_coins: list, outgoing_coins: list, change_coins: list):
|
||||||
|
input_coins_sum = sum(input_coins)
|
||||||
|
outgoing_coins_sum = sum(outgoing_coins)
|
||||||
|
change_coins_sum = sum(change_coins)
|
||||||
|
|
||||||
|
return input_coins_sum - outgoing_coins_sum - change_coins_sum
|
||||||
|
|
||||||
|
def serialise_tx(self):
|
||||||
|
|
||||||
|
self._process_inputs()
|
||||||
|
self._process_outputs()
|
||||||
|
|
||||||
|
inputs_cbor = []
|
||||||
|
for i, output_index in enumerate(self.output_indexes):
|
||||||
|
inputs_cbor.append(
|
||||||
|
[
|
||||||
|
self.types[i],
|
||||||
|
cbor.Tagged(24, cbor.encode([self.input_hashes[i], output_index])),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
inputs_cbor = cbor.IndefiniteLengthArray(inputs_cbor)
|
||||||
|
|
||||||
|
outputs_cbor = []
|
||||||
|
for index, address in enumerate(self.output_addresses):
|
||||||
|
outputs_cbor.append(
|
||||||
|
[cbor.Raw(base58.decode(address)), self.outgoing_coins[index]]
|
||||||
|
)
|
||||||
|
|
||||||
|
for index, address in enumerate(self.change_addresses):
|
||||||
|
outputs_cbor.append(
|
||||||
|
[cbor.Raw(base58.decode(address)), self.change_coins[index]]
|
||||||
|
)
|
||||||
|
|
||||||
|
outputs_cbor = cbor.IndefiniteLengthArray(outputs_cbor)
|
||||||
|
|
||||||
|
tx_aux_cbor = [inputs_cbor, outputs_cbor, self.attributes]
|
||||||
|
tx_hash = hashlib.blake2b(data=cbor.encode(tx_aux_cbor), outlen=32).digest()
|
||||||
|
|
||||||
|
witnesses = self._build_witnesses(tx_hash)
|
||||||
|
tx_body = cbor.encode([tx_aux_cbor, witnesses])
|
||||||
|
|
||||||
|
self.fee = self.compute_fee(
|
||||||
|
self.input_coins, self.outgoing_coins, self.change_coins
|
||||||
|
)
|
||||||
|
|
||||||
|
return tx_body, tx_hash
|
54
src/apps/cardano/ui/__init__.py
Normal file
54
src/apps/cardano/ui/__init__.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
from micropython import const
|
||||||
|
|
||||||
|
from trezor import ui
|
||||||
|
from trezor.messages import ButtonRequestType, MessageType
|
||||||
|
from trezor.messages.ButtonRequest import ButtonRequest
|
||||||
|
from trezor.ui.confirm import CONFIRMED, ConfirmDialog
|
||||||
|
from trezor.ui.scroll import Scrollpage, animate_swipe, paginate
|
||||||
|
from trezor.ui.text import Text
|
||||||
|
from trezor.utils import chunks
|
||||||
|
|
||||||
|
|
||||||
|
async def show_swipable_with_confirmation(
|
||||||
|
ctx, content, title: str, icon=ui.ICON_RESET, icon_color=ui.ORANGE
|
||||||
|
):
|
||||||
|
first_page = const(0)
|
||||||
|
lines_per_page = const(4)
|
||||||
|
|
||||||
|
if isinstance(content, (list, tuple)):
|
||||||
|
lines = content
|
||||||
|
else:
|
||||||
|
lines = list(chunks(content, 17))
|
||||||
|
pages = list(chunks(lines, lines_per_page))
|
||||||
|
|
||||||
|
await ctx.call(ButtonRequest(code=ButtonRequestType.Other), MessageType.ButtonAck)
|
||||||
|
|
||||||
|
paginator = paginate(
|
||||||
|
show_text_page, len(pages), first_page, pages, title, icon, icon_color
|
||||||
|
)
|
||||||
|
return await ctx.wait(paginator) == CONFIRMED
|
||||||
|
|
||||||
|
|
||||||
|
@ui.layout
|
||||||
|
async def show_text_page(
|
||||||
|
page: int,
|
||||||
|
page_count: int,
|
||||||
|
pages: list,
|
||||||
|
title: str,
|
||||||
|
icon=ui.ICON_RESET,
|
||||||
|
icon_color=ui.ORANGE,
|
||||||
|
):
|
||||||
|
if page_count == 1:
|
||||||
|
page = 0
|
||||||
|
|
||||||
|
lines = pages[page]
|
||||||
|
content = Text(title, icon, icon_color=icon_color)
|
||||||
|
content.mono(*lines)
|
||||||
|
|
||||||
|
content = Scrollpage(content, page, page_count)
|
||||||
|
|
||||||
|
if page + 1 >= page_count:
|
||||||
|
return await ConfirmDialog(content)
|
||||||
|
else:
|
||||||
|
content.render()
|
||||||
|
await animate_swipe()
|
28
src/apps/cardano/ui/progress.py
Normal file
28
src/apps/cardano/ui/progress.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from trezor import ui
|
||||||
|
|
||||||
|
_progress = 0
|
||||||
|
_steps = 0
|
||||||
|
|
||||||
|
|
||||||
|
def init(total_steps, text):
|
||||||
|
global _progress, _steps
|
||||||
|
_progress = 0
|
||||||
|
_steps = total_steps
|
||||||
|
report_init(text)
|
||||||
|
report()
|
||||||
|
|
||||||
|
|
||||||
|
def advance():
|
||||||
|
global _progress
|
||||||
|
_progress += 1
|
||||||
|
report()
|
||||||
|
|
||||||
|
|
||||||
|
def report_init(text):
|
||||||
|
ui.display.clear()
|
||||||
|
ui.header(text)
|
||||||
|
|
||||||
|
|
||||||
|
def report():
|
||||||
|
p = int(1000 * _progress / _steps)
|
||||||
|
ui.display.loader(p, 18, ui.WHITE, ui.BG)
|
36
src/apps/cardano/verify_message.py
Normal file
36
src/apps/cardano/verify_message.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
from ubinascii import hexlify
|
||||||
|
|
||||||
|
from trezor import log, ui, wire
|
||||||
|
from trezor.crypto.curve import ed25519
|
||||||
|
from trezor.messages.Failure import Failure
|
||||||
|
from trezor.messages.Success import Success
|
||||||
|
|
||||||
|
from .ui import show_swipable_with_confirmation
|
||||||
|
|
||||||
|
|
||||||
|
async def cardano_verify_message(ctx, msg):
|
||||||
|
try:
|
||||||
|
res = _verify_message(msg.public_key, msg.signature, msg.message)
|
||||||
|
except ValueError as e:
|
||||||
|
if __debug__:
|
||||||
|
log.exception(__name__, e)
|
||||||
|
raise wire.ProcessError("Verifying failed")
|
||||||
|
|
||||||
|
if not res:
|
||||||
|
return Failure(message="Invalid signature")
|
||||||
|
|
||||||
|
if not await show_swipable_with_confirmation(
|
||||||
|
ctx, msg.message, "Verifying message", ui.ICON_RECEIVE, ui.GREEN
|
||||||
|
):
|
||||||
|
raise wire.ActionCancelled("Verifying cancelled")
|
||||||
|
|
||||||
|
if not await show_swipable_with_confirmation(
|
||||||
|
ctx, hexlify(msg.public_key), "With public key", ui.ICON_RECEIVE, ui.GREEN
|
||||||
|
):
|
||||||
|
raise wire.ActionCancelled("Verifying cancelled")
|
||||||
|
|
||||||
|
return Success(message="Message verified")
|
||||||
|
|
||||||
|
|
||||||
|
def _verify_message(public_key: bytes, signature: bytes, message: str):
|
||||||
|
return ed25519.verify(public_key, signature, message)
|
@ -16,6 +16,8 @@ import apps.ethereum
|
|||||||
import apps.lisk
|
import apps.lisk
|
||||||
import apps.nem
|
import apps.nem
|
||||||
import apps.stellar
|
import apps.stellar
|
||||||
|
import apps.cardano
|
||||||
|
|
||||||
|
|
||||||
if __debug__:
|
if __debug__:
|
||||||
import apps.debug
|
import apps.debug
|
||||||
@ -30,6 +32,7 @@ apps.ethereum.boot()
|
|||||||
apps.lisk.boot()
|
apps.lisk.boot()
|
||||||
apps.nem.boot()
|
apps.nem.boot()
|
||||||
apps.stellar.boot()
|
apps.stellar.boot()
|
||||||
|
apps.cardano.boot()
|
||||||
if __debug__:
|
if __debug__:
|
||||||
apps.debug.boot()
|
apps.debug.boot()
|
||||||
else:
|
else:
|
||||||
|
175
tests/test_apps.cardano.address.py
Normal file
175
tests/test_apps.cardano.address.py
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
from common import *
|
||||||
|
from apps.common import seed
|
||||||
|
from trezor import wire
|
||||||
|
|
||||||
|
from apps.cardano.address import (
|
||||||
|
_derive_hd_passphrase,
|
||||||
|
_encrypt_derivation_path,
|
||||||
|
_get_address_root,
|
||||||
|
_address_hash,
|
||||||
|
validate_derivation_path,
|
||||||
|
derive_address_and_node
|
||||||
|
)
|
||||||
|
from trezor.crypto import bip32
|
||||||
|
|
||||||
|
|
||||||
|
class TestCardanoAddress(unittest.TestCase):
|
||||||
|
def test_hardened_address_derivation(self):
|
||||||
|
mnemonic = "plastic that delay conduct police ticket swim gospel intact harsh obtain entire"
|
||||||
|
node = bip32.from_mnemonic_cardano(mnemonic)
|
||||||
|
|
||||||
|
addresses = [
|
||||||
|
"DdzFFzCqrhtDB6YEgPQqFiVnhKsfyEMe9MLQabhayVUL2WRN1dbLLFS7VfKYBy8n3uemZRcDyqMnv7STCU9vj2eAR8CgFgKMDG2mkQN7",
|
||||||
|
"DdzFFzCqrhtCGRQ2UYpcouvRgDnPsAYpmzWVtd5YLvaRrMAMoDmYsKhNMAWePbK7a1XbZ8ghTeyaSLZ2488extnB5F9SwHus4UFaFwkS",
|
||||||
|
"DdzFFzCqrhsqHyZLVLeFrgcxUrPA5YMJJRJCxkESHcPkV1EuuDKhKkJNPkEyrWXhPbuMHxSnz1cNYUCN8tJsLwaFiSxMz3ab19GEvaNP",
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, expected in enumerate(addresses):
|
||||||
|
# 44'/1815'/0'/0/i'
|
||||||
|
address, _ = derive_address_and_node(node, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0x80000000 + i])
|
||||||
|
self.assertEqual(expected, address)
|
||||||
|
|
||||||
|
nodes = [
|
||||||
|
(
|
||||||
|
"d4dd69a2f2a6374f3733f53e03f610d73dd4f1d5131169bc144e6d34c9bcbe04",
|
||||||
|
"21d97a697583630e2cef01e5fc1555ea4fd9625ff8fcde1fc72e67aa42f975ec",
|
||||||
|
"2df46e04ebf0816e242bfaa1c73e5ebe8863d05d7a96c8aac16f059975e63f30",
|
||||||
|
"057658de1308930ad4a5663e4f77477014b04954a9d488e62d73b04fc659a35c"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"3476630290051477e4cc206fd5f6587065d3c9558c9891cc1c0ed5a408d5b60c",
|
||||||
|
"3f1d4beaefd2ffff59a45cb75519960d02f4de62c076a165bc39a7d7b1fec168",
|
||||||
|
"35b0cc0b770e04d86a9cddb0e2068b3a242f6b6e93c9a9d3c4f0899bd62b4266",
|
||||||
|
"35bb811c631b3db3b10559bc15821a39969654ebcad80cedf544ac8bf2a73ce7"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"06a6f53baf84ac6713cd1c441081dff00d1c4abee33091dc5c5ebdec2044270c",
|
||||||
|
"4978871e479a3a58adabb030565162832c63a2909442d306c96eaf03823ff5c9",
|
||||||
|
"9f26aad725aef1bb0609085f2c961b4d2579bceccfb1b01f3c7d1dbdd02b50b1",
|
||||||
|
"70f72ce51d0c984c4bbddd0297f4ffe0b4710c2c3f9a7e17f7d7e3e1810b5c33"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, (priv, ext, pub, chain) in enumerate(nodes):
|
||||||
|
_, n = derive_address_and_node(node, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0x80000000 + i])
|
||||||
|
self.assertEqual(unhexlify(priv), n.private_key())
|
||||||
|
self.assertEqual(unhexlify(ext), n.private_key_ext())
|
||||||
|
self.assertEqual(unhexlify(pub), seed.remove_ed25519_prefix(n.public_key()))
|
||||||
|
self.assertEqual(unhexlify(chain), n.chain_code())
|
||||||
|
|
||||||
|
def test_non_hardened_address_derivation(self):
|
||||||
|
mnemonic = "plastic that delay conduct police ticket swim gospel intact harsh obtain entire"
|
||||||
|
node = bip32.from_mnemonic_cardano(mnemonic)
|
||||||
|
|
||||||
|
addresses = [
|
||||||
|
"2w1sdSJu3GVezU6nw8LodErz7kSrEQ9hKQhsGLWk4JxTCxg7tkJvSowGKLFE7PMxknbkuYjtaWbpnJLhJgwmwNA98GPX2SGSN1t",
|
||||||
|
"2w1sdSJu3GVg7mRbtq2aGUFKxXnpFoP9hesA1n7KJrnQ9QEgyy7DGbLU52L2cytPqCoNNhkvRCF9ZsBLwMv1E35CVh6XBiWj2GE",
|
||||||
|
"2w1sdSJu3GVg193D2yhiiH947J9UwrbPAmNao6ciAZi3GeU7sG1D3fTAnQakzHSe1FVyuRdUjcx52Q7575LxBBNE8aCunKFA4kA",
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, expected in enumerate(addresses):
|
||||||
|
# 44'/1815'/0'/0/i
|
||||||
|
address, _ = derive_address_and_node(node, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i])
|
||||||
|
self.assertEqual(expected, address)
|
||||||
|
|
||||||
|
nodes = [
|
||||||
|
(
|
||||||
|
"a75a851505db79ee8557a8cb3ef561ab7d6bd24d7cc0e97b8496654431fc2e0c",
|
||||||
|
"21fa8154e009a46a1c44709fe23b75735c8abc6256c44cc3c208c1c914f037ce",
|
||||||
|
"723fdc0eb1300fe7f2b9b6989216a831835a88695ba2c2d5c50c8470b7d1b239",
|
||||||
|
"ae09010e921de259b02f34ce7fd76f9c09ad224d436fe8fa38aa212177937ffe"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"48ded246510a563f759fde920016ad1356238ab5936869e45ccec5b4d8fcce0c",
|
||||||
|
"0216c5c777bfe196576b776bd9faf2ac1318966c820edb203754166d5a0f4d92",
|
||||||
|
"6dc82a0d40257cfc1ea5d728c6ccfa52ad5673c2dc4cfed239dff642d29fbc46",
|
||||||
|
"cd490ae08bd2ff18e8b61c39173f6bf0db85709130baa103b9f00e4160ec150f"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"8e651d540f55a4670bb5ec8cd0812731ce734a1e745059c4f445fd8cd8fcb604",
|
||||||
|
"ab7f8d9e7927a1a71b7b08eb3b871246dc4717d9e309b7682df0eee202a5a97a",
|
||||||
|
"e55323d6881ca92a0816695def558145ef22f0d0c4f6133aab7a8a3f2f98ef78",
|
||||||
|
"6c9313fcf93b55a977184514aefa1c778c1abadb2ba9f2c1351b587b7c1e1572"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, (priv, ext, pub, chain) in enumerate(nodes):
|
||||||
|
_, n = derive_address_and_node(node, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i])
|
||||||
|
self.assertEqual(unhexlify(priv), n.private_key())
|
||||||
|
self.assertEqual(unhexlify(ext), n.private_key_ext())
|
||||||
|
self.assertEqual(unhexlify(pub), seed.remove_ed25519_prefix(n.public_key()))
|
||||||
|
self.assertEqual(unhexlify(chain), n.chain_code())
|
||||||
|
|
||||||
|
|
||||||
|
def test_root_address_derivation(self):
|
||||||
|
mnemonic = "plastic that delay conduct police ticket swim gospel intact harsh obtain entire"
|
||||||
|
node = bip32.from_mnemonic_cardano(mnemonic)
|
||||||
|
|
||||||
|
# 44'/1815'
|
||||||
|
address, _ = derive_address_and_node(node, [0x80000000 | 44, 0x80000000 | 1815])
|
||||||
|
self.assertEqual("Ae2tdPwUPEYygPo2ZNZ7Ve6ZExaFZvkGcQFZ5oSyqVNoJn5J65Foyz2XiSU", address)
|
||||||
|
|
||||||
|
priv, ext, pub, chain = (
|
||||||
|
"90bc16ad766aebce31b407f111db3ba95de2780c5bb760f3333dac1b3823ee53",
|
||||||
|
"10f20917dcfa2b3c295386413ae3564365e4a51f063da644d0945f4d3da57699",
|
||||||
|
"7d1de3f22f53904d007ff833fadd7cd6482ea1e83918b985b4ea33e63c16d183",
|
||||||
|
"7a04a6aab0ed12af562a26db4d10344454274d0bfa6e3581df1dc02f13c5fbe5"
|
||||||
|
)
|
||||||
|
|
||||||
|
_, n = derive_address_and_node(node, [0x80000000 | 44, 0x80000000 | 1815])
|
||||||
|
self.assertEqual(unhexlify(priv), n.private_key())
|
||||||
|
self.assertEqual(unhexlify(ext), n.private_key_ext())
|
||||||
|
self.assertEqual(unhexlify(pub), seed.remove_ed25519_prefix(n.public_key()))
|
||||||
|
self.assertEqual(unhexlify(chain), n.chain_code())
|
||||||
|
|
||||||
|
def test_validate_derivation_path(self):
|
||||||
|
incorrect_derivation_paths = [
|
||||||
|
[0x80000000 | 44],
|
||||||
|
[0x80000000 | 44, 0x80000000 | 1815, 0x80000000 | 1815, 0x80000000 | 1815, 0x80000000 | 1815, 0x80000000 | 1815],
|
||||||
|
[0x80000000 | 43, 0x80000000 | 1815, 0x80000000 | 1815, 0x80000000 | 1815, 0x80000000 | 1815],
|
||||||
|
[0x80000000 | 44, 0x80000000 | 1816, 0x80000000 | 1815, 0x80000000 | 1815, 0x80000000 | 1815],
|
||||||
|
]
|
||||||
|
|
||||||
|
correct_derivation_paths = [
|
||||||
|
[0x80000000 | 44, 0x80000000 | 1815, 0x80000000 | 1815, 0x80000000 | 1815, 0x80000000 | 1815],
|
||||||
|
[0x80000000 | 44, 0x80000000 | 1815],
|
||||||
|
[0x80000000 | 44, 0x80000000 | 1815, 0x80000000],
|
||||||
|
[0x80000000 | 44, 0x80000000 | 1815, 0],
|
||||||
|
[0x80000000 | 44, 0x80000000 | 1815, 0, 0],
|
||||||
|
]
|
||||||
|
|
||||||
|
for derivation_path in incorrect_derivation_paths:
|
||||||
|
self.assertRaises(wire.ProcessError, validate_derivation_path, derivation_path)
|
||||||
|
|
||||||
|
for derivation_path in correct_derivation_paths:
|
||||||
|
self.assertEqual(derivation_path, validate_derivation_path(derivation_path))
|
||||||
|
|
||||||
|
def test_derive_hd_passphrase(self):
|
||||||
|
mnemonic = "plastic that delay conduct police ticket swim gospel intact harsh obtain entire"
|
||||||
|
root_node = bip32.from_mnemonic_cardano(mnemonic)
|
||||||
|
|
||||||
|
self.assertEqual(hexlify(_derive_hd_passphrase(root_node)).decode('utf8'), "8ee689a22e1ec569d2ada515c4ee712ad089901b7fe0afb94fe196de944ee814")
|
||||||
|
|
||||||
|
def test_encrypt_derivation_path(self):
|
||||||
|
encrypted_path = _encrypt_derivation_path([0x80000000, 0x80000000], unhexlify("8ee689a22e1ec569d2ada515c4ee712ad089901b7fe0afb94fe196de944ee814"))
|
||||||
|
self.assertEqual(hexlify(encrypted_path).decode('utf8'), "722c7a75813fafde9ff9e6d4dec19adfd57f0d20194fa4c703770020")
|
||||||
|
|
||||||
|
encrypted_path = _encrypt_derivation_path([0x80000000, 0], unhexlify("8ee689a22e1ec569d2ada515c4ee712ad089901b7fe0afb94fe196de944ee814"))
|
||||||
|
self.assertEqual(hexlify(encrypted_path).decode('utf8'), "722c7a75813fb5a13d916748b3fb0561c5c7b59f9bc644ea")
|
||||||
|
|
||||||
|
def test_get_address_root(self):
|
||||||
|
mnemonic = "plastic that delay conduct police ticket swim gospel intact harsh obtain entire"
|
||||||
|
root_node = bip32.from_mnemonic_cardano(mnemonic)
|
||||||
|
|
||||||
|
address_root = _get_address_root(root_node, {1: b'X\x1cr,zu\x81?\xaf\xde\x9f\xf9\xe4\xd4\x90\xadH$\xe9\xf3\x88\x16\xcb\xd2)\x02M\x0c#\xde'})
|
||||||
|
self.assertEqual(address_root, b'\xca\x9bbQ\xa5\xaa}\x01U\xba\xe5\xa5\xaa~\x84M\x0b;\x1dM\xd8z\xe7Y\x01\xc8\x92\x91')
|
||||||
|
|
||||||
|
def test_address_hash(self):
|
||||||
|
data = [0, [0, b"}\x1d\xe3\xf2/S\x90M\x00\x7f\xf83\xfa\xdd|\xd6H.\xa1\xe89\x18\xb9\x85\xb4\xea3\xe6<\x16\xd1\x83z\x04\xa6\xaa\xb0\xed\x12\xafV*&\xdbM\x104DT'M\x0b\xfan5\x81\xdf\x1d\xc0/\x13\xc5\xfb\xe5"], {}]
|
||||||
|
result = _address_hash(data)
|
||||||
|
|
||||||
|
self.assertEqual(result, b'\x1c\xca\xee\xc9\x80\xaf}\xb0\x9a\xa8\x96E\xd6\xa4\xd1\xb4\x13\x85\xb9\xc2q\x1d5/{\x12"\xca')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
56
tests/test_apps.cardano.cbor.py
Normal file
56
tests/test_apps.cardano.cbor.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from common import *
|
||||||
|
|
||||||
|
from apps.cardano.cbor import (
|
||||||
|
Tagged,
|
||||||
|
IndefiniteLengthArray,
|
||||||
|
encode
|
||||||
|
)
|
||||||
|
from ubinascii import unhexlify
|
||||||
|
|
||||||
|
class TestCardanoCbor(unittest.TestCase):
|
||||||
|
def test_cbor_encoding(self):
|
||||||
|
test_vectors = [
|
||||||
|
# integers
|
||||||
|
(0, '00'),
|
||||||
|
(1, '01'),
|
||||||
|
(10, '0a'),
|
||||||
|
(23, '17'),
|
||||||
|
(24, '1818'),
|
||||||
|
(25, '1819'),
|
||||||
|
(100, '1864'),
|
||||||
|
(1000, '1903e8'),
|
||||||
|
(1000000, '1a000f4240'),
|
||||||
|
(1000000000000, '1b000000e8d4a51000'),
|
||||||
|
|
||||||
|
# binary strings
|
||||||
|
(b'', '40'),
|
||||||
|
(unhexlify('01020304'), '4401020304'),
|
||||||
|
|
||||||
|
# tags
|
||||||
|
(Tagged(1, 1363896240), 'c11a514b67b0'),
|
||||||
|
(Tagged(23, unhexlify('01020304')), 'd74401020304'),
|
||||||
|
|
||||||
|
# arrays
|
||||||
|
([], '80'),
|
||||||
|
([1, 2, 3], '83010203'),
|
||||||
|
([1, [2, 3], [4, 5]], '8301820203820405'),
|
||||||
|
(list(range(1, 26)), '98190102030405060708090a0b0c0d0e0f101112131415161718181819'),
|
||||||
|
|
||||||
|
# maps
|
||||||
|
({}, 'a0'),
|
||||||
|
|
||||||
|
# Note: normal python dict doesn't have a fixed item ordering
|
||||||
|
({1: 2, 3: 4}, 'a203040102'),
|
||||||
|
|
||||||
|
# indefinite
|
||||||
|
(IndefiniteLengthArray([]), '9fff'),
|
||||||
|
(IndefiniteLengthArray([1, [2, 3], [4, 5]]), '9f01820203820405ff'),
|
||||||
|
(IndefiniteLengthArray([1, [2, 3], IndefiniteLengthArray([4, 5])]),
|
||||||
|
'9f018202039f0405ffff'),
|
||||||
|
]
|
||||||
|
for val, expected in test_vectors:
|
||||||
|
encoded = encode(val)
|
||||||
|
self.assertEqual(unhexlify(expected), encoded)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
53
tests/test_apps.cardano.get_public_key.py
Normal file
53
tests/test_apps.cardano.get_public_key.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
from common import *
|
||||||
|
|
||||||
|
from apps.cardano.get_public_key import _get_public_key
|
||||||
|
from trezor.crypto import bip32
|
||||||
|
from ubinascii import hexlify
|
||||||
|
|
||||||
|
|
||||||
|
class TestCardanoGetPublicKey(unittest.TestCase):
|
||||||
|
def test_get_public_key(self):
|
||||||
|
mnemonic = "plastic that delay conduct police ticket swim gospel intact harsh obtain entire"
|
||||||
|
node = bip32.from_mnemonic_cardano(mnemonic)
|
||||||
|
|
||||||
|
derivation_paths = [
|
||||||
|
[0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0x80000000],
|
||||||
|
[0x80000000 | 44, 0x80000000 | 1815],
|
||||||
|
[0x80000000 | 44, 0x80000000 | 1815, 0, 0, 0],
|
||||||
|
[0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0],
|
||||||
|
]
|
||||||
|
|
||||||
|
root_hd_passphrase = '8ee689a22e1ec569d2ada515c4ee712ad089901b7fe0afb94fe196de944ee814'
|
||||||
|
|
||||||
|
public_keys = [
|
||||||
|
'2df46e04ebf0816e242bfaa1c73e5ebe8863d05d7a96c8aac16f059975e63f30',
|
||||||
|
'7d1de3f22f53904d007ff833fadd7cd6482ea1e83918b985b4ea33e63c16d183',
|
||||||
|
'f59a28d704df090d8fc641248bdb27d0d001da13ddb332a79cfba8a9fa7233e7',
|
||||||
|
'723fdc0eb1300fe7f2b9b6989216a831835a88695ba2c2d5c50c8470b7d1b239',
|
||||||
|
]
|
||||||
|
|
||||||
|
chain_codes = [
|
||||||
|
'057658de1308930ad4a5663e4f77477014b04954a9d488e62d73b04fc659a35c',
|
||||||
|
'7a04a6aab0ed12af562a26db4d10344454274d0bfa6e3581df1dc02f13c5fbe5',
|
||||||
|
'7f01fc65468ed420e135535261b03845d97b9098f8f08245197c9526d80994f6',
|
||||||
|
'ae09010e921de259b02f34ce7fd76f9c09ad224d436fe8fa38aa212177937ffe',
|
||||||
|
]
|
||||||
|
|
||||||
|
xpub_keys = [
|
||||||
|
'2df46e04ebf0816e242bfaa1c73e5ebe8863d05d7a96c8aac16f059975e63f30057658de1308930ad4a5663e4f77477014b04954a9d488e62d73b04fc659a35c',
|
||||||
|
'7d1de3f22f53904d007ff833fadd7cd6482ea1e83918b985b4ea33e63c16d1837a04a6aab0ed12af562a26db4d10344454274d0bfa6e3581df1dc02f13c5fbe5',
|
||||||
|
'f59a28d704df090d8fc641248bdb27d0d001da13ddb332a79cfba8a9fa7233e77f01fc65468ed420e135535261b03845d97b9098f8f08245197c9526d80994f6',
|
||||||
|
'723fdc0eb1300fe7f2b9b6989216a831835a88695ba2c2d5c50c8470b7d1b239ae09010e921de259b02f34ce7fd76f9c09ad224d436fe8fa38aa212177937ffe',
|
||||||
|
]
|
||||||
|
|
||||||
|
for index, derivation_path in enumerate(derivation_paths):
|
||||||
|
key = _get_public_key(node, derivation_path)
|
||||||
|
|
||||||
|
self.assertEqual(hexlify(key.node.public_key).decode('utf8'), public_keys[index])
|
||||||
|
self.assertEqual(hexlify(key.node.chain_code).decode('utf8'), chain_codes[index])
|
||||||
|
self.assertEqual(key.xpub, xpub_keys[index])
|
||||||
|
self.assertEqual(key.root_hd_passphrase, root_hd_passphrase)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
25
tests/test_apps.cardano.sign_message.py
Normal file
25
tests/test_apps.cardano.sign_message.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from common import *
|
||||||
|
|
||||||
|
from apps.cardano.sign_message import _sign_message
|
||||||
|
from trezor.crypto import bip32
|
||||||
|
|
||||||
|
|
||||||
|
class TestCardanoSignMessage(unittest.TestCase):
|
||||||
|
def test_sign_message(self):
|
||||||
|
mnemonic = "plastic that delay conduct police ticket swim gospel intact harsh obtain entire"
|
||||||
|
node = bip32.from_mnemonic_cardano(mnemonic)
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
('Test message to sign', [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0x80000000], '07f226da2a59c3083e80f01ef7e0ec46fc726ebe6bd15d5e9040031c342d8651bee9aee875019c41a7719674fd417ad43990988ffd371527604b6964df75960d'),
|
||||||
|
('New Test message to sign', [0x80000000 | 44, 0x80000000 | 1815], '8fd3b9d8a4c30326b720de76f8de2bbf57b29b7593576eac4a3017ea23046812017136520dc2f24e9fb4da56bd87c77ea49265686653b36859b5e1e56ba9eb0f'),
|
||||||
|
('Another Test message to sign', [0x80000000 | 44, 0x80000000 | 1815, 0, 0, 0], '89d63bd32c2eb92aa418b9ce0383a7cf489bc56284876c19246b70be72070d83d361fcb136e8e257b7e66029ef4a566405cda0143d251f851debd62c3c38c302'),
|
||||||
|
('Just another Test message to sign', [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0], '49d948090d30e35a88a26d8fb07aca5d68936feba2d5bd49e0d0f7c027a0c8c2955b93a7c930a3b36d23c2502c18bf39cf9b17bbba1a0965090acfb4d10a9305'),
|
||||||
|
]
|
||||||
|
|
||||||
|
for (message, derivation_path, expected_signature) in messages:
|
||||||
|
signature = _sign_message(node, message, derivation_path)
|
||||||
|
self.assertEqual(expected_signature, hexlify(signature.signature).decode('utf8'))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
22
tests/test_apps.cardano.verify_message.py
Normal file
22
tests/test_apps.cardano.verify_message.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from common import *
|
||||||
|
from ubinascii import unhexlify
|
||||||
|
|
||||||
|
from apps.cardano.verify_message import _verify_message
|
||||||
|
|
||||||
|
|
||||||
|
class TestCardanoVerifyMessage(unittest.TestCase):
|
||||||
|
def test_verify_message(self):
|
||||||
|
messages = [
|
||||||
|
('Test message to sign', '2df46e04ebf0816e242bfaa1c73e5ebe8863d05d7a96c8aac16f059975e63f30', '07f226da2a59c3083e80f01ef7e0ec46fc726ebe6bd15d5e9040031c342d8651bee9aee875019c41a7719674fd417ad43990988ffd371527604b6964df75960d'),
|
||||||
|
('New Test message to sign', '7d1de3f22f53904d007ff833fadd7cd6482ea1e83918b985b4ea33e63c16d183', '8fd3b9d8a4c30326b720de76f8de2bbf57b29b7593576eac4a3017ea23046812017136520dc2f24e9fb4da56bd87c77ea49265686653b36859b5e1e56ba9eb0f'),
|
||||||
|
('Another Test message to sign', 'f59a28d704df090d8fc641248bdb27d0d001da13ddb332a79cfba8a9fa7233e7', '89d63bd32c2eb92aa418b9ce0383a7cf489bc56284876c19246b70be72070d83d361fcb136e8e257b7e66029ef4a566405cda0143d251f851debd62c3c38c302'),
|
||||||
|
('Just another Test message to sign', '723fdc0eb1300fe7f2b9b6989216a831835a88695ba2c2d5c50c8470b7d1b239', '49d948090d30e35a88a26d8fb07aca5d68936feba2d5bd49e0d0f7c027a0c8c2955b93a7c930a3b36d23c2502c18bf39cf9b17bbba1a0965090acfb4d10a9305'),
|
||||||
|
]
|
||||||
|
|
||||||
|
for (message, public_key, signature) in messages:
|
||||||
|
result = _verify_message(unhexlify(public_key), unhexlify(signature), message)
|
||||||
|
self.assertEqual(result, True)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
@ -18,7 +18,7 @@ class TestCryptoBlake2b(unittest.TestCase):
|
|||||||
def test_digest(self):
|
def test_digest(self):
|
||||||
key = unhexlify('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f')
|
key = unhexlify('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f')
|
||||||
for d, h in self.vectors:
|
for d, h in self.vectors:
|
||||||
self.assertEqual(hashlib.blake2b(unhexlify(d), key=key).digest(), unhexlify(h))
|
self.assertEqual(hashlib.blake2b(data=unhexlify(d), key=key).digest(), unhexlify(h))
|
||||||
|
|
||||||
def test_update(self):
|
def test_update(self):
|
||||||
key = unhexlify('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f')
|
key = unhexlify('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f')
|
||||||
|
2
vendor/trezor-crypto
vendored
2
vendor/trezor-crypto
vendored
@ -1 +1 @@
|
|||||||
Subproject commit f586155d808be7467e31da907b0106b4c31a0d1d
|
Subproject commit ff001a0f12565a0d7d51ad3ce5e11e98db6afc25
|
Loading…
Reference in New Issue
Block a user