mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-10 11:57:28 -03:00
Implement BIP173 addresses and tests
This commit is contained in:
parent
bd355b8db9
commit
c091b99379
19 changed files with 397 additions and 32 deletions
|
@ -4,9 +4,11 @@
|
||||||
|
|
||||||
#include "base58.h"
|
#include "base58.h"
|
||||||
|
|
||||||
|
#include "bech32.h"
|
||||||
#include "hash.h"
|
#include "hash.h"
|
||||||
#include "script/script.h"
|
#include "script/script.h"
|
||||||
#include "uint256.h"
|
#include "uint256.h"
|
||||||
|
#include "utilstrencodings.h"
|
||||||
|
|
||||||
#include <boost/variant/apply_visitor.hpp>
|
#include <boost/variant/apply_visitor.hpp>
|
||||||
#include <boost/variant/static_visitor.hpp>
|
#include <boost/variant/static_visitor.hpp>
|
||||||
|
@ -235,7 +237,31 @@ public:
|
||||||
return EncodeBase58Check(data);
|
return EncodeBase58Check(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string operator()(const CNoDestination& no) const { return ""; }
|
std::string operator()(const WitnessV0KeyHash& id) const
|
||||||
|
{
|
||||||
|
std::vector<unsigned char> data = {0};
|
||||||
|
ConvertBits<8, 5, true>(data, id.begin(), id.end());
|
||||||
|
return bech32::Encode(m_params.Bech32HRP(), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string operator()(const WitnessV0ScriptHash& id) const
|
||||||
|
{
|
||||||
|
std::vector<unsigned char> data = {0};
|
||||||
|
ConvertBits<8, 5, true>(data, id.begin(), id.end());
|
||||||
|
return bech32::Encode(m_params.Bech32HRP(), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string operator()(const WitnessUnknown& id) const
|
||||||
|
{
|
||||||
|
if (id.version < 1 || id.version > 16 || id.length < 2 || id.length > 40) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
std::vector<unsigned char> data = {(unsigned char)id.version};
|
||||||
|
ConvertBits<8, 5, true>(data, id.program, id.program + id.length);
|
||||||
|
return bech32::Encode(m_params.Bech32HRP(), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string operator()(const CNoDestination& no) const { return {}; }
|
||||||
};
|
};
|
||||||
|
|
||||||
CTxDestination DecodeDestination(const std::string& str, const CChainParams& params)
|
CTxDestination DecodeDestination(const std::string& str, const CChainParams& params)
|
||||||
|
@ -259,6 +285,40 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
|
||||||
return CScriptID(hash);
|
return CScriptID(hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
data.clear();
|
||||||
|
auto bech = bech32::Decode(str);
|
||||||
|
if (bech.second.size() > 0 && bech.first == params.Bech32HRP()) {
|
||||||
|
// Bech32 decoding
|
||||||
|
int version = bech.second[0]; // The first 5 bit symbol is the witness version (0-16)
|
||||||
|
// The rest of the symbols are converted witness program bytes.
|
||||||
|
if (ConvertBits<5, 8, false>(data, bech.second.begin() + 1, bech.second.end())) {
|
||||||
|
if (version == 0) {
|
||||||
|
{
|
||||||
|
WitnessV0KeyHash keyid;
|
||||||
|
if (data.size() == keyid.size()) {
|
||||||
|
std::copy(data.begin(), data.end(), keyid.begin());
|
||||||
|
return keyid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
WitnessV0ScriptHash scriptid;
|
||||||
|
if (data.size() == scriptid.size()) {
|
||||||
|
std::copy(data.begin(), data.end(), scriptid.begin());
|
||||||
|
return scriptid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CNoDestination();
|
||||||
|
}
|
||||||
|
if (version > 16 || data.size() < 2 || data.size() > 40) {
|
||||||
|
return CNoDestination();
|
||||||
|
}
|
||||||
|
WitnessUnknown unk;
|
||||||
|
unk.version = version;
|
||||||
|
std::copy(data.begin(), data.end(), unk.program);
|
||||||
|
unk.length = data.size();
|
||||||
|
return unk;
|
||||||
|
}
|
||||||
|
}
|
||||||
return CNoDestination();
|
return CNoDestination();
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
|
@ -137,6 +137,8 @@ public:
|
||||||
base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x88, 0xB2, 0x1E};
|
base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x88, 0xB2, 0x1E};
|
||||||
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x88, 0xAD, 0xE4};
|
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x88, 0xAD, 0xE4};
|
||||||
|
|
||||||
|
bech32_hrp = "bc";
|
||||||
|
|
||||||
vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main));
|
vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main));
|
||||||
|
|
||||||
fDefaultConsistencyChecks = false;
|
fDefaultConsistencyChecks = false;
|
||||||
|
@ -236,6 +238,8 @@ public:
|
||||||
base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF};
|
base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF};
|
||||||
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94};
|
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94};
|
||||||
|
|
||||||
|
bech32_hrp = "tb";
|
||||||
|
|
||||||
vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test));
|
vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test));
|
||||||
|
|
||||||
fDefaultConsistencyChecks = false;
|
fDefaultConsistencyChecks = false;
|
||||||
|
@ -330,6 +334,8 @@ public:
|
||||||
base58Prefixes[SECRET_KEY] = std::vector<unsigned char>(1,239);
|
base58Prefixes[SECRET_KEY] = std::vector<unsigned char>(1,239);
|
||||||
base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF};
|
base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF};
|
||||||
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94};
|
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94};
|
||||||
|
|
||||||
|
bech32_hrp = "bcrt";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,7 @@ public:
|
||||||
std::string NetworkIDString() const { return strNetworkID; }
|
std::string NetworkIDString() const { return strNetworkID; }
|
||||||
const std::vector<CDNSSeedData>& DNSSeeds() const { return vSeeds; }
|
const std::vector<CDNSSeedData>& DNSSeeds() const { return vSeeds; }
|
||||||
const std::vector<unsigned char>& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; }
|
const std::vector<unsigned char>& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; }
|
||||||
|
const std::string& Bech32HRP() const { return bech32_hrp; }
|
||||||
const std::vector<SeedSpec6>& FixedSeeds() const { return vFixedSeeds; }
|
const std::vector<SeedSpec6>& FixedSeeds() const { return vFixedSeeds; }
|
||||||
const CCheckpointData& Checkpoints() const { return checkpointData; }
|
const CCheckpointData& Checkpoints() const { return checkpointData; }
|
||||||
const ChainTxData& TxData() const { return chainTxData; }
|
const ChainTxData& TxData() const { return chainTxData; }
|
||||||
|
@ -86,6 +87,7 @@ protected:
|
||||||
uint64_t nPruneAfterHeight;
|
uint64_t nPruneAfterHeight;
|
||||||
std::vector<CDNSSeedData> vSeeds;
|
std::vector<CDNSSeedData> vSeeds;
|
||||||
std::vector<unsigned char> base58Prefixes[MAX_BASE58_TYPES];
|
std::vector<unsigned char> base58Prefixes[MAX_BASE58_TYPES];
|
||||||
|
std::string bech32_hrp;
|
||||||
std::string strNetworkID;
|
std::string strNetworkID;
|
||||||
CBlock genesis;
|
CBlock genesis;
|
||||||
std::vector<SeedSpec6> vFixedSeeds;
|
std::vector<SeedSpec6> vFixedSeeds;
|
||||||
|
|
|
@ -76,7 +76,7 @@ bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType, const bool w
|
||||||
else if (!witnessEnabled && (whichType == TX_WITNESS_V0_KEYHASH || whichType == TX_WITNESS_V0_SCRIPTHASH))
|
else if (!witnessEnabled && (whichType == TX_WITNESS_V0_KEYHASH || whichType == TX_WITNESS_V0_SCRIPTHASH))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return whichType != TX_NONSTANDARD;
|
return whichType != TX_NONSTANDARD && whichType != TX_WITNESS_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsStandardTx(const CTransaction& tx, std::string& reason, const bool witnessEnabled)
|
bool IsStandardTx(const CTransaction& tx, std::string& reason, const bool witnessEnabled)
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "chain.h"
|
#include "chain.h"
|
||||||
#include "clientversion.h"
|
#include "clientversion.h"
|
||||||
#include "core_io.h"
|
#include "core_io.h"
|
||||||
|
#include "crypto/ripemd160.h"
|
||||||
#include "init.h"
|
#include "init.h"
|
||||||
#include "validation.h"
|
#include "validation.h"
|
||||||
#include "httpserver.h"
|
#include "httpserver.h"
|
||||||
|
@ -45,6 +46,7 @@ public:
|
||||||
UniValue obj(UniValue::VOBJ);
|
UniValue obj(UniValue::VOBJ);
|
||||||
CPubKey vchPubKey;
|
CPubKey vchPubKey;
|
||||||
obj.push_back(Pair("isscript", false));
|
obj.push_back(Pair("isscript", false));
|
||||||
|
obj.push_back(Pair("iswitness", false));
|
||||||
if (pwallet && pwallet->GetPubKey(keyID, vchPubKey)) {
|
if (pwallet && pwallet->GetPubKey(keyID, vchPubKey)) {
|
||||||
obj.push_back(Pair("pubkey", HexStr(vchPubKey)));
|
obj.push_back(Pair("pubkey", HexStr(vchPubKey)));
|
||||||
obj.push_back(Pair("iscompressed", vchPubKey.IsCompressed()));
|
obj.push_back(Pair("iscompressed", vchPubKey.IsCompressed()));
|
||||||
|
@ -56,6 +58,7 @@ public:
|
||||||
UniValue obj(UniValue::VOBJ);
|
UniValue obj(UniValue::VOBJ);
|
||||||
CScript subscript;
|
CScript subscript;
|
||||||
obj.push_back(Pair("isscript", true));
|
obj.push_back(Pair("isscript", true));
|
||||||
|
obj.push_back(Pair("iswitness", false));
|
||||||
if (pwallet && pwallet->GetCScript(scriptID, subscript)) {
|
if (pwallet && pwallet->GetCScript(scriptID, subscript)) {
|
||||||
std::vector<CTxDestination> addresses;
|
std::vector<CTxDestination> addresses;
|
||||||
txnouttype whichType;
|
txnouttype whichType;
|
||||||
|
@ -73,6 +76,47 @@ public:
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UniValue operator()(const WitnessV0KeyHash& id) const
|
||||||
|
{
|
||||||
|
UniValue obj(UniValue::VOBJ);
|
||||||
|
CPubKey pubkey;
|
||||||
|
obj.push_back(Pair("isscript", false));
|
||||||
|
obj.push_back(Pair("iswitness", true));
|
||||||
|
obj.push_back(Pair("witness_version", 0));
|
||||||
|
obj.push_back(Pair("witness_program", HexStr(id.begin(), id.end())));
|
||||||
|
if (pwallet && pwallet->GetPubKey(CKeyID(id), pubkey)) {
|
||||||
|
obj.push_back(Pair("pubkey", HexStr(pubkey)));
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
UniValue operator()(const WitnessV0ScriptHash& id) const
|
||||||
|
{
|
||||||
|
UniValue obj(UniValue::VOBJ);
|
||||||
|
CScript subscript;
|
||||||
|
obj.push_back(Pair("isscript", true));
|
||||||
|
obj.push_back(Pair("iswitness", true));
|
||||||
|
obj.push_back(Pair("witness_version", 0));
|
||||||
|
obj.push_back(Pair("witness_program", HexStr(id.begin(), id.end())));
|
||||||
|
CRIPEMD160 hasher;
|
||||||
|
uint160 hash;
|
||||||
|
hasher.Write(id.begin(), 32).Finalize(hash.begin());
|
||||||
|
if (pwallet && pwallet->GetCScript(CScriptID(hash), subscript)) {
|
||||||
|
obj.push_back(Pair("hex", HexStr(subscript.begin(), subscript.end())));
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
UniValue operator()(const WitnessUnknown& id) const
|
||||||
|
{
|
||||||
|
UniValue obj(UniValue::VOBJ);
|
||||||
|
CScript subscript;
|
||||||
|
obj.push_back(Pair("iswitness", true));
|
||||||
|
obj.push_back(Pair("witness_version", (int)id.version));
|
||||||
|
obj.push_back(Pair("witness_program", HexStr(id.program, id.program + id.length)));
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,7 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool&
|
||||||
{
|
{
|
||||||
case TX_NONSTANDARD:
|
case TX_NONSTANDARD:
|
||||||
case TX_NULL_DATA:
|
case TX_NULL_DATA:
|
||||||
|
case TX_WITNESS_UNKNOWN:
|
||||||
break;
|
break;
|
||||||
case TX_PUBKEY:
|
case TX_PUBKEY:
|
||||||
keyID = CPubKey(vSolutions[0]).GetID();
|
keyID = CPubKey(vSolutions[0]).GetID();
|
||||||
|
|
|
@ -79,6 +79,7 @@ static bool SignStep(const BaseSignatureCreator& creator, const CScript& scriptP
|
||||||
{
|
{
|
||||||
case TX_NONSTANDARD:
|
case TX_NONSTANDARD:
|
||||||
case TX_NULL_DATA:
|
case TX_NULL_DATA:
|
||||||
|
case TX_WITNESS_UNKNOWN:
|
||||||
return false;
|
return false;
|
||||||
case TX_PUBKEY:
|
case TX_PUBKEY:
|
||||||
keyID = CPubKey(vSolutions[0]).GetID();
|
keyID = CPubKey(vSolutions[0]).GetID();
|
||||||
|
@ -309,6 +310,7 @@ static Stacks CombineSignatures(const CScript& scriptPubKey, const BaseSignature
|
||||||
{
|
{
|
||||||
case TX_NONSTANDARD:
|
case TX_NONSTANDARD:
|
||||||
case TX_NULL_DATA:
|
case TX_NULL_DATA:
|
||||||
|
case TX_WITNESS_UNKNOWN:
|
||||||
// Don't know anything about this, assume bigger one is correct:
|
// Don't know anything about this, assume bigger one is correct:
|
||||||
if (sigs1.script.size() >= sigs2.script.size())
|
if (sigs1.script.size() >= sigs2.script.size())
|
||||||
return sigs1;
|
return sigs1;
|
||||||
|
|
|
@ -30,6 +30,7 @@ const char* GetTxnOutputType(txnouttype t)
|
||||||
case TX_NULL_DATA: return "nulldata";
|
case TX_NULL_DATA: return "nulldata";
|
||||||
case TX_WITNESS_V0_KEYHASH: return "witness_v0_keyhash";
|
case TX_WITNESS_V0_KEYHASH: return "witness_v0_keyhash";
|
||||||
case TX_WITNESS_V0_SCRIPTHASH: return "witness_v0_scripthash";
|
case TX_WITNESS_V0_SCRIPTHASH: return "witness_v0_scripthash";
|
||||||
|
case TX_WITNESS_UNKNOWN: return "witness_unknown";
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -75,6 +76,12 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<std::v
|
||||||
vSolutionsRet.push_back(witnessprogram);
|
vSolutionsRet.push_back(witnessprogram);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (witnessversion != 0) {
|
||||||
|
typeRet = TX_WITNESS_UNKNOWN;
|
||||||
|
vSolutionsRet.push_back(std::vector<unsigned char>{(unsigned char)witnessversion});
|
||||||
|
vSolutionsRet.push_back(std::move(witnessprogram));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,6 +205,23 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
|
||||||
{
|
{
|
||||||
addressRet = CScriptID(uint160(vSolutions[0]));
|
addressRet = CScriptID(uint160(vSolutions[0]));
|
||||||
return true;
|
return true;
|
||||||
|
} else if (whichType == TX_WITNESS_V0_KEYHASH) {
|
||||||
|
WitnessV0KeyHash hash;
|
||||||
|
std::copy(vSolutions[0].begin(), vSolutions[0].end(), hash.begin());
|
||||||
|
addressRet = hash;
|
||||||
|
return true;
|
||||||
|
} else if (whichType == TX_WITNESS_V0_SCRIPTHASH) {
|
||||||
|
WitnessV0ScriptHash hash;
|
||||||
|
std::copy(vSolutions[0].begin(), vSolutions[0].end(), hash.begin());
|
||||||
|
addressRet = hash;
|
||||||
|
return true;
|
||||||
|
} else if (whichType == TX_WITNESS_UNKNOWN) {
|
||||||
|
WitnessUnknown unk;
|
||||||
|
unk.version = vSolutions[0][0];
|
||||||
|
std::copy(vSolutions[1].begin(), vSolutions[1].end(), unk.program);
|
||||||
|
unk.length = vSolutions[1].size();
|
||||||
|
addressRet = unk;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
// Multisig txns have more than one address...
|
// Multisig txns have more than one address...
|
||||||
return false;
|
return false;
|
||||||
|
@ -268,6 +292,27 @@ public:
|
||||||
*script << OP_HASH160 << ToByteVector(scriptID) << OP_EQUAL;
|
*script << OP_HASH160 << ToByteVector(scriptID) << OP_EQUAL;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool operator()(const WitnessV0KeyHash& id) const
|
||||||
|
{
|
||||||
|
script->clear();
|
||||||
|
*script << OP_0 << ToByteVector(id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const WitnessV0ScriptHash& id) const
|
||||||
|
{
|
||||||
|
script->clear();
|
||||||
|
*script << OP_0 << ToByteVector(id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const WitnessUnknown& id) const
|
||||||
|
{
|
||||||
|
script->clear();
|
||||||
|
*script << CScript::EncodeOP_N(id.version) << std::vector<unsigned char>(id.program, id.program + id.length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ enum txnouttype
|
||||||
TX_NULL_DATA, //!< unspendable OP_RETURN script that carries data
|
TX_NULL_DATA, //!< unspendable OP_RETURN script that carries data
|
||||||
TX_WITNESS_V0_SCRIPTHASH,
|
TX_WITNESS_V0_SCRIPTHASH,
|
||||||
TX_WITNESS_V0_KEYHASH,
|
TX_WITNESS_V0_KEYHASH,
|
||||||
|
TX_WITNESS_UNKNOWN, //!< Only for Witness versions not already defined above
|
||||||
};
|
};
|
||||||
|
|
||||||
class CNoDestination {
|
class CNoDestination {
|
||||||
|
@ -72,14 +73,42 @@ public:
|
||||||
friend bool operator<(const CNoDestination &a, const CNoDestination &b) { return true; }
|
friend bool operator<(const CNoDestination &a, const CNoDestination &b) { return true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct WitnessV0ScriptHash : public uint256 {};
|
||||||
|
struct WitnessV0KeyHash : public uint160 {};
|
||||||
|
|
||||||
|
//! CTxDestination subtype to encode any future Witness version
|
||||||
|
struct WitnessUnknown
|
||||||
|
{
|
||||||
|
unsigned int version;
|
||||||
|
unsigned int length;
|
||||||
|
unsigned char program[40];
|
||||||
|
|
||||||
|
friend bool operator==(const WitnessUnknown& w1, const WitnessUnknown& w2) {
|
||||||
|
if (w1.version != w2.version) return false;
|
||||||
|
if (w1.length != w2.length) return false;
|
||||||
|
return std::equal(w1.program, w1.program + w1.length, w2.program);
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator<(const WitnessUnknown& w1, const WitnessUnknown& w2) {
|
||||||
|
if (w1.version < w2.version) return true;
|
||||||
|
if (w1.version > w2.version) return false;
|
||||||
|
if (w1.length < w2.length) return true;
|
||||||
|
if (w1.length > w2.length) return false;
|
||||||
|
return std::lexicographical_compare(w1.program, w1.program + w1.length, w2.program, w2.program + w2.length);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A txout script template with a specific destination. It is either:
|
* A txout script template with a specific destination. It is either:
|
||||||
* * CNoDestination: no destination set
|
* * CNoDestination: no destination set
|
||||||
* * CKeyID: TX_PUBKEYHASH destination
|
* * CKeyID: TX_PUBKEYHASH destination (P2PKH)
|
||||||
* * CScriptID: TX_SCRIPTHASH destination
|
* * CScriptID: TX_SCRIPTHASH destination (P2SH)
|
||||||
|
* * WitnessV0ScriptHash: TX_WITNESS_V0_SCRIPTHASH destination (P2WSH)
|
||||||
|
* * WitnessV0KeyHash: TX_WITNESS_V0_KEYHASH destination (P2WPKH)
|
||||||
|
* * WitnessUnknown: TX_WITNESS_UNKNOWN destination (P2W???)
|
||||||
* A CTxDestination is the internal data type encoded in a bitcoin address
|
* A CTxDestination is the internal data type encoded in a bitcoin address
|
||||||
*/
|
*/
|
||||||
typedef boost::variant<CNoDestination, CKeyID, CScriptID> CTxDestination;
|
typedef boost::variant<CNoDestination, CKeyID, CScriptID, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessUnknown> CTxDestination;
|
||||||
|
|
||||||
/** Check whether a CTxDestination is a CNoDestination. */
|
/** Check whether a CTxDestination is a CNoDestination. */
|
||||||
bool IsValidDestination(const CTxDestination& dest);
|
bool IsValidDestination(const CTxDestination& dest);
|
||||||
|
@ -104,7 +133,7 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<std::v
|
||||||
* Parse a standard scriptPubKey for the destination address. Assigns result to
|
* Parse a standard scriptPubKey for the destination address. Assigns result to
|
||||||
* the addressRet parameter and returns true if successful. For multisig
|
* the addressRet parameter and returns true if successful. For multisig
|
||||||
* scripts, instead use ExtractDestinations. Currently only works for P2PK,
|
* scripts, instead use ExtractDestinations. Currently only works for P2PK,
|
||||||
* P2PKH, and P2SH scripts.
|
* P2PKH, P2SH, P2WPKH, and P2WSH scripts.
|
||||||
*/
|
*/
|
||||||
bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet);
|
bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet);
|
||||||
|
|
||||||
|
|
|
@ -93,6 +93,7 @@ BOOST_AUTO_TEST_CASE(base58_keys_valid_parse)
|
||||||
const UniValue &metadata = test[2].get_obj();
|
const UniValue &metadata = test[2].get_obj();
|
||||||
bool isPrivkey = find_value(metadata, "isPrivkey").get_bool();
|
bool isPrivkey = find_value(metadata, "isPrivkey").get_bool();
|
||||||
SelectParams(find_value(metadata, "chain").get_str());
|
SelectParams(find_value(metadata, "chain").get_str());
|
||||||
|
bool try_case_flip = find_value(metadata, "tryCaseFlip").isNull() ? false : find_value(metadata, "tryCaseFlip").get_bool();
|
||||||
if (isPrivkey) {
|
if (isPrivkey) {
|
||||||
bool isCompressed = find_value(metadata, "isCompressed").get_bool();
|
bool isCompressed = find_value(metadata, "isCompressed").get_bool();
|
||||||
// Must be valid private key
|
// Must be valid private key
|
||||||
|
@ -112,6 +113,21 @@ BOOST_AUTO_TEST_CASE(base58_keys_valid_parse)
|
||||||
BOOST_CHECK_MESSAGE(IsValidDestination(destination), "!IsValid:" + strTest);
|
BOOST_CHECK_MESSAGE(IsValidDestination(destination), "!IsValid:" + strTest);
|
||||||
BOOST_CHECK_EQUAL(HexStr(script), HexStr(exp_payload));
|
BOOST_CHECK_EQUAL(HexStr(script), HexStr(exp_payload));
|
||||||
|
|
||||||
|
// Try flipped case version
|
||||||
|
for (char& c : exp_base58string) {
|
||||||
|
if (c >= 'a' && c <= 'z') {
|
||||||
|
c = (c - 'a') + 'A';
|
||||||
|
} else if (c >= 'A' && c <= 'Z') {
|
||||||
|
c = (c - 'A') + 'a';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
destination = DecodeDestination(exp_base58string);
|
||||||
|
BOOST_CHECK_MESSAGE(IsValidDestination(destination) == try_case_flip, "!IsValid case flipped:" + strTest);
|
||||||
|
if (IsValidDestination(destination)) {
|
||||||
|
script = GetScriptForDestination(destination);
|
||||||
|
BOOST_CHECK_EQUAL(HexStr(script), HexStr(exp_payload));
|
||||||
|
}
|
||||||
|
|
||||||
// Public key must be invalid private key
|
// Public key must be invalid private key
|
||||||
secret.SetString(exp_base58string);
|
secret.SetString(exp_base58string);
|
||||||
BOOST_CHECK_MESSAGE(!secret.IsValid(), "IsValid pubkey as privkey:" + strTest);
|
BOOST_CHECK_MESSAGE(!secret.IsValid(), "IsValid pubkey as privkey:" + strTest);
|
||||||
|
@ -150,6 +166,7 @@ BOOST_AUTO_TEST_CASE(base58_keys_valid_gen)
|
||||||
CScript exp_script(exp_payload.begin(), exp_payload.end());
|
CScript exp_script(exp_payload.begin(), exp_payload.end());
|
||||||
ExtractDestination(exp_script, dest);
|
ExtractDestination(exp_script, dest);
|
||||||
std::string address = EncodeDestination(dest);
|
std::string address = EncodeDestination(dest);
|
||||||
|
|
||||||
BOOST_CHECK_EQUAL(address, exp_base58string);
|
BOOST_CHECK_EQUAL(address, exp_base58string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,6 +174,7 @@ BOOST_AUTO_TEST_CASE(base58_keys_valid_gen)
|
||||||
SelectParams(CBaseChainParams::MAIN);
|
SelectParams(CBaseChainParams::MAIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Goal: check that base58 parsing code is robust against a variety of corrupted data
|
// Goal: check that base58 parsing code is robust against a variety of corrupted data
|
||||||
BOOST_AUTO_TEST_CASE(base58_keys_invalid)
|
BOOST_AUTO_TEST_CASE(base58_keys_invalid)
|
||||||
{
|
{
|
||||||
|
@ -187,4 +205,3 @@ BOOST_AUTO_TEST_CASE(base58_keys_invalid)
|
||||||
|
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
||||||
|
|
|
@ -148,5 +148,35 @@
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"2A1q1YsMZowabbvta7kTy2Fd6qN4r5ZCeG3qLpvZBMzCixMUdkN2Y4dHB1wPsZAeVXUGD83MfRED"
|
"2A1q1YsMZowabbvta7kTy2Fd6qN4r5ZCeG3qLpvZBMzCixMUdkN2Y4dHB1wPsZAeVXUGD83MfRED"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"bc1rw5uspcuh"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"bc1gmk9yu"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
|
@ -457,5 +457,77 @@
|
||||||
"isPrivkey": false,
|
"isPrivkey": false,
|
||||||
"chain": "main"
|
"chain": "main"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4",
|
||||||
|
"0014751e76e8199196d454941c45d1b3a323f1433bd6",
|
||||||
|
{
|
||||||
|
"isPrivkey": false,
|
||||||
|
"chain": "main",
|
||||||
|
"tryCaseFlip": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080",
|
||||||
|
"0014751e76e8199196d454941c45d1b3a323f1433bd6",
|
||||||
|
{
|
||||||
|
"isPrivkey": false,
|
||||||
|
"chain": "regtest",
|
||||||
|
"tryCaseFlip": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
|
||||||
|
"00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262",
|
||||||
|
{
|
||||||
|
"isPrivkey": false,
|
||||||
|
"chain": "test",
|
||||||
|
"tryCaseFlip": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx",
|
||||||
|
"5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6",
|
||||||
|
{
|
||||||
|
"isPrivkey": false,
|
||||||
|
"chain": "main",
|
||||||
|
"tryCaseFlip": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"bc1sw50qa3jx3s",
|
||||||
|
"6002751e",
|
||||||
|
{
|
||||||
|
"isPrivkey": false,
|
||||||
|
"chain": "main",
|
||||||
|
"tryCaseFlip": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj",
|
||||||
|
"5210751e76e8199196d454941c45d1b3a323",
|
||||||
|
{
|
||||||
|
"isPrivkey": false,
|
||||||
|
"chain": "main",
|
||||||
|
"tryCaseFlip": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
|
||||||
|
"0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
|
||||||
|
{
|
||||||
|
"isPrivkey": false,
|
||||||
|
"chain": "test",
|
||||||
|
"tryCaseFlip": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"bcrt1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvseswlauz7",
|
||||||
|
"0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433",
|
||||||
|
{
|
||||||
|
"isPrivkey": false,
|
||||||
|
"chain": "regtest",
|
||||||
|
"tryCaseFlip": true
|
||||||
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
|
@ -170,11 +170,6 @@ BOOST_AUTO_TEST_CASE(script_standard_Solver_failure)
|
||||||
s << OP_RETURN << std::vector<unsigned char>({75}) << OP_ADD;
|
s << OP_RETURN << std::vector<unsigned char>({75}) << OP_ADD;
|
||||||
BOOST_CHECK(!Solver(s, whichType, solutions));
|
BOOST_CHECK(!Solver(s, whichType, solutions));
|
||||||
|
|
||||||
// TX_WITNESS with unknown version
|
|
||||||
s.clear();
|
|
||||||
s << OP_1 << ToByteVector(pubkey);
|
|
||||||
BOOST_CHECK(!Solver(s, whichType, solutions));
|
|
||||||
|
|
||||||
// TX_WITNESS with incorrect program size
|
// TX_WITNESS with incorrect program size
|
||||||
s.clear();
|
s.clear();
|
||||||
s << OP_0 << std::vector<unsigned char>(19, 0x01);
|
s << OP_0 << std::vector<unsigned char>(19, 0x01);
|
||||||
|
@ -225,13 +220,29 @@ BOOST_AUTO_TEST_CASE(script_standard_ExtractDestination)
|
||||||
|
|
||||||
// TX_WITNESS_V0_KEYHASH
|
// TX_WITNESS_V0_KEYHASH
|
||||||
s.clear();
|
s.clear();
|
||||||
s << OP_0 << ToByteVector(pubkey);
|
s << OP_0 << ToByteVector(pubkey.GetID());
|
||||||
BOOST_CHECK(!ExtractDestination(s, address));
|
BOOST_CHECK(ExtractDestination(s, address));
|
||||||
|
WitnessV0KeyHash keyhash;
|
||||||
|
CHash160().Write(pubkey.begin(), pubkey.size()).Finalize(keyhash.begin());
|
||||||
|
BOOST_CHECK(boost::get<WitnessV0KeyHash>(&address) && *boost::get<WitnessV0KeyHash>(&address) == keyhash);
|
||||||
|
|
||||||
// TX_WITNESS_V0_SCRIPTHASH
|
// TX_WITNESS_V0_SCRIPTHASH
|
||||||
s.clear();
|
s.clear();
|
||||||
s << OP_0 << ToByteVector(CScriptID(redeemScript));
|
WitnessV0ScriptHash scripthash;
|
||||||
BOOST_CHECK(!ExtractDestination(s, address));
|
CSHA256().Write(redeemScript.data(), redeemScript.size()).Finalize(scripthash.begin());
|
||||||
|
s << OP_0 << ToByteVector(scripthash);
|
||||||
|
BOOST_CHECK(ExtractDestination(s, address));
|
||||||
|
BOOST_CHECK(boost::get<WitnessV0ScriptHash>(&address) && *boost::get<WitnessV0ScriptHash>(&address) == scripthash);
|
||||||
|
|
||||||
|
// TX_WITNESS with unknown version
|
||||||
|
s.clear();
|
||||||
|
s << OP_1 << ToByteVector(pubkey);
|
||||||
|
BOOST_CHECK(ExtractDestination(s, address));
|
||||||
|
WitnessUnknown unk;
|
||||||
|
unk.length = 33;
|
||||||
|
unk.version = 1;
|
||||||
|
std::copy(pubkey.begin(), pubkey.end(), unk.program);
|
||||||
|
BOOST_CHECK(boost::get<WitnessUnknown>(&address) && *boost::get<WitnessUnknown>(&address) == unk);
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(script_standard_ExtractDestinations)
|
BOOST_AUTO_TEST_CASE(script_standard_ExtractDestinations)
|
||||||
|
@ -298,16 +309,6 @@ BOOST_AUTO_TEST_CASE(script_standard_ExtractDestinations)
|
||||||
s.clear();
|
s.clear();
|
||||||
s << OP_RETURN << std::vector<unsigned char>({75});
|
s << OP_RETURN << std::vector<unsigned char>({75});
|
||||||
BOOST_CHECK(!ExtractDestinations(s, whichType, addresses, nRequired));
|
BOOST_CHECK(!ExtractDestinations(s, whichType, addresses, nRequired));
|
||||||
|
|
||||||
// TX_WITNESS_V0_KEYHASH
|
|
||||||
s.clear();
|
|
||||||
s << OP_0 << ToByteVector(pubkeys[0].GetID());
|
|
||||||
BOOST_CHECK(!ExtractDestinations(s, whichType, addresses, nRequired));
|
|
||||||
|
|
||||||
// TX_WITNESS_V0_SCRIPTHASH
|
|
||||||
s.clear();
|
|
||||||
s << OP_0 << ToByteVector(CScriptID(redeemScript));
|
|
||||||
BOOST_CHECK(!ExtractDestinations(s, whichType, addresses, nRequired));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(script_standard_GetScriptFor_)
|
BOOST_AUTO_TEST_CASE(script_standard_GetScriptFor_)
|
||||||
|
|
|
@ -149,4 +149,28 @@ bool TimingResistantEqual(const T& a, const T& b)
|
||||||
*/
|
*/
|
||||||
bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out);
|
bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out);
|
||||||
|
|
||||||
|
/** Convert from one power-of-2 number base to another. */
|
||||||
|
template<int frombits, int tobits, bool pad, typename O, typename I>
|
||||||
|
bool ConvertBits(O& out, I it, I end) {
|
||||||
|
size_t acc = 0;
|
||||||
|
size_t bits = 0;
|
||||||
|
constexpr size_t maxv = (1 << tobits) - 1;
|
||||||
|
constexpr size_t max_acc = (1 << (frombits + tobits - 1)) - 1;
|
||||||
|
while (it != end) {
|
||||||
|
acc = ((acc << frombits) | *it) & max_acc;
|
||||||
|
bits += frombits;
|
||||||
|
while (bits >= tobits) {
|
||||||
|
bits -= tobits;
|
||||||
|
out.push_back((acc >> bits) & maxv);
|
||||||
|
}
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
if (pad) {
|
||||||
|
if (bits) out.push_back((acc << (tobits - bits)) & maxv);
|
||||||
|
} else if (bits >= frombits || ((acc << (tobits - bits)) & maxv)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
#endif // BITCOIN_UTILSTRENCODINGS_H
|
#endif // BITCOIN_UTILSTRENCODINGS_H
|
||||||
|
|
|
@ -1158,8 +1158,6 @@ public:
|
||||||
|
|
||||||
explicit Witnessifier(CWallet *_pwallet) : pwallet(_pwallet) {}
|
explicit Witnessifier(CWallet *_pwallet) : pwallet(_pwallet) {}
|
||||||
|
|
||||||
bool operator()(const CNoDestination &dest) const { return false; }
|
|
||||||
|
|
||||||
bool operator()(const CKeyID &keyID) {
|
bool operator()(const CKeyID &keyID) {
|
||||||
if (pwallet) {
|
if (pwallet) {
|
||||||
CScript basescript = GetScriptForDestination(keyID);
|
CScript basescript = GetScriptForDestination(keyID);
|
||||||
|
@ -1203,6 +1201,9 @@ public:
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
bool operator()(const T& dest) { return false; }
|
||||||
};
|
};
|
||||||
|
|
||||||
UniValue addwitnessaddress(const JSONRPCRequest& request)
|
UniValue addwitnessaddress(const JSONRPCRequest& request)
|
||||||
|
|
|
@ -111,7 +111,26 @@ public:
|
||||||
Process(script);
|
Process(script);
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(const CNoDestination &none) {}
|
void operator()(const WitnessV0ScriptHash& scriptID)
|
||||||
|
{
|
||||||
|
CScriptID id;
|
||||||
|
CRIPEMD160().Write(scriptID.begin(), 32).Finalize(id.begin());
|
||||||
|
CScript script;
|
||||||
|
if (keystore.GetCScript(id, script)) {
|
||||||
|
Process(script);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()(const WitnessV0KeyHash& keyid)
|
||||||
|
{
|
||||||
|
CKeyID id(keyid);
|
||||||
|
if (keystore.HaveKey(id)) {
|
||||||
|
vKeys.push_back(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename X>
|
||||||
|
void operator()(const X &none) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
|
const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
|
||||||
|
|
|
@ -14,7 +14,11 @@
|
||||||
"scriptPubKey": {
|
"scriptPubKey": {
|
||||||
"asm": "0 e15a86a23178f433d514dbbce042e87d72662b8b5edcacfd2e37ab7a2d135f05",
|
"asm": "0 e15a86a23178f433d514dbbce042e87d72662b8b5edcacfd2e37ab7a2d135f05",
|
||||||
"hex": "0020e15a86a23178f433d514dbbce042e87d72662b8b5edcacfd2e37ab7a2d135f05",
|
"hex": "0020e15a86a23178f433d514dbbce042e87d72662b8b5edcacfd2e37ab7a2d135f05",
|
||||||
"type": "witness_v0_scripthash"
|
"reqSigs": 1,
|
||||||
|
"type": "witness_v0_scripthash",
|
||||||
|
"addresses": [
|
||||||
|
"bc1qu9dgdg330r6r84g5mw7wqshg04exv2uttmw2elfwx74h5tgntuzs44gyfg"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -14,7 +14,11 @@
|
||||||
"scriptPubKey": {
|
"scriptPubKey": {
|
||||||
"asm": "0 a2516e770582864a6a56ed21a102044e388c62e3",
|
"asm": "0 a2516e770582864a6a56ed21a102044e388c62e3",
|
||||||
"hex": "0014a2516e770582864a6a56ed21a102044e388c62e3",
|
"hex": "0014a2516e770582864a6a56ed21a102044e388c62e3",
|
||||||
"type": "witness_v0_keyhash"
|
"reqSigs": 1,
|
||||||
|
"type": "witness_v0_keyhash",
|
||||||
|
"addresses": [
|
||||||
|
"bc1q5fgkuac9s2ry56jka5s6zqsyfcugcchry5cwu0"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -14,7 +14,11 @@
|
||||||
"scriptPubKey": {
|
"scriptPubKey": {
|
||||||
"asm": "0 0bfe935e70c321c7ca3afc75ce0d0ca2f98b5422e008bb31c00c6d7f1f1c0ad6",
|
"asm": "0 0bfe935e70c321c7ca3afc75ce0d0ca2f98b5422e008bb31c00c6d7f1f1c0ad6",
|
||||||
"hex": "00200bfe935e70c321c7ca3afc75ce0d0ca2f98b5422e008bb31c00c6d7f1f1c0ad6",
|
"hex": "00200bfe935e70c321c7ca3afc75ce0d0ca2f98b5422e008bb31c00c6d7f1f1c0ad6",
|
||||||
"type": "witness_v0_scripthash"
|
"reqSigs": 1,
|
||||||
|
"type": "witness_v0_scripthash",
|
||||||
|
"addresses": [
|
||||||
|
"bc1qp0lfxhnscvsu0j36l36uurgv5tuck4pzuqytkvwqp3kh78cupttqyf705v"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in a new issue