mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-17 21:22:10 +00:00
cardano: Implement SLIP-0023 and add SLIP-0039 support for Carda… (#359)
cardano: Implement SLIP-0023 and add SLIP-0039 support for Cardano.
This commit is contained in:
commit
6f66ad9591
@ -601,10 +601,19 @@ STATIC mp_obj_t mod_trezorcrypto_bip32_from_seed(mp_obj_t seed,
|
||||
if (curveb.len == 0) {
|
||||
mp_raise_ValueError("Invalid curve name");
|
||||
}
|
||||
|
||||
HDNode hdnode;
|
||||
if (!hdnode_from_seed(seedb.buf, seedb.len, curveb.buf, &hdnode)) {
|
||||
int res = 0;
|
||||
if (strcmp(curveb.buf, ED25519_CARDANO_NAME) != 0) {
|
||||
res = hdnode_from_seed(seedb.buf, seedb.len, curveb.buf, &hdnode);
|
||||
} else {
|
||||
res = hdnode_from_seed_cardano(seedb.buf, seedb.len, &hdnode);
|
||||
}
|
||||
|
||||
if (!res) {
|
||||
mp_raise_ValueError("Failed to derive the root node");
|
||||
}
|
||||
|
||||
mp_obj_HDNode_t *o = m_new_obj(mp_obj_HDNode_t);
|
||||
o->base.type = &mod_trezorcrypto_HDNode_type;
|
||||
o->hdnode = hdnode;
|
||||
@ -616,7 +625,8 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorcrypto_bip32_from_seed_obj,
|
||||
|
||||
/// def from_mnemonic_cardano(mnemonic: str, passphrase: str) -> bytes:
|
||||
/// """
|
||||
/// Convert mnemonic to hdnode
|
||||
/// Construct a HD node from a BIP-0039 mnemonic using the Icarus derivation
|
||||
/// scheme, aka v2 derivation scheme.
|
||||
/// """
|
||||
STATIC mp_obj_t mod_trezorcrypto_bip32_from_mnemonic_cardano(
|
||||
mp_obj_t mnemonic, mp_obj_t passphrase) {
|
||||
@ -634,9 +644,9 @@ STATIC mp_obj_t mod_trezorcrypto_bip32_from_mnemonic_cardano(
|
||||
mp_raise_ValueError("Invalid mnemonic");
|
||||
}
|
||||
|
||||
const int res =
|
||||
hdnode_from_seed_cardano((const uint8_t *)ppassphrase, phrase.len,
|
||||
entropy, entropy_len / 8, &hdnode);
|
||||
const int res = hdnode_from_entropy_cardano_icarus(
|
||||
(const uint8_t *)ppassphrase, phrase.len, entropy, entropy_len / 8,
|
||||
&hdnode);
|
||||
|
||||
if (!res) {
|
||||
mp_raise_ValueError(
|
||||
|
@ -133,5 +133,6 @@ def from_seed(seed: bytes, curve_name: str) -> HDNode:
|
||||
# extmod/modtrezorcrypto/modtrezorcrypto-bip32.h
|
||||
def from_mnemonic_cardano(mnemonic: str, passphrase: str) -> bytes:
|
||||
"""
|
||||
Convert mnemonic to hdnode
|
||||
Construct a HD node from a BIP-0039 mnemonic using the Icarus derivation
|
||||
scheme, aka v2 derivation scheme.
|
||||
"""
|
||||
|
@ -28,22 +28,30 @@ class Keychain:
|
||||
return node
|
||||
|
||||
|
||||
async def get_keychain(ctx: wire.Context) -> Keychain:
|
||||
if not storage.is_initialized():
|
||||
raise wire.ProcessError("Device is not initialized")
|
||||
|
||||
# derive the root node from mnemonic and passphrase
|
||||
async def _get_passphrase(ctx: wire.Context) -> bytes:
|
||||
passphrase = cache.get_passphrase()
|
||||
if passphrase is None:
|
||||
passphrase = await protect_by_passphrase(ctx)
|
||||
cache.set_passphrase(passphrase)
|
||||
# TODO fix for SLIP-39!
|
||||
mnemonic_secret, mnemonic_module = mnemonic.get()
|
||||
if mnemonic_module == mnemonic.slip39:
|
||||
# TODO: we need to modify bip32.from_mnemonic_cardano to accept entropy directly
|
||||
raise NotImplementedError("SLIP-39 currently does not support Cardano")
|
||||
|
||||
return passphrase
|
||||
|
||||
|
||||
async def get_keychain(ctx: wire.Context) -> Keychain:
|
||||
if not storage.is_initialized():
|
||||
raise wire.ProcessError("Device is not initialized")
|
||||
|
||||
if mnemonic.get_type() == mnemonic.TYPE_SLIP39:
|
||||
seed = cache.get_seed()
|
||||
if seed is None:
|
||||
passphrase = await _get_passphrase(ctx)
|
||||
seed = mnemonic.get_seed(passphrase)
|
||||
cache.set_seed(seed)
|
||||
root = bip32.from_seed(seed, "ed25519 cardano seed")
|
||||
else:
|
||||
root = bip32.from_mnemonic_cardano(mnemonic_secret.decode(), passphrase)
|
||||
# derive the root node from mnemonic and passphrase
|
||||
passphrase = await _get_passphrase(ctx)
|
||||
root = bip32.from_mnemonic_cardano(mnemonic.get_secret().decode(), passphrase)
|
||||
|
||||
# derive the namespaced root node
|
||||
for i in SEED_NAMESPACE:
|
||||
|
@ -6,7 +6,7 @@ from trezor.crypto import bip39, slip39
|
||||
from apps.common import storage
|
||||
|
||||
if False:
|
||||
from typing import Tuple
|
||||
from typing import Optional, Tuple
|
||||
|
||||
TYPE_BIP39 = const(0)
|
||||
TYPE_SLIP39 = const(1)
|
||||
@ -20,16 +20,25 @@ TYPES_WORD_COUNT = {
|
||||
}
|
||||
|
||||
|
||||
def get() -> Tuple[bytes, int]:
|
||||
mnemonic_secret = storage.device.get_mnemonic_secret()
|
||||
def get() -> Tuple[Optional[bytes], int]:
|
||||
return get_secret(), get_type()
|
||||
|
||||
|
||||
def get_secret() -> Optional[bytes]:
|
||||
return storage.device.get_mnemonic_secret()
|
||||
|
||||
|
||||
def get_type() -> int:
|
||||
mnemonic_type = storage.device.get_mnemonic_type() or TYPE_BIP39
|
||||
if mnemonic_type not in (TYPE_BIP39, TYPE_SLIP39):
|
||||
raise RuntimeError("Invalid mnemonic type")
|
||||
return mnemonic_secret, mnemonic_type
|
||||
return mnemonic_type
|
||||
|
||||
|
||||
def get_seed(passphrase: str = "", progress_bar: bool = True) -> bytes:
|
||||
mnemonic_secret, mnemonic_type = get()
|
||||
if mnemonic_secret is None:
|
||||
raise ValueError("Mnemonic not set")
|
||||
|
||||
render_func = None
|
||||
if progress_bar:
|
||||
|
@ -9,7 +9,7 @@ from apps.cardano.address import (
|
||||
derive_address_and_node
|
||||
)
|
||||
from apps.cardano.seed import Keychain
|
||||
from trezor.crypto import bip32
|
||||
from trezor.crypto import bip32, slip39
|
||||
|
||||
|
||||
class TestCardanoAddress(unittest.TestCase):
|
||||
@ -176,5 +176,78 @@ class TestCardanoAddress(unittest.TestCase):
|
||||
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'\xb3\xbbS\xa8;uN:E=\xe8\xe5\x9c\x18\xbcn\xcf\xd0c\xba\x0e\xba\xaelL}\xba\xbb')
|
||||
|
||||
def test_slip39_128(self):
|
||||
mnemonics = ["extra extend academic bishop cricket bundle tofu goat apart victim enlarge program behavior permit course armed jerky faint language modern",
|
||||
"extra extend academic acne away best indicate impact square oasis prospect painting voting guest either argue username racism enemy eclipse",
|
||||
"extra extend academic arcade born dive legal hush gross briefing talent drug much home firefly toxic analysis idea umbrella slice"]
|
||||
passphrase = b"TREZOR"
|
||||
identifier, exponent, ems = slip39.combine_mnemonics(mnemonics)
|
||||
master_secret = slip39.decrypt(identifier, exponent, ems, passphrase)
|
||||
|
||||
node = bip32.from_seed(master_secret, "ed25519 cardano seed")
|
||||
|
||||
# Check root node.
|
||||
root_priv = b"c0fe4a6973df4de06262693fc9186f71faf292960350882d49456bf108d13954"
|
||||
root_pub = b"83e3ecaf57f90f022c45e10d1b8cb78499c30819515ad9a81ad82139fdb12a90"
|
||||
root_ext = b"4064253ffefc4127489bce1b825a47329010c5afb4d21154ef949ef786204405"
|
||||
root_chain = b"22c12755afdd192742613b3062069390743ea232bc1b366c8f41e37292af9305"
|
||||
|
||||
self.assertEqual(hexlify(node.private_key()), root_priv)
|
||||
self.assertEqual(hexlify(node.private_key_ext()), root_ext)
|
||||
self.assertEqual(hexlify(seed.remove_ed25519_prefix(node.public_key())), root_pub)
|
||||
self.assertEqual(hexlify(node.chain_code()), root_chain)
|
||||
|
||||
# Check derived nodes and addresses.
|
||||
node.derive_cardano(0x80000000 | 44)
|
||||
node.derive_cardano(0x80000000 | 1815)
|
||||
keychain = Keychain([0x80000000 | 44, 0x80000000 | 1815], node)
|
||||
|
||||
addresses = [
|
||||
"Ae2tdPwUPEYxF9NAMNdd3v2LZoMeWp7gCZiDb6bZzFQeeVASzoP7HC4V9s6",
|
||||
"Ae2tdPwUPEZ1TjYcvfkWAbiHtGVxv4byEHHZoSyQXjPJ362DifCe1ykgqgy",
|
||||
"Ae2tdPwUPEZGXmSbda1kBNfyhRQGRcQxJFdk7mhWZXAGnapyejv2b2U3aRb"
|
||||
]
|
||||
|
||||
for i, expected in enumerate(addresses):
|
||||
# 44'/1815'/0'/0/i
|
||||
address, _ = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i])
|
||||
self.assertEqual(address, expected)
|
||||
|
||||
def test_slip39_256(self):
|
||||
mnemonics = ["hobo romp academic axis august founder knife legal recover alien expect emphasis loan kitchen involve teacher capture rebuild trial numb spider forward ladle lying voter typical security quantity hawk legs idle leaves gasoline",
|
||||
"hobo romp academic agency ancestor industry argue sister scene midst graduate profile numb paid headset airport daisy flame express scene usual welcome quick silent downtown oral critical step remove says rhythm venture aunt"]
|
||||
passphrase = b"TREZOR"
|
||||
identifier, exponent, ems = slip39.combine_mnemonics(mnemonics)
|
||||
master_secret = slip39.decrypt(identifier, exponent, ems, passphrase)
|
||||
|
||||
node = bip32.from_seed(master_secret, "ed25519 cardano seed")
|
||||
|
||||
# Check root node.
|
||||
root_priv = b"90633724b5daf770a8b420b8658e7d8bc21e066b60ec8cd4d5730681cc294e4f"
|
||||
root_pub = b"eea170f0ef97b59d22907cb429888029721ed67d3e7a1b56b81731086ab7db64"
|
||||
root_ext = b"f9d99bf3cd9c7e12663e8646afa40cb3aecf15d91f2abc15d21056c6bccb3414"
|
||||
root_chain = b"04f1de750b62725fcc1ae1b93ca4063acb53c486b959cadaa100ebd7828e5460"
|
||||
|
||||
self.assertEqual(hexlify(node.private_key()), root_priv)
|
||||
self.assertEqual(hexlify(node.private_key_ext()), root_ext)
|
||||
self.assertEqual(hexlify(seed.remove_ed25519_prefix(node.public_key())), root_pub)
|
||||
self.assertEqual(hexlify(node.chain_code()), root_chain)
|
||||
|
||||
# Check derived nodes and addresses.
|
||||
node.derive_cardano(0x80000000 | 44)
|
||||
node.derive_cardano(0x80000000 | 1815)
|
||||
keychain = Keychain([0x80000000 | 44, 0x80000000 | 1815], node)
|
||||
|
||||
addresses = [
|
||||
"Ae2tdPwUPEYyDD1C2FbVJFAE3FuAxLspfMYt29TJ1urnSKr57cVhEcioSCC",
|
||||
"Ae2tdPwUPEZHJGtyz47F6wD7qAegt1JNRJWuiE36QLvFzeqJPBZ2EBvhr8M",
|
||||
"Ae2tdPwUPEYxD9xNPBJTzYmtFVVWEPB6KW4TCDijQ4pDwU11wt5621PyCi4"
|
||||
]
|
||||
|
||||
for i, expected in enumerate(addresses):
|
||||
# 44'/1815'/0'/0/i
|
||||
address, _ = derive_address_and_node(keychain, [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, i])
|
||||
self.assertEqual(address, expected)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -50,6 +50,8 @@
|
||||
#endif
|
||||
#include "memzero.h"
|
||||
|
||||
#define CARDANO_MAX_NODE_DEPTH 1048576
|
||||
|
||||
const curve_info ed25519_info = {
|
||||
.bip32_name = "ed25519 seed",
|
||||
.params = NULL,
|
||||
@ -288,6 +290,10 @@ static void scalar_add_256bits(const uint8_t *src1, const uint8_t *src2,
|
||||
}
|
||||
|
||||
int hdnode_private_ckd_cardano(HDNode *inout, uint32_t index) {
|
||||
if (inout->depth >= CARDANO_MAX_NODE_DEPTH) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// checks for hardened/non-hardened derivation, keysize 32 means we are
|
||||
// dealing with public key and thus non-h, keysize 64 is for private key
|
||||
int keysize = 32;
|
||||
@ -356,31 +362,61 @@ int hdnode_private_ckd_cardano(HDNode *inout, uint32_t index) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int hdnode_from_seed_cardano(const uint8_t *pass, int pass_len,
|
||||
const uint8_t *seed, int seed_len, HDNode *out) {
|
||||
static CONFIDENTIAL uint8_t secret[96];
|
||||
pbkdf2_hmac_sha512(pass, pass_len, seed, seed_len, 4096, secret, 96);
|
||||
|
||||
secret[0] &= 248;
|
||||
secret[31] &= 31;
|
||||
secret[31] |= 64;
|
||||
|
||||
static int hdnode_from_secret_cardano(const uint8_t *k,
|
||||
const uint8_t *chain_code, HDNode *out) {
|
||||
memzero(out, sizeof(HDNode));
|
||||
out->depth = 0;
|
||||
out->child_num = 0;
|
||||
out->curve = get_curve_by_name(ED25519_CARDANO_NAME);
|
||||
out->curve = &ed25519_cardano_info;
|
||||
memcpy(out->private_key, k, 32);
|
||||
memcpy(out->private_key_extension, k + 32, 32);
|
||||
memcpy(out->chain_code, chain_code, 32);
|
||||
|
||||
memcpy(out->private_key, secret, 32);
|
||||
memcpy(out->private_key_extension, secret + 32, 32);
|
||||
out->private_key[0] &= 0xf8;
|
||||
out->private_key[31] &= 0x1f;
|
||||
out->private_key[31] |= 0x40;
|
||||
|
||||
out->public_key[0] = 0;
|
||||
hdnode_fill_public_key(out);
|
||||
|
||||
memcpy(out->chain_code, secret + 64, 32);
|
||||
memzero(secret, sizeof(secret));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Derives the root Cardano HDNode from a master secret, aka seed, as defined in
|
||||
// SLIP-0023.
|
||||
int hdnode_from_seed_cardano(const uint8_t *seed, int seed_len, HDNode *out) {
|
||||
static CONFIDENTIAL uint8_t I[SHA512_DIGEST_LENGTH];
|
||||
static CONFIDENTIAL uint8_t k[SHA512_DIGEST_LENGTH];
|
||||
static CONFIDENTIAL HMAC_SHA512_CTX ctx;
|
||||
|
||||
hmac_sha512_Init(&ctx, (const uint8_t *)ED25519_CARDANO_NAME,
|
||||
strlen(ED25519_CARDANO_NAME));
|
||||
hmac_sha512_Update(&ctx, seed, seed_len);
|
||||
hmac_sha512_Final(&ctx, I);
|
||||
|
||||
sha512_Raw(I, 32, k);
|
||||
|
||||
int ret = hdnode_from_secret_cardano(k, I + 32, out);
|
||||
|
||||
memzero(I, sizeof(I));
|
||||
memzero(k, sizeof(k));
|
||||
memzero(&ctx, sizeof(ctx));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Derives the root Cardano HDNode from a passphrase and the entropy encoded in
|
||||
// a BIP-0039 mnemonic using the Icarus derivation scheme, aka V2 derivation
|
||||
// scheme.
|
||||
int hdnode_from_entropy_cardano_icarus(const uint8_t *pass, int pass_len,
|
||||
const uint8_t *entropy, int entropy_len,
|
||||
HDNode *out) {
|
||||
static CONFIDENTIAL uint8_t secret[96];
|
||||
pbkdf2_hmac_sha512(pass, pass_len, entropy, entropy_len, 4096, secret, 96);
|
||||
|
||||
int ret = hdnode_from_secret_cardano(secret, secret + 64, out);
|
||||
memzero(secret, sizeof(secret));
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
int hdnode_public_ckd_cp(const ecdsa_curve *curve, const curve_point *parent,
|
||||
|
@ -71,8 +71,10 @@ int hdnode_private_ckd(HDNode *inout, uint32_t i);
|
||||
|
||||
#if USE_CARDANO
|
||||
int hdnode_private_ckd_cardano(HDNode *inout, uint32_t i);
|
||||
int hdnode_from_seed_cardano(const uint8_t *pass, int pass_len,
|
||||
const uint8_t *seed, int seed_len, HDNode *out);
|
||||
int hdnode_from_seed_cardano(const uint8_t *seed, int seed_len, HDNode *out);
|
||||
int hdnode_from_entropy_cardano_icarus(const uint8_t *pass, int pass_len,
|
||||
const uint8_t *seed, int seed_len,
|
||||
HDNode *out);
|
||||
#endif
|
||||
|
||||
int hdnode_public_ckd_cp(const ecdsa_curve *curve, const curve_point *parent,
|
||||
|
@ -118,7 +118,8 @@ START_TEST(test_bip32_cardano_hdnode_vector_1) {
|
||||
"junk",
|
||||
seed);
|
||||
ck_assert_int_eq(seed_len, 132);
|
||||
hdnode_from_seed_cardano((const uint8_t *)"", 0, seed, seed_len / 8, &node);
|
||||
hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, seed, seed_len / 8,
|
||||
&node);
|
||||
|
||||
ck_assert_mem_eq(
|
||||
node.chain_code,
|
||||
@ -153,7 +154,8 @@ START_TEST(test_bip32_cardano_hdnode_vector_2) {
|
||||
"junk",
|
||||
seed);
|
||||
ck_assert_int_eq(seed_len, 132);
|
||||
hdnode_from_seed_cardano((const uint8_t *)"", 0, seed, seed_len / 8, &node);
|
||||
hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, seed, seed_len / 8,
|
||||
&node);
|
||||
|
||||
hdnode_private_ckd_cardano(&node, 0x80000000);
|
||||
|
||||
@ -190,7 +192,8 @@ START_TEST(test_bip32_cardano_hdnode_vector_3) {
|
||||
"junk",
|
||||
seed);
|
||||
ck_assert_int_eq(seed_len, 132);
|
||||
hdnode_from_seed_cardano((const uint8_t *)"", 0, seed, seed_len / 8, &node);
|
||||
hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, seed, seed_len / 8,
|
||||
&node);
|
||||
|
||||
hdnode_private_ckd_cardano(&node, 0x80000001);
|
||||
|
||||
@ -227,7 +230,8 @@ START_TEST(test_bip32_cardano_hdnode_vector_4) {
|
||||
"junk",
|
||||
seed);
|
||||
ck_assert_int_eq(seed_len, 132);
|
||||
hdnode_from_seed_cardano((const uint8_t *)"", 0, seed, seed_len / 8, &node);
|
||||
hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, seed, seed_len / 8,
|
||||
&node);
|
||||
|
||||
hdnode_private_ckd_cardano(&node, 0x80000000);
|
||||
hdnode_private_ckd_cardano(&node, 0x80000001);
|
||||
@ -265,7 +269,8 @@ START_TEST(test_bip32_cardano_hdnode_vector_5) {
|
||||
"junk",
|
||||
seed);
|
||||
ck_assert_int_eq(seed_len, 132);
|
||||
hdnode_from_seed_cardano((const uint8_t *)"", 0, seed, seed_len / 8, &node);
|
||||
hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, seed, seed_len / 8,
|
||||
&node);
|
||||
|
||||
hdnode_private_ckd_cardano(&node, 0x80000000);
|
||||
hdnode_private_ckd_cardano(&node, 0x80000001);
|
||||
@ -304,7 +309,8 @@ START_TEST(test_bip32_cardano_hdnode_vector_6) {
|
||||
"junk",
|
||||
seed);
|
||||
ck_assert_int_eq(seed_len, 132);
|
||||
hdnode_from_seed_cardano((const uint8_t *)"", 0, seed, seed_len / 8, &node);
|
||||
hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, seed, seed_len / 8,
|
||||
&node);
|
||||
|
||||
hdnode_private_ckd_cardano(&node, 0x80000000);
|
||||
hdnode_private_ckd_cardano(&node, 0x80000001);
|
||||
@ -344,7 +350,8 @@ START_TEST(test_bip32_cardano_hdnode_vector_7) {
|
||||
"junk",
|
||||
seed);
|
||||
ck_assert_int_eq(seed_len, 132);
|
||||
hdnode_from_seed_cardano((const uint8_t *)"", 0, seed, seed_len / 8, &node);
|
||||
hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, seed, seed_len / 8,
|
||||
&node);
|
||||
|
||||
hdnode_private_ckd_cardano(&node, 0x80000000);
|
||||
hdnode_private_ckd_cardano(&node, 0x80000001);
|
||||
|
Loading…
Reference in New Issue
Block a user