Auto merge of #3273 - arcalinea:sapling_keys, r=str4d

Sapling keys in keystore, wallet

-  Add/Have/Get SaplingSpendingKey
- Add/Remove/Have/Get SaplingFullViewingKey
- Have/Get SaplingIncomingViewingKey

- SaplingSpendingKeyMap, SaplingFullViewingKeyMap, SaplingIncomingViewingKeyMap

- GenerateNewSaplingZKey()

Not included: note decryptors, crypted keystore
This commit is contained in:
Homu
2018-07-06 15:18:39 -07:00
12 changed files with 331 additions and 18 deletions

View File

@@ -56,7 +56,9 @@ TEST(keystore_tests, sapling_keys) {
// Check that the default address from primitives and from sk method are the same
auto default_addr = sk.default_address();
auto default_addr_2 = in_viewing_key.address(default_d);
auto addrOpt2 = in_viewing_key.address(default_d);
EXPECT_TRUE(addrOpt2);
auto default_addr_2 = addrOpt2.value();
EXPECT_EQ(default_addr, default_addr_2);
auto default_addr_3 = libzcash::SaplingPaymentAddress(default_d, default_pk_d);
@@ -161,6 +163,40 @@ TEST(keystore_tests, StoreAndRetrieveViewingKey) {
EXPECT_EQ(ZCNoteDecryption(sk.receiving_key()), decOut);
}
// Sapling
TEST(keystore_tests, StoreAndRetrieveSaplingSpendingKey) {
CBasicKeyStore keyStore;
libzcash::SaplingSpendingKey skOut;
libzcash::SaplingFullViewingKey fvkOut;
libzcash::SaplingIncomingViewingKey ivkOut;
auto sk = libzcash::SaplingSpendingKey::random();
auto fvk = sk.full_viewing_key();
auto ivk = fvk.in_viewing_key();
auto addr = sk.default_address();
// Sanity-check: we can't get a key we haven't added
EXPECT_FALSE(keyStore.HaveSaplingSpendingKey(fvk));
EXPECT_FALSE(keyStore.GetSaplingSpendingKey(fvk, skOut));
// Sanity-check: we can't get a full viewing key we haven't added
EXPECT_FALSE(keyStore.HaveSaplingFullViewingKey(ivk));
EXPECT_FALSE(keyStore.GetSaplingFullViewingKey(ivk, fvkOut));
// Sanity-check: we can't get an incoming viewing key we haven't added
EXPECT_FALSE(keyStore.HaveSaplingIncomingViewingKey(addr));
EXPECT_FALSE(keyStore.GetSaplingIncomingViewingKey(addr, ivkOut));
keyStore.AddSaplingSpendingKey(sk);
EXPECT_TRUE(keyStore.HaveSaplingSpendingKey(fvk));
EXPECT_TRUE(keyStore.GetSaplingSpendingKey(fvk, skOut));
EXPECT_TRUE(keyStore.HaveSaplingFullViewingKey(ivk));
EXPECT_TRUE(keyStore.GetSaplingFullViewingKey(ivk, fvkOut));
EXPECT_TRUE(keyStore.HaveSaplingIncomingViewingKey(addr));
EXPECT_TRUE(keyStore.GetSaplingIncomingViewingKey(addr, ivkOut));
EXPECT_EQ(sk, skOut);
EXPECT_EQ(fvk, fvkOut);
EXPECT_EQ(ivk, ivkOut);
}
#ifdef ENABLE_WALLET
class TestCCryptoKeyStore : public CCryptoKeyStore
{

View File

@@ -56,7 +56,7 @@ TEST(SaplingNote, TestVectors)
TEST(SaplingNote, Random)
{
// Test creating random notes using the same spending key
auto address = SaplingSpendingKey::random().default_address().get();
auto address = SaplingSpendingKey::random().default_address();
SaplingNote note1(address, GetRand(MAX_MONEY));
SaplingNote note2(address, GetRand(MAX_MONEY));
@@ -66,7 +66,7 @@ TEST(SaplingNote, Random)
ASSERT_NE(note1.r, note2.r);
// Test diversifier and pk_d are not the same for different spending keys
SaplingNote note3(SaplingSpendingKey::random().default_address().get(), GetRand(MAX_MONEY));
SaplingNote note3(SaplingSpendingKey::random().default_address(), GetRand(MAX_MONEY));
ASSERT_NE(note1.d, note3.d);
ASSERT_NE(note1.pk_d, note3.pk_d);
}

View File

@@ -93,6 +93,27 @@ bool CBasicKeyStore::AddSpendingKey(const libzcash::SproutSpendingKey &sk)
return true;
}
//! Sapling
bool CBasicKeyStore::AddSaplingSpendingKey(const libzcash::SaplingSpendingKey &sk)
{
LOCK(cs_SpendingKeyStore);
auto fvk = sk.full_viewing_key();
// if SaplingFullViewingKey is not in SaplingFullViewingKeyMap, add it
if (!AddSaplingFullViewingKey(fvk)){
return false;
}
mapSaplingSpendingKeys[fvk] = sk;
// Add addr -> SaplingIncomingViewing to SaplingIncomingViewingKeyMap
auto ivk = fvk.in_viewing_key();
auto addr = sk.default_address();
mapSaplingIncomingViewingKeys[addr] = ivk;
return true;
}
bool CBasicKeyStore::AddViewingKey(const libzcash::SproutViewingKey &vk)
{
LOCK(cs_SpendingKeyStore);
@@ -102,6 +123,17 @@ bool CBasicKeyStore::AddViewingKey(const libzcash::SproutViewingKey &vk)
return true;
}
bool CBasicKeyStore::AddSaplingFullViewingKey(const libzcash::SaplingFullViewingKey &fvk)
{
LOCK(cs_SpendingKeyStore);
auto ivk = fvk.in_viewing_key();
mapSaplingFullViewingKeys[ivk] = fvk;
//! TODO: Note decryptors for Sapling
return true;
}
bool CBasicKeyStore::RemoveViewingKey(const libzcash::SproutViewingKey &vk)
{
LOCK(cs_SpendingKeyStore);
@@ -115,6 +147,18 @@ bool CBasicKeyStore::HaveViewingKey(const libzcash::SproutPaymentAddress &addres
return mapViewingKeys.count(address) > 0;
}
bool CBasicKeyStore::HaveSaplingFullViewingKey(const libzcash::SaplingIncomingViewingKey &ivk) const
{
LOCK(cs_SpendingKeyStore);
return mapSaplingFullViewingKeys.count(ivk) > 0;
}
bool CBasicKeyStore::HaveSaplingIncomingViewingKey(const libzcash::SaplingPaymentAddress &addr) const
{
LOCK(cs_SpendingKeyStore);
return mapSaplingIncomingViewingKeys.count(addr) > 0;
}
bool CBasicKeyStore::GetViewingKey(const libzcash::SproutPaymentAddress &address,
libzcash::SproutViewingKey &vkOut) const
{
@@ -126,3 +170,27 @@ bool CBasicKeyStore::GetViewingKey(const libzcash::SproutPaymentAddress &address
}
return false;
}
bool CBasicKeyStore::GetSaplingFullViewingKey(const libzcash::SaplingIncomingViewingKey &ivk,
libzcash::SaplingFullViewingKey &fvkOut) const
{
LOCK(cs_SpendingKeyStore);
SaplingFullViewingKeyMap::const_iterator mi = mapSaplingFullViewingKeys.find(ivk);
if (mi != mapSaplingFullViewingKeys.end()) {
fvkOut = mi->second;
return true;
}
return false;
}
bool CBasicKeyStore::GetSaplingIncomingViewingKey(const libzcash::SaplingPaymentAddress &addr,
libzcash::SaplingIncomingViewingKey &ivkOut) const
{
LOCK(cs_SpendingKeyStore);
SaplingIncomingViewingKeyMap::const_iterator mi = mapSaplingIncomingViewingKeys.find(addr);
if (mi != mapSaplingIncomingViewingKeys.end()) {
ivkOut = mi->second;
return true;
}
return false;
}

View File

@@ -55,6 +55,26 @@ public:
virtual bool HaveSpendingKey(const libzcash::SproutPaymentAddress &address) const =0;
virtual bool GetSpendingKey(const libzcash::SproutPaymentAddress &address, libzcash::SproutSpendingKey& skOut) const =0;
virtual void GetPaymentAddresses(std::set<libzcash::SproutPaymentAddress> &setAddress) const =0;
//! Add a Sapling spending key to the store.
virtual bool AddSaplingSpendingKey(const libzcash::SaplingSpendingKey &sk) =0;
//! Check whether a Sapling spending key corresponding to a given Sapling viewing key is present in the store.
virtual bool HaveSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk) const =0;
virtual bool GetSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk, libzcash::SaplingSpendingKey& skOut) const =0;
//! Support for Sapling full viewing keys
virtual bool AddSaplingFullViewingKey(const libzcash::SaplingFullViewingKey &fvk) =0;
virtual bool HaveSaplingFullViewingKey(const libzcash::SaplingIncomingViewingKey &ivk) const =0;
virtual bool GetSaplingFullViewingKey(
const libzcash::SaplingIncomingViewingKey &ivk,
libzcash::SaplingFullViewingKey& fvkOut) const =0;
//! Sapling incoming viewing keys
virtual bool HaveSaplingIncomingViewingKey(const libzcash::SaplingPaymentAddress &addr) const =0;
virtual bool GetSaplingIncomingViewingKey(
const libzcash::SaplingPaymentAddress &addr,
libzcash::SaplingIncomingViewingKey& ivkOut) const =0;
//! Support for viewing keys
virtual bool AddViewingKey(const libzcash::SproutViewingKey &vk) =0;
@@ -70,6 +90,14 @@ typedef std::map<libzcash::SproutPaymentAddress, libzcash::SproutSpendingKey> Sp
typedef std::map<libzcash::SproutPaymentAddress, libzcash::SproutViewingKey> ViewingKeyMap;
typedef std::map<libzcash::SproutPaymentAddress, ZCNoteDecryption> NoteDecryptorMap;
// Full viewing key has equivalent functionality to a transparent address
// When encrypting wallet, encrypt SaplingSpendingKeyMap, while leaving SaplingFullViewingKeyMap unencrypted
// When implementing ZIP 32, add another map from SaplingFullViewingKey -> SaplingExpandedSpendingKey
typedef std::map<libzcash::SaplingFullViewingKey, libzcash::SaplingSpendingKey> SaplingSpendingKeyMap;
typedef std::map<libzcash::SaplingIncomingViewingKey, libzcash::SaplingFullViewingKey> SaplingFullViewingKeyMap;
// Only maps from default addresses to ivk, may need to be reworked when adding diversified addresses.
typedef std::map<libzcash::SaplingPaymentAddress, libzcash::SaplingIncomingViewingKey> SaplingIncomingViewingKeyMap;
/** Basic key store, that keeps keys in an address->secret map */
class CBasicKeyStore : public CKeyStore
{
@@ -80,6 +108,10 @@ protected:
SpendingKeyMap mapSpendingKeys;
ViewingKeyMap mapViewingKeys;
NoteDecryptorMap mapNoteDecryptors;
SaplingSpendingKeyMap mapSaplingSpendingKeys;
SaplingFullViewingKeyMap mapSaplingFullViewingKeys;
SaplingIncomingViewingKeyMap mapSaplingIncomingViewingKeys;
public:
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey);
@@ -182,6 +214,43 @@ public:
}
}
}
//! Sapling
bool AddSaplingSpendingKey(const libzcash::SaplingSpendingKey &sk);
bool HaveSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk) const
{
bool result;
{
LOCK(cs_SpendingKeyStore);
result = (mapSaplingSpendingKeys.count(fvk) > 0);
}
return result;
}
bool GetSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk, libzcash::SaplingSpendingKey &skOut) const
{
{
LOCK(cs_SpendingKeyStore);
SaplingSpendingKeyMap::const_iterator mi = mapSaplingSpendingKeys.find(fvk);
if (mi != mapSaplingSpendingKeys.end())
{
skOut = mi->second;
return true;
}
}
return false;
}
virtual bool AddSaplingFullViewingKey(const libzcash::SaplingFullViewingKey &fvk);
virtual bool HaveSaplingFullViewingKey(const libzcash::SaplingIncomingViewingKey &ivk) const;
virtual bool GetSaplingFullViewingKey(
const libzcash::SaplingIncomingViewingKey &ivk,
libzcash::SaplingFullViewingKey& fvkOut) const;
virtual bool HaveSaplingIncomingViewingKey(const libzcash::SaplingPaymentAddress &addr) const;
virtual bool GetSaplingIncomingViewingKey(
const libzcash::SaplingPaymentAddress &addr,
libzcash::SaplingIncomingViewingKey& ivkOut) const;
virtual bool AddViewingKey(const libzcash::SproutViewingKey &vk);
virtual bool RemoveViewingKey(const libzcash::SproutViewingKey &vk);
@@ -193,4 +262,7 @@ typedef std::vector<unsigned char, secure_allocator<unsigned char> > CKeyingMate
typedef std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char> > > CryptedKeyMap;
typedef std::map<libzcash::SproutPaymentAddress, std::vector<unsigned char> > CryptedSpendingKeyMap;
//! Sapling
typedef std::map<libzcash::SaplingFullViewingKey, std::vector<unsigned char> > CryptedSaplingSpendingKeyMap;
#endif // BITCOIN_KEYSTORE_H

