diff --git a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip32.h b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip32.h index d92aa3a46..c2c1de143 100644 --- a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip32.h +++ b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip32.h @@ -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( diff --git a/core/mocks/generated/trezorcrypto/bip32.pyi b/core/mocks/generated/trezorcrypto/bip32.pyi index 17959bb77..eeb8e6cab 100644 --- a/core/mocks/generated/trezorcrypto/bip32.pyi +++ b/core/mocks/generated/trezorcrypto/bip32.pyi @@ -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. """ diff --git a/core/src/apps/cardano/seed.py b/core/src/apps/cardano/seed.py index 180be111d..50192e588 100644 --- a/core/src/apps/cardano/seed.py +++ b/core/src/apps/cardano/seed.py @@ -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: diff --git a/core/src/apps/common/mnemonic.py b/core/src/apps/common/mnemonic.py index b420cdf79..6d6b7b42b 100644 --- a/core/src/apps/common/mnemonic.py +++ b/core/src/apps/common/mnemonic.py @@ -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: diff --git a/core/tests/test_apps.cardano.address.py b/core/tests/test_apps.cardano.address.py index ee6fd3e2a..4e37d86c2 100644 --- a/core/tests/test_apps.cardano.address.py +++ b/core/tests/test_apps.cardano.address.py @@ -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() diff --git a/crypto/bip32.c b/crypto/bip32.c index 75dbd33ee..552844230 100644 --- a/crypto/bip32.c +++ b/crypto/bip32.c @@ -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, diff --git a/crypto/bip32.h b/crypto/bip32.h index 995a7aea9..84910cd34 100644 --- a/crypto/bip32.h +++ b/crypto/bip32.h @@ -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, diff --git a/crypto/tests/test_check_cardano.h b/crypto/tests/test_check_cardano.h index 5fd830f24..0dbf5fcb6 100644 --- a/crypto/tests/test_check_cardano.h +++ b/crypto/tests/test_check_cardano.h @@ -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);