Skip to content

Commit 0ef7f9c

Browse files
committed
bip32: Only allow uppercase path elements with BIP32_FLAG_ALLOW_UPPER
1 parent 0e71801 commit 0ef7f9c

File tree

4 files changed

+27
-8
lines changed

4 files changed

+27
-8
lines changed

include/wally_bip32.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ extern "C" {
3737
#define BIP32_FLAG_STR_WILDCARD 0x8
3838
/** Do not allow a leading ``m``/``M`` or ``/`` in path string expressions */
3939
#define BIP32_FLAG_STR_BARE 0x10
40+
/** Allow upper as well as lower case 'M'/'H' in path string expressions */
41+
#define BIP32_FLAG_ALLOW_UPPER 0x20
4042

4143
/** Version codes for extended keys */
4244
#define BIP32_VER_MAIN_PUBLIC 0x0488B21E

src/bip32.c

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
BIP32_FLAG_SKIP_HASH | \
1515
BIP32_FLAG_KEY_TWEAK_SUM | \
1616
BIP32_FLAG_STR_WILDCARD | \
17-
BIP32_FLAG_STR_BARE)
17+
BIP32_FLAG_STR_BARE | \
18+
BIP32_FLAG_ALLOW_UPPER)
1819

1920
static const unsigned char SEED[] = {
2021
'B', 'i', 't', 'c', 'o', 'i', 'n', ' ', 's', 'e', 'e', 'd'
@@ -85,16 +86,17 @@ static bool version_is_mainnet(uint32_t ver)
8586
return ver == BIP32_VER_MAIN_PRIVATE || ver == BIP32_VER_MAIN_PUBLIC;
8687
}
8788

88-
static bool is_hardened_indicator(char c)
89+
static bool is_hardened_indicator(char c, bool allow_upper)
8990
{
90-
return c == '\'' || c == 'h' || c == 'H';
91+
return c == '\'' || c == 'h' || (allow_upper && c == 'H');
9192
}
9293

