Add UnifiedFullViewingKey type.

This type is backed by the `zcash_address` implementaion of
unified full viewing keys. It is intended for serialization
and parsing of UFVKs, and provides conversion functions that
allow for construction to and from ZcashdUnifiedFullViewingKey
values.
This commit is contained in:
Kris Nuttycombe 2021-11-29 16:40:29 -07:00
parent c78887a148
commit 217c56811d
14 changed files with 339 additions and 30 deletions

View File

@ -5,6 +5,8 @@
#ifndef ZCASH_KEY_CONSTANTS_H
#define ZCASH_KEY_CONSTANTS_H
#include <string>
class KeyConstants
{
public:

View File

@ -190,6 +190,10 @@ public:
return ret;
}
std::string operator()(const libzcash::UnifiedFullViewingKey& ufvk) const {
return ufvk.Encode(keyConstants);
}
std::string operator()(const libzcash::InvalidEncoding& no) const { return {}; }
};
@ -485,6 +489,13 @@ std::string KeyIO::EncodeViewingKey(const libzcash::ViewingKey& vk)
libzcash::ViewingKey KeyIO::DecodeViewingKey(const std::string& str)
{
// 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>(

View File

@ -134,6 +134,14 @@ bool CExtPubKey::Derive(CExtPubKey &out, unsigned int _nChild) const {
return (!secp256k1_ecdsa_signature_normalize(secp256k1_context_verify, NULL, &sig));
}
/* static */ std::optional<CChainablePubKey> CChainablePubKey::FromParts(ChainCode chaincode, CPubKey pubkey) {
if (pubkey.IsCompressed()) {
return CChainablePubKey(chaincode, pubkey);
} else {
return std::nullopt;
}
}
/* static */ int ECCVerifyHandle::refcount = 0;
ECCVerifyHandle::ECCVerifyHandle()

View File

@ -203,6 +203,55 @@ public:
bool Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const;
};
class CChainablePubKey {
private:
ChainCode chaincode;
CPubKey pubkey;
CChainablePubKey() {}
CChainablePubKey(ChainCode chaincode, CPubKey pubkey): chaincode(chaincode), pubkey(pubkey) {}
public:
static std::optional<CChainablePubKey> FromParts(ChainCode chaincode, CPubKey pubkey);
const ChainCode& GetChainCode() const {
return chaincode;
}
const CPubKey& GetPubKey() const {
return pubkey;
}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(chaincode);
if (ser_action.ForRead()) {
std::array<uint8_t, 33> pubkeyBytes;
READWRITE(pubkeyBytes);
pubkey = CPubKey(pubkeyBytes.begin(), pubkeyBytes.end());
assert(pubkey.IsCompressed());
} else {
assert(pubkey.size() == 33);
std::array<uint8_t, 33> pubkeyBytes;
std::copy(pubkey.begin(), pubkey.end(), pubkeyBytes.begin());
READWRITE(pubkeyBytes);
}
}
template <typename Stream>
static CChainablePubKey Read(Stream& stream) {
CChainablePubKey key;
stream >> key;
return key;
}
friend bool operator==(const CChainablePubKey &a, const CChainablePubKey &b)
{
return a.chaincode == b.chaincode && a.pubkey == b.pubkey;
}
};
struct CExtPubKey {
unsigned char nDepth;
unsigned char vchFingerprint[4];

View File

@ -63,7 +63,7 @@ char* unified_full_viewing_key_serialize(
*/
bool unified_full_viewing_key_read_transparent(
const UnifiedFullViewingKeyPtr* full_viewing_key,
unsigned char *tkeyout);
unsigned char* tkeyout);
/**
* Reads the Sapling component of a unified viewing key.
@ -75,7 +75,7 @@ bool unified_full_viewing_key_read_transparent(
*/
bool unified_full_viewing_key_read_sapling(
const UnifiedFullViewingKeyPtr* full_viewing_key,
unsigned char *skeyout);
unsigned char* skeyout);
/**
* Sets the Sapling component of a unified viewing key.
@ -91,8 +91,8 @@ bool unified_full_viewing_key_read_sapling(
* or the null pointer otherwise.
*/
UnifiedFullViewingKeyPtr* unified_full_viewing_key_from_components(
const unsigned char *t_key,
const unsigned char *sapling_key);
const unsigned char* t_key,
const unsigned char* sapling_key);
#ifdef __cplusplus
}

