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

Unify wallet note #1338

Merged
merged 4 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all 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: 16 additions & 4 deletions libtonode-tests/tests/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ use zingo_netutils::GrpcConnector;
use zingo_sync::sync::sync;
use zingolib::{
config::{construct_lightwalletd_uri, load_clientconfig, DEFAULT_LIGHTWALLETD_SERVER},
get_base_address_macro,
lightclient::LightClient,
testutils::scenarios,
testutils::{increase_server_height, lightclient::from_inputs, scenarios},
testvectors::seeds::HOSPITAL_MUSEUM_SEED,
wallet::WalletBase,
};
Expand Down Expand Up @@ -51,12 +52,23 @@ async fn sync_mainnet_test() {
async fn sync_test() {
tracing_subscriber::fmt().init();

let (_regtest_manager, _cph, _faucet, mut recipient, _txid) =
let (regtest_manager, _cph, faucet, mut recipient, _txid) =
scenarios::orchard_funded_recipient(5_000_000).await;
let uri = recipient.config().lightwalletd_uri.read().unwrap().clone();
from_inputs::quick_send(
&recipient,
vec![(
&get_base_address_macro!(&faucet, "unified"),
100_000,
Some("Outgoing decrypt test"),
)],
)
.await
.unwrap();

let client = GrpcConnector::new(uri).get_client().await.unwrap();
increase_server_height(&regtest_manager, 1).await;

let uri = recipient.config().lightwalletd_uri.read().unwrap().clone();
let client = GrpcConnector::new(uri).get_client().await.unwrap();
sync(
client,
&recipient.config().chain.clone(),
Expand Down
40 changes: 31 additions & 9 deletions zingo-sync/src/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,27 @@ use sapling_crypto::{
use zcash_keys::keys::UnifiedFullViewingKey;
use zcash_note_encryption::Domain;

pub(crate) type KeyId = (zcash_primitives::zip32::AccountId, Scope);
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct KeyId {
account_id: zcash_primitives::zip32::AccountId,
scope: Scope,
}

impl KeyId {
pub fn from_parts(account_id: zcash_primitives::zip32::AccountId, scope: Scope) -> Self {
Self { account_id, scope }
}
}

impl memuse::DynamicUsage for KeyId {
fn dynamic_usage(&self) -> usize {
self.scope.dynamic_usage()
}

fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
self.scope.dynamic_usage_bounds()
}
}

/// A key that can be used to perform trial decryption and nullifier
/// computation for a [`CompactSaplingOutput`] or [`CompactOrchardAction`].
Expand Down Expand Up @@ -85,11 +105,11 @@ impl ScanningKeyOps<SaplingDomain, sapling::Nullifier>
}

fn account_id(&self) -> &zcash_primitives::zip32::AccountId {
&self.key_id.0
&self.key_id.account_id
}

fn key_scope(&self) -> Option<Scope> {
Some(self.key_id.1)
Some(self.key_id.scope)
}
}

Expand All @@ -113,11 +133,11 @@ impl ScanningKeyOps<OrchardDomain, orchard::note::Nullifier>
}

fn account_id(&self) -> &zcash_primitives::zip32::AccountId {
&self.key_id.0
&self.key_id.account_id
}

fn key_scope(&self) -> Option<Scope> {
Some(self.key_id.1)
Some(self.key_id.scope)
}
}

