diff --git a/doc/descriptors.md b/doc/descriptors.md index 90b2f6e313..9659a1263b 100644 --- a/doc/descriptors.md +++ b/doc/descriptors.md @@ -23,10 +23,11 @@ Output descriptors currently support: - `pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)` represents a P2PK output. - `multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)` represents a bare *1-of-2* multisig. - `pkh(xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw/1'/2)` refers to a single P2PKH output, using child key *1'/2* of the specified xpub. +- `pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)` describes a set of P2PKH outputs, but additionally specifies that the specified xpub is a child of a master with fingerprint `d34db33f`, and derived using path `44'/0'/0'`. ## 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: - `pk(KEY)` (anywhere): P2PK output for the given public key. @@ -38,13 +39,19 @@ Descriptors consist of several types of expressions. The top level expression is - `raw(HEX)` (top level only): the script whose hex encoding is HEX. `KEY` expressions: -- Hex encoded public keys (66 characters starting with `02` or `03`, or 130 characters starting with `04`). -- [WIF](https://en.bitcoin.it/wiki/Wallet_import_format) encoded private keys may be specified instead of the corresponding public key, with the same meaning. --`xpub` encoded extended public key or `xprv` encoded private key (as defined in [BIP 32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)). - - Followed by zero or more `/NUM` unhardened and `/NUM'` hardened BIP32 derivation steps. - - Optionally followed by a single `/*` or `/*'` final step to denote all (direct) unhardened or hardened children. - - The usage of hardened derivation steps requires providing the private key. - - Instead of a `'`, the suffix `h` can be used to denote hardened derivation. +- Optionally, key origin information, consisting of: + - An open bracket `[` + - Exactly 8 hex characters for the fingerprint of the key where the derivation starts (see BIP32 for details) + - Followed by zero or more `/NUM` or `/NUM'` path elements to indicate unhardened or hardened derivation steps between the fingerprint and the key or xpub/xprv root that follows + - A closing bracket `]` +- Followed by the actual key, which is either: + - Hex encoded public keys (66 characters starting with `02` or `03`, or 130 characters starting with `04`). + - [WIF](https://en.bitcoin.it/wiki/Wallet_import_format) encoded private keys may be specified instead of the corresponding public key, with the same meaning. + -`xpub` encoded extended public key or `xprv` encoded private key (as defined in [BIP 32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)). + - Followed by zero or more `/NUM` unhardened and `/NUM'` hardened BIP32 derivation steps. + - Optionally followed by a single `/*` or `/*'` final step to denote all (direct) unhardened or hardened children. + - The usage of hardened derivation steps requires providing the private key. +- Anywhere a `'` suffix is permitted to denote hardened derivation, the suffix `h` can be used instead. `ADDR` expressions are any type of supported address: - P2PKH addresses (base58, of the form `X...`). Note that P2PKH addresses in descriptors cannot be used for P2PK outputs (use the `pk` function instead). @@ -90,6 +97,33 @@ Whenever a public key is described using a hardened derivation step, the script cannot be computed without access to the corresponding private key. +### Key origin identification + +In order to describe scripts whose signing keys reside on another device, +it may be necessary to identify the master key and derivation path an +xpub was derived with. + +For example, when following BIP44, it would be useful to describe a +change chain directly as `xpub.../44'/0'/0'/1/*` where `xpub...` +corresponds with the master key `m`. Unfortunately, since there are +hardened derivation steps that follow the xpub, this descriptor does not +let you compute scripts without access to the corresponding private keys. +Instead, it should be written as `xpub.../1/*`, where xpub corresponds to +`m/44'/0'/0'`. + +When interacting with a hardware device, it may be necessary to include +the entire path from the master down. BIP174 standardizes this by +providing the master key *fingerprint* (first 32 bit of the Hash160 of +the master pubkey), plus all derivation steps. To support constructing +these, we permit providing this key origin information inside the +descriptor language, even though it does not affect the actual +scriptPubKeys it refers to. + +Every public key can be prefixed by an 8-character hexadecimal +fingerprint plus optional derivation steps (hardened and unhardened) +surrounded by brackets, identifying the master and derivation path the key or xpub +that follows was derived with. + ### Including private keys Often it is useful to communicate a description of scripts along with the @@ -104,3 +138,20 @@ In order to easily represent the sets of scripts currently supported by existing Dash Core wallets, a convenience function `combo` is provided, which takes as input a public key, and constructs the P2PK and 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. diff --git a/src/Makefile.am b/src/Makefile.am index 9962221d55..2ca2bf7cde 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -277,6 +277,7 @@ BITCOIN_CORE_H = \ util/bytevectorhash.h \ util/error.h \ util/fees.h \ + util/spanparsing.h \ util/system.h \ util/asmap.h \ util/getuniquepath.h \ @@ -644,6 +645,7 @@ libdash_util_a_SOURCES = \ util/system.cpp \ util/asmap.cpp \ util/moneystr.cpp \ + util/spanparsing.cpp \ util/strencodings.cpp \ util/time.cpp \ util/serfloat.cpp \ diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 5c03524a29..1ab20b968d 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -2450,6 +2450,7 @@ UniValue scantxoutset(const JSONRPCRequest& request) " \"txid\" : \"transactionid\", (string) The transaction id\n" " \"vout\": n, (numeric) the vout value\n" " \"scriptPubKey\" : \"script\", (string) the script key\n" + " \"desc\" : \"descriptor\", (string) A specialized descriptor for the matched scriptPubKey\n" " \"amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " of the unspent output\n" " \"height\" : n, (numeric) Height of the unspent transaction output\n" " }\n" @@ -2484,6 +2485,7 @@ UniValue scantxoutset(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\""); } std::set needles; + std::map descriptors; CAmount total_in = 0; // loop through the scan objects @@ -2516,7 +2518,11 @@ UniValue scantxoutset(const JSONRPCRequest& request) if (!desc->Expand(i, provider, scripts, provider)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys: '%s'", desc_str)); } - needles.insert(scripts.begin(), scripts.end()); + for (const auto& script : scripts) { + std::string inferred = InferDescriptor(script, provider)->ToString(); + needles.emplace(script); + descriptors.emplace(std::move(script), std::move(inferred)); + } } } @@ -2549,6 +2555,7 @@ UniValue scantxoutset(const JSONRPCRequest& request) unspent.pushKV("txid", outpoint.hash.GetHex()); unspent.pushKV("vout", (int32_t)outpoint.n); unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey)); + unspent.pushKV("desc", descriptors[txo.scriptPubKey]); unspent.pushKV("amount", ValueFromAmount(txo.nValue)); unspent.pushKV("height", (int32_t)coin.nHeight); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index ac6272d5fe..af5bea8bee 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -83,6 +83,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "sendmany", 6, "use_is" }, { "sendmany", 7, "use_cj" }, { "sendmany", 8, "conf_target" }, + { "deriveaddresses", 1, "begin" }, + { "deriveaddresses", 2, "end" }, { "scantxoutset", 1, "scanobjects" }, { "addmultisigaddress", 0, "nrequired" }, { "addmultisigaddress", 1, "keys" }, diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 9a4f9a2021..6392903856 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -17,6 +17,7 @@ #include #include #include +#include