zcashd/src/key_io.cpp

533 lines
19 KiB
C++

// Copyright (c) 2014-2016 The Bitcoin Core developers
// Copyright (c) 2016-2023 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
#include <key_io.h>
#include <base58.h>
#include <bech32.h>
#include <script/script.h>
#include <util/strencodings.h>
#include <rust/address.h>
#include <assert.h>
#include <string.h>
#include <algorithm>
#include <variant>
#include "util/match.h"
namespace
{
class DestinationEncoder
{
private:
const KeyConstants& keyConstants;
public:
DestinationEncoder(const KeyConstants& keyConstants) : keyConstants(keyConstants) {}
std::string operator()(const CKeyID& id) const
{
std::vector<unsigned char> data = keyConstants.Base58Prefix(KeyConstants::PUBKEY_ADDRESS);
data.insert(data.end(), id.begin(), id.end());
return EncodeBase58Check(data);
}
std::string operator()(const CScriptID& id) const
{
std::vector<unsigned char> data = keyConstants.Base58Prefix(KeyConstants::SCRIPT_ADDRESS);
data.insert(data.end(), id.begin(), id.end());
return EncodeBase58Check(data);
}
std::string operator()(const CNoDestination& no) const { return {}; }
};
static uint32_t GetTypecode(const void* ua, size_t index)
{
return std::visit(
TypecodeForReceiver(),
reinterpret_cast<const libzcash::UnifiedAddress*>(ua)->GetReceiversAsParsed()[index]);
}
class DataLenForReceiver {
public:
DataLenForReceiver() {}
size_t operator()(const libzcash::OrchardRawAddress &zaddr) const { return 43; }
size_t operator()(const libzcash::SaplingPaymentAddress &zaddr) const { return 43; }
size_t operator()(const CScriptID &p2sh) const { return 20; }
size_t operator()(const CKeyID &p2pkh) const { return 20; }
size_t operator()(const libzcash::UnknownReceiver &unknown) const { return unknown.data.size(); }
};
static size_t GetReceiverLen(const void* ua, size_t index)
{
return std::visit(
DataLenForReceiver(),
reinterpret_cast<const libzcash::UnifiedAddress*>(ua)->GetReceiversAsParsed()[index]);
}
class CopyDataForReceiver {
unsigned char* data;
size_t length;
public:
CopyDataForReceiver(unsigned char* data, size_t length) : data(data), length(length) {}
void operator()(const libzcash::OrchardRawAddress &zaddr) const {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << zaddr;
assert(length == ss.size());
memcpy(data, ss.data(), ss.size());
}
void operator()(const libzcash::SaplingPaymentAddress &zaddr) const {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << zaddr;
assert(length == ss.size());
memcpy(data, ss.data(), ss.size());
}
void operator()(const CScriptID &p2sh) const {
memcpy(data, p2sh.begin(), p2sh.size());
}
void operator()(const CKeyID &p2pkh) const {
memcpy(data, p2pkh.begin(), p2pkh.size());
}
void operator()(const libzcash::UnknownReceiver &unknown) const {
memcpy(data, unknown.data.data(), unknown.data.size());
}
};
/**
* `data` MUST be the correct length for the receiver at this index.
*/
static void GetReceiver(const void* ua, size_t index, unsigned char* data, size_t length)
{
std::visit(
CopyDataForReceiver(data, length),
reinterpret_cast<const libzcash::UnifiedAddress*>(ua)->GetReceiversAsParsed()[index]);
}
class PaymentAddressEncoder
{
private:
const KeyConstants& keyConstants;
public:
PaymentAddressEncoder(const KeyConstants& keyConstants) : keyConstants(keyConstants) {}
std::string operator()(const CKeyID& id) const
{
return DestinationEncoder(keyConstants)(id);
}
std::string operator()(const CScriptID& id) const
{
return DestinationEncoder(keyConstants)(id);
}
std::string operator()(const libzcash::SproutPaymentAddress& zaddr) const
{
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << zaddr;
std::vector<unsigned char> data = keyConstants.Base58Prefix(KeyConstants::ZCPAYMENT_ADDRESS);
data.insert(data.end(), ss.begin(), ss.end());
return EncodeBase58Check(data);
}
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(keyConstants.Bech32HRP(KeyConstants::SAPLING_PAYMENT_ADDRESS), data);
}
std::string operator()(const libzcash::UnifiedAddress& uaddr) const
{
// Serialize the UA to a C-string.
auto encoded = zcash_address_serialize_unified(
keyConstants.NetworkIDString().c_str(),
&uaddr,
uaddr.size(),
GetTypecode,
GetReceiverLen,
GetReceiver);
// Copy the C-string into C++.
std::string res(encoded);
// Free the C-string.
zcash_address_string_free(encoded);
return res;
}
};
class ViewingKeyEncoder
{
private:
const KeyConstants& keyConstants;
public:
ViewingKeyEncoder(const KeyConstants& keyConstants) : keyConstants(keyConstants) {}
std::string operator()(const libzcash::SproutViewingKey& vk) const
{
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << vk;
std::vector<unsigned char> data = keyConstants.Base58Prefix(KeyConstants::ZCVIEWING_KEY);
data.insert(data.end(), ss.begin(), ss.end());
std::string ret = EncodeBase58Check(data);
memory_cleanse(data.data(), data.size());
return ret;
}
std::string operator()(const libzcash::SaplingExtendedFullViewingKey& extfvk) const
{
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << extfvk;
// 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(keyConstants.Bech32HRP(KeyConstants::SAPLING_EXTENDED_FVK), data);
memory_cleanse(serkey.data(), serkey.size());
memory_cleanse(data.data(), data.size());
return ret;
}
std::string operator()(const libzcash::UnifiedFullViewingKey& ufvk) const {
return ufvk.Encode(keyConstants);
}
};
class SpendingKeyEncoder
{
private:
const KeyConstants& keyConstants;
public:
SpendingKeyEncoder(const KeyConstants& keyConstants) : keyConstants(keyConstants) {}
std::string operator()(const libzcash::SproutSpendingKey& zkey) const
{
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << zkey;
std::vector<unsigned char> data = keyConstants.Base58Prefix(KeyConstants::ZCSPENDING_KEY);
data.insert(data.end(), ss.begin(), ss.end());
std::string ret = EncodeBase58Check(data);
memory_cleanse(data.data(), data.size());
return ret;
}
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(keyConstants.Bech32HRP(KeyConstants::SAPLING_EXTENDED_SPEND_KEY), data);
memory_cleanse(serkey.data(), serkey.size());
memory_cleanse(data.data(), data.size());
return ret;
}
};
// Sizes of SaplingPaymentAddress, SaplingExtendedFullViewingKey, and
// SaplingExtendedSpendingKey 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 ConvertedSaplingExtendedFullViewingKeySize = (ZIP32_XFVK_SIZE * 8 + 4) / 5;
const size_t ConvertedSaplingExtendedSpendingKeySize = (ZIP32_XSK_SIZE * 8 + 4) / 5;
} // namespace
CTxDestination KeyIO::DecodeDestination(const std::string& str) const
{
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 = keyConstants.Base58Prefix(KeyConstants::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 = keyConstants.Base58Prefix(KeyConstants::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();
};
CKey KeyIO::DecodeSecret(const std::string& str) const
{
CKey key;
std::vector<unsigned char> data;
if (DecodeBase58Check(str, data)) {
const std::vector<unsigned char>& privkey_prefix = keyConstants.Base58Prefix(KeyConstants::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 KeyIO::EncodeSecret(const CKey& key) const
{
assert(key.IsValid());
std::vector<unsigned char> data = keyConstants.Base58Prefix(KeyConstants::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 KeyIO::DecodeExtPubKey(const std::string& str) const
{
CExtPubKey key;
std::vector<unsigned char> data;
if (DecodeBase58Check(str, data)) {
const std::vector<unsigned char>& prefix = keyConstants.Base58Prefix(KeyConstants::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 KeyIO::EncodeExtPubKey(const CExtPubKey& key) const
{
std::vector<unsigned char> data = keyConstants.Base58Prefix(KeyConstants::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 KeyIO::DecodeExtKey(const std::string& str) const
{
CExtKey key;
std::vector<unsigned char> data;
if (DecodeBase58Check(str, data)) {
const std::vector<unsigned char>& prefix = keyConstants.Base58Prefix(KeyConstants::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 KeyIO::EncodeExtKey(const CExtKey& key) const
{
std::vector<unsigned char> data = keyConstants.Base58Prefix(KeyConstants::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 KeyIO::EncodeDestination(const CTxDestination& dest) const
{
return std::visit(DestinationEncoder(keyConstants), dest);
}
bool KeyIO::IsValidDestinationString(const std::string& str) const
{
return IsValidDestination(DecodeDestination(str));
}
std::string KeyIO::EncodePaymentAddress(const libzcash::PaymentAddress& zaddr) const
{
return std::visit(PaymentAddressEncoder(keyConstants), zaddr);
}
template<typename T1, typename T2>
std::optional<T1> DecodeSprout(
const KeyConstants& keyConstants,
const std::string& str,
const std::pair<KeyConstants::Base58Type, size_t>& keyMeta)
{
std::vector<unsigned char> data;
if (DecodeBase58Check(str, data)) {
const std::vector<unsigned char>& prefix = keyConstants.Base58Prefix(keyMeta.first);
if ((data.size() == keyMeta.second + prefix.size()) &&
std::equal(prefix.begin(), prefix.end(), data.begin())) {
CSerializeData serialized(data.begin() + prefix.size(), data.end());
CDataStream ss(serialized, SER_NETWORK, PROTOCOL_VERSION);
T2 ret;
ss >> ret;
memory_cleanse(serialized.data(), serialized.size());
memory_cleanse(data.data(), data.size());
return ret;
}
}
memory_cleanse(data.data(), data.size());
return std::nullopt;
}
template<typename T1, typename T2>
std::optional<T1> DecodeSapling(
const KeyConstants& keyConstants,
const std::string& str,
const std::pair<KeyConstants::Bech32Type, size_t>& keyMeta)
{
std::vector<unsigned char> data;
auto bech = bech32::Decode(str);
if (bech.first == keyConstants.Bech32HRP(keyMeta.first) &&
bech.second.size() == keyMeta.second) {
// 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);
T2 ret;
ss >> ret;
memory_cleanse(data.data(), data.size());
return ret;
}
}
memory_cleanse(data.data(), data.size());
return std::nullopt;
}
template<typename T1, typename T2, typename T3>
std::optional<T1> DecodeAny(
const KeyConstants& keyConstants,
const std::string& str,
const std::pair<KeyConstants::Base58Type, size_t>& sproutKeyMeta,
const std::pair<KeyConstants::Bech32Type, size_t>& saplingKeyMeta)
{
auto sprout = DecodeSprout<T1, T2>(keyConstants, str, sproutKeyMeta);
if (sprout.has_value()) {
return sprout.value();
}
auto sapling = DecodeSapling<T1, T3>(keyConstants, str, saplingKeyMeta);
if (sapling.has_value()) {
return sapling.value();
}
return std::nullopt;
}
std::optional<libzcash::PaymentAddress> KeyIO::DecodePaymentAddress(const std::string& str) const
{
// Try parsing as a Unified Address.
auto ua = libzcash::UnifiedAddress::Parse(keyConstants, str);
if (ua.has_value()) {
return ua.value();
}
// Try parsing as a Sapling address
auto sapling = DecodeSapling<libzcash::SaplingPaymentAddress, libzcash::SaplingPaymentAddress>(
keyConstants,
str,
std::make_pair(KeyConstants::SAPLING_PAYMENT_ADDRESS, ConvertedSaplingPaymentAddressSize));
if (sapling.has_value()) {
return sapling.value();
}
// Try parsing as a Sprout address
auto sprout = DecodeSprout<libzcash::SproutPaymentAddress, libzcash::SproutPaymentAddress>(
keyConstants,
str,
std::make_pair(KeyConstants::ZCPAYMENT_ADDRESS, libzcash::SerializedSproutPaymentAddressSize));
if (sprout.has_value()) {
return sprout.value();
}
// Finally, try parsing as transparent
return examine(DecodeDestination(str), match {
[](const CKeyID& keyIdIn) {
std::optional<libzcash::PaymentAddress> keyId = keyIdIn;
return keyId;
},
[](const CScriptID& scriptIdIn) {
std::optional<libzcash::PaymentAddress> scriptId = scriptIdIn;
return scriptId;
},
[](const CNoDestination& d) {
std::optional<libzcash::PaymentAddress> result = std::nullopt;
return result;
}
});
}
bool KeyIO::IsValidPaymentAddressString(const std::string& str) const
{
return DecodePaymentAddress(str).has_value();
}
std::string KeyIO::EncodeViewingKey(const libzcash::ViewingKey& vk) const
{
return std::visit(ViewingKeyEncoder(keyConstants), vk);
}
std::optional<libzcash::ViewingKey> KeyIO::DecodeViewingKey(const std::string& str) const
{
// Try parsing as a Unified full viewing key
auto ufvk = libzcash::UnifiedFullViewingKey::Decode(str, keyConstants);
if (ufvk.has_value()) {
return ufvk.value();
}
// Fall back on trying Sprout or Sapling.
return DecodeAny<libzcash::ViewingKey,
libzcash::SproutViewingKey,
libzcash::SaplingExtendedFullViewingKey>(
keyConstants,
str,
std::make_pair(KeyConstants::ZCVIEWING_KEY, libzcash::SerializedSproutViewingKeySize),
std::make_pair(KeyConstants::SAPLING_EXTENDED_FVK, ConvertedSaplingExtendedFullViewingKeySize)
);
}
std::string KeyIO::EncodeSpendingKey(const libzcash::SpendingKey& zkey) const
{
return std::visit(SpendingKeyEncoder(keyConstants), zkey);
}
std::optional<libzcash::SpendingKey> KeyIO::DecodeSpendingKey(const std::string& str) const
{
return DecodeAny<libzcash::SpendingKey,
libzcash::SproutSpendingKey,
libzcash::SaplingExtendedSpendingKey>(
keyConstants,
str,
std::make_pair(KeyConstants::ZCSPENDING_KEY, libzcash::SerializedSproutSpendingKeySize),
std::make_pair(KeyConstants::SAPLING_EXTENDED_SPEND_KEY, ConvertedSaplingExtendedSpendingKeySize)
);
}