Skip to content

Commit

Permalink
Implement BIP173 addresses and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
sipa committed Sep 28, 2017
1 parent bd355b8 commit c091b99
Show file tree
Hide file tree
Showing 19 changed files with 397 additions and 32 deletions.
62 changes: 61 additions & 1 deletion src/base58.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

#include "base58.h"

#include "bech32.h"
#include "hash.h"
#include "script/script.h"
#include "uint256.h"
#include "utilstrencodings.h"

#include <boost/variant/apply_visitor.hpp>
#include <boost/variant/static_visitor.hpp>
Expand Down Expand Up @@ -235,7 +237,31 @@ class DestinationEncoder : public boost::static_visitor<std::string>
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)
Expand All @@ -259,6 +285,40 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
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();
}
} // namespace
Expand Down
6 changes: 6 additions & 0 deletions src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ class CMainParams : public CChainParams {
base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x88, 0xB2, 0x1E};
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x88, 0xAD, 0xE4};

bech32_hrp = "bc";

vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main));

fDefaultConsistencyChecks = false;
Expand Down Expand Up @@ -236,6 +238,8 @@ class CTestNetParams : public CChainParams {
base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF};
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94};

bech32_hrp = "tb";

vFixedSeeds = std::vector<SeedSpec6>(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test));

fDefaultConsistencyChecks = false;
Expand Down Expand Up @@ -330,6 +334,8 @@ class CRegTestParams : public CChainParams {
base58Prefixes[SECRET_KEY] = std::vector<unsigned char>(1,239);
base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF};
base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94};

bech32_hrp = "bcrt";
}
};

Expand Down
2 changes: 2 additions & 0 deletions src/chainparams.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class CChainParams
std::string NetworkIDString() const { return strNetworkID; }
const std::vector<CDNSSeedData>& DNSSeeds() const { return vSeeds; }
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 CCheckpointData& Checkpoints() const { return checkpointData; }
const ChainTxData& TxData() const { return chainTxData; }
Expand All @@ -86,6 +87,7 @@ class CChainParams
uint64_t nPruneAfterHeight;
std::vector<CDNSSeedData> vSeeds;
std::vector<unsigned char> base58Prefixes[MAX_BASE58_TYPES];
std::string bech32_hrp;
std::string strNetworkID;
CBlock genesis;
std::vector<SeedSpec6> vFixedSeeds;
Expand Down
2 changes: 1 addition & 1 deletion src/policy/policy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
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)
Expand Down
44 changes: 44 additions & 0 deletions src/rpc/misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "chain.h"
#include "clientversion.h"
#include "core_io.h"
#include "crypto/ripemd160.h"
#include "init.h"
#include "validation.h"
#include "httpserver.h"
Expand Down Expand Up @@ -45,6 +46,7 @@ class DescribeAddressVisitor : public boost::static_visitor<UniValue>
UniValue obj(UniValue::VOBJ);
CPubKey vchPubKey;
obj.push_back(Pair("isscript", false));
obj.push_back(Pair("iswitness", false));
if (pwallet && pwallet->GetPubKey(keyID, vchPubKey)) {
obj.push_back(Pair("pubkey", HexStr(vchPubKey)));
obj.push_back(Pair("iscompressed", vchPubKey.IsCompressed()));
Expand All @@ -56,6 +58,7 @@ class DescribeAddressVisitor : public boost::static_visitor<UniValue>
UniValue obj(UniValue::VOBJ);
CScript subscript;
obj.push_back(Pair("isscript", true));
obj.push_back(Pair("iswitness", false));
if (pwallet && pwallet->GetCScript(scriptID, subscript)) {
std::vector<CTxDestination> addresses;
txnouttype whichType;
Expand All @@ -73,6 +76,47 @@ class DescribeAddressVisitor : public boost::static_visitor<UniValue>
}
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

Expand Down
1 change: 1 addition & 0 deletions src/script/ismine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool&
{
case TX_NONSTANDARD:
case TX_NULL_DATA:
case TX_WITNESS_UNKNOWN:
break;
case TX_PUBKEY:
keyID = CPubKey(vSolutions[0]).GetID();
Expand Down
2 changes: 2 additions & 0 deletions src/script/sign.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ static bool SignStep(const BaseSignatureCreator& creator, const CScript& scriptP
{
case TX_NONSTANDARD:
case TX_NULL_DATA:
case TX_WITNESS_UNKNOWN:
return false;
case TX_PUBKEY:
keyID = CPubKey(vSolutions[0]).GetID();
Expand Down Expand Up @@ -309,6 +310,7 @@ static Stacks CombineSignatures(const CScript& scriptPubKey, const BaseSignature
{
case TX_NONSTANDARD:
case TX_NULL_DATA:
case TX_WITNESS_UNKNOWN:
// Don't know anything about this, assume bigger one is correct:
if (sigs1.script.size() >= sigs2.script.size())
return sigs1;
Expand Down
45 changes: 45 additions & 0 deletions src/script/standard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const char* GetTxnOutputType(txnouttype t)
case TX_NULL_DATA: return "nulldata";
case TX_WITNESS_V0_KEYHASH: return "witness_v0_keyhash";
case TX_WITNESS_V0_SCRIPTHASH: return "witness_v0_scripthash";
case TX_WITNESS_UNKNOWN: return "witness_unknown";
}
return nullptr;
}
Expand Down Expand Up @@ -75,6 +76,12 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<std::v
vSolutionsRet.push_back(witnessprogram);
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;
}

Expand Down Expand Up @@ -198,6 +205,23 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
{
addressRet = CScriptID(uint160(vSolutions[0]));
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...
return false;
Expand Down Expand Up @@ -268,6 +292,27 @@ class CScriptVisitor : public boost::static_visitor<bool>
*script << OP_HASH160 << ToByteVector(scriptID) << OP_EQUAL;
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

Expand Down
37 changes: 33 additions & 4 deletions src/script/standard.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ enum txnouttype
TX_NULL_DATA, //!< unspendable OP_RETURN script that carries data
TX_WITNESS_V0_SCRIPTHASH,
TX_WITNESS_V0_KEYHASH,
TX_WITNESS_UNKNOWN, //!< Only for Witness versions not already defined above
};

class CNoDestination {
Expand All @@ -72,14 +73,42 @@ class CNoDestination {
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:
* * CNoDestination: no destination set
* * CKeyID: TX_PUBKEYHASH destination
* * CScriptID: TX_SCRIPTHASH destination
* * CKeyID: TX_PUBKEYHASH destination (P2PKH)
* * 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
*/
typedef boost::variant<CNoDestination, CKeyID, CScriptID> CTxDestination;
typedef boost::variant<CNoDestination, CKeyID, CScriptID, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessUnknown> CTxDestination;

/** Check whether a CTxDestination is a CNoDestination. */
bool IsValidDestination(const CTxDestination& dest);
Expand All @@ -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
* the addressRet parameter and returns true if successful. For multisig
* 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);

Expand Down
Loading

0 comments on commit c091b99

Please sign in to comment.