Add unified address generation.

Generate unified addresses from UFVKs, and add the associated
metadata to the wallet database.
This commit is contained in:
Kris Nuttycombe 2021-12-09 18:34:04 -07:00
parent ca20cf512c
commit b29ca10b8d
8 changed files with 237 additions and 30 deletions

View File

@ -309,7 +309,7 @@ bool CBasicKeyStore::AddUnifiedFullViewingKey(
return true;
}
bool CBasicKeyStore::AddUnifiedAddress(
void CBasicKeyStore::AddUnifiedAddress(
const libzcash::UFVKId& keyId,
const libzcash::UnifiedAddress& ua)
{
@ -331,3 +331,13 @@ bool CBasicKeyStore::AddUnifiedAddress(
}
}
std::optional<libzcash::ZcashdUnifiedFullViewingKey> CBasicKeyStore::GetUnifiedFullViewingKey(
const libzcash::UFVKId& keyId)
{
auto mi = mapUnifiedFullViewingKeys.find(keyId);
if (mi != mapUnifiedFullViewingKeys.end()) {
return mi->second;
} else {
return std::nullopt;
}
}

View File

@ -108,10 +108,13 @@ public:
const libzcash::ZcashdUnifiedFullViewingKey &ufvk
) = 0;
virtual bool AddUnifiedAddress(
virtual void AddUnifiedAddress(
const libzcash::UFVKId& keyId,
const libzcash::UnifiedAddress &ua
) = 0;
virtual std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUnifiedFullViewingKey(
const libzcash::UFVKId& keyId) = 0;
};
typedef std::map<CKeyID, CKey> KeyMap;
@ -346,9 +349,12 @@ public:
* Add the transparent component of the unified address, if any,
* to the keystore to make it possible to identify the
*/
virtual bool AddUnifiedAddress(
virtual void AddUnifiedAddress(
const libzcash::UFVKId& keyId,
const libzcash::UnifiedAddress &ua);
virtual std::optional<libzcash::ZcashdUnifiedFullViewingKey> GetUnifiedFullViewingKey(
const libzcash::UFVKId& keyId);
};
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CKeyingMaterial;

View File

