diff --git a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip32.h b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip32.h index 15e76a64c..21fa26e9a 100644 --- a/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip32.h +++ b/core/embed/extmod/modtrezorcrypto/modtrezorcrypto-bip32.h @@ -600,16 +600,22 @@ STATIC mp_obj_t mod_trezorcrypto_bip32_from_mnemonic_cardano( const char *pmnemonic = mnemo.len > 0 ? mnemo.buf : ""; const char *ppassphrase = phrase.len > 0 ? phrase.buf : ""; - uint8_t entropy[64] = {0}; - int entropy_len = mnemonic_to_entropy(pmnemonic, entropy); + uint8_t mnemonic_bits[64] = {0}; + int mnemonic_bits_len = mnemonic_to_bits(pmnemonic, mnemonic_bits); - if (entropy_len == 0) { + if (mnemonic_bits_len == 0) { mp_raise_ValueError("Invalid mnemonic"); } + // BEWARE: passing of mnemonic_bits (i.e. entropy + checksum bits) into + // hdnode_from_entropy_cardano_icarus() is actually not correct and we should + // be passing the entropy alone. However, the bug is there since Cardano + // support has been launched for Trezor and its reversal would result in + // people with a 24-word mnemonic on Trezor losing access to their Cardano + // funds. More info at https://github.com/trezor/trezor-firmware/issues/1387 const int res = hdnode_from_entropy_cardano_icarus( - (const uint8_t *)ppassphrase, phrase.len, entropy, entropy_len / 8, - &hdnode); + (const uint8_t *)ppassphrase, phrase.len, mnemonic_bits, + mnemonic_bits_len / 8, &hdnode); if (!res) { mp_raise_ValueError( diff --git a/core/tests/test_apps.cardano.get_public_key.py b/core/tests/test_apps.cardano.get_public_key.py index d4b4aa9c0..bc4da1aa3 100644 --- a/core/tests/test_apps.cardano.get_public_key.py +++ b/core/tests/test_apps.cardano.get_public_key.py @@ -7,7 +7,7 @@ from trezor.crypto import bip32, slip39 @unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") class TestCardanoGetPublicKey(unittest.TestCase): - def test_get_public_key_scheme(self): + def test_get_public_key_scheme_12_words(self): mnemonic = "all all all all all all all all all all all all" passphrase = "" node = bip32.from_mnemonic_cardano(mnemonic, passphrase) @@ -68,6 +68,74 @@ class TestCardanoGetPublicKey(unittest.TestCase): self.assertEqual(hexlify(key.node.chain_code), chain_codes[index]) self.assertEqual(key.xpub, xpub_keys[index]) + def test_get_public_key_scheme_18_words(self): + mnemonic = "found differ bulb shadow wrist blue bind vessel deposit tip pelican action surprise weapon check fiction muscle this" + passphrase = "" + node = bip32.from_mnemonic_cardano(mnemonic, passphrase) + keychain = Keychain(node) + + derivation_paths = [ + [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0x80000000], + [0x80000000 | 1852, 0x80000000 | 1815, 0x80000000, 0, 0], + ] + + public_keys = [ + b'82f12f1916c0c35a412291e72204f17f033b0b7edf148dfd7d75acd3975c9ac0', + b'd92d0590e76bbf0300112a9f283fba2f7f8af5cf1054d634b610d1e4f541df90', + ] + + chain_codes = [ + b'974f9cd4336e23b976f934752026a2d4c32d2e23f0532f4f87152b45fa5ca81f', + b'352499ed19b47f2bc8c164b47df0d55f428cc8c12f96b7e65d7563114ddfd75b', + ] + + xpub_keys = [ + '82f12f1916c0c35a412291e72204f17f033b0b7edf148dfd7d75acd3975c9ac0974f9cd4336e23b976f934752026a2d4c32d2e23f0532f4f87152b45fa5ca81f', + 'd92d0590e76bbf0300112a9f283fba2f7f8af5cf1054d634b610d1e4f541df90352499ed19b47f2bc8c164b47df0d55f428cc8c12f96b7e65d7563114ddfd75b', + ] + + for index, derivation_path in enumerate(derivation_paths): + key = _get_public_key(keychain, derivation_path) + + self.assertEqual(hexlify(key.node.public_key), public_keys[index]) + self.assertEqual(hexlify(key.node.chain_code), chain_codes[index]) + self.assertEqual(key.xpub, xpub_keys[index]) + + + def test_get_public_key_scheme_24_words(self): + mnemonic = "balance exotic ranch knife glory slow tape favorite yard gym awake ill exist useless parent aim pig stay effort into square gasp credit butter" + passphrase = "" + node = bip32.from_mnemonic_cardano(mnemonic, passphrase) + keychain = Keychain(node) + + derivation_paths = [ + [0x80000000 | 44, 0x80000000 | 1815, 0x80000000, 0, 0x80000000], + [0x80000000 | 1852, 0x80000000 | 1815, 0x80000000, 0, 0], + ] + + public_keys = [ + b'9d45d1e979bd0b942adb1896019c85d08fbc562f012775a1f72fc7be8fe9e4b6', + b'a85a339897354931d584f828f6d79d4227ed16f3468990687ab42f13a87c9ea8', + ] + + chain_codes = [ + b'aad67fd6d620f7af88ad816a229de09cfacff3e28008a528759b2e2cf28d859a', + b'e6f844931e7e2ec724e6e62efde662ae2669355322dc3eb9b307bc1c8e75e219', + ] + + xpub_keys = [ + '9d45d1e979bd0b942adb1896019c85d08fbc562f012775a1f72fc7be8fe9e4b6aad67fd6d620f7af88ad816a229de09cfacff3e28008a528759b2e2cf28d859a', + 'a85a339897354931d584f828f6d79d4227ed16f3468990687ab42f13a87c9ea8e6f844931e7e2ec724e6e62efde662ae2669355322dc3eb9b307bc1c8e75e219', + ] + + for index, derivation_path in enumerate(derivation_paths): + key = _get_public_key(keychain, derivation_path) + + self.assertEqual(hexlify(key.node.public_key), public_keys[index]) + self.assertEqual(hexlify(key.node.chain_code), chain_codes[index]) + self.assertEqual(key.xpub, xpub_keys[index]) + + def test_slip39_128(self): mnemonics = [ "extra extend academic bishop cricket bundle tofu goat apart victim " diff --git a/crypto/bip39.c b/crypto/bip39.c index 33455f6c5..0c5c8d65c 100644 --- a/crypto/bip39.c +++ b/crypto/bip39.c @@ -94,7 +94,7 @@ const char *mnemonic_from_data(const uint8_t *data, int len) { void mnemonic_clear(void) { memzero(mnemo, sizeof(mnemo)); } -int mnemonic_to_entropy(const char *mnemonic, uint8_t *entropy) { +int mnemonic_to_bits(const char *mnemonic, uint8_t *bits) { if (!mnemonic) { return 0; } @@ -116,9 +116,9 @@ int mnemonic_to_entropy(const char *mnemonic, uint8_t *entropy) { char current_word[10] = {0}; uint32_t j = 0, k = 0, ki = 0, bi = 0; - uint8_t bits[32 + 1] = {0}; + uint8_t result[32 + 1] = {0}; - memzero(bits, sizeof(bits)); + memzero(result, sizeof(result)); i = 0; while (mnemonic[i]) { j = 0; @@ -142,7 +142,7 @@ int mnemonic_to_entropy(const char *mnemonic, uint8_t *entropy) { if (strcmp(current_word, wordlist[k]) == 0) { // word found on index k for (ki = 0; ki < 11; ki++) { if (k & (1 << (10 - ki))) { - bits[bi / 8] |= 1 << (7 - (bi % 8)); + result[bi / 8] |= 1 << (7 - (bi % 8)); } bi++; } @@ -154,17 +154,21 @@ int mnemonic_to_entropy(const char *mnemonic, uint8_t *entropy) { if (bi != n * 11) { return 0; } - memcpy(entropy, bits, sizeof(bits)); + memcpy(bits, result, sizeof(result)); + memzero(result, sizeof(result)); + + // returns amount of entropy + checksum BITS return n * 11; } int mnemonic_check(const char *mnemonic) { uint8_t bits[32 + 1] = {0}; - int seed_len = mnemonic_to_entropy(mnemonic, bits); - if (seed_len != (12 * 11) && seed_len != (18 * 11) && seed_len != (24 * 11)) { + int mnemonic_bits_len = mnemonic_to_bits(mnemonic, bits); + if (mnemonic_bits_len != (12 * 11) && mnemonic_bits_len != (18 * 11) && + mnemonic_bits_len != (24 * 11)) { return 0; } - int words = seed_len / 11; + int words = mnemonic_bits_len / 11; uint8_t checksum = bits[words * 4 / 3]; sha256_Raw(bits, words * 4 / 3, bits); diff --git a/crypto/bip39.h b/crypto/bip39.h index 07fb21bb2..46e4b1d4f 100644 --- a/crypto/bip39.h +++ b/crypto/bip39.h @@ -36,7 +36,7 @@ void mnemonic_clear(void); int mnemonic_check(const char *mnemonic); -int mnemonic_to_entropy(const char *mnemonic, uint8_t *entropy); +int mnemonic_to_bits(const char *mnemonic, uint8_t *bits); // passphrase must be at most 256 characters otherwise it would be truncated void mnemonic_to_seed(const char *mnemonic, const char *passphrase, diff --git a/crypto/tests/test_check.c b/crypto/tests/test_check.c index 3c6befae5..fb2fe2d1c 100644 --- a/crypto/tests/test_check.c +++ b/crypto/tests/test_check.c @@ -5114,7 +5114,7 @@ START_TEST(test_mnemonic_check) { } END_TEST -START_TEST(test_mnemonic_to_entropy) { +START_TEST(test_mnemonic_to_bits) { static const char *vectors[] = { "00000000000000000000000000000000", "abandon abandon abandon abandon abandon abandon abandon abandon abandon " @@ -5198,16 +5198,16 @@ START_TEST(test_mnemonic_to_entropy) { }; const char **a, **b; - uint8_t entropy[64]; + uint8_t mnemonic_bits[64]; a = vectors; b = vectors + 1; while (*a && *b) { - int seed_len = mnemonic_to_entropy(*b, entropy); - ck_assert_int_eq(seed_len % 33, 0); - seed_len = seed_len * 4 / 33; - ck_assert_int_eq(seed_len, strlen(*a) / 2); - ck_assert_mem_eq(entropy, fromhex(*a), seed_len); + int mnemonic_bits_len = mnemonic_to_bits(*b, mnemonic_bits); + ck_assert_int_eq(mnemonic_bits_len % 33, 0); + mnemonic_bits_len = mnemonic_bits_len * 4 / 33; + ck_assert_int_eq(mnemonic_bits_len, strlen(*a) / 2); + ck_assert_mem_eq(mnemonic_bits, fromhex(*a), mnemonic_bits_len); a += 2; b += 2; } @@ -8836,7 +8836,7 @@ Suite *test_suite(void) { tc = tcase_create("bip39"); tcase_add_test(tc, test_mnemonic); tcase_add_test(tc, test_mnemonic_check); - tcase_add_test(tc, test_mnemonic_to_entropy); + tcase_add_test(tc, test_mnemonic_to_bits); tcase_add_test(tc, test_mnemonic_find_word); suite_add_tcase(s, tc); @@ -8965,6 +8965,8 @@ Suite *test_suite(void) { tcase_add_test(tc, test_bip32_cardano_hdnode_vector_5); tcase_add_test(tc, test_bip32_cardano_hdnode_vector_6); tcase_add_test(tc, test_bip32_cardano_hdnode_vector_7); + tcase_add_test(tc, test_bip32_cardano_hdnode_vector_8); + tcase_add_test(tc, test_bip32_cardano_hdnode_vector_9); tcase_add_test(tc, test_ed25519_cardano_sign_vectors); suite_add_tcase(s, tc); diff --git a/crypto/tests/test_check_cardano.h b/crypto/tests/test_check_cardano.h index 0dbf5fcb6..681fa4426 100644 --- a/crypto/tests/test_check_cardano.h +++ b/crypto/tests/test_check_cardano.h @@ -112,14 +112,14 @@ END_TEST START_TEST(test_bip32_cardano_hdnode_vector_1) { HDNode node; - uint8_t seed[66]; - int seed_len = mnemonic_to_entropy( + uint8_t mnemonic_bits[66]; + int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", - seed); - ck_assert_int_eq(seed_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, seed, seed_len / 8, - &node); + mnemonic_bits); + ck_assert_int_eq(mnemonic_bits_len, 132); + hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, &node); ck_assert_mem_eq( node.chain_code, @@ -148,14 +148,14 @@ END_TEST START_TEST(test_bip32_cardano_hdnode_vector_2) { HDNode node; - uint8_t seed[66]; - int seed_len = mnemonic_to_entropy( + uint8_t mnemonic_bits[66]; + int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", - seed); - ck_assert_int_eq(seed_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, seed, seed_len / 8, - &node); + mnemonic_bits); + ck_assert_int_eq(mnemonic_bits_len, 132); + hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, &node); hdnode_private_ckd_cardano(&node, 0x80000000); @@ -186,14 +186,14 @@ END_TEST START_TEST(test_bip32_cardano_hdnode_vector_3) { HDNode node; - uint8_t seed[66]; - int seed_len = mnemonic_to_entropy( + uint8_t mnemonic_bits[66]; + int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", - seed); - ck_assert_int_eq(seed_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, seed, seed_len / 8, - &node); + mnemonic_bits); + ck_assert_int_eq(mnemonic_bits_len, 132); + hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, &node); hdnode_private_ckd_cardano(&node, 0x80000001); @@ -224,14 +224,14 @@ END_TEST START_TEST(test_bip32_cardano_hdnode_vector_4) { HDNode node; - uint8_t seed[66]; - int seed_len = mnemonic_to_entropy( + uint8_t mnemonic_bits[66]; + int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", - seed); - ck_assert_int_eq(seed_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, seed, seed_len / 8, - &node); + mnemonic_bits); + ck_assert_int_eq(mnemonic_bits_len, 132); + hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, &node); hdnode_private_ckd_cardano(&node, 0x80000000); hdnode_private_ckd_cardano(&node, 0x80000001); @@ -263,14 +263,14 @@ END_TEST START_TEST(test_bip32_cardano_hdnode_vector_5) { HDNode node; - uint8_t seed[66]; - int seed_len = mnemonic_to_entropy( + uint8_t mnemonic_bits[66]; + int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", - seed); - ck_assert_int_eq(seed_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, seed, seed_len / 8, - &node); + mnemonic_bits); + ck_assert_int_eq(mnemonic_bits_len, 132); + hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, &node); hdnode_private_ckd_cardano(&node, 0x80000000); hdnode_private_ckd_cardano(&node, 0x80000001); @@ -303,14 +303,14 @@ END_TEST START_TEST(test_bip32_cardano_hdnode_vector_6) { HDNode node; - uint8_t seed[66]; - int seed_len = mnemonic_to_entropy( + uint8_t mnemonic_bits[66]; + int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", - seed); - ck_assert_int_eq(seed_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, seed, seed_len / 8, - &node); + mnemonic_bits); + ck_assert_int_eq(mnemonic_bits_len, 132); + hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, &node); hdnode_private_ckd_cardano(&node, 0x80000000); hdnode_private_ckd_cardano(&node, 0x80000001); @@ -344,14 +344,14 @@ END_TEST START_TEST(test_bip32_cardano_hdnode_vector_7) { HDNode node; - uint8_t seed[66]; - int seed_len = mnemonic_to_entropy( + uint8_t mnemonic_bits[66]; + int mnemonic_bits_len = mnemonic_to_bits( "ring crime symptom enough erupt lady behave ramp apart settle citizen " "junk", - seed); - ck_assert_int_eq(seed_len, 132); - hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, seed, seed_len / 8, - &node); + mnemonic_bits); + ck_assert_int_eq(mnemonic_bits_len, 132); + hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, &node); hdnode_private_ckd_cardano(&node, 0x80000000); hdnode_private_ckd_cardano(&node, 0x80000001); @@ -382,3 +382,88 @@ START_TEST(test_bip32_cardano_hdnode_vector_7) { 32); } END_TEST + +START_TEST(test_bip32_cardano_hdnode_vector_8) { + HDNode node; + + uint8_t mnemonic_bits[66]; + int mnemonic_bits_len = mnemonic_to_bits( + "found differ bulb shadow wrist blue bind vessel deposit tip pelican " + "action surprise weapon check fiction muscle this", + mnemonic_bits); + ck_assert_int_eq(mnemonic_bits_len, 198); + hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, &node); + + hdnode_private_ckd_cardano(&node, 0x80000000); + hdnode_private_ckd_cardano(&node, 0x80000001); + hdnode_private_ckd_cardano(&node, 0x80000002); + hdnode_private_ckd_cardano(&node, 0x80000002); + hdnode_private_ckd_cardano(&node, 0xBB9ACA00); + + ck_assert_mem_eq( + node.chain_code, + fromhex( + "6fb22a4531ad79e828c4907c5fff3ecf686c16cb195f81243f1f0330173380e4"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "a0baa34e4e24f0500ed6e5e90ab41984b965b7464b0b28640528778dd8a6b854"), + 32); + ck_assert_mem_eq( + node.private_key_extension, + fromhex( + "170e0d3b65ba8d71f27a6db60d0ac26dcb16e52e08cc259db72066f206b258d5"), + 32); + hdnode_fill_public_key(&node); + ck_assert_mem_eq( + node.public_key + 1, + fromhex( + "3dae0c06d87db618d73ee808425898cdd882f9eb43bf139c6b3a4760551ee89f"), + 32); +} +END_TEST + +START_TEST(test_bip32_cardano_hdnode_vector_9) { + HDNode node; + + uint8_t mnemonic_bits[66]; + int mnemonic_bits_len = mnemonic_to_bits( + "balance exotic ranch knife glory slow tape favorite yard gym awake " + "ill exist useless parent aim pig stay effort into square gasp credit " + "butter", + mnemonic_bits); + ck_assert_int_eq(mnemonic_bits_len, 264); + hdnode_from_entropy_cardano_icarus((const uint8_t *)"", 0, mnemonic_bits, + mnemonic_bits_len / 8, &node); + + hdnode_private_ckd_cardano(&node, 0x80000000); + hdnode_private_ckd_cardano(&node, 0x80000001); + hdnode_private_ckd_cardano(&node, 0x80000002); + hdnode_private_ckd_cardano(&node, 0x80000002); + hdnode_private_ckd_cardano(&node, 0xBB9ACA00); + + ck_assert_mem_eq( + node.chain_code, + fromhex( + "9b226add79f90086ea18b260da633089fe121db758aa31284ad1affaf3c9bb68"), + 32); + ck_assert_mem_eq( + node.private_key, + fromhex( + "38eb2a79486e516cb6658700503a3e2c870c03e9d1aec731f780aa6fb7f7de44"), + 32); + ck_assert_mem_eq( + node.private_key_extension, + fromhex( + "80d2c677638e5dbd4395cdec279bf2a42077f2797c9e887949d37cdb317fce6a"), + 32); + hdnode_fill_public_key(&node); + ck_assert_mem_eq( + node.public_key + 1, + fromhex( + "115a365b2aad1d8eba7d379de518f1fa8553855110af24e5695011c32ce9a300"), + 32); +} +END_TEST