9394
static int path_from_string_n(const char *str, size_t str_len,
9495
uint32_t child_num, uint32_t flags,
9596
uint32_t *child_path, uint32_t child_path_len,
9697
size_t *written)
9798
{
99+
const bool allow_upper = flags & BIP32_FLAG_ALLOW_UPPER;
98100
size_t start, i = 0;
99101
uint64_t v;
100102

@@ -107,7 +109,7 @@ static int path_from_string_n(const char *str, size_t str_len,
107109
if (i < str_len && str[i] == '/')
108110
goto fail; /* bare path must start with a number */
109111
} else {
110-
if (i < str_len && (str[i] == 'm' || str[i] == 'M'))
112+
if (i < str_len && (str[i] == 'm' || (allow_upper && str[i] == 'M')))
111113
++i; /* Skip */
112114
if (i < str_len && str[i] == '/')
113115
++i; /* Skip */
@@ -127,7 +129,7 @@ static int path_from_string_n(const char *str, size_t str_len,
127129
/* No number found */
128130
if (str[i] == '/') {
129131
if (i && (str[i - 1] < '0' || str[i - 1] > '9') &&
130-
!is_hardened_indicator(str[i - 1]) && str[i - 1] != '*')
132+
!is_hardened_indicator(str[i - 1], allow_upper) && str[i - 1] != '*')
131133
goto fail; /* Only valid after number/wildcard/hardened indicator */
132134
++i;
133135
if (i == str_len || str[i] == '/')
@@ -147,7 +149,7 @@ static int path_from_string_n(const char *str, size_t str_len,
147149
v = child_num; /* Use the given child number for the wildcard value */
148150
}
149151

150-
if (is_hardened_indicator(str[i])) {
152+
if (is_hardened_indicator(str[i], allow_upper)) {
151153
v |= BIP32_INITIAL_HARDENED_CHILD;
152154
++i;
153155
}

src/ctest/test_descriptor.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,10 @@ static const struct descriptor_err_test {
787787
"descriptor errchk - invalid checksum",
788788
"wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)#8rap84p2",
789789
WALLY_NETWORK_BITCOIN_MAINNET
790+
},{
791+
"descriptor errchk - upper case hardened indicator",
792+
"pkh(xprvA2YKGLieCs6cWCiczALiH1jzk3VCCS5M1pGQfWPkamCdR9UpBgE2Gb8AKAyVjKHkz8v37avcfRjdcnP19dVAmZrvZQfvTcXXSAiFNQ6tTtU/1H/2)",
793+
WALLY_NETWORK_BITCOIN_MAINNET
790794
},{
791795
"descriptor errchk - privkey - unmatch network1",
792796
"wpkh(cSMSHUGbEiZQUXVw9zA33yT3m8fgC27rn2XEGZJupwCpsRS3rAYa)",

src/test/test_bip32.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99

1010
FLAG_KEY_PRIVATE, FLAG_KEY_PUBLIC, FLAG_SKIP_HASH, = 0x0, 0x1, 0x2
1111
FLAG_KEY_TWEAK_SUM, FLAG_STR_WILDCARD, FLAG_STR_BARE = 0x4, 0x8, 0x10
12-
ALL_DEFINED_FLAGS = FLAG_KEY_PRIVATE | FLAG_KEY_PUBLIC | FLAG_SKIP_HASH
12+
FLAG_ALLOW_UPPER = 0x20
13+
ALL_DEFINED_FLAGS = FLAG_KEY_PRIVATE | FLAG_KEY_PUBLIC | FLAG_SKIP_HASH | \
14+
FLAG_KEY_TWEAK_SUM | FLAG_STR_WILDCARD | FLAG_STR_BARE | FLAG_ALLOW_UPPER
1315
BIP32_SERIALIZED_LEN = 78
1416
BIP32_FLAG_SKIP_HASH = 0x2
1517
EMPTY_PRIV_KEY = utf8('01' + ('00') * 32)
@@ -183,6 +185,13 @@ def derive_key_by_path(self, parent, path, flags, expected=WALLY_OK):
183185
ret = bip32_key_from_parent_path_str(byref(parent), str_path, 0,
184186
flags, byref(str_key_out))
185187
self.assertEqual(ret, expected)
188+
if expected == WALLY_OK:
189+
# Verify that upper case is allowed with FLAG_ALLOW_UPPER
190+
str_path = str_path.upper()
191+
flags |= FLAG_ALLOW_UPPER
192+
ret = bip32_key_from_parent_path_str(byref(parent), str_path, 0,
193+
flags, byref(str_key_out))
194+
self.assertEqual(ret, expected)
186195
return key_out, str_key_out
187196

188197
def compare_keys(self, key, expected, flags):
@@ -428,7 +437,7 @@ def get_paths(path):
428437
self.assertEqual(ret, WALLY_EINVAL)
429438

430439
c_path, str_path = get_paths(path_)
431-
master.depth = 0xff # Cant derive from a parent of depth 255
440+
master.depth = 0xff # Can't derive from a parent of depth 255
432441
ret = bip32_key_from_parent(m, 5, FLAG_KEY_PUBLIC, key_out)
433442
self.assertEqual(ret, WALLY_EINVAL)
434443
ret = bip32_key_from_parent_path(m, c_path, len(c_path), FLAG_KEY_PUBLIC, key_out)
@@ -442,6 +451,8 @@ def get_paths(path):
442451
cases = [('m', 0, 0), # Empty resulting path (1)
443452
('m/', 0, 0), # Empty resulting path (2)
444453
('/', 0, 0), # Empty resulting path (3)
454+
('M/1', 0, 0), # Uppercase M without flag
455+
('m/1H', 0, 0), # Uppercase H without flag
445456
('//', 0, 0), # Trailing slash (1)
446457
('/1/', 0, 0), # Trailing slash (2)
447458
('m/1', B, 0), # Non-bare path (1)

0 commit comments

Comments
 (0)