Skip to content

Commit 82df4c6

Browse files
committed
Add descriptor expansion cache
1 parent 1eda33a commit 82df4c6

File tree

3 files changed

+75
-30
lines changed

3 files changed

+75
-30
lines changed

src/script/descriptor.cpp

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ struct PubkeyProvider
4040
{
4141
virtual ~PubkeyProvider() = default;
4242

43-
/** Derive a public key. */
44-
virtual bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info) const = 0;
43+
/** Derive a public key. If key==nullptr, only info is desired. */
44+
virtual bool GetPubKey(int pos, const SigningProvider& arg, CPubKey* key, KeyOriginInfo& info) const = 0;
4545

4646
/** Whether this represent multiple public keys at different positions. */
4747
virtual bool IsRange() const = 0;
@@ -68,7 +68,7 @@ class OriginPubkeyProvider final : public PubkeyProvider
6868

6969
public:
7070
OriginPubkeyProvider(KeyOriginInfo info, std::unique_ptr<PubkeyProvider> provider) : m_origin(std::move(info)), m_provider(std::move(provider)) {}
71-
bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info) const override
71+
bool GetPubKey(int pos, const SigningProvider& arg, CPubKey* key, KeyOriginInfo& info) const override
7272
{
7373
if (!m_provider->GetPubKey(pos, arg, key, info)) return false;
7474
std::copy(std::begin(m_origin.fingerprint), std::end(m_origin.fingerprint), info.fingerprint);
@@ -94,9 +94,9 @@ class ConstPubkeyProvider final : public PubkeyProvider
9494

9595
public:
9696
ConstPubkeyProvider(const CPubKey& pubkey) : m_pubkey(pubkey) {}
97-
bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info) const override
97+
bool GetPubKey(int pos, const SigningProvider& arg, CPubKey* key, KeyOriginInfo& info) const override
9898
{
99-
key = m_pubkey;
99+
if (key) *key = m_pubkey;
100100
info.path.clear();
101101
CKeyID keyid = m_pubkey.GetID();
102102
std::copy(keyid.begin(), keyid.begin() + sizeof(info.fingerprint), info.fingerprint);
@@ -152,26 +152,28 @@ class BIP32PubkeyProvider final : public PubkeyProvider
152152
BIP32PubkeyProvider(const CExtPubKey& extkey, KeyPath path, DeriveType derive) : m_extkey(extkey), m_path(std::move(path)), m_derive(derive) {}
153153
bool IsRange() const override { return m_derive != DeriveType::NO; }
154154
size_t GetSize() const override { return 33; }
155-
bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info) const override
155+
bool GetPubKey(int pos, const SigningProvider& arg, CPubKey* key, KeyOriginInfo& info) const override
156156
{
157-
if (IsHardened()) {
158-
CExtKey extkey;
159-
if (!GetExtKey(arg, extkey)) return false;
160-
for (auto entry : m_path) {
161-
extkey.Derive(extkey, entry);
162-
}
163-
if (m_derive == DeriveType::UNHARDENED) extkey.Derive(extkey, pos);
164-
if (m_derive == DeriveType::HARDENED) extkey.Derive(extkey, pos | 0x80000000UL);
165-
key = extkey.Neuter().pubkey;
166-
} else {
167-
// TODO: optimize by caching
168-
CExtPubKey extkey = m_extkey;
169-
for (auto entry : m_path) {
170-
extkey.Derive(extkey, entry);
157+
if (key) {
158+
if (IsHardened()) {
159+
CExtKey extkey;
160+
if (!GetExtKey(arg, extkey)) return false;
161+
for (auto entry : m_path) {
162+
extkey.Derive(extkey, entry);
163+
}
164+
if (m_derive == DeriveType::UNHARDENED) extkey.Derive(extkey, pos);
165+
if (m_derive == DeriveType::HARDENED) extkey.Derive(extkey, pos | 0x80000000UL);
166+
*key = extkey.Neuter().pubkey;
167+
} else {
168+
// TODO: optimize by caching
169+
CExtPubKey extkey = m_extkey;
170+
for (auto entry : m_path) {
171+
extkey.Derive(extkey, entry);
172+
}
173+
if (m_derive == DeriveType::UNHARDENED) extkey.Derive(extkey, pos);
174+
assert(m_derive != DeriveType::HARDENED);
175+
*key = extkey.pubkey;
171176
}
172-
if (m_derive == DeriveType::UNHARDENED) extkey.Derive(extkey, pos);
173-
assert(m_derive != DeriveType::HARDENED);
174-
key = extkey.pubkey;
175177
}
176178
CKeyID keyid = m_extkey.pubkey.GetID();
177179
std::copy(keyid.begin(), keyid.begin() + sizeof(info.fingerprint), info.fingerprint);
@@ -285,20 +287,33 @@ class DescriptorImpl : public Descriptor
285287

286288
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override final { return ToStringHelper(&arg, out, true); }
287289

288-
bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const final
290+
bool ExpandHelper(int pos, const SigningProvider& arg, Span<const unsigned char>* cache_read, std::vector<CScript>& output_scripts, FlatSigningProvider& out, std::vector<unsigned char>* cache_write) const
289291
{
290292
std::vector<std::pair<CPubKey, KeyOriginInfo>> entries;
291293
entries.reserve(m_pubkey_args.size());
292294

293295
// Construct temporary data in `entries` and `subscripts`, to avoid producing output in case of failure.
294296
for (const auto& p : m_pubkey_args) {
295297
entries.emplace_back();
296-
if (!p->GetPubKey(pos, arg, entries.back().first, entries.back().second)) return false;
298+
if (!p->GetPubKey(pos, arg, cache_read ? nullptr : &entries.back().first, entries.back().second)) return false;
299+
if (cache_read) {
300+
// Cached expanded public key exists, use it.
301+
if (cache_read->size() == 0) return false;
302+
bool compressed = ((*cache_read)[0] == 0x02 || (*cache_read)[0] == 0x03) && cache_read->size() >= 33;
303+
bool uncompressed = ((*cache_read)[0] == 0x04) && cache_read->size() >= 65;
304+
if (!(compressed || uncompressed)) return false;
305+
CPubKey pubkey(cache_read->begin(), cache_read->begin() + (compressed ? 33 : 65));
306+
entries.back().first = pubkey;
307+
*cache_read = cache_read->subspan(compressed ? 33 : 65);
308+
}
309+
if (cache_write) {
310+
cache_write->insert(cache_write->end(), entries.back().first.begin(), entries.back().first.end());
311+
}
297312
}
298313
std::vector<CScript> subscripts;
299314
if (m_script_arg) {
300315
FlatSigningProvider subprovider;
301-
if (!m_script_arg->Expand(pos, arg, subscripts, subprovider)) return false;
316+
if (!m_script_arg->ExpandHelper(pos, arg, cache_read, subscripts, subprovider, cache_write)) return false;
302317
out = Merge(out, subprovider);
303318
}
304319

@@ -322,6 +337,17 @@ class DescriptorImpl : public Descriptor
322337
}
323338
return true;
324339
}
340+
341+
bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out, std::vector<unsigned char>* cache = nullptr) const final
342+
{
343+
return ExpandHelper(pos, provider, nullptr, output_scripts, out, cache);
344+
}
345+
346+
bool ExpandFromCache(int pos, const std::vector<unsigned char>& cache, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const final
347+
{
348+
Span<const unsigned char> span = MakeSpan(cache);
349+
return ExpandHelper(pos, DUMMY_SIGNING_PROVIDER, &span, output_scripts, out, nullptr) && span.size() == 0;
350+
}
325351
};
326352

327353
/** Construct a vector with one element, which is moved into it. */

src/script/descriptor.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,18 @@ struct Descriptor {
4848
* provider: the provider to query for private keys in case of hardened derivation.
4949
* output_script: the expanded scriptPubKeys will be put here.
5050
* out: scripts and public keys necessary for solving the expanded scriptPubKeys will be put here (may be equal to provider).
51+
* cache: vector which will be overwritten with cache data necessary to-evaluate the descriptor at this point without access to private keys.
5152
*/
52-
virtual bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const = 0;
53+
virtual bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out, std::vector<unsigned char>* cache = nullptr) const = 0;
54+
55+
/** Expand a descriptor at a specified position using cached expansion data.
56+
*
57+
* pos: the position at which to expand the descriptor. If IsRange() is false, this is ignored.
58+
* cache: vector from which cached expansion data will be read.
59+
* output_script: the expanded scriptPubKeys will be put here.
60+
* out: scripts and public keys necessary for solving the expanded scriptPubKeys will be put here (may be equal to provider).
61+
*/
62+
virtual bool ExpandFromCache(int pos, const std::vector<unsigned char>& cache, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const = 0;
5363
};
5464

5565
/** Parse a descriptor string. Included private keys are put in out. Returns nullptr if parsing fails. */

src/test/descriptor_tests.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,18 @@ void Check(const std::string& prv, const std::string& pub, int flags, const std:
8888
const auto& ref = scripts[(flags & RANGE) ? i : 0];
8989
for (int t = 0; t < 2; ++t) {
9090
const FlatSigningProvider& key_provider = (flags & HARDENED) ? keys_priv : keys_pub;
91-
FlatSigningProvider script_provider;
92-
std::vector<CScript> spks;
93-
BOOST_CHECK((t ? parse_priv : parse_pub)->Expand(i, key_provider, spks, script_provider));
91+
FlatSigningProvider script_provider, script_provider_cached;
92+
std::vector<CScript> spks, spks_cached;
93+
std::vector<unsigned char> cache;
94+
BOOST_CHECK((t ? parse_priv : parse_pub)->Expand(i, key_provider, spks, script_provider, &cache));
95+
96+
// Try to expand again using cached data, and compare.
97+
BOOST_CHECK(parse_pub->ExpandFromCache(i, cache, spks_cached, script_provider_cached));
98+
BOOST_CHECK(spks == spks_cached);
99+
BOOST_CHECK(script_provider.pubkeys == script_provider_cached.pubkeys);
100+
BOOST_CHECK(script_provider.scripts == script_provider_cached.scripts);
101+
BOOST_CHECK(script_provider.origins == script_provider_cached.origins);
102+
94103
BOOST_CHECK_EQUAL(spks.size(), ref.size());
95104
for (size_t n = 0; n < spks.size(); ++n) {
96105
BOOST_CHECK_EQUAL(ref[n], HexStr(spks[n].begin(), spks[n].end()));

0 commit comments

Comments
 (0)