Add Orchard components to unified full viewing keys

Co-authored-by: Jack Grigg <jack@z.cash>
This commit is contained in:
Kris Nuttycombe 2022-02-18 18:41:38 -06:00 committed by Jack Grigg
parent 1a1522a4f1
commit 258f0fc72f
17 changed files with 253 additions and 22 deletions

View File

@ -8,7 +8,7 @@ replace-with = "vendored-sources"
[source."https://github.com/zcash/orchard.git"]
git = "https://github.com/zcash/orchard.git"
rev = "4dc1ae059a59ee911134cb3e731c7be627a71d4d"
rev = "3b8d07f7b64b2329622089ac9698e4cce97e2f14"
replace-with = "vendored-sources"
[source."https://github.com/nuttycom/hdwallet.git"]

2
Cargo.lock generated
View File

@ -1090,7 +1090,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "orchard"
version = "0.1.0-beta.1"
source = "git+https://github.com/zcash/orchard.git?rev=4dc1ae059a59ee911134cb3e731c7be627a71d4d#4dc1ae059a59ee911134cb3e731c7be627a71d4d"
source = "git+https://github.com/zcash/orchard.git?rev=3b8d07f7b64b2329622089ac9698e4cce97e2f14#3b8d07f7b64b2329622089ac9698e4cce97e2f14"
dependencies = [
"aes",
"arrayvec 0.7.2",

View File

@ -72,7 +72,7 @@ codegen-units = 1
[patch.crates-io]
hdwallet = { git = "https://github.com/nuttycom/hdwallet", rev = "576683b9f2865f1118c309017ff36e01f84420c9" }
orchard = { git = "https://github.com/zcash/orchard.git", rev = "4dc1ae059a59ee911134cb3e731c7be627a71d4d" }
orchard = { git = "https://github.com/zcash/orchard.git", rev = "3b8d07f7b64b2329622089ac9698e4cce97e2f14" }
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "3d935a94e75786a67c3ea4992d7c372af203086f" }
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "3d935a94e75786a67c3ea4992d7c372af203086f" }
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "3d935a94e75786a67c3ea4992d7c372af203086f" }

View File

@ -293,14 +293,13 @@ TEST(Keys, EncodeAndDecodeUnifiedFullViewingKeys)
auto key = libzcash::SaplingDiversifiableFullViewingKey::Read(ss);
ASSERT_TRUE(builder.AddSaplingKey(key));
}
// Orchard keys and unknown items are not yet supported; instead,
// we just test that we're able to parse the unified key string
// and that the constituent items match the elements; if no Sapling
// key is present then UFVK construction would fail because it might
// presume the UFVK to be transparent-only.
if (test[1].isNull())
continue;
if (!test[2].isNull()) {
auto data = ParseHex(test[2].get_str());
ASSERT_EQ(data.size(), 96);
CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION);
auto key = libzcash::OrchardFullViewingKey::Read(ss);
ASSERT_TRUE(builder.AddOrchardKey(key));
}
auto built = builder.build();
ASSERT_TRUE(built.has_value());
@ -313,5 +312,6 @@ TEST(Keys, EncodeAndDecodeUnifiedFullViewingKeys)
EXPECT_EQ(decoded.value().GetTransparentKey(), built.value().GetTransparentKey());
EXPECT_EQ(decoded.value().GetSaplingKey(), built.value().GetSaplingKey());
EXPECT_EQ(decoded.value().GetOrchardKey(), built.value().GetOrchardKey());
}
}

View File