@ -474,7 +474,7 @@ std::optional<std::pair<libzcash::ZcashdUnifiedSpendingKey, ZcashdUnifiedSpendin
// the fingerprint of the associated full viewing key.
auto metaKey = std::make_pair(skmeta.GetSeedFingerprint(), skmeta.GetAccountId());
mapUnifiedKeyMetadata.insert({ufvkid, skmeta});
mapUnifiedKeyMetadata.insert({metaKey, skmeta});
// Add Transparent component to the wallet
if (sk.GetTransparentKey().has_value()) {
@ -532,17 +532,117 @@ bool CWallet::AddUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey &uf
return CWalletDB(strWalletFile).WriteUnifiedFullViewingKey(ufvk);
}
bool CWallet::LoadUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey &key)
{
auto keyId = key.GetKeyID(Params());
auto zufvk = ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(key);
return CCryptoKeyStore::AddUnifiedFullViewingKey(keyId, zufvk);
std::optional<std::pair<UFVKId, ZcashdUnifiedFullViewingKey>> CWallet::GetUnifiedFullViewingKeyByAccount(libzcash::AccountId accountId) {
if (!mnemonicHDChain.has_value()) {
throw std::runtime_error(
"CWallet::GenerateNewUnifiedSpendingKey(): Wallet is missing mnemonic seed metadata.");
}
auto seedfp = mnemonicHDChain.value().GetSeedFingerprint();
auto i = mapUnifiedKeyMetadata.find(std::make_pair(seedfp, accountId));
if (i != mapUnifiedKeyMetadata.end()) {
auto keyId = i->second.GetKeyID();
auto key = CCryptoKeyStore::GetUnifiedFullViewingKey(keyId);
if (key.has_value()) {
return std::make_pair(keyId, key.value());
} else {
return std::nullopt;
}
} else {
return std::nullopt;
}
}
void CWallet::LoadUnifiedKeyMetadata(const ZcashdUnifiedSpendingKeyMetadata &meta)
UAGenerationResult CWallet::GenerateUnifiedAddress(
const libzcash::AccountId& accountId,
const libzcash::diversifier_index_t& j,
const std::set<libzcash::ReceiverType>& receiverTypes)
{
if (!libzcash::HasShielded(receiverTypes)) {
return AddressGenerationError::InvalidReceiverTypes;
}
auto identifiedKey = GetUnifiedFullViewingKeyByAccount(accountId);
if (identifiedKey.has_value()) {
auto ufvkid = identifiedKey.value().first;
auto ufvk = identifiedKey.value().second;
// Check whether an address has already been generated for this
// diversifier index. If so, ensure that the set of receiver types
// being requested is the same as the set of receiver types that was
// previously generated; if so, return the previously generated address,
// otherwise return an error.
if (mapUnifiedAddressMetadata.count(ufvkid) > 0) {
const auto& accountKeys = mapUnifiedAddressMetadata.at(ufvkid);
if (accountKeys.count(j) > 0) {
if (accountKeys.at(j) == receiverTypes) {
ZcashdUnifiedAddressMetadata addrmeta(ufvkid, j, receiverTypes);
auto addr = ufvk.Address(j, receiverTypes);
return std::make_pair(addr.value(), addrmeta);
} else {
return AddressGenerationError::ExistingAddressMismatch;
}
}
}
// Find a working diversifier and construct the associated address.
auto found = ufvk.FindAddress(j, receiverTypes);
auto diversifierIndex = found.second;
// Persist the newly created address to the keystore
AddUnifiedAddress(ufvkid, found.first);
// Save the metadata for the generated address so that we can re-derive
// it in the future.
ZcashdUnifiedAddressMetadata addrmeta(ufvkid, found.second, receiverTypes);
mapUnifiedAddressMetadata[ufvkid].insert({diversifierIndex, receiverTypes});
if (fFileBacked) {
CWalletDB(strWalletFile).WriteUnifiedAddressMetadata(addrmeta);
}
return std::make_pair(found.first, addrmeta);
} else {
return AddressGenerationError::NoSuchAccount;
}
}
bool CWallet::LoadUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey &key)
{
auto ufvkid = key.GetKeyID(Params());
auto zufvk = ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingKey(key);
if (mapUnifiedAddressMetadata.count(ufvkid) > 0) {
// restore unified addresses that have been previously generated to the
// keystore
for (const auto &[j, receiverTypes] : mapUnifiedAddressMetadata[ufvkid]) {
auto addr = zufvk.Address(j, receiverTypes).value();
AddUnifiedAddress(ufvkid, addr);
}
}
return CCryptoKeyStore::AddUnifiedFullViewingKey(ufvkid, zufvk);
}
void CWallet::LoadUnifiedKeyMetadata(const ZcashdUnifiedSpendingKeyMetadata &skmeta)
{
AssertLockHeld(cs_wallet); // mapUnifiedKeyMetadata
mapUnifiedKeyMetadata.insert({meta.GetKeyID(), meta});
auto metaKey = std::make_pair(skmeta.GetSeedFingerprint(), skmeta.GetAccountId());
mapUnifiedKeyMetadata.insert({metaKey, skmeta});
}
bool CWallet::LoadUnifiedAddressMetadata(const ZcashdUnifiedAddressMetadata &addrmeta)
{
AssertLockHeld(cs_wallet); // mapUnifiedKeyMetadata
auto ufvk = GetUnifiedFullViewingKey(addrmeta.GetKeyID());
if (ufvk.has_value()) {
// restore unified addresses that have been previously generated
auto addr = ufvk.value().Address(addrmeta.GetDiversifierIndex(), addrmeta.GetReceiverTypes());
if (addr.has_value()) {
AddUnifiedAddress(addrmeta.GetKeyID(), addr.value());
} else {
// an error has occurred; the ufvk is loaded but cannot reproduce the
// address identified by the address metadata.
return false;
}
}
return true;
}
void CWallet::LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &meta)

View File

