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

Complete ledger integration #3

Merged
5 commits merged into from Jul 22, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/src/lib.rs
Expand Up @@ -87,7 +87,7 @@ pub fn startup(

let lightclient = match seed {
Some(phrase) => Arc::new(LightClient::new_from_phrase(phrase, &config, birthday, false)?),
None if ledger => Arc::new(LightClient::new_with_ledger(&config, birthday)?),
None if ledger => Arc::new(LightClient::with_ledger(&config, birthday)?),
None => {
if config.wallet_exists() {
Arc::new(LightClient::read_from_disk(&config)?)
Expand Down
2 changes: 1 addition & 1 deletion lib/src/blaze/fetch_full_tx.rs
Expand Up @@ -304,7 +304,7 @@ impl FetchFullTxns {
block_time as u64,
note.clone(),
to,
todo!("pending note without extfvk"),
&ivk,
);
}

Expand Down
8 changes: 3 additions & 5 deletions lib/src/blaze/trial_decryptions.rs
Expand Up @@ -18,7 +18,6 @@ use zcash_primitives::{
note_encryption::try_sapling_compact_note_decryption,
primitives::{Nullifier, SaplingIvk},
transaction::{Transaction, TxId},
zip32::ExtendedFullViewingKey,
};

use super::syncdata::BlazeSyncData;
Expand Down Expand Up @@ -143,8 +142,6 @@ impl TrialDecryptions {

workers.push(tokio::spawn(async move {
let keys = keys.read().await;
let extfvk: ExtendedFullViewingKey =
todo!("retrieve extfvk for nullifier and to add to note");
let have_spending_key = keys.have_spending_key(&ivk).await;
let uri = bsync_data.read().await.uri().clone();

Expand All @@ -157,7 +154,7 @@ impl TrialDecryptions {
.await?;

let txid = WalletTx::new_txid(&ctx.hash);
let nullifier = note.nf(&extfvk.fvk.vk, witness.position() as u64);
let nullifier = keys.get_note_nullifier(&ivk, witness.position() as u64, &note).await?;

wallet_txns.write().await.add_new_note(
txid.clone(),
Expand All @@ -166,7 +163,8 @@ impl TrialDecryptions {
timestamp,
note,
to,
&extfvk,
&ivk,
nullifier,
have_spending_key,
witness,
);
Expand Down
19 changes: 16 additions & 3 deletions lib/src/lightclient.rs
Expand Up @@ -234,14 +234,27 @@ impl LightClient {
}

#[cfg(feature = "ledger-support")]
pub fn new_with_ledger(config: &LightClientConfig, latest_block: u64) -> io::Result<Self> {
pub fn with_ledger(config: &LightClientConfig, latest_block: u64) -> io::Result<Self> {
use crate::lightwallet::keys::LedgerKeystore;

if config.wallet_exists() {}

Runtime::new().unwrap().block_on(async move {
let ks = LedgerKeystore::new(config.clone()).map_err(|e| io::Error::new(ErrorKind::NotConnected, e))?;
let wallet = if config.wallet_exists() {
let path = config.get_wallet_path();
let mut file = BufReader::new(File::open(path)?);

LightWallet::read(&mut file, config).await?
} else {
let ks = LedgerKeystore::new(config.clone())
.await
.map_err(|e| io::Error::new(ErrorKind::NotConnected, e))?;

LightWallet::with_keystore(config.clone(), latest_block, ks)
};

let l = LightClient {
wallet: LightWallet::with_keystore(config.clone(), latest_block, ks),
wallet,
config: config.clone(),
mempool_monitor: std::sync::RwLock::new(None),
sync_lock: Mutex::new(()),
Expand Down
126 changes: 68 additions & 58 deletions lib/src/lightwallet.rs
Expand Up @@ -227,7 +227,7 @@ impl LightWallet {
}

pub fn note_address(hrp: &str, note: &SaplingNoteData) -> Option<String> {
match note.extfvk.fvk.vk.to_payment_address(note.diversifier) {
match note.ivk.to_payment_address(note.diversifier) {
Some(pa) => Some(encode_payment_address(hrp, &pa)),
None => None,
}
Expand Down Expand Up @@ -409,7 +409,7 @@ impl LightWallet {
Some(a) => {
*a == encode_payment_address(
self.config.hrp_sapling_address(),
&nd.extfvk.fvk.vk.to_payment_address(nd.diversifier).unwrap(),
&nd.ivk.to_payment_address(nd.diversifier).unwrap(),
)
}
None => true,
Expand Down Expand Up @@ -467,7 +467,7 @@ impl LightWallet {
Some(a) => {
*a == encode_payment_address(
self.config.hrp_sapling_address(),
&nd.extfvk.fvk.vk.to_payment_address(nd.diversifier).unwrap(),
&nd.ivk.to_payment_address(nd.diversifier).unwrap(),
)
}
None => true,
Expand Down Expand Up @@ -543,7 +543,7 @@ impl LightWallet {
}

pub fn serialized_version() -> u64 {
return 24;
return 25;
}

pub fn new(
Expand Down Expand Up @@ -582,9 +582,11 @@ impl LightWallet {
info!("Reading wallet version {}", version);

let keys = if version <= 14 {
InMemoryKeys::read_old(version, &mut reader, config)
InMemoryKeys::read_old(version, &mut reader, config).map(Into::into)
} else if version <= 24 {
InMemoryKeys::read(&mut reader, config).map(Into::into)
} else {
InMemoryKeys::read(&mut reader, config)
Keystores::read(&mut reader, config).await
}?;

let mut blocks = Vector::read(&mut reader, |r| BlockData::read(r))?;
Expand Down Expand Up @@ -638,11 +640,7 @@ impl LightWallet {
// If version <= 8, adjust the "is_spendable" status of each note data
if version <= 8 {
// Collect all spendable keys
let spendable_keys: Vec<_> = keys
.get_all_extfvks()
.into_iter()
.filter(|extfvk| keys.have_spending_key(&extfvk.fvk.vk.ivk()))
.collect();
let spendable_keys = keys.get_all_spendable_ivks().await.collect();

txns.adjust_spendable_status(spendable_keys);
}
Expand All @@ -654,7 +652,7 @@ impl LightWallet {
};

let mut lw = Self {
keys: Arc::new(RwLock::new(keys.into())),
keys: Arc::new(RwLock::new(keys)),
txns: Arc::new(RwLock::new(txns)),
blocks: Arc::new(RwLock::new(blocks)),
config: config.clone(),
Expand All @@ -681,20 +679,20 @@ impl LightWallet {
pub async fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
{
//enclose in scope to avoid holding read lock after these checks
let keys = self.in_memory_keys().await?;
let keys = self.keys().read().await;

if keys.encrypted && keys.unlocked {
if !keys.writable() {
return Err(Error::new(
ErrorKind::InvalidInput,
format!("Cannot write while wallet is unlocked while encrypted."),
format!("Wallet wasn't ready to be written."),
));
}

// Write the version
writer.write_u64::<LittleEndian>(Self::serialized_version())?;

// Write all the keys
keys.write(&mut writer)?;
// Write the keystore
keys.write(&mut writer).await?;
}

Vector::write(&mut writer, &self.blocks.read().await, |w, b| b.write(w))?;
Expand Down Expand Up @@ -754,27 +752,29 @@ impl LightWallet {
// Start collecting sapling funds at every allowed offset
for anchor_offset in &self.config.anchor_offset {
//TODO: allow any keystore (see usage)
let keys = self.in_memory_keys().await.expect("in memory keystore");
let keys = self.keys().read().await;

let mut candidate_notes = self
let mut candidate_notes = Vec::new();
for (txid, note) in self
.txns
.read()
.await
.current
.iter()
.flat_map(|(txid, tx)| tx.notes.iter().map(move |note| (*txid, note)))
.filter(|(_, note)| note.note.value > 0)
.filter_map(|(txid, note)| {
// Filter out notes that are already spent
if note.spent.is_some() || note.unconfirmed_spent.is_some() {
None
} else {
// Get the spending key for the selected fvk, if we have it
let extsk = keys.get_extsk_for_extfvk(&note.extfvk);
SpendableNote::from(txid, note, *anchor_offset as usize, &extsk)
// Filter out notes that are already spent
.filter(|(_, note)| note.spent.is_none() && note.unconfirmed_spent.is_none())
{
// select the note if we have the spending key for it
if keys.have_spending_key(&note.ivk).await {
//None will return if it's actually not spendable
if let Some(spendable) = SpendableNote::from(txid, note, *anchor_offset as usize, &note.ivk) {
candidate_notes.push(spendable);
}
})
.collect::<Vec<_>>();
}
}

candidate_notes.sort_by(|a, b| b.note.value.cmp(&a.note.value));

// Select the minimum number of notes required to satisfy the target value
Expand Down Expand Up @@ -954,14 +954,14 @@ impl LightWallet {
Some(a) => {
a == encode_payment_address(
self.config.hrp_sapling_address(),
&nd.extfvk.fvk.vk.to_payment_address(nd.diversifier).unwrap(),
&nd.ivk.to_payment_address(nd.diversifier).unwrap(),
)
}
None => true,
})
{
// Check to see if we have this note's spending key.
if keys.have_spending_key(&nd.extfvk.fvk.vk.ivk()).await {
if keys.have_spending_key(&nd.ivk).await {
if tx.block > BlockHeight::from_u32(anchor_height) {
// If confirmed but dont have anchor yet, it is unconfirmed
sum += nd.note.value
Expand Down Expand Up @@ -993,14 +993,14 @@ impl LightWallet {
Some(a) => {
*a == encode_payment_address(
self.config.hrp_sapling_address(),
&nd.extfvk.fvk.vk.to_payment_address(nd.diversifier).unwrap(),
&nd.ivk.to_payment_address(nd.diversifier).unwrap(),
)
}
None => true,
})
{
// Check to see if we have this note's spending key and witnesses
if keys.have_spending_key(&nd.extfvk.fvk.vk.ivk()).await && nd.witnesses.len() > 0 {
if keys.have_spending_key(&nd.ivk).await && nd.witnesses.len() > 0 {
sum += nd.note.value;
}
}
Expand Down Expand Up @@ -1049,8 +1049,13 @@ impl LightWallet {
//TODO: allow any keystore (see usage)
let mut keys = self.in_memory_keys_mut().await.expect("in memory keystore");

let zaddrs = keys.get_all_zaddresses();
if zaddrs.len() <= 1 {
let ivks = keys
.get_all_extfvks()
.into_iter()
.map(|extfvk| extfvk.fvk.vk.ivk())
.collect::<Vec<_>>();

if ivks.len() <= 1 {
return;
}

Expand All @@ -1061,11 +1066,9 @@ impl LightWallet {
.current
.values()
.flat_map(|wtx| {
wtx.notes.iter().map(|n| {
let (_, pa) = n.extfvk.default_address().unwrap();
let zaddr = encode_payment_address(self.config.hrp_sapling_address(), &pa);
zaddrs.iter().position(|za| *za == zaddr).unwrap_or(zaddrs.len())
})
wtx.notes
.iter()
.map(|n| ivks.iter().position(|ivk| ivk.0 == n.ivk.0).unwrap_or(ivks.len()))
})
.max();

Expand Down Expand Up @@ -1242,7 +1245,7 @@ impl LightWallet {

for selected in notes.iter() {
if let Err(e) = builder.add_sapling_spend(
&ExtendedFullViewingKey::from(&selected.extsk).fvk.vk,
&selected.ivk,
selected.diversifier,
selected.note.clone(),
selected.witness.path().unwrap(),
Expand Down Expand Up @@ -1401,32 +1404,39 @@ impl LightWallet {
}

pub async fn encrypt(&self, passwd: String) -> io::Result<()> {
//TODO: allow any keystore (see usage)
self.in_memory_keys_mut()
.await
.expect("in memory keystore")
.encrypt(passwd)
match self.in_memory_keys_mut().await {
Ok(mut ks) => ks.encrypt(passwd),
//for now if it's not in-memory just assume it's unlocked
//TODO: do appropriate work here for other keystores
_ => Ok(()),
}
}

pub async fn lock(&self) -> io::Result<()> {
//TODO: allow any keystore (see usage)
self.in_memory_keys_mut().await.expect("in memory keystore").lock()
match self.in_memory_keys_mut().await {
Ok(mut ks) => ks.lock(),
//for now if it's not in-memory just assume it's unlocked
//TODO: do appropriate work here for other keystores
_ => Ok(()),
}
}

pub async fn unlock(&self, passwd: String) -> io::Result<()> {
//TODO: allow any keystore (see usage)
self.in_memory_keys_mut()
.await
.expect("in memory keystore")
.unlock(passwd)
match self.in_memory_keys_mut().await {
Ok(mut ks) => ks.unlock(passwd),
//for now if it's not in-memory just assume it's unlocked
//TODO: do appropriate work here for other keystores
_ => Ok(()),
}
}

pub async fn remove_encryption(&self, passwd: String) -> io::Result<()> {
//TODO: allow any keystore (see usage)
self.in_memory_keys_mut()
.await
.expect("in memory keystore")
.remove_encryption(passwd)
match self.in_memory_keys_mut().await {
Ok(mut ks) => ks.remove_encryption(passwd),
//for now if it's not in-memory just assume it's unlocked
//TODO: do appropriate work here for other keystores
_ => Ok(()),
}
}
}

Expand Down