diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 6be4057366..d1e9682416 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -136,6 +136,7 @@ UniValue getdescriptorinfo(const JSONRPCRequest& request) RPCResult{ "{\n" " \"descriptor\" : \"desc\", (string) The descriptor in canonical form, without private keys\n" + " \"checksum\" : \"chksum\", (string) The checksum for the input descriptor\n" " \"isrange\" : true|false, (boolean) Whether the descriptor is ranged\n" " \"issolvable\" : true|false, (boolean) Whether the descriptor is solvable\n" " \"hasprivatekeys\" : true|false, (boolean) Whether the input descriptor contained at least one private key\n" @@ -156,6 +157,7 @@ UniValue getdescriptorinfo(const JSONRPCRequest& request) UniValue result(UniValue::VOBJ); result.pushKV("descriptor", desc->ToString()); + result.pushKV("checksum", GetDescriptorChecksum(request.params[0].get_str())); result.pushKV("isrange", desc->IsRange()); result.pushKV("issolvable", desc->IsSolvable()); result.pushKV("hasprivatekeys", provider.keys.size() > 0); diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 327af62a4f..d2b370b65d 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -914,27 +914,42 @@ std::unique_ptr InferScript(const CScript& script, ParseScriptCo } // namespace -std::unique_ptr Parse(const std::string& descriptor, FlatSigningProvider& out, bool require_checksum) +/** Check a descriptor checksum, and update desc to be the checksum-less part. */ +bool CheckChecksum(Span& sp, bool require_checksum, std::string* out_checksum = nullptr) { - Span sp(descriptor.data(), descriptor.size()); - - // Checksum checks auto check_split = Split(sp, '#'); - if (check_split.size() > 2) return nullptr; // Multiple '#' symbols - if (check_split.size() == 1 && require_checksum) return nullptr; // Missing checksum + if (check_split.size() > 2) return false; // Multiple '#' symbols + if (check_split.size() == 1 && require_checksum) return false; // Missing checksum + if (check_split.size() == 2) { + if (check_split[1].size() != 8) return false; // Unexpected length for checksum + } + auto checksum = DescriptorChecksum(check_split[0]); + if (checksum.empty()) return false; // Invalid characters in payload if (check_split.size() == 2) { - if (check_split[1].size() != 8) return nullptr; // Unexpected length for checksum - auto checksum = DescriptorChecksum(check_split[0]); - if (checksum.empty()) return nullptr; // Invalid characters in payload - if (!std::equal(checksum.begin(), checksum.end(), check_split[1].begin())) return nullptr; // Checksum mismatch + if (!std::equal(checksum.begin(), checksum.end(), check_split[1].begin())) return false; // Checksum mismatch } + if (out_checksum) *out_checksum = std::move(checksum); sp = check_split[0]; + return true; +} +std::unique_ptr Parse(const std::string& descriptor, FlatSigningProvider& out, bool require_checksum) +{ + Span sp(descriptor.data(), descriptor.size()); + if (!CheckChecksum(sp, require_checksum)) return nullptr; auto ret = ParseScript(sp, ParseScriptContext::TOP, out); if (sp.size() == 0 && ret) return std::unique_ptr(std::move(ret)); return nullptr; } +std::string GetDescriptorChecksum(const std::string& descriptor) +{ + std::string ret; + Span sp(descriptor.data(), descriptor.size()); + if (!CheckChecksum(sp, false, &ret)) return ""; + return ret; +} + std::unique_ptr InferDescriptor(const CScript& script, const SigningProvider& provider) { return InferScript(script, ParseScriptContext::TOP, provider); diff --git a/src/script/descriptor.h b/src/script/descriptor.h index a34e9f0d8a..eae1e262cd 100644 --- a/src/script/descriptor.h +++ b/src/script/descriptor.h @@ -81,6 +81,14 @@ struct Descriptor { */ std::unique_ptr Parse(const std::string& descriptor, FlatSigningProvider& out, bool require_checksum = false); +/** Get the checksum for a descriptor. + * + * If it already has one, and it is correct, return the checksum in the input. + * If it already has one that is wrong, return "". + * If it does not already have one, return the checksum that would need to be added. + */ +std::string GetDescriptorChecksum(const std::string& descriptor); + /** Find a descriptor for the specified script, using information from provider where possible. * * A non-ranged descriptor which only generates the specified script will be returned in all diff --git a/test/functional/wallet_address_types.py b/test/functional/wallet_address_types.py index a40613dfc7..4e4ed8f26b 100755 --- a/test/functional/wallet_address_types.py +++ b/test/functional/wallet_address_types.py @@ -175,6 +175,10 @@ class AddressTypeTest(BitcoinTestFramework): assert info['desc'] == descsum_create(info['desc'][:-9]) # Verify that stripping the checksum and feeding it to getdescriptorinfo roundtrips assert info['desc'] == self.nodes[0].getdescriptorinfo(info['desc'][:-9])['descriptor'] + assert_equal(info['desc'][-8:], self.nodes[0].getdescriptorinfo(info['desc'][:-9])['checksum']) + # Verify that keeping the checksum and feeding it to getdescriptorinfo roundtrips + assert info['desc'] == self.nodes[0].getdescriptorinfo(info['desc'])['descriptor'] + assert_equal(info['desc'][-8:], self.nodes[0].getdescriptorinfo(info['desc'])['checksum']) if not multisig and typ == 'legacy': # P2PKH