diff --git a/bip32.c b/bip32.c index f4a021d57..fb603dfa5 100644 --- a/bip32.c +++ b/bip32.c @@ -22,6 +22,7 @@ */ #include +#include #include "bignum.h" #include "hmac.h" @@ -176,6 +177,73 @@ int hdnode_public_ckd(HDNode *inout, uint32_t i) return 1; } +#if USE_BIP32_CACHE + +static bool private_ckd_cache_root_set = false; +static HDNode private_ckd_cache_root; +static int private_ckd_cache_index = 0; + +static struct { + bool set; + size_t depth; + uint32_t i[BIP32_CACHE_MAXDEPTH]; + HDNode node; +} private_ckd_cache[BIP32_CACHE_SIZE]; + +int hdnode_private_ckd_cached(HDNode *inout, const uint32_t *i, size_t i_count) +{ + if (i_count == 0) { + return 1; + } + if (i_count == 1) { + if (hdnode_private_ckd(inout, i[0]) == 0) return 0; + return 1; + } + + bool found = false; + // if root is not set or not the same + if (!private_ckd_cache_root_set || memcmp(&private_ckd_cache_root, inout, sizeof(HDNode)) != 0) { + // clear the cache + private_ckd_cache_index = 0; + memset(private_ckd_cache, 0, sizeof(private_ckd_cache)); + // setup new root + memcpy(&private_ckd_cache_root, inout, sizeof(HDNode)); + private_ckd_cache_root_set = true; + } else { + // try to find parent + int j; + for (j = 0; j < BIP32_CACHE_SIZE; j++) { + if (private_ckd_cache[j].set && + private_ckd_cache[j].depth == i_count - 1 && + memcmp(private_ckd_cache[j].i, i, (i_count - 1) * sizeof(uint32_t)) == 0) { + memcpy(inout, &(private_ckd_cache[j].node), sizeof(HDNode)); + found = true; + break; + } + } + } + + // else derive parent + if (!found) { + size_t k; + for (k = 0; k < i_count - 1; k++) { + if (hdnode_private_ckd(inout, i[k]) == 0) return 0; + } + // and save it + private_ckd_cache[private_ckd_cache_index].set = true; + private_ckd_cache[private_ckd_cache_index].depth = i_count - 1; + memcpy(private_ckd_cache[private_ckd_cache_index].i, i, (i_count - 1) * sizeof(uint32_t)); + memcpy(&(private_ckd_cache[private_ckd_cache_index].node), inout, sizeof(HDNode)); + private_ckd_cache_index = (private_ckd_cache_index + 1) % BIP32_CACHE_SIZE; + } + + if (hdnode_private_ckd(inout, i[i_count - 1]) == 0) return 0; + + return 1; +} + +#endif + void hdnode_fill_public_key(HDNode *node) { ecdsa_get_public_key33(node->private_key, node->public_key); diff --git a/bip32.h b/bip32.h index ab9791d04..fbf7b1a61 100644 --- a/bip32.h +++ b/bip32.h @@ -25,6 +25,7 @@ #define __BIP32_H__ #include +#include "options.h" typedef struct { uint32_t depth; @@ -47,6 +48,12 @@ int hdnode_private_ckd(HDNode *inout, uint32_t i); int hdnode_public_ckd(HDNode *inout, uint32_t i); +#if USE_BIP32_CACHE + +int hdnode_private_ckd_cached(HDNode *inout, const uint32_t *i, size_t i_count); + +#endif + void hdnode_fill_public_key(HDNode *node); void hdnode_serialize_public(const HDNode *node, char *str, int strsize); diff --git a/options.h b/options.h index 53cdb2df5..0e68178c2 100644 --- a/options.h +++ b/options.h @@ -53,4 +53,11 @@ #define USE_PUBKEY_VALIDATE 1 #endif +// implement BIP32 caching +#ifndef USE_BIP32_CACHE +#define USE_BIP32_CACHE 1 +#define BIP32_CACHE_SIZE 10 +#define BIP32_CACHE_MAXDEPTH 8 +#endif + #endif diff --git a/tests.c b/tests.c index 580bf02ce..fdfea7a5b 100644 --- a/tests.c +++ b/tests.c @@ -427,6 +427,52 @@ START_TEST(test_bip32_compare) } END_TEST +START_TEST(test_bip32_cache) +{ + HDNode node1, node2; + int r; + + // test 1 .. 8 + hdnode_from_seed(fromhex("301133282ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d62788f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), 64, &node1); + hdnode_from_seed(fromhex("301133282ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d62788f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), 64, &node2); + + uint32_t i; + for (i = 1; i < 8; i++) { + r = hdnode_private_ckd(&node1, i); ck_assert_int_eq(r, 1); + } + r = hdnode_private_ckd(&node1, 8); ck_assert_int_eq(r, 1); + + uint32_t ii[] = {1, 2, 3, 4, 5, 6, 7, 8}; + r = hdnode_private_ckd_cached(&node2, ii, 8); ck_assert_int_eq(r, 1); + ck_assert_mem_eq(&node1, &node2, sizeof(HDNode)); + + // test 1 .. 7, 20 + hdnode_from_seed(fromhex("301133282ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d62788f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), 64, &node1); + hdnode_from_seed(fromhex("301133282ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d62788f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), 64, &node2); + + for (i = 1; i < 8; i++) { + r = hdnode_private_ckd(&node1, i); ck_assert_int_eq(r, 1); + } + r = hdnode_private_ckd(&node1, 20); ck_assert_int_eq(r, 1); + + ii[7] = 20; + r = hdnode_private_ckd_cached(&node2, ii, 8); ck_assert_int_eq(r, 1); + ck_assert_mem_eq(&node1, &node2, sizeof(HDNode)); + + // test different root node + hdnode_from_seed(fromhex("000000002ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d62788f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), 64, &node1); + hdnode_from_seed(fromhex("000000002ad079cbeb59bc446ad39d333928f74c46997d3609cd3e2801ca69d62788f9f174429946ff4e9be89f67c22fae28cb296a9b37734f75e73d1477af19"), 64, &node2); + + for (i = 1; i < 8; i++) { + r = hdnode_private_ckd(&node1, i); ck_assert_int_eq(r, 1); + } + r = hdnode_private_ckd(&node1, 20); ck_assert_int_eq(r, 1); + + r = hdnode_private_ckd_cached(&node2, ii, 8); ck_assert_int_eq(r, 1); + ck_assert_mem_eq(&node1, &node2, sizeof(HDNode)); +} +END_TEST + #define test_deterministic(KEY, MSG, K) do { \ sha256_Raw((uint8_t *)MSG, strlen(MSG), buf); \ res = generate_k_rfc6979(&k, fromhex(KEY), buf); \ @@ -1139,6 +1185,7 @@ Suite *test_suite(void) Suite *s = suite_create("trezor-crypto"); TCase *tc; + tc = tcase_create("base58"); tcase_add_test(tc, test_base58); suite_add_tcase(s, tc); @@ -1147,6 +1194,7 @@ Suite *test_suite(void) tcase_add_test(tc, test_bip32_vector_1); tcase_add_test(tc, test_bip32_vector_2); tcase_add_test(tc, test_bip32_compare); + tcase_add_test(tc, test_bip32_cache); suite_add_tcase(s, tc); tc = tcase_create("rfc6979");