Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 24 additions & 14 deletions src/bip32.c
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ int bip32_path_from_str_len(const char *str, uint32_t child_num,
size_t *written)
{
return bip32_path_from_str_n_len(str, str ? strlen(str) : 0, child_num,
multi_index, flags, written);
multi_index, flags, written);
}

int bip32_path_str_n_get_features(const char *str, size_t str_len,
Expand Down Expand Up @@ -419,10 +419,10 @@ int bip32_key_from_seed(const unsigned char *bytes, size_t bytes_len,
}

#define ALLOC_KEY() \
if (!output) \
if (!output) \
return WALLY_EINVAL; \
*output = wally_calloc(sizeof(struct ext_key)); \
if (!*output) \
*output = wally_calloc(sizeof(struct ext_key)); \
if (!*output) \
return WALLY_ENOMEM

int bip32_key_from_seed_custom_alloc(const unsigned char *bytes, size_t bytes_len,
Expand Down Expand Up @@ -471,6 +471,10 @@ static bool key_is_valid(const struct ext_key *hdkey)
mem_is_zero(hdkey->pub_key + 1, sizeof(hdkey->pub_key) - 1))
return false;

if (wally_ec_public_key_verify(hdkey->pub_key,
sizeof(hdkey->pub_key)) != WALLY_OK)
return false;

if (hdkey->priv_key[0] != BIP32_FLAG_KEY_PUBLIC &&
hdkey->priv_key[0] != BIP32_FLAG_KEY_PRIVATE)
return false;
Expand All @@ -480,7 +484,8 @@ static bool key_is_valid(const struct ext_key *hdkey)
return false;

if (is_master &&
!mem_is_zero(hdkey->parent160, sizeof(hdkey->parent160)))
(hdkey->child_num != 0 ||
!mem_is_zero(hdkey->parent160, sizeof(hdkey->parent160))))
return false;

return true;
Expand Down Expand Up @@ -581,6 +586,11 @@ int bip32_key_unserialize(const unsigned char *bytes, size_t bytes_len,
bip32_key_strip_private_key(key_out);
}

/* Validate the fully populated key (covers depth-0 fingerprint/child_num,
* public key point-on-curve, and all other structural checks) */
if (!key_is_valid(key_out))
return wipe_key_fail(key_out);

key_compute_hash160(key_out);
return WALLY_OK;
}
Expand Down Expand Up @@ -1137,9 +1147,9 @@ static int getb_impl(const struct ext_key *hdkey,
}

#define GET_B(name) \
int bip32_key_get_ ## name(const struct ext_key *hdkey, unsigned char *bytes_out, size_t len) { \
return getb_impl(hdkey, hdkey->name, sizeof(hdkey->name), bytes_out, len); \
}
int bip32_key_get_ ## name(const struct ext_key *hdkey, unsigned char *bytes_out, size_t len) { \
return getb_impl(hdkey, hdkey->name, sizeof(hdkey->name), bytes_out, len); \
}

GET_B(chain_code)
GET_B(parent160)
Expand All @@ -1161,12 +1171,12 @@ int bip32_key_get_priv_key(const struct ext_key *hdkey, unsigned char *bytes_out


#define GET_I(name) \
int bip32_key_get_ ## name(const struct ext_key *hdkey, size_t *written) { \
if (written) *written = 0; \
if (!hdkey || !written) return WALLY_EINVAL; \
*written = hdkey->name; \
return WALLY_OK; \
}
int bip32_key_get_ ## name(const struct ext_key *hdkey, size_t *written) { \
if (written) *written = 0; \
if (!hdkey || !written) return WALLY_EINVAL; \
*written = hdkey->name; \
return WALLY_OK; \
}

GET_I(depth)
GET_I(child_num)
Expand Down
76 changes: 76 additions & 0 deletions src/test/test_bip32.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,82 @@ def test_key_init(self):
def test_bip32_vectors(self):
self.do_test_vector(vec_1)
self.do_test_vector(vec_3)

