cardano: add Cardano currency support

pull/25/head
jmuravsky 6 years ago committed by Jan Pochyla
parent 8cd8be9bd1
commit 09ddcc7ac9

@ -27,6 +27,7 @@ CPPDEFINES_MOD += [
'RAND_PLATFORM_INDEPENDENT',
('USE_KECCAK', '1'),
('USE_ETHEREUM', '1'),
('USE_CARDANO', '1'),
('USE_NEM', '1'),
]
SOURCE_MOD += [

@ -25,6 +25,7 @@ CPPDEFINES_MOD += [
'AES_192',
('USE_KECCAK', '1'),
('USE_ETHEREUM', '1'),
('USE_CARDANO', '1'),
('USE_NEM', '1'),
]
SOURCE_MOD += [

@ -22,6 +22,7 @@
#include "embed/extmod/trezorobj.h"
#include "bip32.h"
#include "bip39.h"
#include "curves.h"
#include "memzero.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);
/// 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:
/// '''
/// 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);
/// 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:
/// '''
/// 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[] = {
{ 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_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) },
@ -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_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_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_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) },
@ -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);
/// 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[] = {
{ 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_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_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);

@ -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);
/// 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:
/// '''
/// 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_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_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_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) },

@ -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)

@ -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

@ -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

@ -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)

@ -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
)

@ -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

@ -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

@ -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()

@ -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)

@ -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.nem
import apps.stellar
import apps.cardano
if __debug__:
import apps.debug
@ -30,6 +32,7 @@ apps.ethereum.boot()
apps.lisk.boot()
apps.nem.boot()
apps.stellar.boot()
apps.cardano.boot()
if __debug__:
apps.debug.boot()
else:

@ -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()

@ -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()

@ -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()

@ -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()

@ -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):
key = unhexlify('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f')
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):
key = unhexlify('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f')

@ -1 +1 @@
Subproject commit f586155d808be7467e31da907b0106b4c31a0d1d
Subproject commit ff001a0f12565a0d7d51ad3ce5e11e98db6afc25
Loading…
Cancel
Save