diff --git a/doc/descriptors.md b/doc/descriptors.md index dbdac2c5b6..7fbb3580bd 100644 --- a/doc/descriptors.md +++ b/doc/descriptors.md @@ -22,6 +22,7 @@ Output descriptors currently support: - Pay-to-witness-pubkey-hash scripts (P2WPKH), through the `wpkh` function. - Pay-to-script-hash scripts (P2SH), through the `sh` function. - Pay-to-witness-script-hash scripts (P2WSH), through the `wsh` function. +- Pay-to-taproot outputs (P2TR), through the `tr` function. - Multisig scripts, through the `multi` function. - Any type of supported address through the `addr` function. - Raw hex scripts through the `raw` function. @@ -37,12 +38,15 @@ Output descriptors currently support: - `sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))` describes an (overly complicated) P2SH-P2WSH-P2PKH output with the specified public key. - `multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)` describes a bare *1-of-2* multisig output with keys in the specified order. - `sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))` describes a P2SH *2-of-2* multisig output with keys in the specified order. +- `sh(sortedmulti(2,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01))` describes a P2SH *2-of-2* multisig output with keys sorted lexicographically in the resulting redeemScript. - `wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))` describes a P2WSH *2-of-3* multisig output with keys in the specified order. - `sh(wsh(multi(1,03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8,03499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)))` describes a P2SH-P2WSH *1-of-3* multisig output with keys in the specified order. - `pk(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8)` describes a P2PK output with the public key of the specified xpub. - `pkh(xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw/1'/2)` describes a P2PKH output with 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'`. - `wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))` describes a set of *1-of-2* P2WSH multisig outputs where the first multisig key is the *1/0/`i`* child of the first specified xpub and the second multisig key is the *0/0/`i`* child of the second specified xpub, and `i` is any number in a configurable range (`0-1000` by default). +- `wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))` describes a set of *1-of-2* P2WSH multisig outputs where one multisig key is the *1/0/`i`* child of the first specified xpub and the other multisig key is the *0/0/`i`* child of the second specified xpub, and `i` is any number in a configurable range (`0-1000` by default). The order of public keys in the resulting witnessScripts is determined by the lexicographic order of the public keys at that index. +- `tr(c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,{pk(fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),pk(e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)})` describes a P2TR output with the `c6...` x-only pubkey as internal key, and two script paths. ## Reference @@ -50,12 +54,14 @@ Descriptors consist of several types of expressions. The top level expression is `SCRIPT` expressions: - `sh(SCRIPT)` (top level only): P2SH embed the argument. -- `wsh(SCRIPT)` (not inside another 'wsh'): P2WSH embed the argument. +- `wsh(SCRIPT)` (top level or inside `sh` only): P2WSH embed the argument. - `pk(KEY)` (anywhere): P2PK output for the given public key. -- `pkh(KEY)` (anywhere): P2PKH output for the given public key (use `addr` if you only know the pubkey hash). -- `wpkh(KEY)` (not inside `wsh`): P2WPKH output for the given compressed pubkey. +- `pkh(KEY)` (not inside `tr`): P2PKH output for the given public key (use `addr` if you only know the pubkey hash). +- `wpkh(KEY)` (top level or inside `sh` only): P2WPKH output for the given compressed pubkey. - `combo(KEY)` (top level only): an alias for the collection of `pk(KEY)` and `pkh(KEY)`. If the key is compressed, it also includes `wpkh(KEY)` and `sh(wpkh(KEY))`. -- `multi(k,KEY_1,KEY_2,...,KEY_n)` (anywhere): k-of-n multisig script. +- `multi(k,KEY_1,KEY_2,...,KEY_n)` (not inside `tr`): k-of-n multisig script. +- `sortedmulti(k,KEY_1,KEY_2,...,KEY_n)` (not inside `tr`): k-of-n multisig script with keys sorted lexicographically in the resulting script. +- `tr(KEY)` or `tr(KEY,TREE)` (top level only): P2TR output with the specified key as internal key, and optionally a tree of script paths. - `addr(ADDR)` (top level only): the script which ADDR expands to. - `raw(HEX)` (top level only): the script whose hex encoding is HEX. @@ -68,12 +74,17 @@ Descriptors consist of several types of expressions. The top level expression is - Followed by the actual key, which is either: - Hex encoded public keys (either 66 characters starting with `02` or `03` for a compressed pubkey, or 130 characters starting with `04` for an uncompressed pubkey). - Inside `wpkh` and `wsh`, only compressed public keys are permitted. + - Inside `tr`, x-only pubkeys are also permitted (64 hex characters). - [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 extended 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. +`TREE` expressions: +- any `SCRIPT` expression +- An open brace `{`, a `TREE` expression, a comma `,`, a `TREE` expression, and a closing brace `}` + (Anywhere a `'` suffix is permitted to denote hardened derivation, the suffix `h` can be used instead.) `ADDR` expressions are any type of supported address: @@ -101,11 +112,12 @@ not contain "p2" for brevity. Several pieces of software use multi-signature (multisig) scripts based on Bitcoin's OP_CHECKMULTISIG opcode. To support these, we introduce the -`multi(k,key_1,key_2,...,key_n)` function. It represents a *k-of-n* +`multi(k,key_1,key_2,...,key_n)` and `sortedmulti(k,key_1,key_2,...,key_n)` +functions. They represent a *k-of-n* multisig policy, where any *k* out of the *n* provided `KEY` expressions must sign. -Key order is significant. A `multi()` expression describes a multisig script +Key order is significant for `multi()` expression describes a multisig script with keys in the specified order, and in a search for TXOs, it will not match outputs with multisig scriptPubKeys that have the same keys in a different order. Also, to prevent a combinatorial explosion of the search space, if more @@ -114,6 +126,10 @@ or `*'`, the `multi()` expression only matches multisig scripts with the `i`th child key from each wildcard path in lockstep, rather than scripts with any combination of child keys from each wildcard path. +Key order does not matter for `sortedmulti()`. `sortedmulti()` behaves in the same way +as `multi()` does but the keys are reordered in the resulting script such that they +are lexicographically ordered as described in BIP67. + ### BIP32 derived keys and chains Most modern wallet software and hardware uses keys that are derived using diff --git a/src/Makefile.am b/src/Makefile.am index 1d823f97df..f2f1187c38 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -287,6 +287,7 @@ DEFI_CORE_H = \ util/translation.h \ util/url.h \ util/validation.h \ + util/vector.h \ validation.h \ validationinterface.h \ versionbits.h \ diff --git a/src/bech32.cpp b/src/bech32.cpp index 6722a55d2f..0b67a0c0de 100644 --- a/src/bech32.cpp +++ b/src/bech32.cpp @@ -3,16 +3,22 @@ // file LICENSE or http://www.opensource.org/licenses/mit-license.php. #include +#include + +#include + +namespace bech32 +{ namespace { typedef std::vector data; -/** The Bech32 character set for encoding. */ +/** The Bech32 and Bech32m character set for encoding. */ const char* CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; -/** The Bech32 character set for decoding. */ +/** The Bech32 and Bech32m character set for decoding. */ const int8_t CHARSET_REV[128] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, @@ -24,11 +30,10 @@ const int8_t CHARSET_REV[128] = { 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1 }; -/** Concatenate two byte arrays. */ -data Cat(data x, const data& y) -{ - x.insert(x.end(), y.begin(), y.end()); - return x; +/* Determine the final constant to use for the specified encoding. */ +uint32_t EncodingConstant(Encoding encoding) { + assert(encoding == Encoding::BECH32 || encoding == Encoding::BECH32M); + return encoding == Encoding::BECH32 ? 1 : 0x2bc830a3; } /** This function will compute what 6 5-bit values to XOR into the last 6 input values, in order to @@ -58,7 +63,7 @@ uint32_t PolyMod(const data& v) // During the course of the loop below, `c` contains the bitpacked coefficients of the // polynomial constructed from just the values of v that were processed so far, mod g(x). In - // the above example, `c` initially corresponds to 1 mod (x), and after processing 2 inputs of + // the above example, `c` initially corresponds to 1 mod g(x), and after processing 2 inputs of // v, it corresponds to x^2 + v0*x + v1 mod g(x). As 1 mod g(x) = 1, that is the starting value // for `c`. uint32_t c = 1; @@ -115,21 +120,24 @@ data ExpandHRP(const std::string& hrp) } /** Verify a checksum. */ -bool VerifyChecksum(const std::string& hrp, const data& values) +Encoding VerifyChecksum(const std::string& hrp, const data& values) { // PolyMod computes what value to xor into the final values to make the checksum 0. However, // if we required that the checksum was 0, it would be the case that appending a 0 to a valid // list of values would result in a new valid list. For that reason, Bech32 requires the - // resulting checksum to be 1 instead. - return PolyMod(Cat(ExpandHRP(hrp), values)) == 1; + // resulting checksum to be 1 instead. In Bech32m, this constant was amended. + const uint32_t check = PolyMod(Cat(ExpandHRP(hrp), values)); + if (check == EncodingConstant(Encoding::BECH32)) return Encoding::BECH32; + if (check == EncodingConstant(Encoding::BECH32M)) return Encoding::BECH32M; + return Encoding::INVALID; } /** Create a checksum. */ -data CreateChecksum(const std::string& hrp, const data& values) +data CreateChecksum(Encoding encoding, const std::string& hrp, const data& values) { data enc = Cat(ExpandHRP(hrp), values); enc.resize(enc.size() + 6); // Append 6 zeroes - uint32_t mod = PolyMod(enc) ^ 1; // Determine what to XOR into those 6 zeroes. + uint32_t mod = PolyMod(enc) ^ EncodingConstant(encoding); // Determine what to XOR into those 6 zeroes. data ret(6); for (size_t i = 0; i < 6; ++i) { // Convert the 5-bit groups in mod to checksum values. @@ -140,12 +148,13 @@ data CreateChecksum(const std::string& hrp, const data& values) } // namespace -namespace bech32 -{ - -/** Encode a Bech32 string. */ -std::string Encode(const std::string& hrp, const data& values) { - data checksum = CreateChecksum(hrp, values); +/** Encode a Bech32 or Bech32m string. */ +std::string Encode(Encoding encoding, const std::string& hrp, const data& values) { + // First ensure that the HRP is all lowercase. BIP-173 and BIP350 require an encoder + // to return a lowercase Bech32/Bech32m string, but if given an uppercase HRP, the + // result will always be invalid. + for (const char& c : hrp) assert(c < 'A' || c > 'Z'); + data checksum = CreateChecksum(encoding, hrp, values); data combined = Cat(values, checksum); std::string ret = hrp + '1'; ret.reserve(ret.size() + combined.size()); @@ -155,8 +164,8 @@ std::string Encode(const std::string& hrp, const data& values) { return ret; } -/** Decode a Bech32 string. */ -std::pair Decode(const std::string& str) { +/** Decode a Bech32 or Bech32m string. */ +DecodeResult Decode(const std::string& str) { bool lower = false, upper = false; for (size_t i = 0; i < str.size(); ++i) { unsigned char c = str[i]; @@ -183,10 +192,9 @@ std::pair Decode(const std::string& str) { for (size_t i = 0; i < pos; ++i) { hrp += LowerCase(str[i]); } - if (!VerifyChecksum(hrp, values)) { - return {}; - } - return {hrp, data(values.begin(), values.end() - 6)}; + Encoding result = VerifyChecksum(hrp, values); + if (result == Encoding::INVALID) return {}; + return {result, std::move(hrp), data(values.begin(), values.end() - 6)}; } } // namespace bech32 diff --git a/src/bech32.h b/src/bech32.h index 09b6dd5bd7..775922d482 100644 --- a/src/bech32.h +++ b/src/bech32.h @@ -2,12 +2,13 @@ // Distributed under the MIT software license, see the accompanying // file LICENSE or http://www.opensource.org/licenses/mit-license.php. -// Bech32 is a string encoding format used in newer address types. -// The output consists of a human-readable part (alphanumeric), a -// separator character (1), and a base32 data section, the last -// 6 characters of which are a checksum. +// Bech32 and Bech32m are string encoding formats used in newer +// address types. The outputs consist of a human-readable part +// (alphanumeric), a separator character (1), and a base32 data +// section, the last 6 characters of which are a checksum. The +// module is namespaced under bech32 for historical reasons. // -// For more information, see BIP 173. +// For more information, see BIP 173 and BIP 350. #ifndef DEFI_BECH32_H #define DEFI_BECH32_H @@ -19,11 +20,28 @@ namespace bech32 { -/** Encode a Bech32 string. Returns the empty string in case of failure. */ -std::string Encode(const std::string& hrp, const std::vector& values); +enum class Encoding { + INVALID, //!< Failed decoding + BECH32, //!< Bech32 encoding as defined in BIP173 + BECH32M, //!< Bech32m encoding as defined in BIP350 +}; -/** Decode a Bech32 string. Returns (hrp, data). Empty hrp means failure. */ -std::pair> Decode(const std::string& str); +/** Encode a Bech32 or Bech32m string. If hrp contains uppercase characters, this will cause an + * assertion error. Encoding must be one of BECH32 or BECH32M. */ +std::string Encode(Encoding encoding, const std::string& hrp, const std::vector& values); + +struct DecodeResult +{ + Encoding encoding; //!< What encoding was detected in the result; Encoding::INVALID if failed. + std::string hrp; //!< The human readable part + std::vector data; //!< The payload (excluding checksum) + + DecodeResult() : encoding(Encoding::INVALID) {} + DecodeResult(Encoding enc, std::string&& h, std::vector&& d) : encoding(enc), hrp(std::move(h)), data(std::move(d)) {} +}; + +/** Decode a Bech32 or Bech32m string. */ +DecodeResult Decode(const std::string& str); } // namespace bech32 diff --git a/src/bench/bech32.cpp b/src/bench/bech32.cpp index fcccb72cfc..b9923cacae 100644 --- a/src/bench/bech32.cpp +++ b/src/bench/bech32.cpp @@ -18,7 +18,7 @@ static void Bech32Encode(benchmark::State& state) tmp.reserve(1 + 32 * 8 / 5); ConvertBits<8, 5, true>([&](unsigned char c) { tmp.push_back(c); }, v.begin(), v.end()); while (state.KeepRunning()) { - bech32::Encode("bc", tmp); + bech32::Encode(bech32::Encoding::BECH32, "bc", tmp); } } diff --git a/src/chainparams.cpp b/src/chainparams.cpp index ea477bf397..1938378c1c 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -39,7 +39,7 @@ std::vector CChainParams::CreateGenesisMasternodes() CTxDestination ownerDest = DecodeDestination(addrs.ownerAddress, *this); assert(ownerDest.index() == PKHashType || ownerDest.index() == WitV0KeyHashType); - CKeyID operatorAuthKey = CKeyID::FromOrDefaultDestination(operatorDest, KeyType::MNOperatorKeyType); + CKeyID operatorAuthKey = FromOrDefaultDestination(operatorDest, KeyType::MNOperatorKeyType); genesisTeam.insert(operatorAuthKey); CDataStream metadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION); metadata << static_cast(CustomTxType::CreateMasternode) diff --git a/src/dfi/anchors.cpp b/src/dfi/anchors.cpp index de208b0757..5e186b05df 100644 --- a/src/dfi/anchors.cpp +++ b/src/dfi/anchors.cpp @@ -72,7 +72,7 @@ CAnchor CAnchor::Create(const std::vector &auths, const CTxD for (size_t i = 0; i < auths.size(); ++i) { anchor.sigs.push_back(auths[i].GetSignature()); } - anchor.rewardKeyID = CKeyID::FromOrDefaultDestination(rewardDest, KeyType::MNRewardKeyType); + anchor.rewardKeyID = FromOrDefaultDestination(rewardDest, KeyType::MNRewardKeyType); anchor.rewardKeyType = rewardDest.index(); return anchor; } diff --git a/src/dfi/consensus/masternodes.cpp b/src/dfi/consensus/masternodes.cpp index 2c1b16741d..490d90aff8 100644 --- a/src/dfi/consensus/masternodes.cpp +++ b/src/dfi/consensus/masternodes.cpp @@ -86,7 +86,7 @@ Res CMasternodesConsensus::operator()(const CCreateMasterNodeMessage &obj) const assert(!coin.IsSpent()); CTxDestination pendingDest; assert(ExtractDestination(coin.out.scriptPubKey, pendingDest)); - const CKeyID storedID = CKeyID::FromOrDefaultDestination(pendingDest, KeyType::MNOwnerKeyType); + const CKeyID storedID = FromOrDefaultDestination(pendingDest, KeyType::MNOwnerKeyType); if ((!storedID.IsNull()) && (storedID == node.ownerAuthAddress || storedID == node.operatorAuthAddress)) { duplicate = true; return false; @@ -192,7 +192,7 @@ Res CMasternodesConsensus::operator()(const CUpdateMasterNodeMessage &obj) const CTxDestination dest; ExtractDestination(tx.vout[1].scriptPubKey, dest); - const auto keyID = CKeyID::FromOrDefaultDestination(dest, KeyType::MNOwnerKeyType); + const auto keyID = FromOrDefaultDestination(dest, KeyType::MNOwnerKeyType); if (keyID.IsNull()) { return Res::Err("Owner address must be P2PKH or P2WPKH type"); } @@ -217,7 +217,7 @@ Res CMasternodesConsensus::operator()(const CUpdateMasterNodeMessage &obj) const assert(!coin.IsSpent()); CTxDestination pendingDest; assert(ExtractDestination(coin.out.scriptPubKey, pendingDest)); - const CKeyID storedID = CKeyID::FromOrDefaultDestination(pendingDest, KeyType::MNOwnerKeyType); + const CKeyID storedID = FromOrDefaultDestination(pendingDest, KeyType::MNOwnerKeyType); if (storedID == keyID) { duplicate = true; return false; diff --git a/src/dfi/masternodes.cpp b/src/dfi/masternodes.cpp index 94d86e4dbb..cf02b76775 100644 --- a/src/dfi/masternodes.cpp +++ b/src/dfi/masternodes.cpp @@ -254,7 +254,7 @@ std::optional> CMasternodesView::AmIOperator() const const auto operators = gArgs.GetArgs("-masternode_operator"); for (const auto &key : operators) { const CTxDestination dest = DecodeDestination(key); - const CKeyID authAddress = CKeyID::FromOrDefaultDestination(dest, KeyType::MNOperatorKeyType); + const CKeyID authAddress = FromOrDefaultDestination(dest, KeyType::MNOperatorKeyType); if (!authAddress.IsNull()) { if (auto nodeId = GetMasternodeIdByOperator(authAddress)) { return std::make_pair(authAddress, *nodeId); @@ -269,7 +269,7 @@ std::set> CMasternodesView::GetOperatorsMulti() const std::set> operatorPairs; for (const auto &key : operators) { const CTxDestination dest = DecodeDestination(key); - const CKeyID authAddress = CKeyID::FromOrDefaultDestination(dest, KeyType::MNOperatorKeyType); + const CKeyID authAddress = FromOrDefaultDestination(dest, KeyType::MNOperatorKeyType); if (!authAddress.IsNull()) { if (auto nodeId = GetMasternodeIdByOperator(authAddress)) { operatorPairs.insert(std::make_pair(authAddress, *nodeId)); @@ -282,7 +282,7 @@ std::set> CMasternodesView::GetOperatorsMulti() const std::optional> CMasternodesView::AmIOwner() const { CTxDestination dest = DecodeDestination(gArgs.GetArg("-masternode_owner", "")); - const CKeyID authAddress = CKeyID::FromOrDefaultDestination(dest, KeyType::MNOwnerKeyType); + const CKeyID authAddress = FromOrDefaultDestination(dest, KeyType::MNOwnerKeyType); if (!authAddress.IsNull()) { auto nodeId = GetMasternodeIdByOwner(authAddress); if (nodeId) { diff --git a/src/dfi/rpc_masternodes.cpp b/src/dfi/rpc_masternodes.cpp index b31d75931a..02cb859bbb 100644 --- a/src/dfi/rpc_masternodes.cpp +++ b/src/dfi/rpc_masternodes.cpp @@ -191,7 +191,7 @@ UniValue createmasternode(const JSONRPCRequest &request) { strprintf("Address (%s) is not owned by the wallet", EncodeDestination(ownerDest))); } - const CKeyID operatorAuthKey = CKeyID::FromOrDefaultDestination(operatorDest, KeyType::MNOperatorKeyType); + const CKeyID operatorAuthKey = FromOrDefaultDestination(operatorDest, KeyType::MNOperatorKeyType); CDataStream metadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION); metadata << static_cast(CustomTxType::CreateMasternode) << static_cast(operatorDest.index()) << operatorAuthKey; @@ -468,7 +468,7 @@ UniValue updatemasternode(const JSONRPCRequest &request) { } if (!metaObj["operatorAddress"].isNull()) { - const CKeyID keyID = CKeyID::FromOrDefaultDestination(operatorDest, KeyType::MNOperatorKeyType); + const CKeyID keyID = FromOrDefaultDestination(operatorDest, KeyType::MNOperatorKeyType); msg.updates.emplace_back(static_cast(UpdateMasternodeType::OperatorAddress), std::make_pair(static_cast(operatorDest.index()), std::vector(keyID.begin(), keyID.end()))); @@ -479,7 +479,7 @@ UniValue updatemasternode(const JSONRPCRequest &request) { msg.updates.emplace_back(static_cast(UpdateMasternodeType::RemRewardAddress), std::pair>()); } else { - const CKeyID keyID = CKeyID::FromOrDefaultDestination(rewardDest, KeyType::MNRewardKeyType); + const CKeyID keyID = FromOrDefaultDestination(rewardDest, KeyType::MNRewardKeyType); msg.updates.emplace_back(static_cast(UpdateMasternodeType::SetRewardAddress), std::make_pair(static_cast(rewardDest.index()), std::vector(keyID.begin(), keyID.end()))); @@ -648,7 +648,7 @@ UniValue getmasternode(const JSONRPCRequest &request) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Masternode not found"); } - const auto keyId = CKeyID::TryFromDestination(dest); + const auto keyId = TryFromDestination(dest); if (!keyId) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Masternode not found"); } diff --git a/src/dfi/rpc_proposals.cpp b/src/dfi/rpc_proposals.cpp index 87dbadb169..10b10a1c5b 100644 --- a/src/dfi/rpc_proposals.cpp +++ b/src/dfi/rpc_proposals.cpp @@ -548,7 +548,7 @@ UniValue votegov(const JSONRPCRequest &request) { strprintf("%s does not refer to a P2PKH or P2WPKH address", id)); } - const CKeyID ckeyId = CKeyID::FromOrDefaultDestination(dest, KeyType::MNOwnerKeyType); + const CKeyID ckeyId = FromOrDefaultDestination(dest, KeyType::MNOwnerKeyType); if (auto masterNodeIdByOwner = view.GetMasternodeIdByOwner(ckeyId)) { mnId = masterNodeIdByOwner.value(); } else if (auto masterNodeIdByOperator = view.GetMasternodeIdByOperator(ckeyId)) { @@ -694,7 +694,7 @@ UniValue votegovbatch(const JSONRPCRequest &request) { strprintf("%s does not refer to a P2PKH or P2WPKH address", id)); } - const CKeyID ckeyId = CKeyID::FromOrDefaultDestination(dest, KeyType::MNOwnerKeyType); + const CKeyID ckeyId = FromOrDefaultDestination(dest, KeyType::MNOwnerKeyType); if (auto masterNodeIdByOwner = view.GetMasternodeIdByOwner(ckeyId)) { mnId = masterNodeIdByOwner.value(); } else if (auto masterNodeIdByOperator = view.GetMasternodeIdByOperator(ckeyId)) { diff --git a/src/dfi/validation.cpp b/src/dfi/validation.cpp index 1e84fa11e3..21aa417940 100644 --- a/src/dfi/validation.cpp +++ b/src/dfi/validation.cpp @@ -2586,7 +2586,7 @@ static void ProcessMasternodeUpdates(const CBlockIndex *pindex, assert(!coin.IsSpent()); CTxDestination dest; assert(ExtractDestination(coin.out.scriptPubKey, dest)); - const CKeyID keyId = CKeyID::FromOrDefaultDestination(dest, KeyType::MNOwnerKeyType); + const CKeyID keyId = FromOrDefaultDestination(dest, KeyType::MNOwnerKeyType); cache.UpdateMasternodeOwner(value.masternodeID, *node, dest.index(), keyId); } return true; diff --git a/src/init.cpp b/src/init.cpp index ab33581936..1849c5fdad 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -2423,7 +2423,7 @@ bool AppInitMain(InitInterfaces& interfaces) auto& coinbaseScript = stakerParams.coinbaseScript; CTxDestination destination = DecodeDestination(op); - operatorId = CKeyID::FromOrDefaultDestination(destination, KeyType::MNOperatorKeyType); + operatorId = FromOrDefaultDestination(destination, KeyType::MNOperatorKeyType); if (operatorId.IsNull()) { LogPrintf("Error: wrong masternode_operator address (%s)\n", op); continue; diff --git a/src/key_io.cpp b/src/key_io.cpp index 9d408df90b..095c1ba945 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -9,6 +9,9 @@ #include #include +/// Maximum witness length for Bech32 addresses. +static constexpr std::size_t BECH32_WITNESS_PROG_MAX_LEN = 40; + namespace { class DestinationEncoder { @@ -37,7 +40,7 @@ class DestinationEncoder std::vector data = {0}; data.reserve(33); ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.begin(), id.end()); - return bech32::Encode(m_params.Bech32HRP(), data); + return bech32::Encode(bech32::Encoding::BECH32, m_params.Bech32HRP(), data); } std::string operator()(const WitnessV0ScriptHash& id) const @@ -45,7 +48,15 @@ class DestinationEncoder std::vector data = {0}; data.reserve(53); ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.begin(), id.end()); - return bech32::Encode(m_params.Bech32HRP(), data); + return bech32::Encode(bech32::Encoding::BECH32, m_params.Bech32HRP(), data); + } + + std::string operator()(const WitnessV1Taproot& tap) const + { + std::vector data = {1}; + data.reserve(53); + ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, tap.begin(), tap.end()); + return bech32::Encode(bech32::Encoding::BECH32M, m_params.Bech32HRP(), data); } std::string operator()(const WitnessUnknown& id) const @@ -56,7 +67,7 @@ class DestinationEncoder std::vector data = {(unsigned char)id.version}; data.reserve(1 + (id.length * 8 + 4) / 5); ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, id.program, id.program + id.length); - return bech32::Encode(m_params.Bech32HRP(), data); + return bech32::Encode(bech32::Encoding::BECH32M, m_params.Bech32HRP(), data); } std::string operator()(const WitnessV16EthHash& id) const @@ -83,13 +94,15 @@ class DestinationEncoder }; } // namespace -CTxDestination DecodeDestination(const std::string& str, const CChainParams& params) +CTxDestination DecodeDestination(const std::string& str, const CChainParams& params, std::string& error_str) { std::vector data; uint160 hash; + error_str = ""; if (str.size() == ETH_ADDR_LENGTH_INC_PREFIX && str.substr(0, 2) == ETH_ADDR_PREFIX) { const auto hex = str.substr(2); if (!IsHex(hex)) { + error_str = "erc55 address must be in hex format"; return CNoDestination(); } data = ParseHex(hex); @@ -112,15 +125,35 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par std::copy(data.begin() + script_prefix.size(), data.end(), hash.begin()); return ScriptHash(hash); } + + // Set potential error message. + // This message may be changed if the address can also be interpreted as a Bech32 address. + error_str = "Invalid prefix for Base58-encoded address"; } data.clear(); - auto bech = bech32::Decode(str); - if (bech.second.size() > 0 && bech.first == params.Bech32HRP()) { + const auto dec = bech32::Decode(str); + if ((dec.encoding == bech32::Encoding::BECH32 || dec.encoding == bech32::Encoding::BECH32M) && dec.data.size() > 0) { // Bech32 decoding - int version = bech.second[0]; // The first 5 bit symbol is the witness version (0-16) + error_str = ""; + + if (dec.hrp != params.Bech32HRP()) { + error_str = "Invalid prefix for Bech32 address"; + return CNoDestination(); + } + + int version = dec.data[0]; // The first 5 bit symbol is the witness version (0-16) + if (version == 0 && dec.encoding != bech32::Encoding::BECH32) { + error_str = "Version 0 witness address must use Bech32 checksum"; + return CNoDestination(); + } + if (version != 0 && dec.encoding != bech32::Encoding::BECH32M) { + error_str = "Version 1+ witness address must use Bech32m checksum"; + return CNoDestination(); + } + // The rest of the symbols are converted witness program bytes. - data.reserve(((bech.second.size() - 1) * 5) / 8); - if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, bech.second.begin() + 1, bech.second.end())) { + data.reserve(((dec.data.size() - 1) * 5) / 8); + if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, dec.data.begin() + 1, dec.data.end())) { if (version == 0) { { WitnessV0KeyHash keyid; @@ -136,11 +169,28 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par return scriptid; } } + + error_str = "Invalid Bech32 v0 address data size"; return CNoDestination(); } - if (version > 16 || data.size() < 2 || data.size() > 40) { + + if (version == 1 && data.size() == WITNESS_V1_TAPROOT_SIZE) { + static_assert(WITNESS_V1_TAPROOT_SIZE == WitnessV1Taproot::size()); + WitnessV1Taproot tap; + std::copy(data.begin(), data.end(), tap.begin()); + return tap; + } + + if (version > 16) { + error_str = "Invalid Bech32 address witness version"; + return CNoDestination(); + } + + if (data.size() < 2 || data.size() > BECH32_WITNESS_PROG_MAX_LEN) { + error_str = "Invalid Bech32 address data size"; return CNoDestination(); } + WitnessUnknown unk; unk.version = version; std::copy(data.begin(), data.end(), unk.program); @@ -148,6 +198,10 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par return unk; } } + + // Set error message if address can't be interpreted as Base58 or Bech32. + if (error_str.empty()) error_str = "Invalid address format"; + return CNoDestination(); } @@ -237,14 +291,27 @@ std::string EncodeDestination(const CTxDestination& dest) return std::visit(DestinationEncoder(Params()), dest); } +CTxDestination DecodeDestination(const std::string& str, std::string& error_msg) +{ + return DecodeDestination(str, Params(), error_msg); +} + CTxDestination DecodeDestination(const std::string& str) { - return DecodeDestination(str, Params()); + std::string error_msg; + return DecodeDestination(str, error_msg); +} + +CTxDestination DecodeDestination(const std::string& str, const CChainParams& params) +{ + std::string error_msg; + return DecodeDestination(str, params, error_msg); } bool IsValidDestinationString(const std::string& str, const CChainParams& params) { - return IsValidDestination(DecodeDestination(str, params)); + std::string error_msg; + return IsValidDestination(DecodeDestination(str, params, error_msg)); } bool IsValidDestinationString(const std::string& str) diff --git a/src/key_io.h b/src/key_io.h index b14613c391..a77610bd22 100644 --- a/src/key_io.h +++ b/src/key_io.h @@ -24,6 +24,7 @@ std::string EncodeExtPubKey(const CExtPubKey& extpubkey); std::string EncodeDestination(const CTxDestination& dest); CTxDestination DecodeDestination(const std::string& str); +CTxDestination DecodeDestination(const std::string& str, std::string& error_msg); CTxDestination DecodeDestination(const std::string& str, const CChainParams& params); bool IsValidDestinationString(const std::string& str); bool IsValidDestinationString(const std::string& str, const CChainParams& params); diff --git a/src/outputtype.cpp b/src/outputtype.cpp index 8844e367bd..f3fae903c6 100644 --- a/src/outputtype.cpp +++ b/src/outputtype.cpp @@ -10,6 +10,7 @@ #include