Skip to content

Commit

Permalink
bip32: Add support for key derivation from BIP32 style path strings
Browse files Browse the repository at this point in the history
Reject path lengths longer than 255 up-front, since it would exceed the
maximum depth of a BIP32 key, and add some missing test cases.
  • Loading branch information
jgriffiths committed Dec 16, 2021
1 parent bd2055c commit e01c3b7
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 19 deletions.
24 changes: 24 additions & 0 deletions include/wally.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,30 @@ inline int bip32_key_from_parent_path_alloc(const HDKEY& hdkey, const CHILD_PATH
return ret;
}

template <class HDKEY, class PATH_STR>
inline int bip32_key_from_parent_path_str(const HDKEY& hdkey, const PATH_STR& path_str, uint32_t child_num, uint32_t flags, struct ext_key* output) {
int ret = ::bip32_key_from_parent_path_str(detail::get_p(hdkey), detail::get_p(path_str), child_num, flags, output);
return ret;
}

template <class HDKEY, class PATH_STR>
inline int bip32_key_from_parent_path_str_alloc(const HDKEY& hdkey, const PATH_STR& path_str, uint32_t child_num, uint32_t flags, struct ext_key** output) {
int ret = ::bip32_key_from_parent_path_str_alloc(detail::get_p(hdkey), detail::get_p(path_str), child_num, flags, output);
return ret;
}

template <class HDKEY, class PATH_STR>
inline int bip32_key_from_parent_path_str_n(const HDKEY& hdkey, const PATH_STR& path_str, size_t path_str_len, uint32_t child_num, uint32_t flags, struct ext_key* output) {
int ret = ::bip32_key_from_parent_path_str_n(detail::get_p(hdkey), detail::get_p(path_str), path_str_len, child_num, flags, output);
return ret;
}

template <class HDKEY, class PATH_STR>
inline int bip32_key_from_parent_path_str_n_alloc(const HDKEY& hdkey, const PATH_STR& path_str, size_t path_str_len, uint32_t child_num, uint32_t flags, struct ext_key** output) {
int ret = ::bip32_key_from_parent_path_str_n_alloc(detail::get_p(hdkey), detail::get_p(path_str), path_str_len, child_num, flags, output);
return ret;
}

