Changeset View
Changeset View
Standalone View
Standalone View
src/script/descriptor.cpp
Show First 20 Lines • Show All 794 Lines • ▼ Show 20 Lines | std::vector<Span<const char>> Split(const Span<const char> &sp, char sep) { | ||||
return ret; | return ret; | ||||
} | } | ||||
/** | /** | ||||
* Parse a key path, being passed a split list of elements (the first element is | * Parse a key path, being passed a split list of elements (the first element is | ||||
* ignored). | * ignored). | ||||
*/ | */ | ||||
NODISCARD bool ParseKeyPath(const std::vector<Span<const char>> &split, | NODISCARD bool ParseKeyPath(const std::vector<Span<const char>> &split, | ||||
KeyPath &out) { | KeyPath &out, std::string &error) { | ||||
for (size_t i = 1; i < split.size(); ++i) { | for (size_t i = 1; i < split.size(); ++i) { | ||||
Span<const char> elem = split[i]; | Span<const char> elem = split[i]; | ||||
bool hardened = false; | bool hardened = false; | ||||
if (elem.size() > 0 && | if (elem.size() > 0 && | ||||
(elem[elem.size() - 1] == '\'' || elem[elem.size() - 1] == 'h')) { | (elem[elem.size() - 1] == '\'' || elem[elem.size() - 1] == 'h')) { | ||||
elem = elem.first(elem.size() - 1); | elem = elem.first(elem.size() - 1); | ||||
hardened = true; | hardened = true; | ||||
} | } | ||||
uint32_t p; | uint32_t p; | ||||
if (!ParseUInt32(std::string(elem.begin(), elem.end()), &p) || | if (!ParseUInt32(std::string(elem.begin(), elem.end()), &p) || | ||||
p > 0x7FFFFFFFUL) { | p > 0x7FFFFFFFUL) { | ||||
error = strprintf("Key path value %u is out of range", p); | |||||
return false; | return false; | ||||
} | } | ||||
out.push_back(p | (uint32_t(hardened) << 31)); | out.push_back(p | (uint32_t(hardened) << 31)); | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
/** Parse a public key that excludes origin information. */ | /** Parse a public key that excludes origin information. */ | ||||
std::unique_ptr<PubkeyProvider> ParsePubkeyInner(const Span<const char> &sp, | std::unique_ptr<PubkeyProvider> ParsePubkeyInner(const Span<const char> &sp, | ||||
FlatSigningProvider &out) { | FlatSigningProvider &out, | ||||
std::string &error) { | |||||
auto split = Split(sp, '/'); | auto split = Split(sp, '/'); | ||||
std::string str(split[0].begin(), split[0].end()); | std::string str(split[0].begin(), split[0].end()); | ||||
if (split.size() == 1) { | if (split.size() == 1) { | ||||
if (IsHex(str)) { | if (IsHex(str)) { | ||||
std::vector<uint8_t> data = ParseHex(str); | std::vector<uint8_t> data = ParseHex(str); | ||||
CPubKey pubkey(data); | CPubKey pubkey(data); | ||||
if (pubkey.IsFullyValid()) { | if (pubkey.IsFullyValid()) { | ||||
return std::make_unique<ConstPubkeyProvider>(pubkey); | return std::make_unique<ConstPubkeyProvider>(pubkey); | ||||
} | } | ||||
} | } | ||||
CKey key = DecodeSecret(str); | CKey key = DecodeSecret(str); | ||||
if (key.IsValid()) { | if (key.IsValid()) { | ||||
CPubKey pubkey = key.GetPubKey(); | CPubKey pubkey = key.GetPubKey(); | ||||
out.keys.emplace(pubkey.GetID(), key); | out.keys.emplace(pubkey.GetID(), key); | ||||
return std::make_unique<ConstPubkeyProvider>(pubkey); | return std::make_unique<ConstPubkeyProvider>(pubkey); | ||||
} | } | ||||
} | } | ||||
CExtKey extkey = DecodeExtKey(str); | CExtKey extkey = DecodeExtKey(str); | ||||
CExtPubKey extpubkey = DecodeExtPubKey(str); | CExtPubKey extpubkey = DecodeExtPubKey(str); | ||||
if (!extkey.key.IsValid() && !extpubkey.pubkey.IsValid()) { | if (!extkey.key.IsValid() && !extpubkey.pubkey.IsValid()) { | ||||
error = strprintf("key '%s' is not valid", str); | |||||
return nullptr; | return nullptr; | ||||
} | } | ||||
KeyPath path; | KeyPath path; | ||||
DeriveType type = DeriveType::NO; | DeriveType type = DeriveType::NO; | ||||
if (split.back() == MakeSpan("*").first(1)) { | if (split.back() == MakeSpan("*").first(1)) { | ||||
split.pop_back(); | split.pop_back(); | ||||
type = DeriveType::UNHARDENED; | type = DeriveType::UNHARDENED; | ||||
} else if (split.back() == MakeSpan("*'").first(2) || | } else if (split.back() == MakeSpan("*'").first(2) || | ||||
split.back() == MakeSpan("*h").first(2)) { | split.back() == MakeSpan("*h").first(2)) { | ||||
split.pop_back(); | split.pop_back(); | ||||
type = DeriveType::HARDENED; | type = DeriveType::HARDENED; | ||||
} | } | ||||
if (!ParseKeyPath(split, path)) { | if (!ParseKeyPath(split, path, error)) { | ||||
return nullptr; | return nullptr; | ||||
} | } | ||||
if (extkey.key.IsValid()) { | if (extkey.key.IsValid()) { | ||||
extpubkey = extkey.Neuter(); | extpubkey = extkey.Neuter(); | ||||
out.keys.emplace(extpubkey.pubkey.GetID(), extkey.key); | out.keys.emplace(extpubkey.pubkey.GetID(), extkey.key); | ||||
} | } | ||||
return std::make_unique<BIP32PubkeyProvider>(extpubkey, std::move(path), | return std::make_unique<BIP32PubkeyProvider>(extpubkey, std::move(path), | ||||
type); | type); | ||||
} | } | ||||
/** Parse a public key including origin information (if enabled). */ | /** Parse a public key including origin information (if enabled). */ | ||||
std::unique_ptr<PubkeyProvider> ParsePubkey(const Span<const char> &sp, | std::unique_ptr<PubkeyProvider> ParsePubkey(const Span<const char> &sp, | ||||
FlatSigningProvider &out) { | FlatSigningProvider &out, | ||||
std::string &error) { | |||||
auto origin_split = Split(sp, ']'); | auto origin_split = Split(sp, ']'); | ||||
if (origin_split.size() > 2) { | if (origin_split.size() > 2) { | ||||
error = "Multiple ']' characters found for a single pubkey"; | |||||
return nullptr; | return nullptr; | ||||
} | } | ||||
if (origin_split.size() == 1) { | if (origin_split.size() == 1) { | ||||
return ParsePubkeyInner(origin_split[0], out); | return ParsePubkeyInner(origin_split[0], out, error); | ||||
} | } | ||||
if (origin_split[0].size() < 1 || origin_split[0][0] != '[') { | if (origin_split[0].size() < 1 || origin_split[0][0] != '[') { | ||||
error = strprintf( | |||||
"Key origin expected but not found, got '%s' instead", | |||||
std::string(origin_split[0].begin(), origin_split[0].end())); | |||||
return nullptr; | return nullptr; | ||||
} | } | ||||
auto slash_split = Split(origin_split[0].subspan(1), '/'); | auto slash_split = Split(origin_split[0].subspan(1), '/'); | ||||
if (slash_split[0].size() != 8) { | if (slash_split[0].size() != 8) { | ||||
error = strprintf("Fingerprint is not 4 bytes (%u characters instead " | |||||
"of 8 characters)", | |||||
slash_split[0].size()); | |||||
return nullptr; | return nullptr; | ||||
} | } | ||||
std::string fpr_hex = | std::string fpr_hex = | ||||
std::string(slash_split[0].begin(), slash_split[0].end()); | std::string(slash_split[0].begin(), slash_split[0].end()); | ||||
if (!IsHex(fpr_hex)) { | if (!IsHex(fpr_hex)) { | ||||
error = strprintf("Fingerprint '%s' is not hex", fpr_hex); | |||||
return nullptr; | return nullptr; | ||||
} | } | ||||
auto fpr_bytes = ParseHex(fpr_hex); | auto fpr_bytes = ParseHex(fpr_hex); | ||||
KeyOriginInfo info; | KeyOriginInfo info; | ||||
static_assert(sizeof(info.fingerprint) == 4, "Fingerprint must be 4 bytes"); | static_assert(sizeof(info.fingerprint) == 4, "Fingerprint must be 4 bytes"); | ||||
assert(fpr_bytes.size() == 4); | assert(fpr_bytes.size() == 4); | ||||
std::copy(fpr_bytes.begin(), fpr_bytes.end(), info.fingerprint); | std::copy(fpr_bytes.begin(), fpr_bytes.end(), info.fingerprint); | ||||
if (!ParseKeyPath(slash_split, info.path)) { | if (!ParseKeyPath(slash_split, info.path, error)) { | ||||
return nullptr; | return nullptr; | ||||
} | } | ||||
auto provider = ParsePubkeyInner(origin_split[1], out); | auto provider = ParsePubkeyInner(origin_split[1], out, error); | ||||
if (!provider) { | if (!provider) { | ||||
return nullptr; | return nullptr; | ||||
} | } | ||||
return std::make_unique<OriginPubkeyProvider>(std::move(info), | return std::make_unique<OriginPubkeyProvider>(std::move(info), | ||||
std::move(provider)); | std::move(provider)); | ||||
} | } | ||||
/** Parse a script in a particular context. */ | /** Parse a script in a particular context. */ | ||||
std::unique_ptr<DescriptorImpl> ParseScript(Span<const char> &sp, | std::unique_ptr<DescriptorImpl> ParseScript(Span<const char> &sp, | ||||
ParseScriptContext ctx, | ParseScriptContext ctx, | ||||
FlatSigningProvider &out) { | FlatSigningProvider &out, | ||||
std::string &error) { | |||||
auto expr = Expr(sp); | auto expr = Expr(sp); | ||||
if (Func("pk", expr)) { | if (Func("pk", expr)) { | ||||
auto pubkey = ParsePubkey(expr, out); | auto pubkey = ParsePubkey(expr, out, error); | ||||
if (!pubkey) { | if (!pubkey) { | ||||
return nullptr; | return nullptr; | ||||
} | } | ||||
return std::make_unique<PKDescriptor>(std::move(pubkey)); | return std::make_unique<PKDescriptor>(std::move(pubkey)); | ||||
} | } | ||||
if (Func("pkh", expr)) { | if (Func("pkh", expr)) { | ||||
auto pubkey = ParsePubkey(expr, out); | auto pubkey = ParsePubkey(expr, out, error); | ||||
if (!pubkey) { | if (!pubkey) { | ||||
return nullptr; | return nullptr; | ||||
} | } | ||||
return std::make_unique<PKHDescriptor>(std::move(pubkey)); | return std::make_unique<PKHDescriptor>(std::move(pubkey)); | ||||
} | } | ||||
if (ctx == ParseScriptContext::TOP && Func("combo", expr)) { | if (ctx == ParseScriptContext::TOP && Func("combo", expr)) { | ||||
auto pubkey = ParsePubkey(expr, out); | auto pubkey = ParsePubkey(expr, out, error); | ||||
if (!pubkey) { | if (!pubkey) { | ||||
return nullptr; | return nullptr; | ||||
} | } | ||||
return std::make_unique<ComboDescriptor>(std::move(pubkey)); | return std::make_unique<ComboDescriptor>(std::move(pubkey)); | ||||
} | } | ||||
if (Func("multi", expr)) { | if (Func("multi", expr)) { | ||||
auto threshold = Expr(expr); | auto threshold = Expr(expr); | ||||
uint32_t thres; | uint32_t thres; | ||||
std::vector<std::unique_ptr<PubkeyProvider>> providers; | std::vector<std::unique_ptr<PubkeyProvider>> providers; | ||||
if (!ParseUInt32(std::string(threshold.begin(), threshold.end()), | if (!ParseUInt32(std::string(threshold.begin(), threshold.end()), | ||||
&thres)) { | &thres)) { | ||||
error = strprintf("multi threshold %u out of range", thres); | |||||
return nullptr; | return nullptr; | ||||
} | } | ||||
size_t script_size = 0; | size_t script_size = 0; | ||||
while (expr.size()) { | while (expr.size()) { | ||||
if (!Const(",", expr)) { | if (!Const(",", expr)) { | ||||
error = strprintf("multi: expected ',', got '%c'", expr[0]); | |||||
return nullptr; | return nullptr; | ||||
} | } | ||||
auto arg = Expr(expr); | auto arg = Expr(expr); | ||||
auto pk = ParsePubkey(arg, out); | auto pk = ParsePubkey(arg, out, error); | ||||
if (!pk) { | if (!pk) { | ||||
return nullptr; | return nullptr; | ||||
} | } | ||||
script_size += pk->GetSize() + 1; | script_size += pk->GetSize() + 1; | ||||
providers.emplace_back(std::move(pk)); | providers.emplace_back(std::move(pk)); | ||||
} | } | ||||
if (providers.size() < 1 || providers.size() > 16 || thres < 1 || | if (providers.size() < 1 || providers.size() > 16 || thres < 1 || | ||||
thres > providers.size()) { | thres > providers.size()) { | ||||
return nullptr; | return nullptr; | ||||
} | } | ||||
if (ctx == ParseScriptContext::TOP) { | if (ctx == ParseScriptContext::TOP) { | ||||
if (providers.size() > 3) { | if (providers.size() > 3) { | ||||
// Not more than 3 pubkeys for raw multisig | error = strprintf("Cannot %u pubkeys in bare multisig; only at " | ||||
"most 3 pubkeys", | |||||
providers.size()); | |||||
return nullptr; | return nullptr; | ||||
} | } | ||||
} | } | ||||
if (ctx == ParseScriptContext::P2SH) { | if (ctx == ParseScriptContext::P2SH) { | ||||
if (script_size + 3 > 520) { | if (script_size + 3 > 520) { | ||||
// Enforce P2SH script size limit | error = strprintf("P2SH script is too large, %d bytes is " | ||||
"larger than 520 bytes", | |||||
script_size + 3); | |||||
return nullptr; | return nullptr; | ||||
} | } | ||||
} | } | ||||
return std::make_unique<MultisigDescriptor>(thres, | return std::make_unique<MultisigDescriptor>(thres, | ||||
std::move(providers)); | std::move(providers)); | ||||
} | } | ||||
if (ctx == ParseScriptContext::TOP && Func("sh", expr)) { | if (ctx == ParseScriptContext::TOP && Func("sh", expr)) { | ||||
auto desc = ParseScript(expr, ParseScriptContext::P2SH, out); | auto desc = ParseScript(expr, ParseScriptContext::P2SH, out, error); | ||||
if (!desc || expr.size()) { | if (!desc || expr.size()) { | ||||
return nullptr; | return nullptr; | ||||
} | } | ||||
return std::make_unique<SHDescriptor>(std::move(desc)); | return std::make_unique<SHDescriptor>(std::move(desc)); | ||||
} | } | ||||
if (ctx == ParseScriptContext::TOP && Func("addr", expr)) { | if (ctx == ParseScriptContext::TOP && Func("addr", expr)) { | ||||
CTxDestination dest = | CTxDestination dest = | ||||
DecodeDestination(std::string(expr.begin(), expr.end()), Params()); | DecodeDestination(std::string(expr.begin(), expr.end()), Params()); | ||||
if (!IsValidDestination(dest)) { | if (!IsValidDestination(dest)) { | ||||
error = "Address is not valid"; | |||||
return nullptr; | return nullptr; | ||||
} | } | ||||
return std::make_unique<AddressDescriptor>(std::move(dest)); | return std::make_unique<AddressDescriptor>(std::move(dest)); | ||||
} | } | ||||
if (ctx == ParseScriptContext::TOP && Func("raw", expr)) { | if (ctx == ParseScriptContext::TOP && Func("raw", expr)) { | ||||
std::string str(expr.begin(), expr.end()); | std::string str(expr.begin(), expr.end()); | ||||
if (!IsHex(str)) { | if (!IsHex(str)) { | ||||
error = "Raw script is not hex"; | |||||
return nullptr; | return nullptr; | ||||
} | } | ||||
auto bytes = ParseHex(str); | auto bytes = ParseHex(str); | ||||
return std::make_unique<RawDescriptor>( | return std::make_unique<RawDescriptor>( | ||||
CScript(bytes.begin(), bytes.end())); | CScript(bytes.begin(), bytes.end())); | ||||
} | } | ||||
error = strprintf("%s is not a valid descriptor function", | |||||
std::string(expr.begin(), expr.end())); | |||||
return nullptr; | return nullptr; | ||||
} | } | ||||
static std::unique_ptr<PubkeyProvider> | static std::unique_ptr<PubkeyProvider> | ||||
InferPubkey(const CPubKey &pubkey, ParseScriptContext, | InferPubkey(const CPubKey &pubkey, ParseScriptContext, | ||||
const SigningProvider &provider) { | const SigningProvider &provider) { | ||||
auto key_provider = std::make_unique<ConstPubkeyProvider>(pubkey); | auto key_provider = std::make_unique<ConstPubkeyProvider>(pubkey); | ||||
KeyOriginInfo info; | KeyOriginInfo info; | ||||
▲ Show 20 Lines • Show All 59 Lines • ▼ Show 20 Lines | |||||
} | } | ||||
} // namespace | } // namespace | ||||
/** | /** | ||||
* Check a descriptor checksum, and update desc to be the checksum-less part. | * Check a descriptor checksum, and update desc to be the checksum-less part. | ||||
*/ | */ | ||||
bool CheckChecksum(Span<const char> &sp, bool require_checksum, | bool CheckChecksum(Span<const char> &sp, bool require_checksum, | ||||
std::string *out_checksum = nullptr) { | std::string &error, std::string *out_checksum = nullptr) { | ||||
auto check_split = Split(sp, '#'); | auto check_split = Split(sp, '#'); | ||||
if (check_split.size() > 2) { | if (check_split.size() > 2) { | ||||
// Multiple '#' symbols | error = "Multiple '#' symbols"; | ||||
return false; | return false; | ||||
} | } | ||||
if (check_split.size() == 1 && require_checksum) { | if (check_split.size() == 1 && require_checksum) { | ||||
// Missing checksum | error = "Missing checksum"; | ||||
return false; | return false; | ||||
} | } | ||||
if (check_split.size() == 2) { | if (check_split.size() == 2) { | ||||
if (check_split[1].size() != 8) { | if (check_split[1].size() != 8) { | ||||
// Unexpected length for checksum | error = | ||||
strprintf("Expected 8 character checksum, not %u characters", | |||||
check_split[1].size()); | |||||
return false; | return false; | ||||
} | } | ||||
} | } | ||||
auto checksum = DescriptorChecksum(check_split[0]); | auto checksum = DescriptorChecksum(check_split[0]); | ||||
if (checksum.empty()) { | if (checksum.empty()) { | ||||
// Invalid characters in payload | error = "Invalid characters in payload"; | ||||
return false; | return false; | ||||
} | } | ||||
if (check_split.size() == 2) { | if (check_split.size() == 2) { | ||||
if (!std::equal(checksum.begin(), checksum.end(), | if (!std::equal(checksum.begin(), checksum.end(), | ||||
check_split[1].begin())) { | check_split[1].begin())) { | ||||
// Checksum mismatch | error = strprintf( | ||||
"Provided checksum '%s' does not match computed checksum '%s'", | |||||
std::string(check_split[1].begin(), check_split[1].end()), | |||||
checksum); | |||||
return false; | return false; | ||||
} | } | ||||
} | } | ||||
if (out_checksum) { | if (out_checksum) { | ||||
*out_checksum = std::move(checksum); | *out_checksum = std::move(checksum); | ||||
} | } | ||||
sp = check_split[0]; | sp = check_split[0]; | ||||
return true; | return true; | ||||
} | } | ||||
std::unique_ptr<Descriptor> Parse(const std::string &descriptor, | std::unique_ptr<Descriptor> Parse(const std::string &descriptor, | ||||
FlatSigningProvider &out, | FlatSigningProvider &out, std::string &error, | ||||
bool require_checksum) { | bool require_checksum) { | ||||
Span<const char> sp(descriptor.data(), descriptor.size()); | Span<const char> sp(descriptor.data(), descriptor.size()); | ||||
if (!CheckChecksum(sp, require_checksum)) { | if (!CheckChecksum(sp, require_checksum, error)) { | ||||
return nullptr; | return nullptr; | ||||
} | } | ||||
auto ret = ParseScript(sp, ParseScriptContext::TOP, out); | auto ret = ParseScript(sp, ParseScriptContext::TOP, out, error); | ||||
if (sp.size() == 0 && ret) { | if (sp.size() == 0 && ret) { | ||||
return std::unique_ptr<Descriptor>(std::move(ret)); | return std::unique_ptr<Descriptor>(std::move(ret)); | ||||
} | } | ||||
return nullptr; | return nullptr; | ||||
} | } | ||||
std::string GetDescriptorChecksum(const std::string &descriptor) { | std::string GetDescriptorChecksum(const std::string &descriptor) { | ||||
std::string ret; | std::string ret; | ||||
std::string error; | |||||
Span<const char> sp(descriptor.data(), descriptor.size()); | Span<const char> sp(descriptor.data(), descriptor.size()); | ||||
if (!CheckChecksum(sp, false, &ret)) { | if (!CheckChecksum(sp, false, error, &ret)) { | ||||
return ""; | return ""; | ||||
} | } | ||||
return ret; | return ret; | ||||
} | } | ||||
std::unique_ptr<Descriptor> InferDescriptor(const CScript &script, | std::unique_ptr<Descriptor> InferDescriptor(const CScript &script, | ||||
const SigningProvider &provider) { | const SigningProvider &provider) { | ||||
return InferScript(script, ParseScriptContext::TOP, provider); | return InferScript(script, ParseScriptContext::TOP, provider); | ||||
} | } |