View File

@ -212,7 +212,7 @@ pub extern "C" fn zcash_address_serialize_unified(
}
};
let ua = match unified::Address::try_from_items(receivers) {
let ua = match unified::Address::try_from_items_preserving_order(receivers) {
Ok(ua) => ua,
Err(e) => {
tracing::error!("{}", e);

View File

@ -27,11 +27,14 @@ pub extern "C" fn unified_full_viewing_key_parse(
) -> *mut Ufvk {
let network = match network_from_cstr(network) {
Some(n) => n,
None => return std::ptr::null_mut(),
None => {
return std::ptr::null_mut();
}
};
match unsafe { CStr::from_ptr(encoded) }.to_str() {
Ok(encoded) => match Ufvk::decode(encoded) {
Ok(encoded) => {
match Ufvk::decode(encoded) {
Ok((parsed_network, fvk)) => {
if parsed_network == network {
Box::into_raw(Box::new(fvk))
@ -47,7 +50,7 @@ pub extern "C" fn unified_full_viewing_key_parse(
error!("Failure decoding unified full viewing key: {}", e);
std::ptr::null_mut()
}
},
}},
Err(e) => {
error!("Failure reading bytes of unified full viewing key: {}", e);
std::ptr::null_mut()

View File

@ -5506,6 +5506,10 @@ KeyAddResult AddViewingKeyToWallet::operator()(const libzcash::SaplingExtendedFu
}
}
KeyAddResult AddViewingKeyToWallet::operator()(const libzcash::UnifiedFullViewingKey& no) const {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unified full viewing key import is not yet supported.");
}
KeyAddResult AddViewingKeyToWallet::operator()(const libzcash::InvalidEncoding& no) const {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid viewing key");
}

View File

@ -1481,6 +1481,7 @@ public:
KeyAddResult operator()(const libzcash::SproutViewingKey &sk) const;
KeyAddResult operator()(const libzcash::SaplingExtendedFullViewingKey &sk) const;
KeyAddResult operator()(const libzcash::UnifiedFullViewingKey &sk) const;
KeyAddResult operator()(const libzcash::InvalidEncoding& no) const;
};

View File

@ -1,6 +1,10 @@
#include "Address.hpp"
#include "zcash/address/unified.h"
#include "utilstrencodings.h"
#include <algorithm>
#include <iostream>
#include <rust/address.h>
const uint8_t ZCASH_UA_TYPECODE_P2PKH = 0x00;
const uint8_t ZCASH_UA_TYPECODE_P2SH = 0x01;
@ -51,6 +55,14 @@ std::pair<std::string, PaymentAddress> AddressInfoFromViewingKey::operator()(con
std::pair<std::string, PaymentAddress> AddressInfoFromViewingKey::operator()(const SaplingExtendedFullViewingKey &xfvk) const {
return std::make_pair("sapling", xfvk.DefaultAddress());
}
std::pair<std::string, PaymentAddress> AddressInfoFromViewingKey::operator()(const UnifiedFullViewingKey &ufvk) const {
return std::make_pair(
"unified",
ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(ufvk)
.FindAddress(diversifier_index_t(0))
.first
);
}
std::pair<std::string, PaymentAddress> AddressInfoFromViewingKey::operator()(const InvalidEncoding&) const {
throw std::invalid_argument("Cannot derive default address from invalid viewing key");
}
@ -176,3 +188,95 @@ std::set<libzcash::RawAddress> GetRawAddresses::operator()(
}
return ret;
}
std::optional<libzcash::UnifiedFullViewingKey> libzcash::UnifiedFullViewingKey::Decode(
const std::string& str,
const KeyConstants& keyConstants) {
UnifiedFullViewingKeyPtr* ptr = unified_full_viewing_key_parse(
keyConstants.NetworkIDString().c_str(),
str.c_str());
if (ptr == nullptr) {
return std::nullopt;
} else {
return UnifiedFullViewingKey(ptr);
}
}
std::string libzcash::UnifiedFullViewingKey::Encode(const KeyConstants& keyConstants) const {
auto encoded = unified_full_viewing_key_serialize(
keyConstants.NetworkIDString().c_str(),
inner.get());
// Copy the C-string into C++.
std::string res(encoded);
// Free the C-string.
zcash_address_string_free(encoded);
return res;
}
std::optional<libzcash::SaplingDiversifiableFullViewingKey> libzcash::UnifiedFullViewingKey::GetSaplingKey() const {
std::vector<uint8_t> buffer(128);
if (unified_full_viewing_key_read_sapling(inner.get(), buffer.data())) {
CDataStream ss(buffer, SER_NETWORK, PROTOCOL_VERSION);
return SaplingDiversifiableFullViewingKey::Read(ss);
} else {
return std::nullopt;
}
}
std::optional<CChainablePubKey> libzcash::UnifiedFullViewingKey::GetTransparentKey() const {
std::vector<uint8_t> buffer(65);
if (unified_full_viewing_key_read_transparent(inner.get(), buffer.data())) {
CDataStream ss(buffer, SER_NETWORK, PROTOCOL_VERSION);
return CChainablePubKey::Read(ss);
} else {
return std::nullopt;
}
}
bool libzcash::UnifiedFullViewingKeyBuilder::AddTransparentKey(const CChainablePubKey& key) {
if (t_bytes.has_value()) return false;
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << key;
assert(ss.size() == 65);
std::vector<uint8_t> ss_bytes(ss.begin(), ss.end());
t_bytes = ss_bytes;
return true;
}
bool libzcash::UnifiedFullViewingKeyBuilder::AddSaplingKey(const SaplingDiversifiableFullViewingKey& key) {
if (sapling_bytes.has_value()) return false;
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << key;
assert(ss.size() == 128);
std::vector<uint8_t> ss_bytes(ss.begin(), ss.end());
sapling_bytes = ss_bytes;
return true;
}
std::optional<libzcash::UnifiedFullViewingKey> libzcash::UnifiedFullViewingKeyBuilder::build() const {
UnifiedFullViewingKeyPtr* ptr = unified_full_viewing_key_from_components(
t_bytes.has_value() ? t_bytes.value().data() : nullptr,
sapling_bytes.has_value() ? sapling_bytes.value().data() : nullptr);
if (ptr == nullptr) {
return std::nullopt;
} else {
return UnifiedFullViewingKey(ptr);
}
}
libzcash::UnifiedFullViewingKey libzcash::UnifiedFullViewingKey::FromZcashdUFVK(const ZcashdUnifiedFullViewingKey& key) {
UnifiedFullViewingKeyBuilder builder;
if (key.GetTransparentKey().has_value()) {
builder.AddTransparentKey(key.GetTransparentKey().value());
}
if (key.GetSaplingKey().has_value()) {
builder.AddSaplingKey(key.GetSaplingKey().value());
}
auto result = builder.build();
if (!result.has_value()) {
throw std::invalid_argument("Cannot convert from invalid viewing key.");
}
return result.value();
}

View File

@ -1,15 +1,18 @@
#ifndef ZC_ADDRESS_H_
#define ZC_ADDRESS_H_
#include "uint256.h"
#include "key_constants.h"
#include "pubkey.h"
#include "script/script.h"
#include "uint256.h"
#include "zcash/address/orchard.hpp"
#include "zcash/address/sapling.hpp"
#include "zcash/address/sprout.hpp"
#include "zcash/address/unified.h"
#include "zcash/address/zip32.h"
#include <variant>
#include <rust/unified_keys.h>
namespace libzcash {
@ -127,14 +130,85 @@ public:
}
};
class UnifiedFullViewingKeyBuilder;
/**
* Wrapper for a zcash_address::unified::Ufvk.
*/
class UnifiedFullViewingKey {
private:
std::unique_ptr<UnifiedFullViewingKeyPtr, decltype(&unified_full_viewing_key_free)> inner;
UnifiedFullViewingKey() :
inner(nullptr, unified_full_viewing_key_free) {}
UnifiedFullViewingKey(UnifiedFullViewingKeyPtr* ptr) :
inner(ptr, unified_full_viewing_key_free) {}
friend class UnifiedFullViewingKeyBuilder;
public:
static std::optional<UnifiedFullViewingKey> Decode(
const std::string& str,
const KeyConstants& keyConstants);
static UnifiedFullViewingKey FromZcashdUFVK(const ZcashdUnifiedFullViewingKey&);
std::string Encode(const KeyConstants& keyConstants) const;
std::optional<SaplingDiversifiableFullViewingKey> GetSaplingKey() const;
std::optional<CChainablePubKey> GetTransparentKey() const;
UnifiedFullViewingKey(const UnifiedFullViewingKey& key) :
inner(unified_full_viewing_key_clone(key.inner.get()), unified_full_viewing_key_free) {}
UnifiedFullViewingKey& operator=(UnifiedFullViewingKey&& key)
{
if (this != &key) {
inner = std::move(key.inner);
}
return *this;
}
UnifiedFullViewingKey& operator=(const UnifiedFullViewingKey& key)
{
if (this != &key) {
inner.reset(unified_full_viewing_key_clone(key.inner.get()));
}
return *this;
}
};
class UnifiedFullViewingKeyBuilder {
private:
std::optional<std::vector<uint8_t>> t_bytes;
std::optional<std::vector<uint8_t>> sapling_bytes;
public:
UnifiedFullViewingKeyBuilder(): t_bytes(std::nullopt), sapling_bytes(std::nullopt) {}
bool AddTransparentKey(const CChainablePubKey&);
bool AddSaplingKey(const SaplingDiversifiableFullViewingKey&);
std::optional<UnifiedFullViewingKey> build() const;
};
/** Addresses that can appear in a string encoding. */
typedef std::variant<
InvalidEncoding,
SproutPaymentAddress,
SaplingPaymentAddress,
UnifiedAddress> PaymentAddress;
typedef std::variant<InvalidEncoding, SproutViewingKey, SaplingExtendedFullViewingKey> ViewingKey;
typedef std::variant<InvalidEncoding, SproutSpendingKey, SaplingExtendedSpendingKey> SpendingKey;
/** Viewing keys that can have a string encoding. */
typedef std::variant<
InvalidEncoding,
SproutViewingKey,
SaplingExtendedFullViewingKey,
UnifiedFullViewingKey> ViewingKey;
/** Spending keys that can have a string encoding. */
typedef std::variant<
InvalidEncoding,
SproutSpendingKey,
SaplingExtendedSpendingKey> SpendingKey;
class AddressInfoFromSpendingKey {
public:
@ -147,10 +221,11 @@ class AddressInfoFromViewingKey {
public:
std::pair<std::string, PaymentAddress> operator()(const SproutViewingKey&) const;
std::pair<std::string, PaymentAddress> operator()(const struct SaplingExtendedFullViewingKey&) const;
std::pair<std::string, PaymentAddress> operator()(const UnifiedFullViewingKey&) const;
std::pair<std::string, PaymentAddress> operator()(const InvalidEncoding&) const;
};
}
} //namespace libzcash
/** Check whether a PaymentAddress is not an InvalidEncoding. */
bool IsValidPaymentAddress(const libzcash::PaymentAddress& zaddr);

View File

@ -2,6 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
#include "zcash/Address.hpp"
#include "unified.h"
#include "bip44.h"
@ -29,7 +30,11 @@ ZcashdUnifiedFullViewingKey ZcashdUnifiedSpendingKey::ToFullViewingKey() const {
ZcashdUnifiedFullViewingKey ufvk;
if (transparentKey.has_value()) {
ufvk.transparentKey = transparentKey.value().Neuter();
auto extPubKey = transparentKey.value().Neuter();
// TODO: how to ensure that the pubkey is in its compressed representation?
// Is that already guaranteed?
ufvk.transparentKey = CChainablePubKey::FromParts(extPubKey.chaincode, extPubKey.pubkey).value();
}
if (saplingKey.has_value()) {
@ -39,6 +44,23 @@ ZcashdUnifiedFullViewingKey ZcashdUnifiedSpendingKey::ToFullViewingKey() const {
return ufvk;
}
ZcashdUnifiedFullViewingKey ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(
const UnifiedFullViewingKey& ufvk) {
ZcashdUnifiedFullViewingKey result;
auto transparentKey = ufvk.GetTransparentKey();
if (transparentKey.has_value()) {
result.transparentKey = transparentKey.value();
}
auto saplingKey = ufvk.GetSaplingKey();
if (saplingKey.has_value()) {
result.saplingKey = saplingKey.value();
}
return result;
}
std::optional<UnifiedAddress> ZcashdUnifiedFullViewingKey::Address(diversifier_index_t j) const {
UnifiedAddress ua;
@ -52,17 +74,20 @@ std::optional<UnifiedAddress> ZcashdUnifiedFullViewingKey::Address(diversifier_i
}
if (transparentKey.has_value()) {
const auto& tkey = transparentKey.value();
auto childIndex = j.ToTransparentChildIndex();
if (!childIndex.has_value()) return std::nullopt;
CExtPubKey externalKey;
if (!transparentKey.value().Derive(externalKey, 0)) {
CPubKey externalKey;
ChainCode externalChainCode;
if (!tkey.GetPubKey().Derive(externalKey, externalChainCode, 0, tkey.GetChainCode())) {
return std::nullopt;
}
CExtPubKey childKey;
if (externalKey.Derive(childKey, childIndex.value())) {
ua.AddReceiver(childKey.pubkey.GetID());
CPubKey childKey;
ChainCode childChainCode;
if (externalKey.Derive(childKey, childChainCode, childIndex.value(), externalChainCode)) {
ua.AddReceiver(childKey.GetID());
} else {
return std::nullopt;
}
@ -71,3 +96,13 @@ std::optional<UnifiedAddress> ZcashdUnifiedFullViewingKey::Address(diversifier_i
return ua;
}
std::pair<UnifiedAddress, diversifier_index_t> ZcashdUnifiedFullViewingKey::FindAddress(diversifier_index_t j) const {
auto addr = Address(j);
while (!addr.has_value()) {
if (!j.increment())
throw std::runtime_error(std::string(__func__) + ": diversifier index overflow.");;
addr = Address(j);
}
return std::make_pair(addr.value(), j);
}

View File

@ -7,23 +7,28 @@
#include "zip32.h"
#include "bip44.h"
#include "zcash/Address.hpp"
namespace libzcash {
class ZcashdUnifiedSpendingKey;
class ZcashdUnifiedFullViewingKey;
// prototypes for the classes handling ZIP-316 encoding (in Address.hpp)
class UnifiedAddress;
class UnifiedFullViewingKey;
class ZcashdUnifiedFullViewingKey {
private:
std::optional<CExtPubKey> transparentKey;
std::optional<CChainablePubKey> transparentKey;
std::optional<SaplingDiversifiableFullViewingKey> saplingKey;
ZcashdUnifiedFullViewingKey() {}
friend class ZcashdUnifiedSpendingKey;
public:
const std::optional<CExtPubKey>& GetTransparentKey() const {
static ZcashdUnifiedFullViewingKey FromUnifiedFullViewingKey(const UnifiedFullViewingKey& ufvk);
const std::optional<CChainablePubKey>& GetTransparentKey() const {
return transparentKey;
}
@ -33,15 +38,7 @@ public:
std::optional<UnifiedAddress> Address(diversifier_index_t j) const;
std::pair<UnifiedAddress, diversifier_index_t> FindAddress(diversifier_index_t j) const {
auto addr = Address(j);
while (!addr.has_value()) {
if (!j.increment())
throw std::runtime_error(std::string(__func__) + ": diversifier index overflow.");;
addr = Address(j);
}
return std::make_pair(addr.value(), j);
}
std::pair<UnifiedAddress, diversifier_index_t> FindAddress(diversifier_index_t j) const;
};
class ZcashdUnifiedSpendingKey {

View File

@ -141,6 +141,26 @@ public:
}
libzcash::SaplingPaymentAddress DefaultAddress() const;
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(fvk);
READWRITE(dk);
}
template <typename Stream>
static SaplingDiversifiableFullViewingKey Read(Stream& stream) {
SaplingDiversifiableFullViewingKey key;
stream >> key;
return key;
}
friend inline bool operator==(const SaplingDiversifiableFullViewingKey& a, const SaplingDiversifiableFullViewingKey& b) {
return (a.fvk == b.fvk && a.dk == b.dk);
}
};
class SaplingExtendedFullViewingKey: public SaplingDiversifiableFullViewingKey {