diff --git a/src/lightclient.rs b/src/lightclient.rs index f9c22b2..14a7009 100644 --- a/src/lightclient.rs +++ b/src/lightclient.rs @@ -272,7 +272,7 @@ impl LightClient { "created_in_txid" => format!("{}", txid), "value" => nd.note.value, "is_change" => nd.is_change, - "address" => LightWallet::address_from_extfvk(&nd.extfvk, nd.diversifier), + "address" => nd.note_address(), "spent" => nd.spent.map(|spent_txid| format!("{}", spent_txid)), "unconfirmed_spent" => nd.unconfirmed_spent.map(|spent_txid| format!("{}", spent_txid)), }) diff --git a/src/lightwallet/data.rs b/src/lightwallet/data.rs new file mode 100644 index 0000000..5cd4f2f --- /dev/null +++ b/src/lightwallet/data.rs @@ -0,0 +1,446 @@ +use std::io::{self, Read, Write}; + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use pairing::bls12_381::{Bls12}; +use ff::{PrimeField, PrimeFieldRepr}; + +use zcash_client_backend::{ + constants::testnet::{HRP_SAPLING_PAYMENT_ADDRESS,}, + encoding::encode_payment_address, +}; + +use zcash_primitives::{ + block::BlockHash, + merkle_tree::{CommitmentTree, IncrementalWitness}, + sapling::Node, + serialize::{Vector, Optional}, + transaction::{ + components::{OutPoint}, + TxId, + }, + note_encryption::{Memo,}, + zip32::{ExtendedFullViewingKey,}, + JUBJUB, + primitives::{Diversifier, Note,}, + jubjub::{ + JubjubEngine, + fs::{Fs, FsRepr}, + } +}; + + +pub struct BlockData { + pub height: i32, + pub hash: BlockHash, + pub tree: CommitmentTree, +} + +impl BlockData { + pub fn read(mut reader: R) -> io::Result { + let height = reader.read_i32::()?; + + let mut hash_bytes = [0; 32]; + reader.read_exact(&mut hash_bytes)?; + + let tree = CommitmentTree::::read(&mut reader)?; + + let endtag = reader.read_u64::()?; + if endtag != 11 { + println!("End tag for blockdata {}", endtag); + } + + + Ok(BlockData{ + height, + hash: BlockHash{ 0: hash_bytes }, + tree + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_i32::(self.height)?; + writer.write_all(&self.hash.0)?; + self.tree.write(&mut writer)?; + writer.write_u64::(11) + } +} + +pub struct SaplingNoteData { + pub(super) account: usize, + pub(super) extfvk: ExtendedFullViewingKey, // Technically, this should be recoverable from the account number, but we're going to refactor this in the future, so I'll write it again here. + pub diversifier: Diversifier, + pub note: Note, + pub(super) witnesses: Vec>, + pub(super) nullifier: [u8; 32], + pub spent: Option, // If this note was confirmed spent + pub unconfirmed_spent: Option, // If this note was spent in a send, but has not yet been confirmed. + pub memo: Option, + pub is_change: bool, + // TODO: We need to remove the unconfirmed_spent (i.e., set it to None) if the Tx has expired +} + + +/// Reads an FsRepr from [u8] of length 32 +/// This will panic (abort) if length provided is +/// not correct +/// TODO: This is duplicate from rustzcash.rs +fn read_fs(from: &[u8]) -> FsRepr { + assert_eq!(from.len(), 32); + + let mut f = <::Fs as PrimeField>::Repr::default(); + f.read_le(from).expect("length is 32 bytes"); + + f +} + +// Reading a note also needs the corresponding address to read from. +pub fn read_note(mut reader: R) -> io::Result<(u64, Fs)> { + let value = reader.read_u64::()?; + + let mut r_bytes: [u8; 32] = [0; 32]; + reader.read_exact(&mut r_bytes)?; + + let r = match Fs::from_repr(read_fs(&r_bytes)) { + Ok(r) => r, + Err(_) => return Err(io::Error::new( + io::ErrorKind::InvalidInput, "Couldn't parse randomness")) + }; + + Ok((value, r)) +} + +impl SaplingNoteData { + fn serialized_version() -> u64 { + 1 + } + + pub fn new( + extfvk: &ExtendedFullViewingKey, + output: zcash_client_backend::wallet::WalletShieldedOutput + ) -> Self { + let witness = output.witness; + let nf = { + let mut nf = [0; 32]; + nf.copy_from_slice( + &output + .note + .nf(&extfvk.fvk.vk, witness.position() as u64, &JUBJUB), + ); + nf + }; + + SaplingNoteData { + account: output.account, + extfvk: extfvk.clone(), + diversifier: output.to.diversifier, + note: output.note, + witnesses: vec![witness], + nullifier: nf, + spent: None, + unconfirmed_spent: None, + memo: None, + is_change: output.is_change, + } + } + + // Reading a note also needs the corresponding address to read from. + pub fn read(mut reader: R) -> io::Result { + let version = reader.read_u64::()?; + assert_eq!(version, SaplingNoteData::serialized_version()); + + let account = reader.read_u64::()? as usize; + + let extfvk = ExtendedFullViewingKey::read(&mut reader)?; + + let mut diversifier_bytes = [0u8; 11]; + reader.read_exact(&mut diversifier_bytes)?; + let diversifier = Diversifier{0: diversifier_bytes}; + + // To recover the note, read the value and r, and then use the payment address + // to recreate the note + let (value, r) = read_note(&mut reader)?; // TODO: This method is in a different package, because of some fields that are private + + let maybe_note = extfvk.fvk.vk.into_payment_address(diversifier, &JUBJUB).unwrap().create_note(value, r, &JUBJUB); + + let note = match maybe_note { + Some(n) => Ok(n), + None => Err(io::Error::new(io::ErrorKind::InvalidInput, "Couldn't create the note for the address")) + }?; + + let witnesses = Vector::read(&mut reader, |r| IncrementalWitness::::read(r))?; + + let mut nullifier = [0u8; 32]; + reader.read_exact(&mut nullifier)?; + + // Note that this is only the spent field, we ignore the unconfirmed_spent field. + // The reason is that unconfirmed spents are only in memory, and we need to get the actual value of spent + // from the blockchain anyway. + let spent = Optional::read(&mut reader, |r| { + let mut txid_bytes = [0u8; 32]; + r.read_exact(&mut txid_bytes)?; + Ok(TxId{0: txid_bytes}) + })?; + + let memo = Optional::read(&mut reader, |r| { + let mut memo_bytes = [0u8; 512]; + r.read_exact(&mut memo_bytes)?; + match Memo::from_bytes(&memo_bytes) { + Some(m) => Ok(m), + None => Err(io::Error::new(io::ErrorKind::InvalidInput, "Couldn't create the memo")) + } + })?; + + let is_change: bool = reader.read_u8()? > 0; + + Ok(SaplingNoteData { + account, + extfvk, + diversifier, + note, + witnesses, + nullifier, + spent, + unconfirmed_spent: None, + memo, + is_change, + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + // Write a version number first, so we can later upgrade this if needed. + writer.write_u64::(SaplingNoteData::serialized_version())?; + + writer.write_u64::(self.account as u64)?; + + self.extfvk.write(&mut writer)?; + + writer.write_all(&self.diversifier.0)?; + + // Writing the note means writing the note.value and note.r. The Note is recoverable + // from these 2 values and the Payment address. + writer.write_u64::(self.note.value)?; + + let mut rcm = [0; 32]; + self.note.r.into_repr().write_le(&mut rcm[..])?; + writer.write_all(&rcm)?; + + Vector::write(&mut writer, &self.witnesses, |wr, wi| wi.write(wr) )?; + + writer.write_all(&self.nullifier)?; + Optional::write(&mut writer, &self.spent, |w, t| w.write_all(&t.0))?; + + Optional::write(&mut writer, &self.memo, |w, m| w.write_all(m.as_bytes()))?; + + writer.write_u8(if self.is_change {1} else {0})?; + + // Note that we don't write the unconfirmed_spent field, because if the wallet is restarted, + // we don't want to be beholden to any expired txns + + Ok(()) + } + + pub fn note_address(&self) -> Option { + match self.extfvk.fvk.vk.into_payment_address(self.diversifier, &JUBJUB) { + Some(pa) => Some(encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, &pa)), + None => None + } + } +} + +#[derive(Clone, Debug)] +pub struct Utxo { + pub address: String, + pub txid: TxId, + pub output_index: u64, + pub script: Vec, + pub value: u64, + pub height: i32, + + pub spent: Option, // If this utxo was confirmed spent + pub unconfirmed_spent: Option, // If this utxo was spent in a send, but has not yet been confirmed. +} + +impl Utxo { + pub fn serialized_version() -> u64 { + return 1; + } + + pub fn to_outpoint(&self) -> OutPoint { + OutPoint { hash: self.txid.0, n: self.output_index as u32 } + } + + pub fn read(mut reader: R) -> io::Result { + let version = reader.read_u64::()?; + assert_eq!(version, Utxo::serialized_version()); + + let address_len = reader.read_i32::()?; + let mut address_bytes = vec![0; address_len as usize]; + reader.read_exact(&mut address_bytes)?; + let address = String::from_utf8(address_bytes).unwrap(); + assert_eq!(address.chars().take(1).collect::>()[0], 't'); + + let mut txid_bytes = [0; 32]; + reader.read_exact(&mut txid_bytes)?; + let txid = TxId { 0: txid_bytes }; + + let output_index = reader.read_u64::()?; + let value = reader.read_u64::()?; + let height = reader.read_i32::()?; + + let script = Vector::read(&mut reader, |r| { + let mut byte = [0; 1]; + r.read_exact(&mut byte)?; + Ok(byte[0]) + })?; + + let spent = Optional::read(&mut reader, |r| { + let mut txbytes = [0u8; 32]; + r.read_exact(&mut txbytes)?; + Ok(TxId{0: txbytes}) + })?; + + // Note that we don't write the unconfirmed spent field, because if the wallet is restarted, we'll reset any unconfirmed stuff. + + Ok(Utxo { + address, + txid, + output_index, + script, + value, + height, + spent, + unconfirmed_spent: None::, + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_u64::(Utxo::serialized_version())?; + + writer.write_u32::(self.address.as_bytes().len() as u32)?; + writer.write_all(self.address.as_bytes())?; + + writer.write_all(&self.txid.0)?; + + writer.write_u64::(self.output_index)?; + writer.write_u64::(self.value)?; + writer.write_i32::(self.height)?; + + Vector::write(&mut writer, &self.script, |w, b| w.write_all(&[*b]))?; + + Optional::write(&mut writer, &self.spent, |w, txid| w.write_all(&txid.0))?; + + // Note that we don't write the unconfirmed spent field, because if the wallet is restarted, we'll reset any unconfirmed stuff. + + Ok(()) + } +} + +pub struct WalletTx { + pub block: i32, + + // Txid of this transcation. It's duplicated here (It is also the Key in the HashMap that points to this + // WalletTx in LightWallet::txs) + pub txid: TxId, + + // List of all notes recieved in this tx. Some of these might be change notes. + pub notes: Vec, + + // List of all Utxos recieved in this Tx. Some of these might be change notes + pub utxos: Vec, + + // Total shielded value spent in this Tx. Note that this is the value of the wallet's notes spent. + // Some change may be returned in one of the notes above. Subtract the two to get the actual value spent. + // Also note that even after subtraction, you might need to account for transparent inputs and outputs + // to make sure the value is accurate. + pub total_shielded_value_spent: u64, + + // Total amount of transparent funds that belong to us that were spent in this Tx. + pub total_transparent_value_spent : u64, +} + +impl WalletTx { + pub fn serialized_version() -> u64 { + return 1; + } + + pub fn new(height: i32, txid: &TxId) -> Self { + WalletTx { + block: height, + txid: txid.clone(), + notes: vec![], + utxos: vec![], + total_shielded_value_spent: 0, + total_transparent_value_spent: 0 + } + } + + pub fn read(mut reader: R) -> io::Result { + let version = reader.read_u64::()?; + assert_eq!(version, WalletTx::serialized_version()); + + let block = reader.read_i32::()?; + + let mut txid_bytes = [0u8; 32]; + reader.read_exact(&mut txid_bytes)?; + + let txid = TxId{0: txid_bytes}; + + let notes = Vector::read(&mut reader, |r| SaplingNoteData::read(r))?; + let utxos = Vector::read(&mut reader, |r| Utxo::read(r))?; + + let total_shielded_value_spent = reader.read_u64::()?; + let total_transparent_value_spent = reader.read_u64::()?; + + Ok(WalletTx{ + block, + txid, + notes, + utxos, + total_shielded_value_spent, + total_transparent_value_spent + }) + } + + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_u64::(WalletTx::serialized_version())?; + + writer.write_i32::(self.block)?; + + writer.write_all(&self.txid.0)?; + + Vector::write(&mut writer, &self.notes, |w, nd| nd.write(w))?; + Vector::write(&mut writer, &self.utxos, |w, u| u.write(w))?; + + writer.write_u64::(self.total_shielded_value_spent)?; + writer.write_u64::(self.total_transparent_value_spent)?; + + Ok(()) + } +} + +pub struct SpendableNote { + pub txid: TxId, + pub nullifier: [u8; 32], + pub diversifier: Diversifier, + pub note: Note, + pub witness: IncrementalWitness, +} + +impl SpendableNote { + pub fn from(txid: TxId, nd: &SaplingNoteData, anchor_offset: usize) -> Option { + // Include only notes that haven't been spent, or haven't been included in an unconfirmed spend yet. + if nd.spent.is_none() && nd.unconfirmed_spent.is_none() { + let witness = nd.witnesses.get(nd.witnesses.len() - anchor_offset - 1); + + witness.map(|w| SpendableNote { + txid, + nullifier: nd.nullifier, + diversifier: nd.diversifier, + note: nd.note.clone(), + witness: w.clone(), + }) + } else { + None + } + } +} diff --git a/src/lightwallet.rs b/src/lightwallet/mod.rs similarity index 69% rename from src/lightwallet.rs rename to src/lightwallet/mod.rs index e20c753..6edb2eb 100644 --- a/src/lightwallet.rs +++ b/src/lightwallet/mod.rs @@ -12,7 +12,6 @@ use bip39::{Mnemonic, Language}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use pairing::bls12_381::{Bls12}; -use ff::{PrimeField, PrimeFieldRepr}; use zcash_client_backend::{ constants::testnet::{HRP_SAPLING_PAYMENT_ADDRESS,B58_PUBKEY_ADDRESS_PREFIX,}, @@ -22,9 +21,8 @@ use zcash_client_backend::{ use zcash_primitives::{ block::BlockHash, - merkle_tree::{CommitmentTree, IncrementalWitness}, - sapling::Node, - serialize::{Vector, Optional}, + merkle_tree::{CommitmentTree}, + serialize::{Vector}, transaction::{ builder::{Builder}, components::{Amount, OutPoint, TxOut}, components::amount::DEFAULT_FEE, @@ -34,19 +32,29 @@ use zcash_primitives::{ note_encryption::{Memo, try_sapling_note_decryption}, zip32::{ExtendedFullViewingKey, ExtendedSpendingKey, ChildIndex}, JUBJUB, - primitives::{Diversifier, Note, PaymentAddress}, - jubjub::{ - JubjubEngine, - fs::{Fs, FsRepr}, - } + primitives::{PaymentAddress}, }; +pub mod data; + +use data::{BlockData, WalletTx, Utxo, SaplingNoteData, SpendableNote}; + use crate::address; use crate::prover; use sha2::{Sha256, Digest}; + +const ANCHOR_OFFSET: u32 = 1; + +const SAPLING_ACTIVATION_HEIGHT: i32 = 280_000; + +fn now() -> f64 { + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() as f64 +} + + /// Sha256(Sha256(value)) pub fn double_sha256(payload: &[u8]) -> Vec { let h1 = Sha256::digest(&payload); @@ -56,16 +64,11 @@ pub fn double_sha256(payload: &[u8]) -> Vec { use base58::{ToBase58, FromBase58}; -const ANCHOR_OFFSET: u32 = 1; - -const SAPLING_ACTIVATION_HEIGHT: i32 = 280_000; - - /// A trait for converting a [u8] to base58 encoded string. pub trait ToBase58Check { /// Converts a value of `self` to a base58 value, returning the owned string. - /// The version is a coin-specific prefix that is added. - /// The suffix is any bytes that we want to add at the end (like the "iscompressed" flag for + /// The version is a coin-specific prefix that is added. + /// The suffix is any bytes that we want to add at the end (like the "iscompressed" flag for /// Secret key encoding) fn to_base58check(&self, version: &[u8], suffix: &[u8]) -> String; } @@ -76,7 +79,7 @@ impl ToBase58Check for [u8] { payload.extend_from_slice(version); payload.extend_from_slice(self); payload.extend_from_slice(suffix); - + let mut checksum = double_sha256(&payload); payload.append(&mut checksum[..4].to_vec()); payload.to_base58() @@ -97,437 +100,12 @@ impl FromBase58Check for str { let end = bytes.len() - (4 + suffix.len()); payload.extend(&bytes[start..end]); - + payload } } -fn now() -> f64 { - // web_sys::window() - // .expect("should have a Window") - // .performance() - // .expect("should have a Performance") - // .now() - SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() as f64 -} - -struct BlockData { - height: i32, - hash: BlockHash, - tree: CommitmentTree, -} - -impl BlockData { - pub fn read(mut reader: R) -> io::Result { - let height = reader.read_i32::()?; - - let mut hash_bytes = [0; 32]; - reader.read_exact(&mut hash_bytes)?; - - let tree = CommitmentTree::::read(&mut reader)?; - - let endtag = reader.read_u64::()?; - if endtag != 11 { - println!("End tag for blockdata {}", endtag); - } - - - Ok(BlockData{ - height, - hash: BlockHash{ 0: hash_bytes }, - tree - }) - } - - pub fn write(&self, mut writer: W) -> io::Result<()> { - writer.write_i32::(self.height)?; - writer.write_all(&self.hash.0)?; - self.tree.write(&mut writer)?; - writer.write_u64::(11) - } -} - -pub struct SaplingNoteData { - account: usize, - pub extfvk: ExtendedFullViewingKey, // Technically, this should be recoverable from the account number, but we're going to refactor this in the future, so I'll write it again here. - pub diversifier: Diversifier, - pub note: Note, - witnesses: Vec>, - nullifier: [u8; 32], - pub spent: Option, // If this note was confirmed spent - pub unconfirmed_spent: Option, // If this note was spent in a send, but has not yet been confirmed. - pub memo: Option, - pub is_change: bool, - // TODO: We need to remove the unconfirmed_spent (i.e., set it to None) if the Tx has expired -} - - -/// Reads an FsRepr from [u8] of length 32 -/// This will panic (abort) if length provided is -/// not correct -/// TODO: This is duplicate from rustzcash.rs -fn read_fs(from: &[u8]) -> FsRepr { - assert_eq!(from.len(), 32); - - let mut f = <::Fs as PrimeField>::Repr::default(); - f.read_le(from).expect("length is 32 bytes"); - - f -} - -// Reading a note also needs the corresponding address to read from. -pub fn read_note(mut reader: R) -> io::Result<(u64, Fs)> { - let value = reader.read_u64::()?; - - let mut r_bytes: [u8; 32] = [0; 32]; - reader.read_exact(&mut r_bytes)?; - - let r = match Fs::from_repr(read_fs(&r_bytes)) { - Ok(r) => r, - Err(_) => return Err(io::Error::new( - io::ErrorKind::InvalidInput, "Couldn't parse randomness")) - }; - - Ok((value, r)) -} - -impl SaplingNoteData { - fn serialized_version() -> u64 { - 1 - } - - fn new( - extfvk: &ExtendedFullViewingKey, - output: zcash_client_backend::wallet::WalletShieldedOutput - ) -> Self { - let witness = output.witness; - let nf = { - let mut nf = [0; 32]; - nf.copy_from_slice( - &output - .note - .nf(&extfvk.fvk.vk, witness.position() as u64, &JUBJUB), - ); - nf - }; - - SaplingNoteData { - account: output.account, - extfvk: extfvk.clone(), - diversifier: output.to.diversifier, - note: output.note, - witnesses: vec![witness], - nullifier: nf, - spent: None, - unconfirmed_spent: None, - memo: None, - is_change: output.is_change, - } - } - - // Reading a note also needs the corresponding address to read from. - pub fn read(mut reader: R) -> io::Result { - let version = reader.read_u64::()?; - assert_eq!(version, SaplingNoteData::serialized_version()); - - let account = reader.read_u64::()? as usize; - - let extfvk = ExtendedFullViewingKey::read(&mut reader)?; - - let mut diversifier_bytes = [0u8; 11]; - reader.read_exact(&mut diversifier_bytes)?; - let diversifier = Diversifier{0: diversifier_bytes}; - - // To recover the note, read the value and r, and then use the payment address - // to recreate the note - let (value, r) = read_note(&mut reader)?; // TODO: This method is in a different package, because of some fields that are private - - let maybe_note = extfvk.fvk.vk.into_payment_address(diversifier, &JUBJUB).unwrap().create_note(value, r, &JUBJUB); - - let note = match maybe_note { - Some(n) => Ok(n), - None => Err(io::Error::new(io::ErrorKind::InvalidInput, "Couldn't create the note for the address")) - }?; - - let witnesses = Vector::read(&mut reader, |r| IncrementalWitness::::read(r))?; - - let mut nullifier = [0u8; 32]; - reader.read_exact(&mut nullifier)?; - - // Note that this is only the spent field, we ignore the unconfirmed_spent field. - // The reason is that unconfirmed spents are only in memory, and we need to get the actual value of spent - // from the blockchain anyway. - let spent = Optional::read(&mut reader, |r| { - let mut txid_bytes = [0u8; 32]; - r.read_exact(&mut txid_bytes)?; - Ok(TxId{0: txid_bytes}) - })?; - - let memo = Optional::read(&mut reader, |r| { - let mut memo_bytes = [0u8; 512]; - r.read_exact(&mut memo_bytes)?; - match Memo::from_bytes(&memo_bytes) { - Some(m) => Ok(m), - None => Err(io::Error::new(io::ErrorKind::InvalidInput, "Couldn't create the memo")) - } - })?; - - let is_change: bool = reader.read_u8()? > 0; - - Ok(SaplingNoteData { - account, - extfvk, - diversifier, - note, - witnesses, - nullifier, - spent, - unconfirmed_spent: None, - memo, - is_change, - }) - } - - pub fn write(&self, mut writer: W) -> io::Result<()> { - // Write a version number first, so we can later upgrade this if needed. - writer.write_u64::(SaplingNoteData::serialized_version())?; - - writer.write_u64::(self.account as u64)?; - - self.extfvk.write(&mut writer)?; - - writer.write_all(&self.diversifier.0)?; - - // Writing the note means writing the note.value and note.r. The Note is recoverable - // from these 2 values and the Payment address. - writer.write_u64::(self.note.value)?; - - let mut rcm = [0; 32]; - self.note.r.into_repr().write_le(&mut rcm[..])?; - writer.write_all(&rcm)?; - - Vector::write(&mut writer, &self.witnesses, |wr, wi| wi.write(wr) )?; - - writer.write_all(&self.nullifier)?; - Optional::write(&mut writer, &self.spent, |w, t| w.write_all(&t.0))?; - - Optional::write(&mut writer, &self.memo, |w, m| w.write_all(m.as_bytes()))?; - - writer.write_u8(if self.is_change {1} else {0})?; - - // Note that we don't write the unconfirmed_spent field, because if the wallet is restarted, - // we don't want to be beholden to any expired txns - - Ok(()) - } - - pub fn note_address(&self) -> Option { - match self.extfvk.fvk.vk.into_payment_address(self.diversifier, &JUBJUB) { - Some(pa) => Some(encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, &pa)), - None => None - } - } -} - -#[derive(Clone, Debug)] -pub struct Utxo { - pub address: String, - pub txid: TxId, - pub output_index: u64, - pub script: Vec, - pub value: u64, - pub height: i32, - - pub spent: Option, // If this utxo was confirmed spent - pub unconfirmed_spent: Option, // If this utxo was spent in a send, but has not yet been confirmed. -} - -impl Utxo { - pub fn serialized_version() -> u64 { - return 1; - } - - fn to_outpoint(&self) -> OutPoint { - OutPoint { hash: self.txid.0, n: self.output_index as u32 } - } - - pub fn read(mut reader: R) -> io::Result { - let version = reader.read_u64::()?; - assert_eq!(version, Utxo::serialized_version()); - - let address_len = reader.read_i32::()?; - let mut address_bytes = vec![0; address_len as usize]; - reader.read_exact(&mut address_bytes)?; - let address = String::from_utf8(address_bytes).unwrap(); - assert_eq!(address.chars().take(1).collect::>()[0], 't'); - - let mut txid_bytes = [0; 32]; - reader.read_exact(&mut txid_bytes)?; - let txid = TxId { 0: txid_bytes }; - - let output_index = reader.read_u64::()?; - let value = reader.read_u64::()?; - let height = reader.read_i32::()?; - - let script = Vector::read(&mut reader, |r| { - let mut byte = [0; 1]; - r.read_exact(&mut byte)?; - Ok(byte[0]) - })?; - - let spent = Optional::read(&mut reader, |r| { - let mut txbytes = [0u8; 32]; - r.read_exact(&mut txbytes)?; - Ok(TxId{0: txbytes}) - })?; - - // Note that we don't write the unconfirmed spent field, because if the wallet is restarted, we'll reset any unconfirmed stuff. - - Ok(Utxo { - address, - txid, - output_index, - script, - value, - height, - spent, - unconfirmed_spent: None::, - }) - } - - pub fn write(&self, mut writer: W) -> io::Result<()> { - writer.write_u64::(Utxo::serialized_version())?; - - writer.write_u32::(self.address.as_bytes().len() as u32)?; - writer.write_all(self.address.as_bytes())?; - - writer.write_all(&self.txid.0)?; - - writer.write_u64::(self.output_index)?; - writer.write_u64::(self.value)?; - writer.write_i32::(self.height)?; - - Vector::write(&mut writer, &self.script, |w, b| w.write_all(&[*b]))?; - - Optional::write(&mut writer, &self.spent, |w, txid| w.write_all(&txid.0))?; - - // Note that we don't write the unconfirmed spent field, because if the wallet is restarted, we'll reset any unconfirmed stuff. - - Ok(()) - } -} - -pub struct WalletTx { - pub block: i32, - - // Txid of this transcation. It's duplicated here (It is also the Key in the HashMap that points to this - // WalletTx in LightWallet::txs) - pub txid: TxId, - - // List of all notes recieved in this tx. Some of these might be change notes. - pub notes: Vec, - - // List of all Utxos recieved in this Tx. Some of these might be change notes - pub utxos: Vec, - - // Total shielded value spent in this Tx. Note that this is the value of the wallet's notes spent. - // Some change may be returned in one of the notes above. Subtract the two to get the actual value spent. - // Also note that even after subtraction, you might need to account for transparent inputs and outputs - // to make sure the value is accurate. - pub total_shielded_value_spent: u64, - - // Total amount of transparent funds that belong to us that were spent in this Tx. - pub total_transparent_value_spent : u64, -} - -impl WalletTx { - pub fn serialized_version() -> u64 { - return 1; - } - - pub fn new(height: i32, txid: &TxId) -> Self { - WalletTx { - block: height, - txid: txid.clone(), - notes: vec![], - utxos: vec![], - total_shielded_value_spent: 0, - total_transparent_value_spent: 0 - } - } - - pub fn read(mut reader: R) -> io::Result { - let version = reader.read_u64::()?; - assert_eq!(version, WalletTx::serialized_version()); - - let block = reader.read_i32::()?; - - let mut txid_bytes = [0u8; 32]; - reader.read_exact(&mut txid_bytes)?; - - let txid = TxId{0: txid_bytes}; - - let notes = Vector::read(&mut reader, |r| SaplingNoteData::read(r))?; - let utxos = Vector::read(&mut reader, |r| Utxo::read(r))?; - - let total_shielded_value_spent = reader.read_u64::()?; - let total_transparent_value_spent = reader.read_u64::()?; - - Ok(WalletTx{ - block, - txid, - notes, - utxos, - total_shielded_value_spent, - total_transparent_value_spent - }) - } - - pub fn write(&self, mut writer: W) -> io::Result<()> { - writer.write_u64::(WalletTx::serialized_version())?; - - writer.write_i32::(self.block)?; - - writer.write_all(&self.txid.0)?; - - Vector::write(&mut writer, &self.notes, |w, nd| nd.write(w))?; - Vector::write(&mut writer, &self.utxos, |w, u| u.write(w))?; - - writer.write_u64::(self.total_shielded_value_spent)?; - writer.write_u64::(self.total_transparent_value_spent)?; - - Ok(()) - } -} - -struct SpendableNote { - txid: TxId, - nullifier: [u8; 32], - diversifier: Diversifier, - note: Note, - witness: IncrementalWitness, -} - -impl SpendableNote { - fn from(txid: TxId, nd: &SaplingNoteData, anchor_offset: usize) -> Option { - // Include only notes that haven't been spent, or haven't been included in an unconfirmed spend yet. - if nd.spent.is_none() && nd.unconfirmed_spent.is_none() { - let witness = nd.witnesses.get(nd.witnesses.len() - anchor_offset - 1); - - witness.map(|w| SpendableNote { - txid, - nullifier: nd.nullifier, - diversifier: nd.diversifier, - note: nd.note.clone(), - witness: w.clone(), - }) - } else { - None - } - } -} - pub struct LightWallet { seed: [u8; 32], // Seed phrase for this wallet. @@ -768,11 +346,6 @@ impl LightWallet { } } - pub fn address_from_extfvk(extfvk: &ExtendedFullViewingKey, diversifier: Diversifier) -> String { - encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, - &extfvk.fvk.vk.into_payment_address(diversifier, &JUBJUB).unwrap()) - } - pub fn address_from_sk(sk: &secp256k1::SecretKey) -> String { let secp = secp256k1::Secp256k1::new(); let pk = secp256k1::PublicKey::from_secret_key(&secp, &sk); @@ -908,7 +481,7 @@ impl LightWallet { output_index: n, script: vout.script_pubkey.0.clone(), value: vout.value.into(), - height: height, + height, spent: None, unconfirmed_spent: None, });