diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 841951f75c..48a4d98a04 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -566,6 +566,8 @@ class CCustomParams : public CRegTestParams { base58Prefixes[PARENT_SCRIPT_ADDRESS] = std::vector(1, args.GetArg("-parentscriptprefix", 196)); parent_bech32_hrp = args.GetArg("-parent_bech32_hrp", "bcrt"); + base58Prefixes[BLINDED_ADDRESS] = std::vector(1, args.GetArg("-blindedprefix", 4)); + // END ELEMENTS fields // CSV always active by default, unlike regtest diff --git a/src/chainparams.h b/src/chainparams.h index 8a9e760f8b..1b9e14668c 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -54,6 +54,7 @@ public: EXT_PUBLIC_KEY, EXT_SECRET_KEY, // ELEMENTS + BLINDED_ADDRESS, PARENT_PUBKEY_ADDRESS, PARENT_SCRIPT_ADDRESS, diff --git a/src/key_io.cpp b/src/key_io.cpp index 97ac87c367..50e534b26c 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -31,6 +31,17 @@ public: std::string operator()(const PKHash& id) const { + if (id.blinding_pubkey.IsFullyValid()) { + assert(!for_parent); + std::vector data = m_params.Base58Prefix(CChainParams::BLINDED_ADDRESS); + // Blinded addresses have the actual address type prefix inside the payload. + std::vector prefix = m_params.Base58Prefix(CChainParams::PUBKEY_ADDRESS); + data.insert(data.end(), prefix.begin(), prefix.end()); + data.insert(data.end(), id.blinding_pubkey.begin(), id.blinding_pubkey.end()); + data.insert(data.end(), id.begin(), id.end()); + return EncodeBase58Check(data); + } + CChainParams::Base58Type type = for_parent ? CChainParams::PARENT_PUBKEY_ADDRESS : CChainParams::PUBKEY_ADDRESS; std::vector data = m_params.Base58Prefix(type); data.insert(data.end(), id.begin(), id.end()); @@ -39,6 +50,17 @@ public: std::string operator()(const ScriptHash& id) const { + if (id.blinding_pubkey.IsFullyValid()) { + assert(!for_parent); + std::vector data = m_params.Base58Prefix(CChainParams::BLINDED_ADDRESS); + // Blinded addresses have the actual address type prefix inside the payload. + std::vector prefix = m_params.Base58Prefix(CChainParams::SCRIPT_ADDRESS); + data.insert(data.end(), prefix.begin(), prefix.end()); + data.insert(data.end(), id.blinding_pubkey.begin(), id.blinding_pubkey.end()); + data.insert(data.end(), id.begin(), id.end()); + return EncodeBase58Check(data); + } + CChainParams::Base58Type type = for_parent ? CChainParams::PARENT_SCRIPT_ADDRESS : CChainParams::SCRIPT_ADDRESS; std::vector data = m_params.Base58Prefix(type); data.insert(data.end(), id.begin(), id.end()); @@ -82,17 +104,31 @@ public: CTxDestination DecodeDestination(const std::string& str, const CChainParams& params, const bool for_parent) { std::vector data; + size_t pk_size = CPubKey::COMPRESSED_PUBLIC_KEY_SIZE; uint160 hash; if (DecodeBase58Check(str, data)) { // base58-encoded Bitcoin addresses. // Public-key-hash-addresses have version 0 (or 111 testnet). // The data vector contains RIPEMD160(SHA256(pubkey)), where pubkey is the serialized public key. + + // Blinded addresses have two prefixes: first the blinded one, then the traditional one. + const std::vector& blinded_prefix = params.Base58Prefix(CChainParams::BLINDED_ADDRESS); + CChainParams::Base58Type type_pkh = for_parent ? CChainParams::PARENT_PUBKEY_ADDRESS : CChainParams::PUBKEY_ADDRESS; const std::vector& pubkey_prefix = params.Base58Prefix(type_pkh); if (data.size() == hash.size() + pubkey_prefix.size() && std::equal(pubkey_prefix.begin(), pubkey_prefix.end(), data.begin())) { std::copy(data.begin() + pubkey_prefix.size(), data.end(), hash.begin()); return PKHash(hash); + } else if (data.size() == hash.size() + blinded_prefix.size() + pubkey_prefix.size() + pk_size && + std::equal(blinded_prefix.begin(), blinded_prefix.end(), data.begin()) && + std::equal(pubkey_prefix.begin(), pubkey_prefix.end(), data.begin() + blinded_prefix.size())) { + auto payload_start = data.begin() + blinded_prefix.size() + pubkey_prefix.size(); + CPubKey pubkey; + pubkey.Set(payload_start, payload_start + pk_size); + std::copy(payload_start + pk_size, data.end(), hash.begin()); + return PKHash(hash, pubkey); } + // Script-hash-addresses have version 5 (or 196 testnet). // The data vector contains RIPEMD160(SHA256(cscript)), where cscript is the serialized redemption script. CChainParams::Base58Type type_sh = for_parent ? CChainParams::PARENT_SCRIPT_ADDRESS : CChainParams::SCRIPT_ADDRESS; @@ -100,6 +136,14 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par if (data.size() == hash.size() + script_prefix.size() && std::equal(script_prefix.begin(), script_prefix.end(), data.begin())) { std::copy(data.begin() + script_prefix.size(), data.end(), hash.begin()); return ScriptHash(hash); + } else if (data.size() == hash.size() + blinded_prefix.size() + pubkey_prefix.size() + pk_size && + std::equal(blinded_prefix.begin(), blinded_prefix.end(), data.begin()) && + std::equal(script_prefix.begin(), script_prefix.end(), data.begin() + blinded_prefix.size())) { + auto payload_start = data.begin() + blinded_prefix.size() + script_prefix.size(); + CPubKey pubkey; + pubkey.Set(payload_start, payload_start + pk_size); + std::copy(payload_start + pk_size, data.end(), hash.begin()); + return ScriptHash(hash, pubkey); } } data.clear(); diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index ded78f0879..5be2b342fb 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -543,6 +543,88 @@ UniValue calcfastmerkleroot(const JSONRPCRequest& request) } +class BlindingPubkeyAdderVisitor : public boost::static_visitor<> +{ +public: + CPubKey blind_key; + explicit BlindingPubkeyAdderVisitor(const CPubKey& blind_key_in) : blind_key(blind_key_in) {} + + void operator()(const CNoDestination& dest) const {} + + void operator()(PKHash& keyID) const + { + keyID.blinding_pubkey = blind_key; + } + + void operator()(ScriptHash& scriptID) const + { + scriptID.blinding_pubkey = blind_key; + } + + void operator()(WitnessV0KeyHash& id) const + { + id.blinding_pubkey = blind_key; + } + + void operator()(WitnessV0ScriptHash& id) const + { + id.blinding_pubkey = blind_key; + } + + void operator()(WitnessUnknown& id) const + { + id.blinding_pubkey = blind_key; + } + + void operator()(const NullData& id) const {} +}; + + +UniValue createblindedaddress(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 2) + { + throw std::runtime_error( + "createblindedaddress address blinding_key\n" + "\nCreates a blinded address using the provided blinding key.\n" + "\nArguments:\n" + "1. \"address\" (string, required) The unblinded address to be blinded.\n" + "2. \"blinding_key\" (string, required) The blinding public key. This can be obtained for a given address using `validateaddress`.\n" + "\nResult:\n" + "\"blinded_address\" (string) The blinded address.\n" + "\nExamples:\n" + "\nCreate a multisig address from 2 addresses\n" + + HelpExampleCli("createblindedaddress", "HEZk3iQi1jC49bxUriTtynnXgWWWdAYx16 ec09811118b6febfa5ebe68642e5091c418fbace07e655da26b4a845a691fc2d") + + "\nAs a json rpc call\n" + + HelpExampleRpc("createblindedaddress", "HEZk3iQi1jC49bxUriTtynnXgWWWdAYx16, ec09811118b6febfa5ebe68642e5091c418fbace07e655da26b4a845a691fc2d") + ); + } + + CTxDestination address = DecodeDestination(request.params[0].get_str()); + if (!IsValidDestination(address)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script"); + } + if (IsBlindDestination(address)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not an unblinded address"); + } + + if (!IsHex(request.params[1].get_str())) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid hexadecimal for key"); + } + std::vector keydata = ParseHex(request.params[1].get_str()); + if (keydata.size() != 33) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid hexadecimal key length, must be length 66."); + } + + CPubKey key; + key.Set(keydata.begin(), keydata.end()); + + // Append blinding key and return + boost::apply_visitor(BlindingPubkeyAdderVisitor(key), address); + return EncodeDestination(address); +} + + // END ELEMENTS CALLS // @@ -560,6 +642,7 @@ static const CRPCCommand commands[] = // ELEMENTS: { "util", "getpakinfo", &getpakinfo, {}}, { "util", "tweakfedpegscript", &tweakfedpegscript, {"claim_script"} }, + { "util", "createblindedaddress", &createblindedaddress, {"address", "blinding_key"}}, { "hidden", "calcfastmerkleroot", &calcfastmerkleroot, {"leaves"} }, /* Not shown in help */ diff --git a/src/script/standard.cpp b/src/script/standard.cpp index afedcc240b..3ac8f1f4fd 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -22,14 +22,25 @@ unsigned nMaxDatacarrierBytes = MAX_OP_RETURN_RELAY; CScriptID::CScriptID(const CScript& in) : uint160(Hash160(in.begin(), in.end())) {} ScriptHash::ScriptHash(const CScript& in) : uint160(Hash160(in.begin(), in.end())) {} +ScriptHash::ScriptHash(const CScript& in, const CPubKey& blinding_pubkey_in) : uint160(Hash160(in.begin(), in.end())), blinding_pubkey(blinding_pubkey_in) {} +ScriptHash::ScriptHash(const uint160& hash, const CPubKey& blinding_pubkey_in) : uint160(hash), blinding_pubkey(blinding_pubkey_in) {} PKHash::PKHash(const CPubKey& pubkey) : uint160(pubkey.GetID()) {} +PKHash::PKHash(const CPubKey& pubkey, const CPubKey& blinding_pubkey_in) : uint160(pubkey.GetID()), blinding_pubkey(blinding_pubkey_in) {} +PKHash::PKHash(const uint160& hash, const CPubKey& blinding_pubkey_in) : uint160(hash), blinding_pubkey(blinding_pubkey_in) {} WitnessV0ScriptHash::WitnessV0ScriptHash(const CScript& in) { CSHA256().Write(in.data(), in.size()).Finalize(begin()); } +WitnessV0ScriptHash::WitnessV0ScriptHash(const CScript& in, const CPubKey& blinding_pubkey_in) +{ + CSHA256().Write(in.data(), in.size()).Finalize(begin()); + blinding_pubkey = blinding_pubkey_in; +} + + const char* GetTxnOutputType(txnouttype t) { switch (t) diff --git a/src/script/standard.h b/src/script/standard.h index 67374efbc0..f5501ae300 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -13,6 +13,8 @@ #include +#include // blinding_pubkey + static const bool DEFAULT_ACCEPT_DATACARRIER = true; class CKeyID; @@ -79,7 +81,10 @@ struct PKHash : public uint160 PKHash() : uint160() {} explicit PKHash(const uint160& hash) : uint160(hash) {} explicit PKHash(const CPubKey& pubkey); + explicit PKHash(const CPubKey& pubkey, const CPubKey& blinding_pubkey); + explicit PKHash(const uint160& hash, const CPubKey& blinding_pubkey); using uint160::uint160; + CPubKey blinding_pubkey; }; struct ScriptHash : public uint160 @@ -87,7 +92,10 @@ struct ScriptHash : public uint160 ScriptHash() : uint160() {} explicit ScriptHash(const uint160& hash) : uint160(hash) {} explicit ScriptHash(const CScript& script); + explicit ScriptHash(const CScript& script, const CPubKey& blinding_pubkey); + explicit ScriptHash(const uint160& hash, const CPubKey& blinding_pubkey); using uint160::uint160; + CPubKey blinding_pubkey; }; struct WitnessV0ScriptHash : public uint256 @@ -95,14 +103,18 @@ struct WitnessV0ScriptHash : public uint256 WitnessV0ScriptHash() : uint256() {} explicit WitnessV0ScriptHash(const uint256& hash) : uint256(hash) {} explicit WitnessV0ScriptHash(const CScript& script); + explicit WitnessV0ScriptHash(const CScript& script, const CPubKey& blinding_pubkey); using uint256::uint256; + CPubKey blinding_pubkey; }; struct WitnessV0KeyHash : public uint160 { WitnessV0KeyHash() : uint160() {} explicit WitnessV0KeyHash(const uint160& hash) : uint160(hash) {} + explicit WitnessV0KeyHash(const uint160& hash, const CPubKey& blinding_pubkey_in) : uint160(hash), blinding_pubkey(blinding_pubkey_in) {} using uint160::uint160; + CPubKey blinding_pubkey; }; //! CTxDestination subtype to encode any future Witness version @@ -111,6 +123,7 @@ struct WitnessUnknown unsigned int version; unsigned int length; unsigned char program[40]; + CPubKey blinding_pubkey; friend bool operator==(const WitnessUnknown& w1, const WitnessUnknown& w2) { if (w1.version != w2.version) return false;