View File

@@ -239,7 +239,7 @@ BOOST_AUTO_TEST_CASE(zs_address_test)
{
auto addr = sk.default_address();
std::string addr_string = EncodePaymentAddress(*addr);
std::string addr_string = EncodePaymentAddress(addr);
BOOST_CHECK(addr_string.compare(0, 2, Params().Bech32HRP(CChainParams::SAPLING_PAYMENT_ADDRESS)) == 0);
auto paymentaddr2 = DecodePaymentAddress(addr_string);

View File

@@ -322,6 +322,35 @@ bool CCryptoKeyStore::AddSpendingKey(const libzcash::SproutSpendingKey &sk)
return true;
}
bool CCryptoKeyStore::AddSaplingSpendingKey(const libzcash::SaplingSpendingKey &sk)
{
{
LOCK(cs_SpendingKeyStore);
if (!IsCrypted()) {
return CBasicKeyStore::AddSaplingSpendingKey(sk);
}
if (IsLocked()) {
return false;
}
std::vector<unsigned char> vchCryptedSecret;
CSecureDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << sk;
CKeyingMaterial vchSecret(ss.begin(), ss.end());
auto address = sk.default_address();
auto fvk = sk.full_viewing_key();
if (!EncryptSecret(vMasterKey, vchSecret, address.GetHash(), vchCryptedSecret)) {
return false;
}
if (!AddCryptedSaplingSpendingKey(fvk, vchCryptedSecret)) {
return false;
}
}
return true;
}
bool CCryptoKeyStore::AddCryptedSpendingKey(const libzcash::SproutPaymentAddress &address,
const libzcash::ReceivingKey &rk,
const std::vector<unsigned char> &vchCryptedSecret)
@@ -337,6 +366,21 @@ bool CCryptoKeyStore::AddCryptedSpendingKey(const libzcash::SproutPaymentAddress
return true;
}
// TODO: Handle note decryptors
bool CCryptoKeyStore::AddCryptedSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk,
const std::vector<unsigned char> &vchCryptedSecret)
{
{
LOCK(cs_SpendingKeyStore);
if (!SetCrypted()) {
return false;
}
mapCryptedSaplingSpendingKeys[fvk] = vchCryptedSecret;
}
return true;
}
bool CCryptoKeyStore::GetSpendingKey(const libzcash::SproutPaymentAddress &address, libzcash::SproutSpendingKey &skOut) const
{
{

View File

@@ -129,7 +129,9 @@ class CCryptoKeyStore : public CBasicKeyStore
private:
CryptedKeyMap mapCryptedKeys;
CryptedSpendingKeyMap mapCryptedSpendingKeys;
CryptedSaplingSpendingKeyMap mapCryptedSaplingSpendingKeys;
CKeyingMaterial vMasterKey;
//! if fUseCrypto is true, mapKeys and mapSpendingKeys must be empty
@@ -230,6 +232,10 @@ public:
mi++;
}
}
//! Sapling
virtual bool AddCryptedSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk,
const std::vector<unsigned char> &vchCryptedSecret);
bool AddSaplingSpendingKey(const libzcash::SaplingSpendingKey &sk);
/**
* Wallet status (encrypted, locked) changed.

View File

@@ -7,6 +7,29 @@
#include <boost/filesystem.hpp>
/**
* This test covers Sapling methods on CWallet
* GenerateNewSaplingZKey()
*/
TEST(wallet_zkeys_tests, store_and_load_sapling_zkeys) {
SelectParams(CBaseChainParams::MAIN);
CWallet wallet;
auto address = wallet.GenerateNewSaplingZKey();
// verify wallet has incoming viewing key for the address
ASSERT_TRUE(wallet.HaveSaplingIncomingViewingKey(address));
// manually add new spending key to wallet
auto sk = libzcash::SaplingSpendingKey::random();
ASSERT_TRUE(wallet.AddSaplingZKey(sk));
// verify wallet did add it
auto fvk = sk.full_viewing_key();
ASSERT_TRUE(wallet.HaveSaplingSpendingKey(fvk));
}
/**
* This test covers methods on CWallet
* GenerateNewZKey()

View File

@@ -100,8 +100,51 @@ libzcash::PaymentAddress CWallet::GenerateNewZKey()
return addr;
}
// Generate a new Sapling spending key and return its public payment address
SaplingPaymentAddress CWallet::GenerateNewSaplingZKey()
{
AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata
auto sk = SaplingSpendingKey::random();
auto fvk = sk.full_viewing_key();
auto addr = sk.default_address();
// Check for collision, even though it is unlikely to ever occur
if (CCryptoKeyStore::HaveSaplingSpendingKey(fvk)) {
throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): Collision detected");
}
// Create new metadata
int64_t nCreationTime = GetTime();
mapSaplingZKeyMetadata[addr] = CKeyMetadata(nCreationTime);
if (!AddSaplingZKey(sk)) {
throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): AddSaplingZKey failed");
}
// return default sapling payment address.
return addr;
}
// Add spending key to keystore
bool CWallet::AddSaplingZKey(const libzcash::SaplingSpendingKey &sk)
{
AssertLockHeld(cs_wallet); // mapSaplingZKeyMetadata
if (!CCryptoKeyStore::AddSaplingSpendingKey(sk)) {
return false;
}
if (!fFileBacked) {
return true;
}
// TODO: Persist to disk
return true;
}
// Add spending key to keystore and persist to disk
// TODO: Add Sapling support
bool CWallet::AddZKey(const libzcash::SproutSpendingKey &key)
{
AssertLockHeld(cs_wallet); // mapZKeyMetadata
@@ -176,7 +219,7 @@ bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey)
bool CWallet::AddCryptedKey(const CPubKey &vchPubKey,
const vector<unsigned char> &vchCryptedSecret)
{
if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret))
return false;
if (!fFileBacked)
@@ -516,7 +559,7 @@ bool CWallet::Verify(const string& walletFile, string& warningString, string& er
} catch (const boost::filesystem::filesystem_error&) {
// failure is ok (well, not really, but it's not worse than what we started with)
}
// try again
if (!bitdb.Open(GetDataDir())) {
// if it still fails, it probably means we can't even create the database env
@@ -525,14 +568,14 @@ bool CWallet::Verify(const string& walletFile, string& warningString, string& er
return true;
}
}
if (GetBoolArg("-salvagewallet", false))
{
// Recover readable keypairs:
if (!CWalletDB::Recover(bitdb, walletFile, true))
return false;
}
if (boost::filesystem::exists(GetDataDir() / walletFile))
{
CDBEnv::VerifyResult r = bitdb.Verify(walletFile, CWalletDB::Recover);
@@ -546,7 +589,7 @@ bool CWallet::Verify(const string& walletFile, string& warningString, string& er
if (r == CDBEnv::RECOVER_FAIL)
errorString += _("wallet.dat corrupt, salvage failed");
}
return true;
}
@@ -2523,7 +2566,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nC
CReserveKey reservekey(this);
CWalletTx wtx;
if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosRet, strFailReason, &coinControl, false))
return false;
@@ -2590,7 +2633,7 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) {
max_tx_size = MAX_TX_SIZE_BEFORE_SAPLING;
}
// Discourage fee sniping.
//
// However because of a off-by-one-error in previous versions we need to
@@ -3062,7 +3105,7 @@ bool CWallet::SetDefaultKey(const CPubKey &vchPubKey)
/**
* Mark old keypool keys as used,
* and generate all new keys
* and generate all new keys
*/
bool CWallet::NewKeyPool()
{
@@ -3767,7 +3810,7 @@ void CWallet::GetFilteredNotes(
if (ignoreUnspendable && !HaveSpendingKey(pa)) {
continue;
}
// skip locked notes
if (IsLockedNote(jsop)) {
continue;

View File

@@ -786,6 +786,7 @@ public:
std::set<int64_t> setKeyPool;
std::map<CKeyID, CKeyMetadata> mapKeyMetadata;
std::map<libzcash::SproutPaymentAddress, CKeyMetadata> mapZKeyMetadata;
std::map<libzcash::SaplingPaymentAddress, CKeyMetadata> mapSaplingZKeyMetadata;
typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
MasterKeyMap mapMasterKeys;
@@ -977,6 +978,14 @@ public:
bool RemoveViewingKey(const libzcash::SproutViewingKey &vk);
//! Adds a viewing key to the store, without saving it to disk (used by LoadWallet)
bool LoadViewingKey(const libzcash::SproutViewingKey &dest);
/**
* Sapling ZKeys
*/
//! Generates new Sapling key
libzcash::SaplingPaymentAddress GenerateNewSaplingZKey();
//! Adds Sapling spending key to the store, and saves it to disk
bool AddSaplingZKey(const libzcash::SaplingSpendingKey &key);
/**
* Increment the next transaction order id

View File

@@ -39,6 +39,12 @@ SproutPaymentAddress SproutSpendingKey::address() const {
}
//! Sapling
uint256 SaplingPaymentAddress::GetHash() const {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << *this;
return Hash(ss.begin(), ss.end());
}
SaplingFullViewingKey SaplingExpandedSpendingKey::full_viewing_key() const {
uint256 ak;
uint256 nk;
@@ -75,8 +81,11 @@ boost::optional<SaplingPaymentAddress> SaplingIncomingViewingKey::address(divers
}
}
boost::optional<SaplingPaymentAddress> SaplingSpendingKey::default_address() const {
return full_viewing_key().in_viewing_key().address(default_diversifier(*this));
SaplingPaymentAddress SaplingSpendingKey::default_address() const {
// Iterates within default_diversifier to ensure a valid address is returned
auto addrOpt = full_viewing_key().in_viewing_key().address(default_diversifier(*this));
assert(addrOpt != boost::none);
return addrOpt.value();
}
}

View File

@@ -111,6 +111,9 @@ public:
READWRITE(d);
READWRITE(pk_d);
}
//! Get the 256-bit SHA256d hash of this payment address.
uint256 GetHash() const;
friend inline bool operator==(const SaplingPaymentAddress& a, const SaplingPaymentAddress& b) {
return a.d == b.d && a.pk_d == b.pk_d;
@@ -202,7 +205,7 @@ public:
SaplingFullViewingKey full_viewing_key() const;
// Can derive Sapling addr from default diversifier
boost::optional<SaplingPaymentAddress> default_address() const;
SaplingPaymentAddress default_address() const;
};
typedef boost::variant<InvalidEncoding, SproutPaymentAddress, SaplingPaymentAddress> PaymentAddress;