@ -312,14 +312,26 @@ bool CBasicKeyStore::AddUnifiedFullViewingKey(
{
LOCK(cs_KeyStore);
auto ufvkId = ufvk.GetKeyID();
// Add the Orchard component of the UFVK to the wallet.
auto orchardKey = ufvk.GetOrchardKey();
if (orchardKey.has_value()) {
auto ivk = orchardKey.value().ToIncomingViewingKey();
mapOrchardKeyUnified.insert(std::make_pair(ivk, ufvkId));
auto ivkInternal = orchardKey.value().ToInternalIncomingViewingKey();
mapOrchardKeyUnified.insert(std::make_pair(ivkInternal, ufvkId));
}
// Add the Sapling component of the UFVK to the wallet.
auto saplingKey = ufvk.GetSaplingKey();
if (saplingKey.has_value()) {
auto ivk = saplingKey.value().ToIncomingViewingKey();
mapSaplingKeyUnified.insert(std::make_pair(ivk, ufvk.GetKeyID()));
mapSaplingKeyUnified.insert(std::make_pair(ivk, ufvkId));
auto changeIvk = saplingKey.value().GetChangeIVK();
mapSaplingKeyUnified.insert(std::make_pair(changeIvk, ufvk.GetKeyID()));
mapSaplingKeyUnified.insert(std::make_pair(changeIvk, ufvkId));
}
// We can't reasonably add the transparent component here, because
@ -329,7 +341,7 @@ bool CBasicKeyStore::AddUnifiedFullViewingKey(
// transparent part of the address must be added to the keystore.
// Add the UFVK by key identifier.
mapUnifiedFullViewingKeys.insert({ufvk.GetKeyID(), ufvk});
mapUnifiedFullViewingKeys.insert({ufvkId, ufvk});
return true;
}
@ -390,6 +402,15 @@ std::optional<libzcash::UFVKId> CBasicKeyStore::GetUFVKIdForViewingKey(const lib
}
},
[&](const libzcash::UnifiedFullViewingKey& ufvk) {
const auto orchardFvk = ufvk.GetOrchardKey();
if (orchardFvk.has_value()) {
const auto orchardIvk = orchardFvk.value().ToIncomingViewingKey();
const auto ufvkId = mapOrchardKeyUnified.find(orchardIvk);
if (ufvkId != mapOrchardKeyUnified.end()) {
result = ufvkId->second;
return;
}
}
const auto saplingDfvk = ufvk.GetSaplingKey();
if (saplingDfvk.has_value()) {
const auto saplingIvk = saplingDfvk.value().ToIncomingViewingKey();
@ -405,7 +426,15 @@ std::optional<libzcash::UFVKId> CBasicKeyStore::GetUFVKIdForViewingKey(const lib
std::optional<std::pair<libzcash::UFVKId, std::optional<libzcash::diversifier_index_t>>>
FindUFVKId::operator()(const libzcash::OrchardRawAddress& orchardAddr) const {
// TODO: Implement once we have Orchard in UFVKs
for (const auto& [k, v] : keystore.mapUnifiedFullViewingKeys) {
auto fvk = v.GetOrchardKey();
if (fvk.has_value()) {
auto d_idx = fvk.value().ToIncomingViewingKey().DecryptDiversifier(orchardAddr);
if (d_idx.has_value()) {
return std::make_pair(k, d_idx);
}
}
}
return std::nullopt;
}

View File

@ -185,6 +185,7 @@ protected:
std::map<CKeyID, std::pair<libzcash::UFVKId, libzcash::diversifier_index_t>> mapP2PKHUnified;
std::map<CScriptID, std::pair<libzcash::UFVKId, libzcash::diversifier_index_t>> mapP2SHUnified;
std::map<libzcash::SaplingIncomingViewingKey, libzcash::UFVKId> mapSaplingKeyUnified;
std::map<libzcash::OrchardIncomingViewingKey, libzcash::UFVKId> mapOrchardKeyUnified;
std::map<libzcash::UFVKId, libzcash::ZcashdUnifiedFullViewingKey> mapUnifiedFullViewingKeys;
friend class FindUFVKId;

View File

@ -97,6 +97,18 @@ OrchardRawAddressPtr* orchard_incoming_viewing_key_to_address(
const OrchardIncomingViewingKeyPtr* incoming_viewing_key,
const unsigned char* j);
/**
* Decrypts the diversifier component of an Orchard raw address with the
* specified IVK, and verifies that the address was derived from that IVK.
*
* Returns `false` and leaves the `j_ret` parameter unmodified if the address
* was not derived from the specified IVK.
*/
bool orchard_incoming_viewing_key_decrypt_diversifier(
const OrchardIncomingViewingKeyPtr* incoming_viewing_key,
const OrchardRawAddressPtr* addr,
uint8_t *j_ret);
/**
* Parses an Orchard incoming viewing key from the given stream.
*
@ -179,6 +191,12 @@ bool orchard_full_viewing_key_serialize(
OrchardIncomingViewingKeyPtr* orchard_full_viewing_key_to_incoming_viewing_key(
const OrchardFullViewingKeyPtr* key);
/**
* Returns the internal incoming viewing key for the specified full viewing key.
*/
OrchardIncomingViewingKeyPtr* orchard_full_viewing_key_to_internal_incoming_viewing_key(
const OrchardFullViewingKeyPtr* key);
/**
* Implements equality testing between full viewing keys.
*/

View File

@ -84,6 +84,22 @@ bool unified_full_viewing_key_read_sapling(
const UnifiedFullViewingKeyPtr* full_viewing_key,
unsigned char* skeyout);
/**
* Reads the Orchard component of a unified full viewing key.
*
* `skeyout` must be of length 96.
*
* Returns `true` if the UFVK contained an Orchard component, `false` otherwise.
* The bytes of the Orchard Raw Full Viewing Key, in the encoding given in
* section 5.6.4.4 of the Zcash Protocol Specification, will be copied to
* `skeyout` if `true` is returned.
*
* If `false` is returned then `skeyout` will be unchanged.
*/
bool unified_full_viewing_key_read_orchard(
const UnifiedFullViewingKeyPtr* full_viewing_key,
unsigned char* skeyout);
/**
* Constructs a unified full viewing key from the binary encodings
* of its constituent parts.
@ -101,7 +117,8 @@ bool unified_full_viewing_key_read_sapling(
*/
UnifiedFullViewingKeyPtr* unified_full_viewing_key_from_components(
const unsigned char* t_key,
const unsigned char* sapling_key);
const unsigned char* sapling_key,
const unsigned char* orchard_key);
/**
* Derive the internal and external OVKs for the binary encoding

View File

@ -145,6 +145,26 @@ pub extern "C" fn orchard_incoming_viewing_key_to_address(
Box::into_raw(Box::new(key.address_at(diversifier_index)))
}
#[no_mangle]
pub extern "C" fn orchard_incoming_viewing_key_decrypt_diversifier(
key: *const IncomingViewingKey,
addr: *const Address,
j_ret: *mut [u8; 11],
) -> bool {
let key =
unsafe { key.as_ref() }.expect("Orchard incoming viewing key pointer may not be null.");
let addr = unsafe { addr.as_ref() }.expect("Orchard raw address pointer may not be null.");
let j_ret = unsafe { j_ret.as_mut() }.expect("j_ret may not be null.");
match key.diversifier_index(addr) {
Some(j) => {
j_ret.copy_from_slice(j.to_bytes());
true
}
None => false,
}
}
#[no_mangle]
pub extern "C" fn orchard_incoming_viewing_key_serialize(
key: *const IncomingViewingKey,
@ -250,6 +270,18 @@ pub extern "C" fn orchard_full_viewing_key_to_incoming_viewing_key(
.unwrap_or(std::ptr::null_mut())
}
#[no_mangle]
pub extern "C" fn orchard_full_viewing_key_to_internal_incoming_viewing_key(
fvk: *const FullViewingKey,
) -> *mut IncomingViewingKey {
unsafe { fvk.as_ref() }
.map(|fvk| {
let internal_fvk = fvk.derive_internal();
Box::into_raw(Box::new(IncomingViewingKey::from(&internal_fvk)))
})
.unwrap_or(std::ptr::null_mut())
}
#[no_mangle]
pub extern "C" fn orchard_full_viewing_key_eq(
k0: *const FullViewingKey,

View File

@ -139,13 +139,33 @@ pub extern "C" fn unified_full_viewing_key_read_sapling(
false
}
#[no_mangle]
pub extern "C" fn unified_full_viewing_key_read_orchard(
key: *const Ufvk,
out: *mut [u8; 96],
) -> bool {
let key = unsafe { key.as_ref() }.expect("Unified full viewing key pointer may not be null.");
let out = unsafe { &mut *out };
for r in &key.items() {
if let Fvk::Orchard(data) = r {
*out = *data;
return true;
}
}
false
}
#[no_mangle]
pub extern "C" fn unified_full_viewing_key_from_components(
t_key: *const [u8; 65],
sapling_key: *const [u8; 128],
orchard_key: *const [u8; 96],
) -> *mut Ufvk {
let t_key = unsafe { t_key.as_ref() };
let sapling_key = unsafe { sapling_key.as_ref() };
let orchard_key = unsafe { orchard_key.as_ref() };
let mut items = vec![];
if let Some(t_bytes) = t_key {
@ -154,6 +174,9 @@ pub extern "C" fn unified_full_viewing_key_from_components(
if let Some(sapling_bytes) = sapling_key {
items.push(Fvk::Sapling(*sapling_bytes));
}
if let Some(orchard_bytes) = orchard_key {
items.push(Fvk::Orchard(*orchard_bytes));
}
match Ufvk::try_from_items(items) {
Ok(ufvk) => Box::into_raw(Box::new(ufvk)),

View File

@ -6556,8 +6556,18 @@ KeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedS
// UFVKForReceiver :: (CWallet&, Receiver) -> std::optional<ZcashdUnifiedFullViewingKey>
std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()(const libzcash::OrchardRawAddress& orchardAddr) const {
// TODO: Implement once we have Orchard in UFVKs
return std::nullopt;
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(orchardAddr);
if (ufvkPair.has_value()) {
auto ufvkid = ufvkPair.value().first;
auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
// If we have UFVK metadata, `GetUnifiedFullViewingKey` should always
// return a non-nullopt value, and since we obtained that metadata by
// lookup from an Orchard address, it should have a Orchard key component.
assert(ufvk.has_value() && ufvk.value().GetOrchardKey().has_value());
return ufvk.value();
} else {
return std::nullopt;
}
}
std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()(const libzcash::SaplingPaymentAddress& saplingAddr) const {
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(saplingAddr);
@ -6600,7 +6610,30 @@ std::optional<libzcash::ZcashdUnifiedFullViewingKey> UFVKForReceiver::operator()
std::optional<libzcash::UnifiedAddress> UnifiedAddressForReceiver::operator()(
const libzcash::OrchardRawAddress& orchardAddr) const {
// TODO: Implement once we have Orchard in UFVKs
auto ufvkPair = wallet.GetUFVKMetadataForReceiver(orchardAddr);
if (ufvkPair.has_value()) {
auto ufvkid = ufvkPair.value().first;
auto ufvk = wallet.GetUnifiedFullViewingKey(ufvkid);
assert(ufvk.has_value());
// If the wallet is missing metadata at this UFVK id, it is probably
// corrupt and the node should shut down.
const auto& metadata = wallet.mapUfvkAddressMetadata.at(ufvkid);
auto orchardKey = ufvk.value().GetOrchardKey();
if (orchardKey.has_value()) {
auto j = orchardKey.value().ToIncomingViewingKey().DecryptDiversifier(orchardAddr);
if (j.has_value()) {
auto receivers = metadata.GetReceivers(j.value());
if (receivers.has_value()) {
auto addr = ufvk.value().Address(j.value(), receivers.value());
auto addrPtr = std::get_if<std::pair<UnifiedAddress, diversifier_index_t>>(&addr);
if (addrPtr != nullptr) {
return addrPtr->first;
}
}
}
}
}
return std::nullopt;
}

View File

@ -192,6 +192,16 @@ std::string libzcash::UnifiedFullViewingKey::Encode(const KeyConstants& keyConst
return res;
}
std::optional<libzcash::OrchardFullViewingKey> libzcash::UnifiedFullViewingKey::GetOrchardKey() const {
std::vector<uint8_t> buffer(96);
if (unified_full_viewing_key_read_orchard(inner.get(), buffer.data())) {
CDataStream ss(buffer, SER_NETWORK, PROTOCOL_VERSION);
return OrchardFullViewingKey::Read(ss);
} else {
return std::nullopt;
}
}
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())) {
@ -232,10 +242,21 @@ bool libzcash::UnifiedFullViewingKeyBuilder::AddSaplingKey(const SaplingDiversif
return true;
}
bool libzcash::UnifiedFullViewingKeyBuilder::AddOrchardKey(const OrchardFullViewingKey& key) {
if (orchard_bytes.has_value()) return false;
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << key;
assert(ss.size() == 96);
std::vector<uint8_t> ss_bytes(ss.begin(), ss.end());
orchard_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);
sapling_bytes.has_value() ? sapling_bytes.value().data() : nullptr,
orchard_bytes.has_value() ? orchard_bytes.value().data() : nullptr);
if (ptr == nullptr) {
return std::nullopt;
@ -252,6 +273,9 @@ libzcash::UnifiedFullViewingKey libzcash::UnifiedFullViewingKey::FromZcashdUFVK(
if (key.GetSaplingKey().has_value()) {
builder.AddSaplingKey(key.GetSaplingKey().value());
}
if (key.GetOrchardKey().has_value()) {
builder.AddOrchardKey(key.GetOrchardKey().value());
}
auto result = builder.build();
if (!result.has_value()) {

View File

@ -176,6 +176,8 @@ public:
std::string Encode(const KeyConstants& keyConstants) const;
std::optional<OrchardFullViewingKey> GetOrchardKey() const;
std::optional<SaplingDiversifiableFullViewingKey> GetSaplingKey() const;
std::optional<transparent::AccountPubKey> GetTransparentKey() const;
@ -190,6 +192,9 @@ public:
if (GetSaplingKey().has_value()) {
result.insert(ReceiverType::Sapling);
}
if (GetOrchardKey().has_value()) {
result.insert(ReceiverType::Orchard);
}
return result;
}
@ -214,11 +219,16 @@ class UnifiedFullViewingKeyBuilder {
private:
std::optional<std::vector<uint8_t>> t_bytes;
std::optional<std::vector<uint8_t>> sapling_bytes;
std::optional<std::vector<uint8_t>> orchard_bytes;
public:
UnifiedFullViewingKeyBuilder(): t_bytes(std::nullopt), sapling_bytes(std::nullopt) {}
UnifiedFullViewingKeyBuilder():
t_bytes(std::nullopt),
sapling_bytes(std::nullopt),
orchard_bytes(std::nullopt) {}
bool AddTransparentKey(const transparent::AccountPubKey&);
bool AddSaplingKey(const SaplingDiversifiableFullViewingKey&);
bool AddOrchardKey(const OrchardFullViewingKey&);
std::optional<UnifiedFullViewingKey> build() const;
};

View File

@ -10,10 +10,23 @@ OrchardRawAddress OrchardIncomingViewingKey::Address(const diversifier_index_t&
return OrchardRawAddress(orchard_incoming_viewing_key_to_address(inner.get(), j.begin()));
}
std::optional<diversifier_index_t> OrchardIncomingViewingKey::DecryptDiversifier(const OrchardRawAddress& addr) const {
diversifier_index_t j_ret;
if (orchard_incoming_viewing_key_decrypt_diversifier(inner.get(), addr.inner.get(), j_ret.begin())) {
return j_ret;
} else {
return std::nullopt;
}
}
OrchardIncomingViewingKey OrchardFullViewingKey::ToIncomingViewingKey() const {
return OrchardIncomingViewingKey(orchard_full_viewing_key_to_incoming_viewing_key(inner.get()));
}
OrchardIncomingViewingKey OrchardFullViewingKey::ToInternalIncomingViewingKey() const {
return OrchardIncomingViewingKey(orchard_full_viewing_key_to_incoming_viewing_key(inner.get()));
}
OrchardSpendingKey OrchardSpendingKey::ForAccount(
const HDSeed& seed,
uint32_t bip44CoinType,

View File

@ -9,6 +9,8 @@
#include "zcash/address/zip32.h"
#include <rust/orchard/keys.h>
#include <optional>
class OrchardWallet;
namespace orchard { class Builder; }
@ -113,6 +115,12 @@ public:
OrchardRawAddress Address(const diversifier_index_t& j) const;
/**
* Decrypts the diversifier for the given raw address, and returns it if that
* address was derived from this IVK; otherwise returns std::nullopt;
*/
std::optional<diversifier_index_t> DecryptDiversifier(const OrchardRawAddress& addr) const;
OrchardIncomingViewingKey& operator=(OrchardIncomingViewingKey&& key)
{
if (this != &key) {
@ -184,6 +192,8 @@ public:
OrchardIncomingViewingKey ToIncomingViewingKey() const;
OrchardIncomingViewingKey ToInternalIncomingViewingKey() const;
OrchardFullViewingKey& operator=(OrchardFullViewingKey&& key)
{
if (this != &key) {

View File

@ -16,7 +16,7 @@ using namespace libzcash;
bool libzcash::HasShielded(const std::set<ReceiverType>& receiverTypes) {
auto has_shielded = [](ReceiverType r) {
// TODO: update this as support for new shielded protocols is added.
return r == ReceiverType::Sapling;
return r == ReceiverType::Sapling || r == ReceiverType::Orchard;
};
return std::find_if(receiverTypes.begin(), receiverTypes.end(), has_shielded) != receiverTypes.end();
}
@ -69,6 +69,11 @@ ZcashdUnifiedFullViewingKey ZcashdUnifiedFullViewingKey::FromUnifiedFullViewingK
result.saplingKey = saplingKey.value();
}
auto orchardKey = ufvk.GetOrchardKey();
if (orchardKey.has_value()) {
result.orchardKey = orchardKey.value();
}
return result;
}
@ -85,6 +90,14 @@ UnifiedAddressGenerationResult ZcashdUnifiedFullViewingKey::Address(
}
UnifiedAddress ua;
if (receiverTypes.count(ReceiverType::Orchard) > 0) {
if (orchardKey.has_value()) {
ua.AddReceiver(orchardKey.value().ToIncomingViewingKey().Address(j));
} else {
return UnifiedAddressGenerationError::ReceiverTypeNotAvailable;
}
}
if (receiverTypes.count(ReceiverType::Sapling) > 0) {
if (saplingKey.has_value()) {
auto saplingAddress = saplingKey.value().Address(j);
@ -180,6 +193,9 @@ UnifiedFullViewingKey ZcashdUnifiedFullViewingKey::ToFullViewingKey() const {
if (saplingKey.has_value()) {
builder.AddSaplingKey(saplingKey.value());
}
if (orchardKey.has_value()) {
builder.AddOrchardKey(orchardKey.value());
}
// This call to .value() is safe as ZcashdUnifiedFullViewingKey values are always
// constructed to contain all required components.

View File

@ -141,6 +141,7 @@ private:
UFVKId keyId;
std::optional<transparent::AccountPubKey> transparentKey;
std::optional<SaplingDiversifiableFullViewingKey> saplingKey;
std::optional<OrchardFullViewingKey> orchardKey;
ZcashdUnifiedFullViewingKey() {}
@ -169,6 +170,10 @@ public:
return saplingKey;
}
const std::optional<OrchardFullViewingKey>& GetOrchardKey() const {
return orchardKey;
}
/**
* Creates a new unified address having the specified receiver types, at the specified
* diversifier index, unless the diversifer index would generate an invalid receiver.