Skip to content

Commit f093843

Browse files
achow101knst
authored andcommitted
Merge bitcoin#25642: Don't wrap around when deriving an extended key at a too large depth
fb9faff extended keys: fail to derive too large depth instead of wrapping around (Antoine Poinsot) 8dc6670 descriptor: don't assert success of extended key derivation (Antoine Poinsot) 50cfc9e (pubk)key: mark Derive() as nodiscard (Antoine Poinsot) 0ca258a descriptor: never ignore the return value when deriving an extended key (Antoine Poinsot) d3599c2 spkman: don't ignore the return value when deriving an extended key (Antoine Poinsot) Pull request description: We would previously silently wrap the derived child's depth back to `0`. Instead, explicitly fail when trying to derive an impossible depth, and handle the error in callers. An extended fuzzing corpus of `descriptor_parse` triggered this behaviour, which was reported by MarcoFalke. Fixes bitcoin#25751. ACKs for top commit: achow101: re-ACK fb9faff instagibbs: utACK bitcoin@fb9faff Tree-SHA512: 9f75c23572ce847239bd15e5497df2960b6bd63c61ea72347959d968b5c4c9a4bfeee284e76bdcd7bacbf9eeb70feee85ffd3e316f353ca6eca30e93aafad343
1 parent 4d0373f commit f093843

File tree

8 files changed

+44
-17
lines changed

8 files changed

+44
-17
lines changed

src/key.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ ECDHSecret CKey::ComputeBIP324ECDHSecret(const EllSwiftPubKey& their_ellswift, c
341341
}
342342

343343
bool CExtKey::Derive(CExtKey &out, unsigned int _nChild) const {
344+
if (nDepth == std::numeric_limits<unsigned char>::max()) return false;
344345
out.nDepth = nDepth + 1;
345346
CKeyID id = key.GetPubKey().GetID();
346347
memcpy(out.vchFingerprint, &id, 4);

src/key.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ class CKey
135135
bool SignCompact(const uint256& hash, std::vector<unsigned char>& vchSig) const;
136136

137137
//! Derive BIP32 child key.
138-
bool Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const;
138+
[[nodiscard]] bool Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const;
139139

140140
/**
141141
* Verify thoroughly whether a private key and a public key match.
@@ -186,7 +186,7 @@ struct CExtKey {
186186

187187
void Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const;
188188
void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]);
189-
bool Derive(CExtKey& out, unsigned int nChild) const;
189+
[[nodiscard]] bool Derive(CExtKey& out, unsigned int nChild) const;
190190
CExtPubKey Neuter() const;
191191
void SetSeed(Span<const std::byte> seed);
192192
};

src/pubkey.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ void CExtPubKey::DecodeWithVersion(const unsigned char code[BIP32_EXTKEY_WITH_VE
309309
}
310310

311311
bool CExtPubKey::Derive(CExtPubKey &out, unsigned int _nChild) const {
312+
if (nDepth == std::numeric_limits<unsigned char>::max()) return false;
312313
out.nDepth = nDepth + 1;
313314
CKeyID id = pubkey.GetID();
314315
memcpy(out.vchFingerprint, &id, 4);

src/pubkey.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ class CPubKey
208208
bool Decompress();
209209

210210
//! Derive BIP32 child pubkey.
211-
bool Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const;
211+
[[nodiscard]] bool Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const;
212212
};
213213

214214
/** An ElligatorSwift-encoded public key. */
@@ -281,7 +281,7 @@ struct CExtPubKey {
281281
void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]);
282282
void EncodeWithVersion(unsigned char code[BIP32_EXTKEY_WITH_VERSION_SIZE]) const;
283283
void DecodeWithVersion(const unsigned char code[BIP32_EXTKEY_WITH_VERSION_SIZE]);
284-
bool Derive(CExtPubKey& out, unsigned int nChild) const;
284+
[[nodiscard]] bool Derive(CExtPubKey& out, unsigned int nChild) const;
285285

286286
void Serialize(CSizeComputer& s) const
287287
{

src/script/descriptor.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ class BIP32PubkeyProvider final : public PubkeyProvider
304304
{
305305
if (!GetExtKey(arg, xprv)) return false;
306306
for (auto entry : m_path) {
307-
xprv.Derive(xprv, entry);
307+
if (!xprv.Derive(xprv, entry)) return false;
308308
if (entry >> 31) {
309309
last_hardened = xprv;
310310
}
@@ -364,14 +364,13 @@ class BIP32PubkeyProvider final : public PubkeyProvider
364364
}
365365
} else {
366366
for (auto entry : m_path) {
367-
der = parent_extkey.Derive(parent_extkey, entry);
368-
assert(der);
367+
if (!parent_extkey.Derive(parent_extkey, entry)) return false;
369368
}
370369
final_extkey = parent_extkey;
371370
if (m_derive == DeriveType::UNHARDENED) der = parent_extkey.Derive(final_extkey, pos);
372371
assert(m_derive != DeriveType::HARDENED);
373372
}
374-
assert(der);
373+
if (!der) return false;
375374

376375
final_info_out = final_info_out_tmp;
377376
key_out = final_extkey.pubkey;
@@ -474,8 +473,8 @@ class BIP32PubkeyProvider final : public PubkeyProvider
474473
CExtKey extkey;
475474
CExtKey dummy;
476475
if (!GetDerivedExtKey(arg, extkey, dummy)) return false;
477-
if (m_derive == DeriveType::UNHARDENED) extkey.Derive(extkey, pos);
478-
if (m_derive == DeriveType::HARDENED) extkey.Derive(extkey, pos | 0x80000000UL);
476+
if (m_derive == DeriveType::UNHARDENED && !extkey.Derive(extkey, pos)) return false;
477+
if (m_derive == DeriveType::HARDENED && !extkey.Derive(extkey, pos | 0x80000000UL)) return false;
479478
key = extkey.key;
480479
return true;
481480
}