@ -402,6 +402,17 @@ public:
bool AcceptToMemoryPool(bool fLimitFree=true, bool fRejectAbsurdFee=true);
};
enum class AddressGenerationError {
NoSuchAccount,
InvalidReceiverTypes,
ExistingAddressMismatch,
NoSaplingAddressForDiversifier
};
typedef std::variant<
std::pair<libzcash::UnifiedAddress, ZcashdUnifiedAddressMetadata>,
AddressGenerationError> UAGenerationResult;
/**
* A transaction with a bunch of additional info that only the owner cares about.
* It includes any unrecorded transactions needed to link it back to the block chain.
@ -843,7 +854,8 @@ public:
std::map<libzcash::SproutPaymentAddress, CKeyMetadata> mapSproutZKeyMetadata;
std::map<libzcash::SaplingIncomingViewingKey, CKeyMetadata> mapSaplingZKeyMetadata;
std::map<libzcash::UFVKId, ZcashdUnifiedSpendingKeyMetadata> mapUnifiedKeyMetadata;
std::map<std::pair<libzcash::SeedFingerprint, libzcash::AccountId>, ZcashdUnifiedSpendingKeyMetadata> mapUnifiedKeyMetadata;
std::map<libzcash::UFVKId, std::map<libzcash::diversifier_index_t, std::set<libzcash::ReceiverType>>> mapUnifiedAddressMetadata;
typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
MasterKeyMap mapMasterKeys;
@ -1129,10 +1141,22 @@ public:
std::optional<std::pair<libzcash::ZcashdUnifiedSpendingKey, ZcashdUnifiedSpendingKeyMetadata>>
GenerateUnifiedSpendingKeyForAccount(libzcash::AccountId accountId);
bool AddUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey &ufvk);
bool LoadUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey &key);
//! Retrieves the UFVK derived from the wallet's mnemonic seed for the specified account.
std::optional<std::pair<libzcash::UFVKId, libzcash::ZcashdUnifiedFullViewingKey>>
GetUnifiedFullViewingKeyByAccount(libzcash::AccountId account);
void LoadUnifiedKeyMetadata(const ZcashdUnifiedSpendingKeyMetadata &meta);
//! Generate a new unified address for the specified account, diversifier, and
//! set of receiver types.
UAGenerationResult GenerateUnifiedAddress(
const libzcash::AccountId& accountId,
const libzcash::diversifier_index_t& j,
const std::set<libzcash::ReceiverType>& receivers);
bool AddUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey &ufvk);
bool LoadUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey &ufvk);
void LoadUnifiedKeyMetadata(const ZcashdUnifiedSpendingKeyMetadata &skmeta);
bool LoadUnifiedAddressMetadata(const ZcashdUnifiedAddressMetadata &addrmeta);
/**
* Increment the next transaction order id

View File

@ -235,6 +235,13 @@ bool CWalletDB::WriteUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey
return Write(std::make_pair(std::string("unifiedfvk"), ufvkId), ufvk.Encode(Params()));
}
bool CWalletDB::WriteUnifiedAddressMetadata(const ZcashdUnifiedAddressMetadata& addrmeta)
{
nWalletDBUpdateCounter++;
auto ufvkId = addrmeta.GetKeyID();
return Write(std::make_pair(std::string("unifiedaddrmeta"), ufvkId), addrmeta);
}
//
//
//
@ -684,6 +691,11 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
auto keymeta = ZcashdUnifiedSpendingKeyMetadata::Read(ssValue);
pwallet->LoadUnifiedKeyMetadata(keymeta);
}
else if (strType == "unifiedaddrmeta")
{
auto keymeta = ZcashdUnifiedAddressMetadata::Read(ssValue);
pwallet->LoadUnifiedAddressMetadata(keymeta);
}
else if (strType == "pool")
{
int64_t nIndex;

View File

@ -251,14 +251,14 @@ class ZcashdUnifiedAddressMetadata {
private:
libzcash::UFVKId ufvkId;
libzcash::diversifier_index_t diversifierIndex;
std::vector<libzcash::ReceiverType> receiverTypes;
std::set<libzcash::ReceiverType> receiverTypes;
ZcashdUnifiedAddressMetadata() {}
public:
ZcashdUnifiedAddressMetadata(
libzcash::UFVKId ufvkId,
libzcash::diversifier_index_t diversifierIndex,
std::vector<libzcash::ReceiverType> receiverTypes):
std::set<libzcash::ReceiverType> receiverTypes):
ufvkId(ufvkId), diversifierIndex(diversifierIndex), receiverTypes(receiverTypes) {}
libzcash::UFVKId GetKeyID() const {
@ -267,7 +267,7 @@ public:
libzcash::diversifier_index_t GetDiversifierIndex() const {
return diversifierIndex;
}
const std::vector<libzcash::ReceiverType>& GetReceiverTypes() const {
const std::set<libzcash::ReceiverType>& GetReceiverTypes() const {
return receiverTypes;
}
@ -282,7 +282,7 @@ public:
READWRITE(serReceiverTypes);
receiverTypes.clear();
for (ReceiverTypeSer r : serReceiverTypes)
receiverTypes.push_back(r.t);
receiverTypes.insert(r.t);
} else {
std::vector<ReceiverTypeSer> serReceiverTypes;
for (libzcash::ReceiverType r : receiverTypes)
@ -381,6 +381,7 @@ public:
bool WriteUnifiedSpendingKeyMetadata(const ZcashdUnifiedSpendingKeyMetadata& keymeta);
bool WriteUnifiedFullViewingKey(const libzcash::UnifiedFullViewingKey& ufvk);
bool WriteUnifiedAddressMetadata(const ZcashdUnifiedAddressMetadata& addrmeta);
static void IncrementUpdateCounter();
static unsigned int GetUpdateCounter();

View File

@ -12,6 +12,14 @@ using namespace libzcash;
// Unified Keys
//
bool libzcash::HasShielded(const std::set<ReceiverType>& receiverTypes) {
auto has_shielded = [](ReceiverType r) {
// TODO: update this as support for new protocols is added.
return r == ReceiverType::Sapling;
};
return std::find_if(receiverTypes.begin(), receiverTypes.end(), has_shielded) != receiverTypes.end();
}
std::optional<ZcashdUnifiedSpendingKey> ZcashdUnifiedSpendingKey::ForAccount(
const HDSeed& seed,
uint32_t bip44CoinType,
@ -63,10 +71,17 @@ ZcashdUnifiedFullViewingKey ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingK
return result;
}
std::optional<UnifiedAddress> ZcashdUnifiedFullViewingKey::Address(diversifier_index_t j) const {
UnifiedAddress ua;
std::optional<UnifiedAddress> ZcashdUnifiedFullViewingKey::Address(
const diversifier_index_t& j,
const std::set<ReceiverType>& receiverTypes) const
{
if (!HasShielded(receiverTypes)) {
throw std::runtime_error("Unified addresses must include a shielded receiver.");
}
if (saplingKey.has_value()) {
UnifiedAddress ua;
if (saplingKey.has_value() &&
std::find(receiverTypes.begin(), receiverTypes.end(), ReceiverType::Sapling) != receiverTypes.end()) {
auto saplingAddress = saplingKey.value().Address(j);
if (saplingAddress.has_value()) {
ua.AddReceiver(saplingAddress.value());
@ -75,7 +90,8 @@ std::optional<UnifiedAddress> ZcashdUnifiedFullViewingKey::Address(diversifier_i
}
}
if (transparentKey.has_value()) {
if (transparentKey.has_value() &&
std::find(receiverTypes.begin(), receiverTypes.end(), ReceiverType::P2PKH) != receiverTypes.end()) {
const auto& tkey = transparentKey.value();
auto childIndex = j.ToTransparentChildIndex();
if (!childIndex.has_value()) return std::nullopt;
@ -98,12 +114,20 @@ 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);
std::pair<UnifiedAddress, diversifier_index_t> ZcashdUnifiedFullViewingKey::FindAddress(
const diversifier_index_t& j,
const std::set<ReceiverType>& receiverTypes) const {
diversifier_index_t j0(j);
auto addr = Address(j0, receiverTypes);
while (!addr.has_value()) {
if (!j.increment())
if (!j0.increment())
throw std::runtime_error(std::string(__func__) + ": diversifier index overflow.");;
addr = Address(j);
addr = Address(j0, receiverTypes);
}
return std::make_pair(addr.value(), j);
return std::make_pair(addr.value(), j0);
}
std::pair<UnifiedAddress, diversifier_index_t> ZcashdUnifiedFullViewingKey::FindAddress(
const diversifier_index_t& j) const {
return FindAddress(j, {ReceiverType::P2PKH, ReceiverType::Sapling, ReceiverType::Orchard});
}

View File

@ -17,6 +17,12 @@ enum class ReceiverType: uint32_t {
Orchard = 0x03
};
/**
* Test whether the specified list of receiver types contains a
* shielded receiver type
*/
bool HasShielded(const std::set<ReceiverType>& receiverTypes);
class ZcashdUnifiedSpendingKey;
// prototypes for the classes handling ZIP-316 encoding (in Address.hpp)
@ -51,9 +57,33 @@ public:
return saplingKey;
}
std::optional<UnifiedAddress> Address(diversifier_index_t j) const;
/**
* Creates a new unified address having the specified receiver types, at the specified
* diversifier index, unless the diversifer index would generate an invalid receiver.
* Returns `std::nullopt` if the diversifier index does not produce a valid receiver
* for one or more of the specified receiver types; under this circumstance, the caller
* should usually try successive diversifier indices until the operation returns a
* non-null value.
*
* This method will throw if `receiverTypes` does not include a shielded receiver type.
*/
std::optional<UnifiedAddress> Address(
const diversifier_index_t& j,
const std::set<ReceiverType>& receiverTypes) const;
std::pair<UnifiedAddress, diversifier_index_t> FindAddress(diversifier_index_t j) const;
/**
* Find the smallest diversifier index >= `j` such that it generates a valid
* unified address according to the conditions specified in the documentation
* for the `Address` method above, and returns the newly created address along
* with the diversifier index used to produce it.
*
* This method will throw if `receiverTypes` does not include a shielded receiver type.
*/
std::pair<UnifiedAddress, diversifier_index_t> FindAddress(
const diversifier_index_t& j,
const std::set<ReceiverType>& receiverTypes) const;
std::pair<UnifiedAddress, diversifier_index_t> FindAddress(const diversifier_index_t& j) const;
};
/**