diff --git a/crypto/bip32.c b/crypto/bip32.c index b3b3702e8..7811d66a4 100644 --- a/crypto/bip32.c +++ b/crypto/bip32.c @@ -49,7 +49,7 @@ #include "memzero.h" const curve_info ed25519_info = { - .bip32_name = "ed25519 seed", + .bip32_name = ED25519_SEED_NAME, .params = NULL, .hasher_base58 = HASHER_SHA2D, .hasher_sign = HASHER_SHA2D, diff --git a/crypto/cardano.c b/crypto/cardano.c index 4cf817351..485d6514a 100644 --- a/crypto/cardano.c +++ b/crypto/cardano.c @@ -40,7 +40,7 @@ #define CARDANO_MAX_NODE_DEPTH 1048576 const curve_info ed25519_cardano_info = { - .bip32_name = "ed25519 cardano seed", + .bip32_name = ED25519_CARDANO_NAME, .params = NULL, .hasher_base58 = HASHER_SHA2D, .hasher_sign = HASHER_SHA2D, @@ -194,13 +194,67 @@ int secret_from_seed_cardano_slip23(const uint8_t *seed, int seed_len, return 1; } +// Derives the root Cardano secret from a BIP-32 master secret via the Ledger +// derivation: +// https://github.com/cardano-foundation/CIPs/blob/09d7d8ee1bd64f7e6b20b5a6cae088039dce00cb/CIP-0003/Ledger.md +int secret_from_seed_cardano_ledger(const uint8_t *seed, int seed_len, + uint8_t secret_out[CARDANO_SECRET_LENGTH]) { + static CONFIDENTIAL uint8_t chain_code[SHA256_DIGEST_LENGTH]; + static CONFIDENTIAL uint8_t root_key[SHA512_DIGEST_LENGTH]; + static CONFIDENTIAL HMAC_SHA256_CTX ctx; + static CONFIDENTIAL HMAC_SHA512_CTX sctx; + + const uint8_t *intermediate_result = seed; + int intermediate_result_len = seed_len; + do { + // STEP 1: derive a master secret like in BIP-32/SLIP-10 + hmac_sha512_Init(&sctx, (const uint8_t *)ED25519_SEED_NAME, + strlen(ED25519_SEED_NAME)); + hmac_sha512_Update(&sctx, intermediate_result, intermediate_result_len); + hmac_sha512_Final(&sctx, root_key); + + // STEP 2: check that the resulting key does not have a particular bit set, + // otherwise iterate like in SLIP-10 + intermediate_result = root_key; + intermediate_result_len = sizeof(root_key); + } while (root_key[31] & 0x20); + + // STEP 3: calculate the chain code as a HMAC-SHA256 of "\x01" + seed, + // key is "ed25519 seed" + hmac_sha256_Init(&ctx, (const unsigned char *)ED25519_SEED_NAME, + strlen(ED25519_SEED_NAME)); + hmac_sha256_Update(&ctx, (const unsigned char *)"\x01", 1); + hmac_sha256_Update(&ctx, seed, seed_len); + hmac_sha256_Final(&ctx, chain_code); + + // STEP 4: extract information into output + _Static_assert( + SHA512_DIGEST_LENGTH + SHA256_DIGEST_LENGTH == CARDANO_SECRET_LENGTH, + "Invalid configuration of Cardano secret size"); + memcpy(secret_out, root_key, SHA512_DIGEST_LENGTH); + memcpy(secret_out + SHA512_DIGEST_LENGTH, chain_code, SHA256_DIGEST_LENGTH); + + // STEP 5: tweak bits of the private key + cardano_ed25519_tweak_bits(secret_out); + + memzero(&ctx, sizeof(ctx)); + memzero(&sctx, sizeof(sctx)); + memzero(root_key, sizeof(root_key)); + memzero(chain_code, sizeof(chain_code)); + return 1; +} + #define CARDANO_ICARUS_STEPS 32 +_Static_assert( + CARDANO_ICARUS_PBKDF2_ROUNDS % CARDANO_ICARUS_STEPS == 0, + "CARDANO_ICARUS_STEPS does not divide CARDANO_ICARUS_PBKDF2_ROUNDS"); #define CARDANO_ICARUS_ROUNDS_PER_STEP \ (CARDANO_ICARUS_PBKDF2_ROUNDS / CARDANO_ICARUS_STEPS) // 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. +// scheme: +// https://github.com/cardano-foundation/CIPs/blob/09d7d8ee1bd64f7e6b20b5a6cae088039dce00cb/CIP-0003/Icarus.md int secret_from_entropy_cardano_icarus( const uint8_t *pass, int pass_len, const uint8_t *entropy, int entropy_len, uint8_t secret_out[CARDANO_SECRET_LENGTH], diff --git a/crypto/curves.c b/crypto/curves.c index 972339bdd..dcdaf7ba4 100644 --- a/crypto/curves.c +++ b/crypto/curves.c @@ -28,7 +28,10 @@ const char SECP256K1_GROESTL_NAME[] = "secp256k1-groestl"; const char SECP256K1_SMART_NAME[] = "secp256k1-smart"; const char NIST256P1_NAME[] = "nist256p1"; const char ED25519_NAME[] = "ed25519"; +const char ED25519_SEED_NAME[] = "ed25519 seed"; +#if USE_CARDANO const char ED25519_CARDANO_NAME[] = "ed25519 cardano seed"; +#endif const char ED25519_SHA3_NAME[] = "ed25519-sha3"; #if USE_KECCAK const char ED25519_KECCAK_NAME[] = "ed25519-keccak"; diff --git a/crypto/curves.h b/crypto/curves.h index 34b796eb0..aabf5b369 100644 --- a/crypto/curves.h +++ b/crypto/curves.h @@ -31,6 +31,7 @@ extern const char SECP256K1_GROESTL_NAME[]; extern const char SECP256K1_SMART_NAME[]; extern const char NIST256P1_NAME[]; extern const char ED25519_NAME[]; +extern const char ED25519_SEED_NAME[]; extern const char ED25519_CARDANO_NAME[]; extern const char ED25519_SHA3_NAME[]; #if USE_KECCAK diff --git a/crypto/tests/test_check.c b/crypto/tests/test_check.c index c24fb6329..3ecbd8ec8 100644 --- a/crypto/tests/test_check.c +++ b/crypto/tests/test_check.c @@ -9686,6 +9686,10 @@ Suite *test_suite(void) { 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_cardano_ledger_vector_1); + tcase_add_test(tc, test_cardano_ledger_vector_2); + tcase_add_test(tc, test_cardano_ledger_vector_3); + tcase_add_test(tc, test_ed25519_cardano_sign_vectors); suite_add_tcase(s, tc); #endif diff --git a/crypto/tests/test_check_cardano.h b/crypto/tests/test_check_cardano.h index f5ab7c02d..78dabe0f0 100644 --- a/crypto/tests/test_check_cardano.h +++ b/crypto/tests/test_check_cardano.h @@ -501,3 +501,72 @@ START_TEST(test_bip32_cardano_hdnode_vector_9) { 32); } END_TEST + +START_TEST(test_cardano_ledger_vector_1) { + uint8_t seed[512 / 8]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; + + const char *mnemonic = + "recall grace sport punch exhibit mad harbor stand obey " + "short width stem awkward used stairs wool ugly " + "trap season stove worth toward congress jaguar"; + + mnemonic_to_seed(mnemonic, "", seed, NULL); + const int res = + secret_from_seed_cardano_ledger(seed, sizeof(seed), cardano_secret); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq( + cardano_secret, + fromhex( + "a08cf85b564ecf3b947d8d4321fb96d70ee7bb760877e371899b14e2ccf88658" + "104b884682b57efd97decbb318a45c05a527b9cc5c2f64f7352935a049ceea60" + "680d52308194ccef2a18e6812b452a5815fbd7f5babc083856919aaf668fe7e4"), + CARDANO_SECRET_LENGTH); +} +END_TEST + +START_TEST(test_cardano_ledger_vector_2) { + uint8_t seed[512 / 8]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; + + const char *mnemonic = + "correct cherry mammal bubble want mandate polar hazard " + "crater better craft exotic choice fun tourist census " + "gap lottery neglect address glow carry old business"; + + mnemonic_to_seed(mnemonic, "", seed, NULL); + const int res = + secret_from_seed_cardano_ledger(seed, sizeof(seed), cardano_secret); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq( + cardano_secret, + fromhex( + "587c6774357ecbf840d4db6404ff7af016dace0400769751ad2abfc77b9a3844" + "cc71702520ef1a4d1b68b91187787a9b8faab0a9bb6b160de541b6ee62469901" + "fc0beda0975fe4763beabd83b7051a5fd5cbce5b88e82c4bbaca265014e524bd"), + CARDANO_SECRET_LENGTH); +} +END_TEST + +START_TEST(test_cardano_ledger_vector_3) { + uint8_t seed[512 / 8]; + uint8_t cardano_secret[CARDANO_SECRET_LENGTH]; + + const char *mnemonic = + "abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon abandon abandon abandon " + "abandon abandon abandon abandon abandon abandon abandon art"; + + mnemonic_to_seed(mnemonic, "foo", seed, NULL); + const int res = + secret_from_seed_cardano_ledger(seed, sizeof(seed), cardano_secret); + ck_assert_int_eq(res, 1); + ck_assert_mem_eq( + cardano_secret, + fromhex( + "f053a1e752de5c26197b60f032a4809f08bb3e5d90484fe42024be31efcba757" + "8d914d3ff992e21652fee6a4d99f6091006938fac2c0c0f9d2de0ba64b754e92" + "a4f3723f23472077aa4cd4dd8a8a175dba07ea1852dad1cf268c61a2679c3890"), + CARDANO_SECRET_LENGTH); +} +END_TEST