Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strongly typed ivk scopes #889

Merged
merged 8 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions zingolib/src/blaze/trial_decryptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use crate::error::ZingoLibResult;

use crate::wallet::keys::unified::{External, Fvk as _, Ivk};
use crate::wallet::notes::ShieldedNoteInterface;
use crate::wallet::{
data::PoolNullifier,
Expand All @@ -17,9 +18,8 @@ use crate::wallet::{
use futures::{stream::FuturesUnordered, StreamExt};
use incrementalmerkletree::{Position, Retention};
use log::debug;
use orchard::{keys::IncomingViewingKey as OrchardIvk, note_encryption::OrchardDomain};
use orchard::note_encryption::OrchardDomain;
use sapling_crypto::note_encryption::SaplingDomain;
use sapling_crypto::SaplingIvk;
use std::sync::Arc;
use tokio::{
sync::{
Expand Down Expand Up @@ -89,8 +89,12 @@ impl TrialDecryptions {
let mut workers = FuturesUnordered::new();
let mut cbs = vec![];

let sapling_ivk = SaplingIvk::try_from(&*wc).ok();
let orchard_ivk = orchard::keys::IncomingViewingKey::try_from(&*wc).ok();
let sapling_ivk = sapling_crypto::zip32::DiversifiableFullViewingKey::try_from(&*wc)
.ok()
.map(|key| key.derive_ivk());
let orchard_ivk = orchard::keys::FullViewingKey::try_from(&*wc)
.ok()
.map(|key| key.derive_ivk());

while let Some(cb) = receiver.recv().await {
cbs.push(cb);
Expand Down Expand Up @@ -128,8 +132,8 @@ impl TrialDecryptions {
compact_blocks: Vec<CompactBlock>,
wc: Arc<WalletCapability>,
bsync_data: Arc<RwLock<BlazeSyncData>>,
sapling_ivk: Option<SaplingIvk>,
orchard_ivk: Option<OrchardIvk>,
sapling_ivk: Option<Ivk<SaplingDomain, External>>,
orchard_ivk: Option<Ivk<OrchardDomain, External>>,
transaction_metadata_set: Arc<RwLock<TxMapAndMaybeTrees>>,
transaction_size_filter: Option<u32>,
detected_transaction_id_sender: UnboundedSender<(
Expand Down Expand Up @@ -179,7 +183,7 @@ impl TrialDecryptions {
compact_transaction,
transaction_num,
&compact_block,
sapling_crypto::note_encryption::PreparedIncomingViewingKey::new(ivk),
ivk.ivk.clone(),
height,
&config,
&wc,
Expand All @@ -199,7 +203,7 @@ impl TrialDecryptions {
compact_transaction,
transaction_num,
&compact_block,
orchard::keys::PreparedIncomingViewingKey::new(ivk),
ivk.ivk.clone(),
height,
&config,
&wc,
Expand Down
8 changes: 5 additions & 3 deletions zingolib/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use rand::rngs::OsRng;
use rand::Rng;
use sapling_crypto::note_encryption::SaplingDomain;

use sapling_crypto::SaplingIvk;
use sapling_crypto::zip32::DiversifiableFullViewingKey;
use shardtree::error::ShardTreeError;
use shardtree::store::memory::MemoryShardStore;
use shardtree::ShardTree;
Expand All @@ -40,6 +40,7 @@ use zcash_primitives::{consensus::BlockHeight, memo::Memo, transaction::componen
use zingo_status::confirmation_status::ConfirmationStatus;

use self::data::{WitnessTrees, COMMITMENT_TREE_LEVELS, MAX_SHARD_LEVEL};
use self::keys::unified::Fvk as _;
use self::keys::unified::{Capability, WalletCapability};
use self::traits::Recipient;
use self::traits::{DomainWalletExt, SpendableNote};
Expand Down Expand Up @@ -309,9 +310,10 @@ impl LightWallet {

///TODO: Make this work for orchard too
pub async fn decrypt_message(&self, enc: Vec<u8>) -> Result<Message, String> {
let sapling_ivk = SaplingIvk::try_from(&*self.wallet_capability())?;
let sapling_ivk = DiversifiableFullViewingKey::try_from(&*self.wallet_capability())?
.derive_ivk::<keys::unified::External>();

if let Ok(msg) = Message::decrypt(&enc, &sapling_ivk) {
if let Ok(msg) = Message::decrypt(&enc, &sapling_ivk.ivk) {
// If decryption succeeded for this IVK, return the decrypted memo and the matched address
return Ok(msg);
}
Expand Down
124 changes: 90 additions & 34 deletions zingolib/src/wallet/keys/unified.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::marker::PhantomData;
use std::sync::atomic;
use std::{
collections::{HashMap, HashSet},
Expand All @@ -7,20 +8,21 @@ use std::{

use append_only_vec::AppendOnlyVec;
use byteorder::{ReadBytesExt, WriteBytesExt};
use orchard::keys::Scope;
use orchard::note_encryption::OrchardDomain;
use sapling_crypto::note_encryption::SaplingDomain;
use zcash_primitives::consensus::{NetworkConstants, Parameters};
use zcash_primitives::zip339::Mnemonic;

use secp256k1::SecretKey;
use zcash_address::unified::{Container, Encoding, Fvk, Ufvk};
use zcash_address::unified::{Container, Encoding, Ufvk};
use zcash_client_backend::address::UnifiedAddress;
use zcash_client_backend::keys::{Era, UnifiedSpendingKey};
use zcash_encoding::Vector;
use zcash_primitives::zip32::AccountId;
use zcash_primitives::{legacy::TransparentAddress, zip32::DiversifierIndex};
use zingoconfig::ZingoConfig;

use crate::wallet::traits::ReadableWriteable;
use crate::wallet::traits::{DomainWalletExt, ReadableWriteable, Recipient};

use super::{
extended_transparent::{ExtendedPrivKey, ExtendedPubKey, KeyIndex},
Expand Down Expand Up @@ -164,16 +166,18 @@ impl WalletCapability {
}

pub fn ufvk(&self) -> Result<Ufvk, std::string::String> {
let o_fvk = Fvk::Orchard(orchard::keys::FullViewingKey::try_from(self)?.to_bytes());
let s_fvk = Fvk::Sapling(
use zcash_address::unified::Fvk as UfvkComponent;
let o_fvk =
UfvkComponent::Orchard(orchard::keys::FullViewingKey::try_from(self)?.to_bytes());
let s_fvk = UfvkComponent::Sapling(
sapling_crypto::zip32::DiversifiableFullViewingKey::try_from(self)?.to_bytes(),
);
let mut t_fvk_bytes = [0u8; 65];
let possible_transparent_key: Result<ExtendedPubKey, String> = self.try_into();
if let Ok(t_ext_pk) = possible_transparent_key {
t_fvk_bytes[0..32].copy_from_slice(&t_ext_pk.chain_code[..]);
t_fvk_bytes[32..65].copy_from_slice(&t_ext_pk.public_key.serialize()[..]);
let t_fvk = Fvk::P2pkh(t_fvk_bytes);
let t_fvk = UfvkComponent::P2pkh(t_fvk_bytes);
Ufvk::try_from_items(vec![o_fvk, s_fvk, t_fvk]).map_err(|e| e.to_string())
} else {
Ufvk::try_from_items(vec![o_fvk, s_fvk]).map_err(|e| e.to_string())
Expand Down Expand Up @@ -206,7 +210,7 @@ impl WalletCapability {
return Err(e);
}
};
Some(fvk.address_at(self.addresses.len(), Scope::External))
Some(fvk.address_at(self.addresses.len(), orchard::keys::Scope::External))
} else {
None
};
Expand Down Expand Up @@ -395,14 +399,15 @@ impl WalletCapability {
// Initialize an instance with no capabilities.
let mut wc = WalletCapability::default();
for fvk in ufvk.items() {
use zcash_address::unified::Fvk as UfvkComponent;
match fvk {
Fvk::Orchard(key_bytes) => {
UfvkComponent::Orchard(key_bytes) => {
wc.orchard = Capability::View(
orchard::keys::FullViewingKey::from_bytes(&key_bytes)
.ok_or("Orchard FVK deserialization failed")?,
);
}
Fvk::Sapling(key_bytes) => {
UfvkComponent::Sapling(key_bytes) => {
wc.sapling = Capability::View(
sapling_crypto::zip32::DiversifiableFullViewingKey::read(
&key_bytes[..],
Expand All @@ -411,14 +416,14 @@ impl WalletCapability {
.map_err(|e| e.to_string())?,
);
}
Fvk::P2pkh(key_bytes) => {
UfvkComponent::P2pkh(key_bytes) => {
wc.transparent = Capability::View(ExtendedPubKey {
chain_code: key_bytes[0..32].to_vec(),
public_key: secp256k1::PublicKey::from_slice(&key_bytes[32..65])
.map_err(|e| e.to_string())?,
});
}
Fvk::Unknown { typecode, data: _ } => {
UfvkComponent::Unknown { typecode, data: _ } => {
log::info!(
"Unknown receiver of type {} found in Unified Viewing Key",
typecode
Expand Down Expand Up @@ -591,6 +596,49 @@ impl ReadableWriteable<()> for WalletCapability {
}
}

/// The external, default scope for deriving an fvk's component viewing keys
pub struct External;

/// The internal scope, used for change only
pub struct Internal;

mod scope {
use super::*;
use zcash_primitives::zip32::Scope as ScopeEnum;
pub trait Scope {
fn scope() -> ScopeEnum;
}

impl Scope for External {
fn scope() -> ScopeEnum {
ScopeEnum::External
}
}
impl Scope for Internal {
fn scope() -> ScopeEnum {
ScopeEnum::Internal
}
}
}
pub struct Ivk<D, Scope>
where
D: zcash_note_encryption::Domain,
{
pub ivk: D::IncomingViewingKey,
__scope: PhantomData<Scope>,
}

/// This is of questionable utility, but internally-scoped ovks
/// exist, and so we represent them at the type level despite
/// having no current use for them
pub struct Ovk<D, Scope>
where
D: zcash_note_encryption::Domain,
{
pub ovk: D::OutgoingViewingKey,
__scope: PhantomData<Scope>,
}

impl TryFrom<&WalletCapability> for super::extended_transparent::ExtendedPrivKey {
type Error = String;
fn try_from(wc: &WalletCapability) -> Result<Self, String> {
Expand Down Expand Up @@ -663,44 +711,52 @@ impl TryFrom<&WalletCapability> for sapling_crypto::zip32::DiversifiableFullView
}
}

impl TryFrom<&WalletCapability> for sapling_crypto::note_encryption::PreparedIncomingViewingKey {
type Error = String;
pub trait Fvk<D: DomainWalletExt>
where
<D as zcash_note_encryption::Domain>::Note: PartialEq + Clone,
<D as zcash_note_encryption::Domain>::Recipient: Recipient,
{
fn derive_ivk<S: scope::Scope>(&self) -> Ivk<D, S>;
fn derive_ovk<S: scope::Scope>(&self) -> Ovk<D, S>;
}

fn try_from(value: &WalletCapability) -> Result<Self, Self::Error> {
sapling_crypto::SaplingIvk::try_from(value)
.map(|k| sapling_crypto::note_encryption::PreparedIncomingViewingKey::new(&k))
impl Fvk<OrchardDomain> for orchard::keys::FullViewingKey {
fn derive_ivk<S: scope::Scope>(&self) -> Ivk<OrchardDomain, S> {
Ivk {
ivk: orchard::keys::PreparedIncomingViewingKey::new(&self.to_ivk(S::scope())),
__scope: PhantomData,
}
}
}

impl TryFrom<&WalletCapability> for orchard::keys::IncomingViewingKey {
type Error = String;
fn try_from(wc: &WalletCapability) -> Result<Self, String> {
let fvk: orchard::keys::FullViewingKey = wc.try_into()?;
Ok(fvk.to_ivk(Scope::External))
fn derive_ovk<S: scope::Scope>(&self) -> Ovk<OrchardDomain, S> {
Ovk {
ovk: self.to_ovk(S::scope()),
__scope: PhantomData,
}
}
}

impl TryFrom<&WalletCapability> for orchard::keys::PreparedIncomingViewingKey {
type Error = String;
fn try_from(wc: &WalletCapability) -> Result<Self, String> {
orchard::keys::IncomingViewingKey::try_from(wc)
.map(|k| orchard::keys::PreparedIncomingViewingKey::new(&k))
impl Fvk<SaplingDomain> for sapling_crypto::zip32::DiversifiableFullViewingKey {
fn derive_ivk<S: scope::Scope>(&self) -> Ivk<SaplingDomain, S> {
Ivk {
ivk: sapling_crypto::keys::PreparedIncomingViewingKey::new(&self.to_ivk(S::scope())),
__scope: PhantomData,
}
}
}

impl TryFrom<&WalletCapability> for sapling_crypto::SaplingIvk {
type Error = String;
fn try_from(wc: &WalletCapability) -> Result<Self, String> {
let fvk: sapling_crypto::zip32::DiversifiableFullViewingKey = wc.try_into()?;
Ok(fvk.fvk().vk.ivk())
fn derive_ovk<S: scope::Scope>(&self) -> Ovk<SaplingDomain, S> {
Ovk {
ovk: self.to_ovk(S::scope()),
__scope: PhantomData,
}
}
}

impl TryFrom<&WalletCapability> for orchard::keys::OutgoingViewingKey {
type Error = String;
fn try_from(wc: &WalletCapability) -> Result<Self, String> {
let fvk: orchard::keys::FullViewingKey = wc.try_into()?;
Ok(fvk.to_ovk(Scope::External))
Ok(fvk.to_ovk(orchard::keys::Scope::External))
}
}

Expand Down
18 changes: 13 additions & 5 deletions zingolib/src/wallet/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use sapling_crypto::{
note::ExtractedNoteCommitment,
note_encryption::{try_sapling_note_decryption, PreparedIncomingViewingKey, SaplingDomain},
value::NoteValue,
PaymentAddress, Rseed, SaplingIvk,
PaymentAddress, Rseed,
};
use std::io::{self, ErrorKind, Read};
use zcash_note_encryption::{
Expand Down Expand Up @@ -104,7 +104,7 @@ impl Message {
Ok(data)
}

pub fn decrypt(data: &[u8], ivk: &SaplingIvk) -> io::Result<Message> {
pub fn decrypt(data: &[u8], ivk: &PreparedIncomingViewingKey) -> io::Result<Message> {
if data.len() != 1 + Message::magic_word().len() + 32 + 32 + ENC_CIPHERTEXT_SIZE {
return Err(io::Error::new(
ErrorKind::InvalidData,
Expand Down Expand Up @@ -182,7 +182,7 @@ impl Message {
// really apply, since this note is not spendable anyway, so the rseed and the note itself
// are not usable.
match try_sapling_note_decryption(
&PreparedIncomingViewingKey::new(ivk),
&ivk,
&Unspendable {
cmu_bytes,
epk_bytes,
Expand Down Expand Up @@ -215,7 +215,11 @@ pub mod tests {

use super::*;

fn get_random_zaddr() -> (ExtendedSpendingKey, SaplingIvk, PaymentAddress) {
fn get_random_zaddr() -> (
ExtendedSpendingKey,
PreparedIncomingViewingKey,
PaymentAddress,
) {
let mut rng = OsRng;
let mut seed = [0u8; 32];
rng.fill(&mut seed);
Expand All @@ -225,7 +229,11 @@ pub mod tests {
let fvk = dfvk;
let (_, addr) = fvk.default_address();

(extsk, fvk.fvk().vk.ivk(), addr)
(
extsk,
PreparedIncomingViewingKey::new(&fvk.fvk().vk.ivk()),
addr,
)
}

#[test]
Expand Down
Loading
Loading