Expand Down Expand Up @@ -155,10 +175,11 @@ impl ScanningKeys {
for (account_id, ufvk) in ufvks {
if let Some(dfvk) = ufvk.sapling() {
for scope in [Scope::External, Scope::Internal] {
let key_id = KeyId::from_parts(account_id, scope);
sapling.insert(
(account_id, scope),
key_id,
ScanningKey {
key_id: (account_id, scope),
key_id,
ivk: dfvk.to_ivk(scope),
nk: Some(dfvk.to_nk(scope)),
ovk: dfvk.to_ovk(scope),
Expand All @@ -169,10 +190,11 @@ impl ScanningKeys {

if let Some(fvk) = ufvk.orchard() {
for scope in [Scope::External, Scope::Internal] {
let key_id = KeyId::from_parts(account_id, scope);
orchard.insert(
(account_id, scope),
key_id,
ScanningKey {
key_id: (account_id, scope),
key_id,
ivk: fvk.to_ivk(scope),
nk: Some(fvk.clone()),
ovk: fvk.to_ovk(scope),
Expand Down
117 changes: 45 additions & 72 deletions zingo-sync/src/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use zcash_primitives::{
transaction::TxId,
};

use crate::utils;
use crate::{keys::KeyId, utils};

/// Encapsulates the current state of sync
#[derive(Debug, Getters, MutGetters)]
Expand Down Expand Up @@ -130,6 +130,10 @@ pub struct WalletTransaction {
sapling_notes: Vec<SaplingNote>,
#[getset(skip)]
orchard_notes: Vec<OrchardNote>,
#[getset(skip)]
outgoing_sapling_notes: Vec<OutgoingSaplingNote>,
#[getset(skip)]
outgoing_orchard_notes: Vec<OutgoingOrchardNote>,
}

impl WalletTransaction {
Expand All @@ -138,12 +142,16 @@ impl WalletTransaction {
block_height: BlockHeight,
sapling_notes: Vec<SaplingNote>,
orchard_notes: Vec<OrchardNote>,
outgoing_sapling_notes: Vec<OutgoingSaplingNote>,
outgoing_orchard_notes: Vec<OutgoingOrchardNote>,
) -> Self {
Self {
txid,
block_height,
sapling_notes,
orchard_notes,
outgoing_sapling_notes,
outgoing_orchard_notes,
}
}

Expand All @@ -154,112 +162,74 @@ impl WalletTransaction {
pub fn orchard_notes(&self) -> &[OrchardNote] {
&self.orchard_notes
}
}

#[derive(Debug, Getters, CopyGetters)]
pub struct SaplingNote {
#[getset(get_copy = "pub")]
output_id: OutputId,
#[getset(get = "pub")]
note: sapling_crypto::Note,
#[getset(get_copy = "pub")]
nullifier: sapling_crypto::Nullifier, //TODO: make option and add handling for syncing without nullfiier deriving key
#[getset(get_copy = "pub")]
position: Position,
#[getset(get = "pub")]
memo: Memo,
}

impl SyncNote for SaplingNote {
type WalletNote = Self;
type ZcashNote = sapling_crypto::Note;
type Nullifier = sapling_crypto::Nullifier;
type Memo = Memo;

fn from_parts(
output_id: OutputId,
note: Self::ZcashNote,
nullifier: Self::Nullifier,
position: Position,
memo: Self::Memo,
) -> Self::WalletNote {
Self {
output_id,
note,
nullifier,
position,
memo,
}
pub fn outgoing_sapling_notes(&self) -> &[OutgoingSaplingNote] {
&self.outgoing_sapling_notes
}

fn memo(&self) -> &Self::Memo {
&self.memo
pub fn outgoing_orchard_notes(&self) -> &[OutgoingOrchardNote] {
&self.outgoing_orchard_notes
}
}

pub type SaplingNote = WalletNote<sapling_crypto::Note, sapling_crypto::Nullifier>;
pub type OrchardNote = WalletNote<orchard::Note, orchard::note::Nullifier>;

/// Wallet note, shielded output with metadata relevant to the wallet
#[derive(Debug, Getters, CopyGetters)]
pub struct OrchardNote {
pub struct WalletNote<N, Nf: Copy> {
/// Output ID
#[getset(get_copy = "pub")]
output_id: OutputId,
/// Identifier for key used to decrypt output
#[getset(get_copy = "pub")]
key_id: KeyId,
/// Decrypted note with recipient and value
#[getset(get = "pub")]
note: orchard::Note,
note: N,
/// Derived nullifier
#[getset(get_copy = "pub")]
nullifier: orchard::note::Nullifier, //TODO: make option and add handling for syncing without nullfiier deriving key
nullifier: Option<Nf>, //TODO: syncing without nullfiier deriving key
/// Commitment tree leaf position
#[getset(get_copy = "pub")]
position: Position,
/// Memo
#[getset(get = "pub")]
memo: Memo,
}

impl SyncNote for OrchardNote {
type WalletNote = Self;
type ZcashNote = orchard::Note;
type Nullifier = orchard::note::Nullifier;
type Memo = Memo;

fn from_parts(
impl<N, Nf: Copy> WalletNote<N, Nf> {
pub fn from_parts(
output_id: OutputId,
note: Self::ZcashNote,
nullifier: Self::Nullifier,
key_id: KeyId,
note: N,
nullifier: Option<Nf>,
position: Position,
memo: Self::Memo,
) -> Self::WalletNote {
memo: Memo,
) -> Self {
Self {
output_id,
key_id,
note,
nullifier,
position,
memo,
}
}

fn memo(&self) -> &Self::Memo {
&self.memo
}
}

pub(crate) trait SyncNote {
type WalletNote;
type ZcashNote;
type Nullifier: Copy;
type Memo;

fn from_parts(
output_id: OutputId,
note: Self::ZcashNote,
nullifier: Self::Nullifier,
position: Position,
memo: Self::Memo,
) -> Self::WalletNote;

fn memo(&self) -> &Self::Memo;
}
pub type OutgoingSaplingNote = OutgoingNote<sapling_crypto::Note>;
pub type OutgoingOrchardNote = OutgoingNote<orchard::Note>;

/// Note sent from this capability to a recipient
#[derive(Debug, Clone, Getters, CopyGetters, MutGetters)]
pub struct OutgoingNote<N> {
/// Output ID
#[getset(get_copy = "pub")]
output_id: OutputId,
/// Identifier for key used to decrypt output
#[getset(get_copy = "pub")]
key_id: KeyId,
/// Decrypted note with recipient and value
#[getset(get = "pub")]
note: N,
Expand All @@ -274,12 +244,14 @@ pub struct OutgoingNote<N> {
impl<N> OutgoingNote<N> {
pub fn from_parts(
output_id: OutputId,
key_id: KeyId,
note: N,
memo: Memo,
recipient_ua: Option<UnifiedAddress>,
) -> Self {
Self {
output_id,
key_id,
note,
memo,
recipient_ua,
Expand Down Expand Up @@ -308,6 +280,7 @@ impl SyncOutgoingNotes for OutgoingNote<orchard::Note> {
}
}

// TODO: condsider replacing with address enum instead of encoding to string
pub(crate) trait SyncOutgoingNotes {
fn encoded_recipient<P>(&self, parameters: &P) -> String
where
Expand Down
Loading