mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 03:52:49 +01:00
merge bitcoin#15368: Descriptor checksums
This commit is contained in:
parent
c1ae9093e2
commit
3c63ffa68c
@ -27,7 +27,7 @@ Output descriptors currently support:
|
|||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
Descriptors consist of several types of expressions. The top level expression is always a `SCRIPT`.
|
Descriptors consist of several types of expressions. The top level expression is either a `SCRIPT`, or `SCRIPT#CHECKSUM` where `CHECKSUM` is an 8-character alphanumeric descriptor checksum.
|
||||||
|
|
||||||
`SCRIPT` expressions:
|
`SCRIPT` expressions:
|
||||||
- `pk(KEY)` (anywhere): P2PK output for the given public key.
|
- `pk(KEY)` (anywhere): P2PK output for the given public key.
|
||||||
@ -138,3 +138,20 @@ In order to easily represent the sets of scripts currently supported by
|
|||||||
existing Dash Core wallets, a convenience function `combo` is
|
existing Dash Core wallets, a convenience function `combo` is
|
||||||
provided, which takes as input a public key, and constructs the P2PK and
|
provided, which takes as input a public key, and constructs the P2PK and
|
||||||
P2PKH scripts for that key.
|
P2PKH scripts for that key.
|
||||||
|
|
||||||
|
### Checksums
|
||||||
|
|
||||||
|
Descriptors can optionally be suffixed with a checksum to protect against
|
||||||
|
typos or copy-paste errors.
|
||||||
|
|
||||||
|
These checksums consist of 8 alphanumeric characters. As long as errors are
|
||||||
|
restricted to substituting characters in `0123456789()[],'/*abcdefgh@:$%{}`
|
||||||
|
for others in that set and changes in letter case, up to 4 errors will always
|
||||||
|
be detected in descriptors up to 501 characters, and up to 3 errors in longer
|
||||||
|
ones. For larger numbers of errors, or other types of errors, there is a
|
||||||
|
roughly 1 in a trillion chance of not detecting the errors.
|
||||||
|
|
||||||
|
All RPCs in Dash Core will include the checksum in their output. Only
|
||||||
|
certain RPCs require checksums on input, including `deriveaddress` and
|
||||||
|
`importmulti`. The checksum for a descriptor without one can be computed
|
||||||
|
using the `getdescriptorinfo` RPC.
|
||||||
|
@ -292,6 +292,44 @@ static UniValue createmultisig(const JSONRPCRequest& request)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UniValue getdescriptorinfo(const JSONRPCRequest& request)
|
||||||
|
{
|
||||||
|
if (request.fHelp || request.params.size() != 1) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
RPCHelpMan{"getdescriptorinfo",
|
||||||
|
{"\nAnalyses a descriptor.\n"},
|
||||||
|
{
|
||||||
|
{"descriptor", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The descriptor"},
|
||||||
|
}}.ToString() +
|
||||||
|
"\nResult:\n"
|
||||||
|
"{\n"
|
||||||
|
" \"descriptor\" : \"desc\", (string) The descriptor in canonical form, without private keys\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"
|
||||||
|
"}\n"
|
||||||
|
"\nExamples:\n"
|
||||||
|
"\nAnalyse a descriptor\n"
|
||||||
|
+ HelpExampleCli("getdescriptorinfo", "\"pkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)\"")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
RPCTypeCheck(request.params, {UniValue::VSTR});
|
||||||
|
|
||||||
|
FlatSigningProvider provider;
|
||||||
|
auto desc = Parse(request.params[0].get_str(), provider);
|
||||||
|
if (!desc) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid descriptor"));
|
||||||
|
}
|
||||||
|
|
||||||
|
UniValue result(UniValue::VOBJ);
|
||||||
|
result.pushKV("descriptor", desc->ToString());
|
||||||
|
result.pushKV("isrange", desc->IsRange());
|
||||||
|
result.pushKV("issolvable", desc->IsSolvable());
|
||||||
|
result.pushKV("hasprivatekeys", provider.keys.size() > 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
UniValue deriveaddresses(const JSONRPCRequest& request)
|
UniValue deriveaddresses(const JSONRPCRequest& request)
|
||||||
{
|
{
|
||||||
if (request.fHelp || request.params.empty() || request.params.size() > 3) {
|
if (request.fHelp || request.params.empty() || request.params.size() > 3) {
|
||||||
@ -317,7 +355,7 @@ UniValue deriveaddresses(const JSONRPCRequest& request)
|
|||||||
" ]\n"
|
" ]\n"
|
||||||
"\nExamples:\n"
|
"\nExamples:\n"
|
||||||
"\nFirst three receive addresses\n"
|
"\nFirst three receive addresses\n"
|
||||||
+ HelpExampleCli("deriveaddresses", "\"pkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)\" 0 2")
|
+ HelpExampleCli("deriveaddresses", "\"pkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#trd0mf0l\" 0 2")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,7 +380,7 @@ UniValue deriveaddresses(const JSONRPCRequest& request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
FlatSigningProvider provider;
|
FlatSigningProvider provider;
|
||||||
auto desc = Parse(desc_str, provider);
|
auto desc = Parse(desc_str, provider, /* require_checksum = */ true);
|
||||||
if (!desc) {
|
if (!desc) {
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid descriptor"));
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid descriptor"));
|
||||||
}
|
}
|
||||||
@ -1230,6 +1268,7 @@ static const CRPCCommand commands[] =
|
|||||||
{ "util", "validateaddress", &validateaddress, {"address"} },
|
{ "util", "validateaddress", &validateaddress, {"address"} },
|
||||||
{ "util", "createmultisig", &createmultisig, {"nrequired","keys"} },
|
{ "util", "createmultisig", &createmultisig, {"nrequired","keys"} },
|
||||||
{ "util", "deriveaddresses", &deriveaddresses, {"descriptor", "begin", "end"} },
|
{ "util", "deriveaddresses", &deriveaddresses, {"descriptor", "begin", "end"} },
|
||||||
|
{ "util", "getdescriptorinfo", &getdescriptorinfo, {"descriptor"} },
|
||||||
{ "util", "verifymessage", &verifymessage, {"address","signature","message"} },
|
{ "util", "verifymessage", &verifymessage, {"address","signature","message"} },
|
||||||
{ "util", "signmessagewithprivkey", &signmessagewithprivkey, {"privkey","message"} },
|
{ "util", "signmessagewithprivkey", &signmessagewithprivkey, {"privkey","message"} },
|
||||||
{ "blockchain", "getspentinfo", &getspentinfo, {"json"} },
|
{ "blockchain", "getspentinfo", &getspentinfo, {"json"} },
|
||||||
|
@ -20,6 +20,125 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Checksum //
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// This section implements a checksum algorithm for descriptors with the
|
||||||
|
// following properties:
|
||||||
|
// * Mistakes in a descriptor string are measured in "symbol errors". The higher
|
||||||
|
// the number of symbol errors, the harder it is to detect:
|
||||||
|
// * An error substituting a character from 0123456789()[],'/*abcdefgh@:$%{} for
|
||||||
|
// another in that set always counts as 1 symbol error.
|
||||||
|
// * Note that hex encoded keys are covered by these characters. Xprvs and
|
||||||
|
// xpubs use other characters too, but already have their own checksum
|
||||||
|
// mechanism.
|
||||||
|
// * Function names like "multi()" use other characters, but mistakes in
|
||||||
|
// these would generally result in an unparseable descriptor.
|
||||||
|
// * A case error always counts as 1 symbol error.
|
||||||
|
// * Any other 1 character substitution error counts as 1 or 2 symbol errors.
|
||||||
|
// * Any 1 symbol error is always detected.
|
||||||
|
// * Any 2 or 3 symbol error in a descriptor of up to 49154 characters is always detected.
|
||||||
|
// * Any 4 symbol error in a descriptor of up to 507 characters is always detected.
|
||||||
|
// * Any 5 symbol error in a descriptor of up to 77 characters is always detected.
|
||||||
|
// * Is optimized to minimize the chance a 5 symbol error in a descriptor up to 387 characters is undetected
|
||||||
|
// * Random errors have a chance of 1 in 2**40 of being undetected.
|
||||||
|
//
|
||||||
|
// These properties are achieved by expanding every group of 3 (non checksum) characters into
|
||||||
|
// 4 GF(32) symbols, over which a cyclic code is defined.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Interprets c as 8 groups of 5 bits which are the coefficients of a degree 8 polynomial over GF(32),
|
||||||
|
* multiplies that polynomial by x, computes its remainder modulo a generator, and adds the constant term val.
|
||||||
|
*
|
||||||
|
* This generator is G(x) = x^8 + {30}x^7 + {23}x^6 + {15}x^5 + {14}x^4 + {10}x^3 + {6}x^2 + {12}x + {9}.
|
||||||
|
* It is chosen to define an cyclic error detecting code which is selected by:
|
||||||
|
* - Starting from all BCH codes over GF(32) of degree 8 and below, which by construction guarantee detecting
|
||||||
|
* 3 errors in windows up to 19000 symbols.
|
||||||
|
* - Taking all those generators, and for degree 7 ones, extend them to degree 8 by adding all degree-1 factors.
|
||||||
|
* - Selecting just the set of generators that guarantee detecting 4 errors in a window of length 512.
|
||||||
|
* - Selecting one of those with best worst-case behavior for 5 errors in windows of length up to 512.
|
||||||
|
*
|
||||||
|
* The generator and the constants to implement it can be verified using this Sage code:
|
||||||
|
* B = GF(2) # Binary field
|
||||||
|
* BP.<b> = B[] # Polynomials over the binary field
|
||||||
|
* F_mod = b**5 + b**3 + 1
|
||||||
|
* F.<f> = GF(32, modulus=F_mod, repr='int') # GF(32) definition
|
||||||
|
* FP.<x> = F[] # Polynomials over GF(32)
|
||||||
|
* E_mod = x**3 + x + F.fetch_int(8)
|
||||||
|
* E.<e> = F.extension(E_mod) # Extension field definition
|
||||||
|
* alpha = e**2743 # Choice of an element in extension field
|
||||||
|
* for p in divisors(E.order() - 1): # Verify alpha has order 32767.
|
||||||
|
* assert((alpha**p == 1) == (p % 32767 == 0))
|
||||||
|
* G = lcm([(alpha**i).minpoly() for i in [1056,1057,1058]] + [x + 1])
|
||||||
|
* print(G) # Print out the generator
|
||||||
|
* for i in [1,2,4,8,16]: # Print out {1,2,4,8,16}*(G mod x^8), packed in hex integers.
|
||||||
|
* v = 0
|
||||||
|
* for coef in reversed((F.fetch_int(i)*(G % x**8)).coefficients(sparse=True)):
|
||||||
|
* v = v*32 + coef.integer_representation()
|
||||||
|
* print("0x%x" % v)
|
||||||
|
*/
|
||||||
|
uint64_t PolyMod(uint64_t c, int val)
|
||||||
|
{
|
||||||
|
uint8_t c0 = c >> 35;
|
||||||
|
c = ((c & 0x7ffffffff) << 5) ^ val;
|
||||||
|
if (c0 & 1) c ^= 0xf5dee51989;
|
||||||
|
if (c0 & 2) c ^= 0xa9fdca3312;
|
||||||
|
if (c0 & 4) c ^= 0x1bab10e32d;
|
||||||
|
if (c0 & 8) c ^= 0x3706b1677a;
|
||||||
|
if (c0 & 16) c ^= 0x644d626ffd;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DescriptorChecksum(const Span<const char>& span)
|
||||||
|
{
|
||||||
|
/** A character set designed such that:
|
||||||
|
* - The most common 'unprotected' descriptor characters (hex, keypaths) are in the first group of 32.
|
||||||
|
* - Case errors cause an offset that's a multiple of 32.
|
||||||
|
* - As many alphabetic characters are in the same group (while following the above restrictions).
|
||||||
|
*
|
||||||
|
* If p(x) gives the position of a character c in this character set, every group of 3 characters
|
||||||
|
* (a,b,c) is encoded as the 4 symbols (p(a) & 31, p(b) & 31, p(c) & 31, (p(a) / 32) + 3 * (p(b) / 32) + 9 * (p(c) / 32).
|
||||||
|
* This means that changes that only affect the lower 5 bits of the position, or only the higher 2 bits, will just
|
||||||
|
* affect a single symbol.
|
||||||
|
*
|
||||||
|
* As a result, within-group-of-32 errors count as 1 symbol, as do cross-group errors that don't affect
|
||||||
|
* the position within the groups.
|
||||||
|
*/
|
||||||
|
static std::string INPUT_CHARSET =
|
||||||
|
"0123456789()[],'/*abcdefgh@:$%{}"
|
||||||
|
"IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~"
|
||||||
|
"ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
|
||||||
|
|
||||||
|
/** The character set for the checksum itself (same as bech32). */
|
||||||
|
static std::string CHECKSUM_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
||||||
|
|
||||||
|
uint64_t c = 1;
|
||||||
|
int cls = 0;
|
||||||
|
int clscount = 0;
|
||||||
|
for (auto ch : span) {
|
||||||
|
auto pos = INPUT_CHARSET.find(ch);
|
||||||
|
if (pos == std::string::npos) return "";
|
||||||
|
c = PolyMod(c, pos & 31); // Emit a symbol for the position inside the group, for every character.
|
||||||
|
cls = cls * 3 + (pos >> 5); // Accumulate the group numbers
|
||||||
|
if (++clscount == 3) {
|
||||||
|
// Emit an extra symbol representing the group numbers, for every 3 characters.
|
||||||
|
c = PolyMod(c, cls);
|
||||||
|
cls = 0;
|
||||||
|
clscount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (clscount > 0) c = PolyMod(c, cls);
|
||||||
|
for (int j = 0; j < 8; ++j) c = PolyMod(c, 0); // Shift further to determine the checksum.
|
||||||
|
c ^= 1; // Prevent appending zeroes from not affecting the checksum.
|
||||||
|
|
||||||
|
std::string ret(8, ' ');
|
||||||
|
for (int j = 0; j < 8; ++j) ret[j] = CHECKSUM_CHARSET[(c >> (5 * (7 - j))) & 31];
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AddChecksum(const std::string& str) { return str + "#" + DescriptorChecksum(MakeSpan(str)); }
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
// Internal representation //
|
// Internal representation //
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
@ -283,10 +402,15 @@ public:
|
|||||||
{
|
{
|
||||||
std::string ret;
|
std::string ret;
|
||||||
ToStringHelper(nullptr, ret, false);
|
ToStringHelper(nullptr, ret, false);
|
||||||
return ret;
|
return AddChecksum(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override final { return ToStringHelper(&arg, out, true); }
|
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override final
|
||||||
|
{
|
||||||
|
bool ret = ToStringHelper(&arg, out, true);
|
||||||
|
out = AddChecksum(out);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
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
|
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
|
||||||
{
|
{
|
||||||
@ -710,11 +834,25 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
|
|||||||
return MakeUnique<RawDescriptor>(script);
|
return MakeUnique<RawDescriptor>(script);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProvider& out)
|
std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProvider& out, bool require_checksum)
|
||||||
{
|
{
|
||||||
Span<const char> sp(descriptor.data(), descriptor.size());
|
Span<const char> 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) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
sp = check_split[0];
|
||||||
|
|
||||||
auto ret = ParseScript(sp, ParseScriptContext::TOP, out);
|
auto ret = ParseScript(sp, ParseScriptContext::TOP, out);
|
||||||
if (sp.size() == 0 && ret) return std::unique_ptr<Descriptor>(std::move(ret));
|
if (sp.size() == 0 && ret) return std::unique_ptr<Descriptor>(std::move(ret));
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -62,8 +62,15 @@ struct Descriptor {
|
|||||||
virtual bool ExpandFromCache(int pos, const std::vector<unsigned char>& cache, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const = 0;
|
virtual bool ExpandFromCache(int pos, const std::vector<unsigned char>& cache, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Parse a descriptor string. Included private keys are put in out. Returns nullptr if parsing fails. */
|
/** Parse a descriptor string. Included private keys are put in out.
|
||||||
std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProvider& out);
|
*
|
||||||
|
* If the descriptor has a checksum, it must be valid. If require_checksum
|
||||||
|
* is set, the checksum is mandatory - otherwise it is optional.
|
||||||
|
*
|
||||||
|
* If a parse error occurs, or the checksum is missing/invalid, or anything
|
||||||
|
* else is wrong, nullptr is returned.
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProvider& out, bool require_checksum = false);
|
||||||
|
|
||||||
/** Find a descriptor for the specified script, using information from provider where possible.
|
/** Find a descriptor for the specified script, using information from provider where possible.
|
||||||
*
|
*
|
||||||
|
@ -18,8 +18,8 @@ void CheckUnparsable(const std::string& prv, const std::string& pub)
|
|||||||
FlatSigningProvider keys_priv, keys_pub;
|
FlatSigningProvider keys_priv, keys_pub;
|
||||||
auto parse_priv = Parse(prv, keys_priv);
|
auto parse_priv = Parse(prv, keys_priv);
|
||||||
auto parse_pub = Parse(pub, keys_pub);
|
auto parse_pub = Parse(pub, keys_pub);
|
||||||
BOOST_CHECK(!parse_priv);
|
BOOST_CHECK_MESSAGE(!parse_priv, prv);
|
||||||
BOOST_CHECK(!parse_pub);
|
BOOST_CHECK_MESSAGE(!parse_pub, pub);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr int DEFAULT = 0;
|
constexpr int DEFAULT = 0;
|
||||||
@ -28,6 +28,18 @@ constexpr int HARDENED = 2; // Derivation needs access to private keys
|
|||||||
constexpr int UNSOLVABLE = 4; // This descriptor is not expected to be solvable
|
constexpr int UNSOLVABLE = 4; // This descriptor is not expected to be solvable
|
||||||
constexpr int SIGNABLE = 8; // We can sign with this descriptor (this is not true when actual BIP32 derivation is used, as that's not integrated in our signing code)
|
constexpr int SIGNABLE = 8; // We can sign with this descriptor (this is not true when actual BIP32 derivation is used, as that's not integrated in our signing code)
|
||||||
|
|
||||||
|
/** Compare two descriptors. If only one of them has a checksum, the checksum is ignored. */
|
||||||
|
bool EqualDescriptor(std::string a, std::string b)
|
||||||
|
{
|
||||||
|
bool a_check = (a.size() > 9 && a[a.size() - 9] == '#');
|
||||||
|
bool b_check = (b.size() > 9 && b[b.size() - 9] == '#');
|
||||||
|
if (a_check != b_check) {
|
||||||
|
if (a_check) a = a.substr(0, a.size() - 9);
|
||||||
|
if (b_check) b = b.substr(0, b.size() - 9);
|
||||||
|
}
|
||||||
|
return a == b;
|
||||||
|
}
|
||||||
|
|
||||||
std::string MaybeUseHInsteadOfApostrophy(std::string ret)
|
std::string MaybeUseHInsteadOfApostrophy(std::string ret)
|
||||||
{
|
{
|
||||||
if (InsecureRandBool()) {
|
if (InsecureRandBool()) {
|
||||||
@ -35,6 +47,7 @@ std::string MaybeUseHInsteadOfApostrophy(std::string ret)
|
|||||||
auto it = ret.find("'");
|
auto it = ret.find("'");
|
||||||
if (it != std::string::npos) {
|
if (it != std::string::npos) {
|
||||||
ret[it] = 'h';
|
ret[it] = 'h';
|
||||||
|
if (ret.size() > 9 && ret[ret.size() - 9] == '#') ret = ret.substr(0, ret.size() - 9); // Changing apostrophe to h breaks the checksum
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -63,16 +76,16 @@ void Check(const std::string& prv, const std::string& pub, int flags, const std:
|
|||||||
// Check that both versions serialize back to the public version.
|
// Check that both versions serialize back to the public version.
|
||||||
std::string pub1 = parse_priv->ToString();
|
std::string pub1 = parse_priv->ToString();
|
||||||
std::string pub2 = parse_pub->ToString();
|
std::string pub2 = parse_pub->ToString();
|
||||||
BOOST_CHECK_EQUAL(pub, pub1);
|
BOOST_CHECK(EqualDescriptor(pub, pub1));
|
||||||
BOOST_CHECK_EQUAL(pub, pub2);
|
BOOST_CHECK(EqualDescriptor(pub, pub2));
|
||||||
|
|
||||||
// Check that both can be serialized with private key back to the private version, but not without private key.
|
// Check that both can be serialized with private key back to the private version, but not without private key.
|
||||||
std::string prv1;
|
std::string prv1;
|
||||||
BOOST_CHECK(parse_priv->ToPrivateString(keys_priv, prv1));
|
BOOST_CHECK(parse_priv->ToPrivateString(keys_priv, prv1));
|
||||||
BOOST_CHECK_EQUAL(prv, prv1);
|
BOOST_CHECK(EqualDescriptor(prv, prv1));
|
||||||
BOOST_CHECK(!parse_priv->ToPrivateString(keys_pub, prv1));
|
BOOST_CHECK(!parse_priv->ToPrivateString(keys_pub, prv1));
|
||||||
BOOST_CHECK(parse_pub->ToPrivateString(keys_priv, prv1));
|
BOOST_CHECK(parse_pub->ToPrivateString(keys_priv, prv1));
|
||||||
BOOST_CHECK_EQUAL(prv, prv1);
|
BOOST_CHECK(EqualDescriptor(prv, prv1));
|
||||||
BOOST_CHECK(!parse_pub->ToPrivateString(keys_pub, prv1));
|
BOOST_CHECK(!parse_pub->ToPrivateString(keys_pub, prv1));
|
||||||
|
|
||||||
// Check whether IsRange on both returns the expected result
|
// Check whether IsRange on both returns the expected result
|
||||||
@ -188,6 +201,15 @@ BOOST_AUTO_TEST_CASE(descriptor_test)
|
|||||||
CheckUnparsable("sh(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2)", "sh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)"); // P2SH needs a script, not a key
|
CheckUnparsable("sh(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2)", "sh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)"); // P2SH needs a script, not a key
|
||||||
CheckUnparsable("sh(sh(pk(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2)))", "sh(sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))"); // Cannot embed P2SH inside P2SH
|
CheckUnparsable("sh(sh(pk(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2)))", "sh(sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))"); // Cannot embed P2SH inside P2SH
|
||||||
CheckUnparsable("sh(combo(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2))", "sh(combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))"); // Old must be top level
|
CheckUnparsable("sh(combo(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2))", "sh(combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))"); // Old must be top level
|
||||||
|
|
||||||
|
// Checksums
|
||||||
|
Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, {{0x8000006FUL,222},{0}});
|
||||||
|
Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, {{0x8000006FUL,222},{0}});
|
||||||
|
CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#"); // Empty checksum
|
||||||
|
CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfyq", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5tq"); // Too long checksum
|
||||||
|
CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxf", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5"); // Too short checksum
|
||||||
|
CheckUnparsable("sh(multi(3,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", "sh(multi(3,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t"); // Error in payload
|
||||||
|
CheckUnparsable("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggssrxfy", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjq09x4t"); // Error in checksum
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
@ -1262,7 +1262,7 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
|
|||||||
|
|
||||||
const std::string& descriptor = data["desc"].get_str();
|
const std::string& descriptor = data["desc"].get_str();
|
||||||
FlatSigningProvider keys;
|
FlatSigningProvider keys;
|
||||||
auto parsed_desc = Parse(descriptor, keys);
|
auto parsed_desc = Parse(descriptor, keys, /* require_checksum = */ true);
|
||||||
if (!parsed_desc) {
|
if (!parsed_desc) {
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor is invalid");
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor is invalid");
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
"""Test the deriveaddresses rpc call."""
|
"""Test the deriveaddresses rpc call."""
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
|
from test_framework.descriptors import descsum_create
|
||||||
from test_framework.util import assert_equal, assert_raises_rpc_error
|
from test_framework.util import assert_equal, assert_raises_rpc_error
|
||||||
|
|
||||||
class DeriveaddressesTest(BitcoinTestFramework):
|
class DeriveaddressesTest(BitcoinTestFramework):
|
||||||
@ -14,36 +15,37 @@ class DeriveaddressesTest(BitcoinTestFramework):
|
|||||||
def run_test(self):
|
def run_test(self):
|
||||||
assert_raises_rpc_error(-5, "Invalid descriptor", self.nodes[0].deriveaddresses, "a")
|
assert_raises_rpc_error(-5, "Invalid descriptor", self.nodes[0].deriveaddresses, "a")
|
||||||
|
|
||||||
descriptor = "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"
|
descriptor = descsum_create("pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)")
|
||||||
address = "yZTyMdEJjZWJi6CwY6g3WurLESH3UsWrrM"
|
address = "yZTyMdEJjZWJi6CwY6g3WurLESH3UsWrrM"
|
||||||
|
|
||||||
assert_equal(self.nodes[0].deriveaddresses(descriptor), [address])
|
assert_equal(self.nodes[0].deriveaddresses(descriptor), [address])
|
||||||
|
|
||||||
descriptor_pubkey = "pkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0)"
|
descriptor = descriptor[:-9]
|
||||||
address = "yZTyMdEJjZWJi6CwY6g3WurLESH3UsWrrM"
|
assert_raises_rpc_error(-5, "Invalid descriptor", self.nodes[0].deriveaddresses, descriptor)
|
||||||
|
|
||||||
|
descriptor_pubkey = descsum_create("pkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0)")
|
||||||
|
address = "yZTyMdEJjZWJi6CwY6g3WurLESH3UsWrrM"
|
||||||
assert_equal(self.nodes[0].deriveaddresses(descriptor_pubkey), [address])
|
assert_equal(self.nodes[0].deriveaddresses(descriptor_pubkey), [address])
|
||||||
|
|
||||||
ranged_descriptor = "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"
|
ranged_descriptor = "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)#77vpsvm5"
|
||||||
assert_equal(self.nodes[0].deriveaddresses(ranged_descriptor, 0, 2), [address, "ydccVGNV2EcEouAxbbgdu8pi8gkdaqkiav", "yMENst4XYP3ZSNvsCEm587GbSSXZUfhpWG"])
|
assert_equal(self.nodes[0].deriveaddresses(ranged_descriptor, 0, 2), [address, "ydccVGNV2EcEouAxbbgdu8pi8gkdaqkiav", "yMENst4XYP3ZSNvsCEm587GbSSXZUfhpWG"])
|
||||||
|
|
||||||
assert_raises_rpc_error(-8, "Range should not be specified for an un-ranged descriptor", self.nodes[0].deriveaddresses, "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)", 0, 2)
|
assert_raises_rpc_error(-8, "Range should not be specified for an un-ranged descriptor", self.nodes[0].deriveaddresses, descsum_create("pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"), 0, 2)
|
||||||
|
|
||||||
assert_raises_rpc_error(-8, "Range must be specified for a ranged descriptor", self.nodes[0].deriveaddresses, "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)")
|
assert_raises_rpc_error(-8, "Range must be specified for a ranged descriptor", self.nodes[0].deriveaddresses, descsum_create("pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"))
|
||||||
|
|
||||||
assert_raises_rpc_error(-8, "Missing range end parameter", self.nodes[0].deriveaddresses, "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", 0)
|
assert_raises_rpc_error(-8, "Missing range end parameter", self.nodes[0].deriveaddresses, descsum_create("pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"), 0)
|
||||||
|
|
||||||
assert_raises_rpc_error(-8, "Range end should be equal to or greater than begin", self.nodes[0].deriveaddresses, "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", 2, 0)
|
assert_raises_rpc_error(-8, "Range end should be equal to or greater than begin", self.nodes[0].deriveaddresses, descsum_create("pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"), 2, 0)
|
||||||
|
|
||||||
assert_raises_rpc_error(-8, "Range should be greater or equal than 0", self.nodes[0].deriveaddresses, "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", -1, 0)
|
assert_raises_rpc_error(-8, "Range should be greater or equal than 0", self.nodes[0].deriveaddresses, descsum_create("pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"), -1, 0)
|
||||||
|
|
||||||
combo_descriptor = "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"
|
combo_descriptor = descsum_create("combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)")
|
||||||
assert_equal(self.nodes[0].deriveaddresses(combo_descriptor), ["yZTyMdEJjZWJi6CwY6g3WurLESH3UsWrrM", "yZTyMdEJjZWJi6CwY6g3WurLESH3UsWrrM", "93EpXofs6W7eNiuj4gu2LJh8L8opowW1jz"])
|
assert_equal(self.nodes[0].deriveaddresses(combo_descriptor), ["yZTyMdEJjZWJi6CwY6g3WurLESH3UsWrrM", "yZTyMdEJjZWJi6CwY6g3WurLESH3UsWrrM", "93EpXofs6W7eNiuj4gu2LJh8L8opowW1jz"])
|
||||||
|
|
||||||
hardened_without_privkey_descriptor = "pkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1'/1/0)"
|
hardened_without_privkey_descriptor = descsum_create("pkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1'/1/0)")
|
||||||
assert_raises_rpc_error(-5, "Cannot derive script without private keys", self.nodes[0].deriveaddresses, hardened_without_privkey_descriptor)
|
assert_raises_rpc_error(-5, "Cannot derive script without private keys", self.nodes[0].deriveaddresses, hardened_without_privkey_descriptor)
|
||||||
|
|
||||||
bare_multisig_descriptor = "multi(1, tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0, tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1)"
|
bare_multisig_descriptor = descsum_create("multi(1,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0,tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1)")
|
||||||
assert_raises_rpc_error(-5, "Descriptor does not have a corresponding address", self.nodes[0].deriveaddresses, bare_multisig_descriptor)
|
assert_raises_rpc_error(-5, "Descriptor does not have a corresponding address", self.nodes[0].deriveaddresses, bare_multisig_descriptor)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -95,9 +95,9 @@ class ScantxoutsetTest(BitcoinTestFramework):
|
|||||||
assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1500}])['total_amount'], Decimal("28.672"))
|
assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1500}])['total_amount'], Decimal("28.672"))
|
||||||
|
|
||||||
# Test the reported descriptors for a few matches
|
# Test the reported descriptors for a few matches
|
||||||
assert_equal(descriptors(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/*)", "range": 1499}])), ["pkh([0c5f9a1e/0'/0'/0]026dbd8b2315f296d36e6b6920b1579ca75569464875c7ebe869b536a7d9503c8c)", "pkh([0c5f9a1e/0'/0'/1]033e6f25d76c00bedb3a8993c7d5739ee806397f0529b1b31dda31ef890f19a60c)"])
|
assert_equal(descriptors(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/*)", "range": 1499}])), ["pkh([0c5f9a1e/0'/0'/0]026dbd8b2315f296d36e6b6920b1579ca75569464875c7ebe869b536a7d9503c8c)#dzxw429x", "pkh([0c5f9a1e/0'/0'/1]033e6f25d76c00bedb3a8993c7d5739ee806397f0529b1b31dda31ef890f19a60c)#43rvceed"])
|
||||||
assert_equal(descriptors(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"])), ["pkh([0c5f9a1e/1/1/0]03e1c5b6e650966971d7e71ef2674f80222752740fc1dfd63bbbd220d2da9bd0fb)"])
|
assert_equal(descriptors(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"])), ["pkh([0c5f9a1e/1/1/0]03e1c5b6e650966971d7e71ef2674f80222752740fc1dfd63bbbd220d2da9bd0fb)#cxmct4w8"])
|
||||||
assert_equal(descriptors(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1500}])), ['pkh([0c5f9a1e/1/1/0]03e1c5b6e650966971d7e71ef2674f80222752740fc1dfd63bbbd220d2da9bd0fb)', 'pkh([0c5f9a1e/1/1/1500]03832901c250025da2aebae2bfb38d5c703a57ab66ad477f9c578bfbcd78abca6f)', 'pkh([0c5f9a1e/1/1/1]030d820fc9e8211c4169be8530efbc632775d8286167afd178caaf1089b77daba7)'])
|
assert_equal(descriptors(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1500}])), ['pkh([0c5f9a1e/1/1/0]03e1c5b6e650966971d7e71ef2674f80222752740fc1dfd63bbbd220d2da9bd0fb)#cxmct4w8', 'pkh([0c5f9a1e/1/1/1500]03832901c250025da2aebae2bfb38d5c703a57ab66ad477f9c578bfbcd78abca6f)#vchwd07g', 'pkh([0c5f9a1e/1/1/1]030d820fc9e8211c4169be8530efbc632775d8286167afd178caaf1089b77daba7)#z2t3ypsa'])
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
ScantxoutsetTest().main()
|
ScantxoutsetTest().main()
|
||||||
|
55
test/functional/test_framework/descriptors.py
Normal file
55
test/functional/test_framework/descriptors.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2019 Pieter Wuille
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
"""Utility functions related to output descriptors"""
|
||||||
|
|
||||||
|
INPUT_CHARSET = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ "
|
||||||
|
CHECKSUM_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
|
||||||
|
GENERATOR = [0xf5dee51989, 0xa9fdca3312, 0x1bab10e32d, 0x3706b1677a, 0x644d626ffd]
|
||||||
|
|
||||||
|
def descsum_polymod(symbols):
|
||||||
|
"""Internal function that computes the descriptor checksum."""
|
||||||
|
chk = 1
|
||||||
|
for value in symbols:
|
||||||
|
top = chk >> 35
|
||||||
|
chk = (chk & 0x7ffffffff) << 5 ^ value
|
||||||
|
for i in range(5):
|
||||||
|
chk ^= GENERATOR[i] if ((top >> i) & 1) else 0
|
||||||
|
return chk
|
||||||
|
|
||||||
|
def descsum_expand(s):
|
||||||
|
"""Internal function that does the character to symbol expansion"""
|
||||||
|
groups = []
|
||||||
|
symbols = []
|
||||||
|
for c in s:
|
||||||
|
if not c in INPUT_CHARSET:
|
||||||
|
return None
|
||||||
|
v = INPUT_CHARSET.find(c)
|
||||||
|
symbols.append(v & 31)
|
||||||
|
groups.append(v >> 5)
|
||||||
|
if len(groups) == 3:
|
||||||
|
symbols.append(groups[0] * 9 + groups[1] * 3 + groups[2])
|
||||||
|
groups = []
|
||||||
|
if len(groups) == 1:
|
||||||
|
symbols.append(groups[0])
|
||||||
|
elif len(groups) == 2:
|
||||||
|
symbols.append(groups[0] * 3 + groups[1])
|
||||||
|
return symbols
|
||||||
|
|
||||||
|
def descsum_create(s):
|
||||||
|
"""Add a checksum to a descriptor without"""
|
||||||
|
symbols = descsum_expand(s) + [0, 0, 0, 0, 0, 0, 0, 0]
|
||||||
|
checksum = descsum_polymod(symbols) ^ 1
|
||||||
|
return s + '#' + ''.join(CHECKSUM_CHARSET[(checksum >> (5 * (7 - i))) & 31] for i in range(8))
|
||||||
|
|
||||||
|
def descsum_check(s, require=True):
|
||||||
|
"""Verify that the checksum is correct in a descriptor"""
|
||||||
|
if not '#' in s:
|
||||||
|
return not require
|
||||||
|
if s[-9] != '#':
|
||||||
|
return False
|
||||||
|
if not all(x in CHECKSUM_CHARSET for x in s[-8:]):
|
||||||
|
return False
|
||||||
|
symbols = descsum_expand(s[:-9]) + [CHECKSUM_CHARSET.find(x) for x in s[-8:]]
|
||||||
|
return descsum_polymod(symbols) == 1
|
@ -34,6 +34,7 @@ from test_framework.script import (
|
|||||||
hash160,
|
hash160,
|
||||||
)
|
)
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
|
from test_framework.descriptors import descsum_create
|
||||||
from test_framework.util import (
|
from test_framework.util import (
|
||||||
assert_equal,
|
assert_equal,
|
||||||
assert_greater_than,
|
assert_greater_than,
|
||||||
@ -487,7 +488,7 @@ class ImportMultiTest(BitcoinTestFramework):
|
|||||||
xpriv = "tprv8ZgxMBicQKsPeuVhWwi6wuMQGfPKi9Li5GtX35jVNknACgqe3CY4g5xgkfDDJcmtF7o1QnxWDRYw4H5P26PXq7sbcUkEqeR4fg3Kxp2tigg"
|
xpriv = "tprv8ZgxMBicQKsPeuVhWwi6wuMQGfPKi9Li5GtX35jVNknACgqe3CY4g5xgkfDDJcmtF7o1QnxWDRYw4H5P26PXq7sbcUkEqeR4fg3Kxp2tigg"
|
||||||
desc = "sh(pkh(" + xpriv + "/0'/0'/*'" + "))"
|
desc = "sh(pkh(" + xpriv + "/0'/0'/*'" + "))"
|
||||||
self.log.info("Ranged descriptor import should fail without a specified range")
|
self.log.info("Ranged descriptor import should fail without a specified range")
|
||||||
self.test_importmulti({"desc": desc,
|
self.test_importmulti({"desc": descsum_create(desc),
|
||||||
"timestamp": "now"},
|
"timestamp": "now"},
|
||||||
success=False,
|
success=False,
|
||||||
error_code=-8,
|
error_code=-8,
|
||||||
@ -495,7 +496,7 @@ class ImportMultiTest(BitcoinTestFramework):
|
|||||||
|
|
||||||
# Test importing of a ranged descriptor without keys
|
# Test importing of a ranged descriptor without keys
|
||||||
self.log.info("Should import the ranged descriptor with specified range as solvable")
|
self.log.info("Should import the ranged descriptor with specified range as solvable")
|
||||||
self.test_importmulti({"desc": desc,
|
self.test_importmulti({"desc": descsum_create(desc),
|
||||||
"timestamp": "now",
|
"timestamp": "now",
|
||||||
"range": {"end": 1}},
|
"range": {"end": 1}},
|
||||||
success=True,
|
success=True,
|
||||||
@ -504,7 +505,7 @@ class ImportMultiTest(BitcoinTestFramework):
|
|||||||
# Test importing of a P2PKH address via descriptor
|
# Test importing of a P2PKH address via descriptor
|
||||||
key = self.get_key()
|
key = self.get_key()
|
||||||
self.log.info("Should import a p2pkh address from descriptor")
|
self.log.info("Should import a p2pkh address from descriptor")
|
||||||
self.test_importmulti({"desc": "pkh(" + key.pubkey + ")",
|
self.test_importmulti({"desc": descsum_create("pkh(" + key.pubkey + ")"),
|
||||||
"timestamp": "now",
|
"timestamp": "now",
|
||||||
"label": "Descriptor import test"},
|
"label": "Descriptor import test"},
|
||||||
True,
|
True,
|
||||||
@ -517,7 +518,7 @@ class ImportMultiTest(BitcoinTestFramework):
|
|||||||
# Test import fails if both desc and scriptPubKey are provided
|
# Test import fails if both desc and scriptPubKey are provided
|
||||||
key = self.get_key()
|
key = self.get_key()
|
||||||
self.log.info("Import should fail if both scriptPubKey and desc are provided")
|
self.log.info("Import should fail if both scriptPubKey and desc are provided")
|
||||||
self.test_importmulti({"desc": "pkh(" + key.pubkey + ")",
|
self.test_importmulti({"desc": descsum_create("pkh(" + key.pubkey + ")"),
|
||||||
"scriptPubKey": {"address": key.p2pkh_addr},
|
"scriptPubKey": {"address": key.p2pkh_addr},
|
||||||
"timestamp": "now"},
|
"timestamp": "now"},
|
||||||
success=False,
|
success=False,
|
||||||
@ -536,7 +537,7 @@ class ImportMultiTest(BitcoinTestFramework):
|
|||||||
key1 = self.get_key()
|
key1 = self.get_key()
|
||||||
key2 = self.get_key()
|
key2 = self.get_key()
|
||||||
self.log.info("Should import a 1-of-2 bare multisig from descriptor")
|
self.log.info("Should import a 1-of-2 bare multisig from descriptor")
|
||||||
self.test_importmulti({"desc": "multi(1," + key1.pubkey + "," + key2.pubkey + ")",
|
self.test_importmulti({"desc": descsum_create("multi(1," + key1.pubkey + "," + key2.pubkey + ")"),
|
||||||
"timestamp": "now"},
|
"timestamp": "now"},
|
||||||
success=True)
|
success=True)
|
||||||
self.log.info("Should not treat individual keys from the imported bare multisig as watchonly")
|
self.log.info("Should not treat individual keys from the imported bare multisig as watchonly")
|
||||||
|
Loading…
Reference in New Issue
Block a user