def test_vector_5_invalid_keys(self):
"""BIP32 Test Vector 5: invalid extended keys must be rejected"""
# Each entry is (base58_key, description)
invalid_keys = [
# pubkey version / prvkey mismatch
('xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3'
'zgtU6LBpB85b3D2yc8sfvZU521AAwdZafEz7mnzBBsz4wKY5fTtTQBm',
'pubkey version / prvkey mismatch'),
# prvkey version / pubkey mismatch
('xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63o'
'StZzFGTQQD3dC4H2D5GBj7vWvSQaaBv5cxi9gafk7NF3pnBju6dwKvH',
'prvkey version / pubkey mismatch'),
# invalid pubkey prefix 04
('xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3'
'zgtU6Txnt3siSujt9RCVYsx4qHZGc62TG4McvMGcAUjeuwZdduYEvFn',
'invalid pubkey prefix 04'),
# invalid prvkey prefix 04
('xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63o'
'StZzFGpWnsj83BHtEy5Zt8CcDr1UiRXuWCmTQLxEK9vbz5gPstX92JQ',
'invalid prvkey prefix 04'),
# invalid pubkey prefix 01
('xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3'
'zgtU6N8ZMMXctdiCjxTNq964yKkwrkBJJwpzZS4HS2fxvyYUA4q2Xe4',
'invalid pubkey prefix 01'),
# invalid prvkey prefix 01
('xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63o'
'StZzFAzHGBP2UuGCqWLTAPLcMtD9y5gkZ6Eq3Rjuahrv17fEQ3Qen6J',
'invalid prvkey prefix 01'),
# zero depth with non-zero parent fingerprint (xprv)
('xprv9s2SPatNQ9Vc6GTbVMFPFo7jsaZySyzk7L8n2uqKXJen3KUmvQNT'
'uLh3fhZMBoG3G4ZW1N2kZuHEPY53qmbZzCHshoQnNf4GvELZfqTUrcv',
'zero depth with non-zero parent fingerprint (xprv)'),
# zero depth with non-zero parent fingerprint (xpub)
('xpub661no6RGEX3uJkY4bNnPcw4URcQTrSibUZ4NqJEw5eBkv7ovTwgi'
'T91XX27VbEXGENhYRCf7hyEbWrR3FewATdCEebj6znwMfQkhRYHRLpJ',
'zero depth with non-zero parent fingerprint (xpub)'),
# zero depth with non-zero index (xprv)
('xprv9s21ZrQH4r4TsiLvyLXqM9P7k1K3EYhA1kkD6xuquB5i39AU8KF4'
'2acDyL3qsDbU9NmZn6MsGSUYZEsuoePmjzsB3eFKSUEh3Gu1N3cqVUN',
'zero depth with non-zero index (xprv)'),
# zero depth with non-zero index (xpub)
('xpub661MyMwAuDcm6CRQ5N4qiHKrJ39Xe1R1NyfouMKTTWcguwVcfrZJ'
'aNvhpebzGerh7gucBvzEQWRugZDuDXjNDRmXzSZe4c7mnTK97pTvGS8',
'zero depth with non-zero index (xpub)'),
# unknown extended key version (1)
('DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8X'
'nmdfHGMQzT7ayAmfo4z3gY5KfbrZWZ6St24UVf2Qgo6oujFktLHdHY4',
'unknown extended key version (1)'),
# unknown extended key version (2)
('DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8X'
'nmdfHPmHJiEDXkTiJTVV9rHEBUem2mwVbbNfvT2MTcAqj3nesx8uBf9',
'unknown extended key version (2)'),
# private key 0 not in 1..n-1
('xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63o'
'StZzF93Y5wvzdUayhgkkFoicQZcP3y52uPPxFnfoLZB21Teqt1VvEHx',
'private key 0 not in 1..n-1'),
# private key n not in 1..n-1
('xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63o'
'StZzFAzHGBP2UuGCqWLTAPLcMtD5SDKr24z3aiUvKr9bJpdrcLg1y3G',
'private key n not in 1..n-1'),
# invalid pubkey 020000000000000000000000000000000000000000000000000000000000000007
('xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3'
'zgtU6Q5JXayek4PRsn35jii4veMimro1xefsM58PgBMrvdYre8QyULY',
'invalid pubkey'),
# invalid checksum
('xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPP'
'qjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHL',
'invalid checksum'),
]

key_out = POINTER(ext_key)()
for base58_key, description in invalid_keys:
ret = bip32_key_from_base58_alloc(utf8(base58_key), byref(key_out))
self.assertEqual(ret, WALLY_EINVAL,
'Expected EINVAL for: ' + description)

def do_test_vector(self, vec):

Expand Down