src/test/bip32_tests.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,4 +184,22 @@ BOOST_AUTO_TEST_CASE(bip32_test5) {
184184
}
185185
}
186186

187+
BOOST_AUTO_TEST_CASE(bip32_max_depth) {
188+
CExtKey key_parent{DecodeExtKey(test1.vDerive[0].prv)}, key_child;
189+
CExtPubKey pubkey_parent{DecodeExtPubKey(test1.vDerive[0].pub)}, pubkey_child;
190+
191+
// We can derive up to the 255th depth..
192+
for (auto i = 0; i++ < 255;) {
193+
BOOST_CHECK(key_parent.Derive(key_child, 0));
194+
std::swap(key_parent, key_child);
195+
BOOST_CHECK(pubkey_parent.Derive(pubkey_child, 0));
196+
std::swap(pubkey_parent, pubkey_child);
197+
}
198+
199+
// But trying to derive a non-existent 256th depth will fail!
200+
BOOST_CHECK(key_parent.nDepth == 255 && pubkey_parent.nDepth == 255);
201+
BOOST_CHECK(!key_parent.Derive(key_child, 0));
202+
BOOST_CHECK(!pubkey_parent.Derive(pubkey_child, 0));
203+
}
204+
187205
BOOST_AUTO_TEST_SUITE_END()

src/test/descriptor_tests.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string&
180180
for (const auto& xpub_pair : parent_xpub_cache) {
181181
const CExtPubKey& xpub = xpub_pair.second;
182182
CExtPubKey der;
183-
xpub.Derive(der, i);
183+
BOOST_CHECK(xpub.Derive(der, i));
184184
pubkeys.insert(der.pubkey);
185185
}
186186
for (const auto& origin_pair : script_provider_cached.origins) {
@@ -203,7 +203,7 @@ void DoCheck(const std::string& prv, const std::string& pub, const std::string&
203203
const CExtPubKey& xpub = xpub_pair.second;
204204
pubkeys.insert(xpub.pubkey);
205205
CExtPubKey der;
206-
xpub.Derive(der, i);
206+
BOOST_CHECK(xpub.Derive(der, i));
207207
pubkeys.insert(der.pubkey);
208208
}
209209
for (const auto& origin_pair : script_provider_cached.origins) {

src/wallet/hdchain.cpp

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,14 @@ uint256 CHDChain::GetSeedHash()
129129
return Hash(vchSeed);
130130
}
131131

132+
//! Try to derive an extended key, throw if it fails.
133+
static void DeriveExtKey(CExtKey& key_in, unsigned int index, CExtKey& key_out)
134+
{
135+
if (!key_in.Derive(key_out, index)) {
136+
throw std::runtime_error("Could not derive extended key");
137+
}
138+
}
139+
132140
void CHDChain::DeriveChildExtKey(uint32_t nAccountIndex, bool fInternal, uint32_t nChildIndex, CExtKey& extKeyRet, KeyOriginInfo& key_origin)
133141
{
134142
LOCK(cs);
@@ -146,15 +154,15 @@ void CHDChain::DeriveChildExtKey(uint32_t nAccountIndex, bool fInternal, uint32_
146154
// (keys >= 0x80000000 are hardened after bip32)
147155

148156
// derive m/purpose'
149-
masterKey.Derive(purposeKey, 44 | 0x80000000);
157+
DeriveExtKey(masterKey, 44 | 0x80000000, purposeKey);
150158
// derive m/purpose'/coin_type'
151-
purposeKey.Derive(cointypeKey, Params().ExtCoinType() | 0x80000000);
159+
DeriveExtKey(purposeKey, Params().ExtCoinType() | 0x80000000, cointypeKey);
152160
// derive m/purpose'/coin_type'/account'
153-
cointypeKey.Derive(accountKey, nAccountIndex | 0x80000000);
161+
DeriveExtKey(cointypeKey, nAccountIndex | 0x80000000, accountKey);
154162
// derive m/purpose'/coin_type'/account'/change
155-
accountKey.Derive(changeKey, fInternal ? 1 : 0);
163+
DeriveExtKey(accountKey, fInternal ? 1 : 0, changeKey);
156164
// derive m/purpose'/coin_type'/account'/change/address_index
157-
changeKey.Derive(extKeyRet, nChildIndex);
165+
DeriveExtKey(changeKey, nChildIndex, extKeyRet);
158166

159167
#ifdef ENABLE_WALLET
160168
// We should never ever update an already existing key_origin here

0 commit comments

Comments
 (0)