template <class BYTES>
inline int bip32_key_from_seed(const BYTES& bytes, uint32_t version, uint32_t flags, struct ext_key* output) {
int ret = ::bip32_key_from_seed(bytes.data(), bytes.size(), version, flags, output);
Expand Down
63 changes: 62 additions & 1 deletion include/wally_bip32.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ extern "C" {
/** Child number of the first hardened child */
#define BIP32_INITIAL_HARDENED_CHILD 0x80000000

/** The maximum number of path elements allowed in a path */
#define BIP32_PATH_MAX_LEN 255

/** Indicate that we want to derive a private key in `bip32_key_from_parent` */
#define BIP32_FLAG_KEY_PRIVATE 0x0
/** Indicate that we want to derive a public key in `bip32_key_from_parent` */
Expand All @@ -30,6 +33,10 @@ extern "C" {
/** Indicate that we want the pub tweak to be added to the calculation when deriving a key in `bip32_key_from_parent` */
/** Only used with elements */
#define BIP32_FLAG_KEY_TWEAK_SUM 0x4
/** Allow a wildcard ``*`` or ``*'``/``*h`` in path string expressions */
#define BIP32_FLAG_STR_WILDCARD 0x8
/** Do not allow a leading ``m``/``M`` or ``/`` in path string expressions */
#define BIP32_FLAG_STR_BARE 0x10

/** Version codes for extended keys */
#define BIP32_VER_MAIN_PUBLIC 0x0488B21E
Expand Down Expand Up @@ -231,7 +238,7 @@ WALLY_CORE_API int bip32_key_from_parent_alloc(
* :param hdkey: The parent extended key.
* :param child_path: The path of child numbers to create.
* :param child_path_len: The number of child numbers in ``child_path``.
* :param flags: ``BIP32_FLAG_KEY_`` Flags indicating the type of derivation wanted.
* :param flags: ``BIP32_FLAG_`` Flags indicating the type of derivation wanted.
* :param output: Destination for the resulting child extended key.
*/
WALLY_CORE_API int bip32_key_from_parent_path(
Expand All @@ -253,6 +260,60 @@ WALLY_CORE_API int bip32_key_from_parent_path_alloc(
uint32_t flags,
struct ext_key **output);

#ifndef SWIG
/**
* Create a new child extended key from a parent extended key and a path string.
*
* :param hdkey: The parent extended key.
* :param path_str: The BIP32 path string of child numbers to create.
* :param child_num: The child number to use if ``path_str`` contains a ``*`` wildcard.
* :param flags: ``BIP32_FLAG_`` Flags indicating the type of derivation wanted.
* :param output: Destination for the resulting child extended key.
*/
int bip32_key_from_parent_path_str(
const struct ext_key *hdkey,
const char *path_str,
uint32_t child_num,
uint32_t flags,
struct ext_key *output);

/**
* Create a new child extended key from a parent extended key and a known-length path string.
*
* See `bip32_key_from_parent_path_str`.
*/
int bip32_key_from_parent_path_str_n(
const struct ext_key *hdkey,
const char *path_str,
size_t path_str_len,
uint32_t child_num,
uint32_t flags,
struct ext_key *output);
#endif

/**
* As per `bip32_key_from_parent_path_str`, but allocates the key.
* .. note:: The returned key should be freed with `bip32_key_free`.
*/
int bip32_key_from_parent_path_str_alloc(
const struct ext_key *hdkey,
const char *path_str,
uint32_t child_num,
uint32_t flags,
struct ext_key **output);

/**
* As per `bip32_key_from_parent_path_str_n`, but allocates the key.
* .. note:: The returned key should be freed with `bip32_key_free`.
*/
int bip32_key_from_parent_path_str_n_alloc(
const struct ext_key *hdkey,
const char *path_str,
size_t path_str_len,
uint32_t child_num,
uint32_t flags,
struct ext_key **output);

#ifdef BUILD_ELEMENTS
#ifndef SWIG
/**
Expand Down
152 changes: 146 additions & 6 deletions src/bip32.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@
#include "bip32_int.h"
#include <stdbool.h>

#define BIP32_ALL_DEFINED_FLAGS (BIP32_FLAG_KEY_PRIVATE | BIP32_FLAG_KEY_PUBLIC | BIP32_FLAG_SKIP_HASH | BIP32_FLAG_KEY_TWEAK_SUM)
#define BIP32_ALL_DEFINED_FLAGS (BIP32_FLAG_KEY_PRIVATE | \
BIP32_FLAG_KEY_PUBLIC | \
BIP32_FLAG_SKIP_HASH | \
BIP32_FLAG_KEY_TWEAK_SUM | \
BIP32_FLAG_STR_WILDCARD | \
BIP32_FLAG_STR_BARE)

static const unsigned char SEED[] = {
'B', 'i', 't', 'c', 'o', 'i', 'n', ' ', 's', 'e', 'e', 'd'
Expand Down Expand Up @@ -80,6 +85,90 @@ static bool version_is_mainnet(uint32_t ver)
return ver == BIP32_VER_MAIN_PRIVATE || ver == BIP32_VER_MAIN_PUBLIC;
}

static bool is_hardened_indicator(char c)
{
return c == '\'' || c == 'h' || c == 'H';
}

static int path_from_string_n(const char *str, size_t str_len,
uint32_t child_num, uint32_t flags,
uint32_t *child_path, uint32_t child_path_len,
size_t *written)
{
size_t start, i = 0;
uint64_t v;

if (!str || !str_len || child_num >= BIP32_INITIAL_HARDENED_CHILD || !written)
goto fail;

*written = 0;

if (flags & BIP32_FLAG_STR_BARE) {
if (i < str_len && str[i] == '/')
goto fail; /* bare path must start with a number */
} else {
if (i < str_len && (str[i] == 'm' || str[i] == 'M'))
++i; /* Skip */
if (i < str_len && str[i] == '/')
++i; /* Skip */
}

while (i < str_len) {
bool is_wildcard = false;
start = i;
v = 0;
while (str[i] >= '0' && str[i] <= '9' && i < str_len) {
v = v * 10 + (str[i] - '0');
if (v >= BIP32_INITIAL_HARDENED_CHILD)
goto fail; /* Derivation index too large */
++i;
}
if (i == start) {
/* No number found */
if (str[i] == '/') {
if (i && (str[i - 1] < '0' || str[i - 1] > '9') &&
!is_hardened_indicator(str[i - 1]) && str[i - 1] != '*')
goto fail; /* Only valid after number/wildcard/hardened indicator */
++i;
if (i == str_len || str[i] == '/')
goto fail; /* Trailing slash, invalid */
continue;
}
if (!(is_wildcard = str[i] == '*'))
goto fail; /* Unknown character */

/* Wildcard */
if (!(flags & BIP32_FLAG_STR_WILDCARD))
goto fail; /* Wildcard not allowed, or previously seen */
flags &= ~BIP32_FLAG_STR_WILDCARD;
if (i && str[i - 1] != '/')
goto fail; /* Must follow a slash */
++i;
v = child_num; /* Use the given child number for the wildcard value */
}

if (is_hardened_indicator(str[i])) {
v |= BIP32_INITIAL_HARDENED_CHILD;
++i;
}
if (is_wildcard && i != str_len && str[i] != '/')
goto fail; /* Wildcard followed by something other than a slash */
if (*written == child_path_len) {
/* Continue counting the resulting length, but don't write any more */
child_path = NULL;
}
if (child_path)
child_path[*written] = v;
++*written;
}

return *written ? WALLY_OK : WALLY_EINVAL;
fail:
if (written)
*written = 0;
return WALLY_EINVAL;
}

static bool key_is_private(const struct ext_key *hdkey)
{
return hdkey->priv_key[0] == BIP32_FLAG_KEY_PRIVATE;
Expand Down Expand Up @@ -337,7 +426,7 @@ int bip32_key_unserialize_alloc(const unsigned char *bytes, size_t bytes_len,
ret = bip32_key_unserialize(bytes, bytes_len, *output);
if (ret != WALLY_OK) {
wally_free(*output);
*output = 0;
*output = NULL;
}
return ret;
}
Expand Down Expand Up @@ -519,7 +608,7 @@ int bip32_key_from_parent_alloc(const struct ext_key *hdkey,
ret = bip32_key_from_parent(hdkey, child_num, flags, *output);
if (ret != WALLY_OK) {
wally_free(*output);
*output = 0;
*output = NULL;
}
return ret;
}
Expand All @@ -537,7 +626,7 @@ int bip32_key_from_parent_path(const struct ext_key *hdkey,
if (flags & ~BIP32_ALL_DEFINED_FLAGS)
return WALLY_EINVAL; /* These flags are not defined yet */

if (!hdkey || !child_path || !child_path_len || !key_out)
if (!hdkey || !child_path || !child_path_len || child_path_len > BIP32_PATH_MAX_LEN || !key_out)
return WALLY_EINVAL;

for (i = 0; i < child_path_len; ++i) {
Expand Down Expand Up @@ -575,7 +664,7 @@ int bip32_key_from_parent_path_alloc(const struct ext_key *hdkey,
flags, *output);
if (ret != WALLY_OK) {
wally_free(*output);
*output = 0;
*output = NULL;
}
return ret;
}
Expand Down Expand Up @@ -628,6 +717,57 @@ int bip32_key_with_tweak_from_parent_path_alloc(const struct ext_key *hdkey,
}
#endif /* BUILD_ELEMENTS */

int bip32_key_from_parent_path_str_n(const struct ext_key *hdkey,
const char *str, size_t str_len,
uint32_t child_num, uint32_t flags,
struct ext_key *key_out)
{
uint32_t path[BIP32_PATH_MAX_LEN], *path_p = path;
size_t written;
int ret = path_from_string_n(str, str_len, child_num, flags,
path_p, BIP32_PATH_MAX_LEN, &written);

if (ret == WALLY_OK)
ret = bip32_key_from_parent_path(hdkey, path, written, flags, key_out);

return ret;
}

int bip32_key_from_parent_path_str(const struct ext_key *hdkey,
const char *str,
uint32_t child_num, uint32_t flags,
struct ext_key *key_out)
{
return bip32_key_from_parent_path_str_n(hdkey, str, str ? strlen(str) : 0,
child_num, flags, key_out);
}

int bip32_key_from_parent_path_str_n_alloc(const struct ext_key *hdkey,
const char *str, size_t str_len,
uint32_t child_num, uint32_t flags,
struct ext_key **output)
{
int ret;

ALLOC_KEY();
ret = bip32_key_from_parent_path_str_n(hdkey, str, str_len, child_num, flags, *output);
if (ret != WALLY_OK) {
wally_free(*output);
*output = NULL;
}
return ret;
}

int bip32_key_from_parent_path_str_alloc(const struct ext_key *hdkey,
const char *str,
uint32_t child_num, uint32_t flags,
struct ext_key **output)
{
return bip32_key_from_parent_path_str_n_alloc(hdkey, str, str ? strlen(str) : 0,
child_num, flags, output);
}


int bip32_key_init_alloc(uint32_t version, uint32_t depth, uint32_t child_num,
const unsigned char *chain_code, size_t chain_code_len,
const unsigned char *pub_key, size_t pub_key_len,
Expand Down Expand Up @@ -764,7 +904,7 @@ int bip32_key_from_base58_n_alloc(const char *base58, size_t base58_len,
ret = bip32_key_from_base58_n(base58, base58_len, *output);
if (ret != WALLY_OK) {
wally_free(*output);
*output = 0;
*output = NULL;
}
return ret;
}
Expand Down
4 changes: 4 additions & 0 deletions src/swig_java/swig.i
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,10 @@ static jbyteArray create_array(JNIEnv *jenv, const unsigned char* p, size_t len)
%rename("bip32_key_from_parent") bip32_key_from_parent_alloc;
%returns_struct(bip32_key_from_parent_path_alloc, ext_key);
%rename("bip32_key_from_parent_path") bip32_key_from_parent_path_alloc;
%returns_struct(bip32_key_from_parent_path_str_alloc, ext_key);
%rename("bip32_key_from_parent_path_str") bip32_key_from_parent_path_str_alloc;
%returns_struct(bip32_key_from_parent_path_str_n_alloc, ext_key);
%rename("bip32_key_from_parent_path_str_n") bip32_key_from_parent_path_str_n_alloc;
%returns_struct(bip32_key_from_seed_alloc, ext_key);
%rename("bip32_key_from_seed") bip32_key_from_seed_alloc;
%returns_array_(bip32_key_get_chain_code, 2, 3, member_size(ext_key, chain_code));
Expand Down
2 changes: 2 additions & 0 deletions src/swig_python/swig.i
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ static void destroy_words(PyObject *obj) { (void)obj; }
%rename("bip32_key_from_base58") bip32_key_from_base58_alloc;
%rename("bip32_key_from_parent") bip32_key_from_parent_alloc;
%rename("bip32_key_from_parent_path") bip32_key_from_parent_path_alloc;
%rename("bip32_key_from_parent_path_str") bip32_key_from_parent_path_str_alloc;
%rename("bip32_key_from_parent_path_str_n") bip32_key_from_parent_path_str_n_alloc;
%rename("bip32_key_from_seed") bip32_key_from_seed_alloc;
%rename("bip32_key_init") bip32_key_init_alloc;
%rename("bip32_key_unserialize") bip32_key_unserialize_alloc;
Expand Down
Loading

0 comments on commit e01c3b7

Please sign in to comment.