Any projects which want to use Hush code from now on will need to be licensed as GPLv3 or we will send the lawyers: https://www.softwarefreedom.org/ Notably, Komodo (KMD) is licensed as GPLv2 and is no longer compatible to receive code changes, without causing legal issues. MIT projects, such as Zcash, also cannot pull in changes from the Hush Full Node without permission from The Hush Developers, which may in some circumstances grant an MIT license on a case-by-case basis.
364 lines
13 KiB
C++
364 lines
13 KiB
C++
// Copyright (c) 2014-2016 The Bitcoin Core developers
|
|
// Copyright (c) 2016-2018 The Zcash developers
|
|
// Copyright (c) 2019-2020 The Hush developers
|
|
// Distributed under the GPLv3 software license, see the accompanying
|
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
|
|
|
#include <key_io.h>
|
|
|
|
#include <base58.h>
|
|
#include <bech32.h>
|
|
#include <script/script.h>
|
|
#include <utilstrencodings.h>
|
|
|
|
#include <boost/variant/apply_visitor.hpp>
|
|
#include <boost/variant/static_visitor.hpp>
|
|
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <algorithm>
|
|
|
|
namespace
|
|
{
|
|
class DestinationEncoder : public boost::static_visitor<std::string>
|
|
{
|
|
private:
|
|
const CChainParams& m_params;
|
|
|
|
public:
|
|
DestinationEncoder(const CChainParams& params) : m_params(params) {}
|
|
|
|
std::string operator()(const CKeyID& id) const
|
|
{
|
|
std::vector<unsigned char> data = m_params.Base58Prefix(CChainParams::PUBKEY_ADDRESS);
|
|
data.insert(data.end(), id.begin(), id.end());
|
|
return EncodeBase58Check(data);
|
|
}
|
|
|
|
std::string operator()(const CPubKey& key) const
|
|
{
|
|
std::vector<unsigned char> data = m_params.Base58Prefix(CChainParams::PUBKEY_ADDRESS);
|
|
CKeyID id = key.GetID();
|
|
data.insert(data.end(), id.begin(), id.end());
|
|
return EncodeBase58Check(data);
|
|
}
|
|
|
|
std::string operator()(const CScriptID& id) const
|
|
{
|
|
std::vector<unsigned char> data = m_params.Base58Prefix(CChainParams::SCRIPT_ADDRESS);
|
|
data.insert(data.end(), id.begin(), id.end());
|
|
return EncodeBase58Check(data);
|
|
}
|
|
|
|
std::string operator()(const CNoDestination& no) const { return {}; }
|
|
};
|
|
|
|
CTxDestination DecodeDestination(const std::string& str, const CChainParams& params)
|
|
{
|
|
std::vector<unsigned char> data;
|
|
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.
|
|
const std::vector<unsigned char>& pubkey_prefix = params.Base58Prefix(CChainParams::PUBKEY_ADDRESS);
|
|
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 CKeyID(hash);
|
|
}
|
|
// Script-hash-addresses have version 5 (or 196 testnet).
|
|
// The data vector contains RIPEMD160(SHA256(cscript)), where cscript is the serialized redemption script.
|
|
const std::vector<unsigned char>& script_prefix = params.Base58Prefix(CChainParams::SCRIPT_ADDRESS);
|
|
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 CScriptID(hash);
|
|
}
|
|
}
|
|
return CNoDestination();
|
|
}
|
|
|
|
class PaymentAddressEncoder : public boost::static_visitor<std::string>
|
|
{
|
|
private:
|
|
const CChainParams& m_params;
|
|
|
|
public:
|
|
PaymentAddressEncoder(const CChainParams& params) : m_params(params) {}
|
|
|
|
std::string operator()(const libzcash::SaplingPaymentAddress& zaddr) const
|
|
{
|
|
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
|
ss << zaddr;
|
|
// ConvertBits requires unsigned char, but CDataStream uses char
|
|
std::vector<unsigned char> seraddr(ss.begin(), ss.end());
|
|
std::vector<unsigned char> data;
|
|
// See calculation comment below
|
|
data.reserve((seraddr.size() * 8 + 4) / 5);
|
|
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, seraddr.begin(), seraddr.end());
|
|
return bech32::Encode(m_params.Bech32HRP(CChainParams::SAPLING_PAYMENT_ADDRESS), data);
|
|
}
|
|
|
|
std::string operator()(const libzcash::InvalidEncoding& no) const { return {}; }
|
|
std::string operator()(const libzcash::SproutPaymentAddress& zaddr) const { return {}; }
|
|
};
|
|
|
|
class ViewingKeyEncoder : public boost::static_visitor<std::string>
|
|
{
|
|
private:
|
|
const CChainParams& m_params;
|
|
|
|
public:
|
|
ViewingKeyEncoder(const CChainParams& params) : m_params(params) {}
|
|
|
|
std::string operator()(const libzcash::SaplingIncomingViewingKey& vk) const
|
|
{
|
|
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
|
ss << vk;
|
|
std::vector<unsigned char> serkey(ss.begin(), ss.end());
|
|
std::vector<unsigned char> data;
|
|
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, serkey.begin(), serkey.end());
|
|
std::string ret = bech32::Encode(m_params.Bech32HRP(CChainParams::SAPLING_INCOMING_VIEWING_KEY), data);
|
|
memory_cleanse(serkey.data(), serkey.size());
|
|
memory_cleanse(data.data(), data.size());
|
|
return ret;
|
|
}
|
|
|
|
std::string operator()(const libzcash::InvalidEncoding& no) const { return {}; }
|
|
};
|
|
|
|
class SpendingKeyEncoder : public boost::static_visitor<std::string>
|
|
{
|
|
private:
|
|
const CChainParams& m_params;
|
|
|
|
public:
|
|
SpendingKeyEncoder(const CChainParams& params) : m_params(params) {}
|
|
|
|
std::string operator()(const libzcash::SaplingExtendedSpendingKey& zkey) const
|
|
{
|
|
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
|
ss << zkey;
|
|
// ConvertBits requires unsigned char, but CDataStream uses char
|
|
std::vector<unsigned char> serkey(ss.begin(), ss.end());
|
|
std::vector<unsigned char> data;
|
|
// See calculation comment below
|
|
data.reserve((serkey.size() * 8 + 4) / 5);
|
|
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, serkey.begin(), serkey.end());
|
|
std::string ret = bech32::Encode(m_params.Bech32HRP(CChainParams::SAPLING_EXTENDED_SPEND_KEY), data);
|
|
memory_cleanse(serkey.data(), serkey.size());
|
|
memory_cleanse(data.data(), data.size());
|
|
return ret;
|
|
}
|
|
|
|
std::string operator()(const libzcash::InvalidEncoding& no) const { return {}; }
|
|
};
|
|
|
|
// Sizes of SaplingPaymentAddress and SaplingSpendingKey after
|
|
// ConvertBits<8, 5, true>(). The calculations below take the
|
|
// regular serialized size in bytes, convert to bits, and then
|
|
// perform ceiling division to get the number of 5-bit clusters.
|
|
const size_t ConvertedSaplingPaymentAddressSize = ((32 + 11) * 8 + 4) / 5;
|
|
const size_t ConvertedSaplingExtendedSpendingKeySize = (ZIP32_XSK_SIZE * 8 + 4) / 5;
|
|
const size_t ConvertedSaplingIncomingViewingKeySize = (32 * 8 + 4) / 5;
|
|
} // namespace
|
|
|
|
CKey DecodeSecret(const std::string& str)
|
|
{
|
|
CKey key;
|
|
std::vector<unsigned char> data;
|
|
if (DecodeBase58Check(str, data)) {
|
|
const std::vector<unsigned char>& privkey_prefix = Params().Base58Prefix(CChainParams::SECRET_KEY);
|
|
if ((data.size() == 32 + privkey_prefix.size() || (data.size() == 33 + privkey_prefix.size() && data.back() == 1)) &&
|
|
std::equal(privkey_prefix.begin(), privkey_prefix.end(), data.begin())) {
|
|
bool compressed = data.size() == 33 + privkey_prefix.size();
|
|
key.Set(data.begin() + privkey_prefix.size(), data.begin() + privkey_prefix.size() + 32, compressed);
|
|
}
|
|
}
|
|
memory_cleanse(data.data(), data.size());
|
|
return key;
|
|
}
|
|
|
|
CKey DecodeCustomSecret(const std::string& str, uint8_t secret_key)
|
|
{
|
|
CKey key;
|
|
std::vector<unsigned char> data;
|
|
if (DecodeBase58Check(str, data)) {
|
|
const std::vector<unsigned char>& privkey_prefix = std::vector<unsigned char>(1, secret_key);
|
|
if ((data.size() == 32 + privkey_prefix.size() || (data.size() == 33 + privkey_prefix.size() && data.back() == 1)) &&
|
|
std::equal(privkey_prefix.begin(), privkey_prefix.end(), data.begin())) {
|
|
bool compressed = data.size() == 33 + privkey_prefix.size();
|
|
key.Set(data.begin() + privkey_prefix.size(), data.begin() + privkey_prefix.size() + 32, compressed);
|
|
}
|
|
}
|
|
memory_cleanse(data.data(), data.size());
|
|
return key;
|
|
}
|
|
|
|
std::string EncodeSecret(const CKey& key)
|
|
{
|
|
assert(key.IsValid());
|
|
std::vector<unsigned char> data = Params().Base58Prefix(CChainParams::SECRET_KEY);
|
|
data.insert(data.end(), key.begin(), key.end());
|
|
if (key.IsCompressed()) {
|
|
data.push_back(1);
|
|
}
|
|
std::string ret = EncodeBase58Check(data);
|
|
memory_cleanse(data.data(), data.size());
|
|
return ret;
|
|
}
|
|
|
|
std::string EncodeCustomSecret(const CKey& key,uint8_t secret_key)
|
|
{
|
|
assert(key.IsValid());
|
|
std::vector<unsigned char> data = std::vector<unsigned char>(1,secret_key);;
|
|
data.insert(data.end(), key.begin(), key.end());
|
|
if (key.IsCompressed()) {
|
|
data.push_back(1);
|
|
}
|
|
std::string ret = EncodeBase58Check(data);
|
|
memory_cleanse(data.data(), data.size());
|
|
return ret;
|
|
}
|
|
|
|
CExtPubKey DecodeExtPubKey(const std::string& str)
|
|
{
|
|
CExtPubKey key;
|
|
std::vector<unsigned char> data;
|
|
if (DecodeBase58Check(str, data)) {
|
|
const std::vector<unsigned char>& prefix = Params().Base58Prefix(CChainParams::EXT_PUBLIC_KEY);
|
|
if (data.size() == BIP32_EXTKEY_SIZE + prefix.size() && std::equal(prefix.begin(), prefix.end(), data.begin())) {
|
|
key.Decode(data.data() + prefix.size());
|
|
}
|
|
}
|
|
return key;
|
|
}
|
|
|
|
std::string EncodeExtPubKey(const CExtPubKey& key)
|
|
{
|
|
std::vector<unsigned char> data = Params().Base58Prefix(CChainParams::EXT_PUBLIC_KEY);
|
|
size_t size = data.size();
|
|
data.resize(size + BIP32_EXTKEY_SIZE);
|
|
key.Encode(data.data() + size);
|
|
std::string ret = EncodeBase58Check(data);
|
|
return ret;
|
|
}
|
|
|
|
CExtKey DecodeExtKey(const std::string& str)
|
|
{
|
|
CExtKey key;
|
|
std::vector<unsigned char> data;
|
|
if (DecodeBase58Check(str, data)) {
|
|
const std::vector<unsigned char>& prefix = Params().Base58Prefix(CChainParams::EXT_SECRET_KEY);
|
|
if (data.size() == BIP32_EXTKEY_SIZE + prefix.size() && std::equal(prefix.begin(), prefix.end(), data.begin())) {
|
|
key.Decode(data.data() + prefix.size());
|
|
}
|
|
}
|
|
return key;
|
|
}
|
|
|
|
std::string EncodeExtKey(const CExtKey& key)
|
|
{
|
|
std::vector<unsigned char> data = Params().Base58Prefix(CChainParams::EXT_SECRET_KEY);
|
|
size_t size = data.size();
|
|
data.resize(size + BIP32_EXTKEY_SIZE);
|
|
key.Encode(data.data() + size);
|
|
std::string ret = EncodeBase58Check(data);
|
|
memory_cleanse(data.data(), data.size());
|
|
return ret;
|
|
}
|
|
|
|
std::string EncodeDestination(const CTxDestination& dest)
|
|
{
|
|
return boost::apply_visitor(DestinationEncoder(Params()), dest);
|
|
}
|
|
|
|
CTxDestination DecodeDestination(const std::string& str)
|
|
{
|
|
return DecodeDestination(str, Params());
|
|
}
|
|
|
|
bool IsValidDestinationString(const std::string& str, const CChainParams& params)
|
|
{
|
|
return IsValidDestination(DecodeDestination(str, params));
|
|
}
|
|
|
|
bool IsValidDestinationString(const std::string& str)
|
|
{
|
|
return IsValidDestinationString(str, Params());
|
|
}
|
|
|
|
std::string EncodePaymentAddress(const libzcash::PaymentAddress& zaddr)
|
|
{
|
|
return boost::apply_visitor(PaymentAddressEncoder(Params()), zaddr);
|
|
}
|
|
|
|
libzcash::PaymentAddress DecodePaymentAddress(const std::string& str)
|
|
{
|
|
std::vector<unsigned char> data;
|
|
auto bech = bech32::Decode(str);
|
|
if (bech.first == Params().Bech32HRP(CChainParams::SAPLING_PAYMENT_ADDRESS) &&
|
|
bech.second.size() == ConvertedSaplingPaymentAddressSize) {
|
|
// Bech32 decoding
|
|
data.reserve((bech.second.size() * 5) / 8);
|
|
if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, bech.second.begin(), bech.second.end())) {
|
|
CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION);
|
|
libzcash::SaplingPaymentAddress ret;
|
|
ss >> ret;
|
|
return ret;
|
|
}
|
|
}
|
|
return libzcash::InvalidEncoding();
|
|
}
|
|
|
|
bool IsValidPaymentAddressString(const std::string& str, uint32_t consensusBranchId) {
|
|
return IsValidPaymentAddress(DecodePaymentAddress(str), consensusBranchId);
|
|
}
|
|
|
|
std::string EncodeViewingKey(const libzcash::ViewingKey& vk)
|
|
{
|
|
return boost::apply_visitor(ViewingKeyEncoder(Params()), vk);
|
|
}
|
|
|
|
libzcash::ViewingKey DecodeViewingKey(const std::string& str)
|
|
{
|
|
std::vector<unsigned char> data;
|
|
auto bech = bech32::Decode(str);
|
|
if(bech.first == Params().Bech32HRP(CChainParams::SAPLING_INCOMING_VIEWING_KEY) &&
|
|
bech.second.size() == ConvertedSaplingIncomingViewingKeySize) {
|
|
// Bech32 decoding
|
|
data.reserve((bech.second.size() * 5) / 8);
|
|
if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, bech.second.begin(), bech.second.end())) {
|
|
CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION);
|
|
libzcash::SaplingIncomingViewingKey ret;
|
|
ss >> ret;
|
|
return ret;
|
|
}
|
|
}
|
|
return libzcash::InvalidEncoding();
|
|
}
|
|
|
|
std::string EncodeSpendingKey(const libzcash::SpendingKey& zkey)
|
|
{
|
|
return boost::apply_visitor(SpendingKeyEncoder(Params()), zkey);
|
|
}
|
|
|
|
libzcash::SpendingKey DecodeSpendingKey(const std::string& str)
|
|
{
|
|
std::vector<unsigned char> data;
|
|
auto bech = bech32::Decode(str);
|
|
if (bech.first == Params().Bech32HRP(CChainParams::SAPLING_EXTENDED_SPEND_KEY) &&
|
|
bech.second.size() == ConvertedSaplingExtendedSpendingKeySize) {
|
|
// Bech32 decoding
|
|
data.reserve((bech.second.size() * 5) / 8);
|
|
if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, bech.second.begin(), bech.second.end())) {
|
|
CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION);
|
|
libzcash::SaplingExtendedSpendingKey ret;
|
|
ss >> ret;
|
|
memory_cleanse(data.data(), data.size());
|
|
return ret;
|
|
}
|
|
}
|
|
memory_cleanse(data.data(), data.size());
|
|
return libzcash::InvalidEncoding();
|
|
}
|