2019-09-05 10:56:19 -07:00
use std ::time ::SystemTime ;
2019-09-06 09:57:13 -07:00
use std ::io ::{ self , Read , Write } ;
2019-09-05 10:56:19 -07:00
use std ::cmp ;
use std ::collections ::HashMap ;
use std ::sync ::{ Arc , RwLock } ;
2019-09-06 14:09:12 -07:00
2019-09-08 19:52:25 -07:00
use protobuf ::parse_from_bytes ;
use bip39 ::{ Mnemonic , Language } ;
2019-09-06 14:09:12 -07:00
use byteorder ::{ LittleEndian , ReadBytesExt , WriteBytesExt } ;
use pairing ::bls12_381 ::{ Bls12 } ;
use ff ::{ PrimeField , PrimeFieldRepr } ;
2019-09-05 10:56:19 -07:00
use zcash_client_backend ::{
constants ::testnet ::HRP_SAPLING_PAYMENT_ADDRESS , encoding ::encode_payment_address ,
proto ::compact_formats ::CompactBlock , welding_rig ::scan_block ,
} ;
2019-09-06 14:09:12 -07:00
2019-09-05 10:56:19 -07:00
use zcash_primitives ::{
block ::BlockHash ,
merkle_tree ::{ CommitmentTree , IncrementalWitness } ,
sapling ::Node ,
2019-09-06 13:13:14 -07:00
serialize ::{ Vector , Optional } ,
2019-09-05 10:56:19 -07:00
transaction ::{
builder ::{ Builder } ,
components ::Amount , components ::amount ::DEFAULT_FEE ,
TxId , Transaction
} ,
2019-09-09 17:40:00 -07:00
legacy ::{ TransparentAddress ::PublicKey } ,
2019-09-05 10:56:19 -07:00
note_encryption ::{ Memo , try_sapling_note_decryption } ,
2019-09-07 22:27:35 -07:00
zip32 ::{ ExtendedFullViewingKey , ExtendedSpendingKey , ChildIndex } ,
2019-09-05 10:56:19 -07:00
JUBJUB ,
2019-09-06 14:09:12 -07:00
primitives ::{ Diversifier , Note , PaymentAddress } ,
jubjub ::{
JubjubEngine ,
fs ::{ Fs , FsRepr } ,
}
2019-09-06 13:13:14 -07:00
} ;
2019-09-05 10:56:19 -07:00
use crate ::address ;
use crate ::prover ;
2019-09-09 17:40:00 -07:00
use sha2 ::{ Sha256 , Digest } ;
/// Sha256(Sha256(value))
pub fn double_sha256 ( payload : & [ u8 ] ) -> Vec < u8 > {
let h1 = Sha256 ::digest ( & payload ) ;
let h2 = Sha256 ::digest ( & h1 ) ;
h2 . to_vec ( )
}
use base58 ::{ ToBase58 , FromBase58 } ;
2019-09-09 10:29:51 -07:00
const ANCHOR_OFFSET : u32 = 1 ;
2019-09-05 10:56:19 -07:00
const SAPLING_ACTIVATION_HEIGHT : i32 = 280_000 ;
2019-09-09 17:40:00 -07:00
/// 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
/// Secret key encoding)
fn to_base58check ( & self , version : & [ u8 ] , suffix : & [ u8 ] ) -> String ;
}
impl ToBase58Check for [ u8 ] {
fn to_base58check ( & self , version : & [ u8 ] , suffix : & [ u8 ] ) -> String {
let mut payload : Vec < u8 > = Vec ::new ( ) ;
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 ( )
}
}
pub trait FromBase58Check {
fn from_base58check ( & self , version : & [ u8 ] , suffix : & [ u8 ] ) -> Vec < u8 > ;
}
impl FromBase58Check for str {
fn from_base58check ( & self , version : & [ u8 ] , suffix : & [ u8 ] ) -> Vec < u8 > {
let mut payload : Vec < u8 > = Vec ::new ( ) ;
let bytes = self . from_base58 ( ) . unwrap ( ) ;
let start = version . len ( ) ;
let end = bytes . len ( ) - ( 4 + suffix . len ( ) ) ;
payload . extend ( & bytes [ start .. end ] ) ;
payload
}
}
2019-09-05 10:56:19 -07:00
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 < Node > ,
}
2019-09-06 09:57:13 -07:00
impl BlockData {
pub fn read < R : Read > ( mut reader : R ) -> io ::Result < Self > {
let height = reader . read_i32 ::< LittleEndian > ( ) ? ;
let mut hash_bytes = [ 0 ; 32 ] ;
reader . read_exact ( & mut hash_bytes ) ? ;
let tree = CommitmentTree ::< Node > ::read ( & mut reader ) ? ;
2019-09-06 13:13:14 -07:00
let endtag = reader . read_u64 ::< LittleEndian > ( ) ? ;
if endtag ! = 11 {
println! ( " End tag for blockdata {} " , endtag ) ;
}
2019-09-06 09:57:13 -07:00
Ok ( BlockData {
height ,
hash : BlockHash { 0 : hash_bytes } ,
tree
} )
}
pub fn write < W : Write > ( & self , mut writer : W ) -> io ::Result < ( ) > {
writer . write_i32 ::< LittleEndian > ( self . height ) ? ;
writer . write_all ( & self . hash . 0 ) ? ;
2019-09-06 13:13:14 -07:00
self . tree . write ( & mut writer ) ? ;
writer . write_u64 ::< LittleEndian > ( 11 )
2019-09-06 09:57:13 -07:00
}
}
2019-09-05 10:56:19 -07:00
pub struct SaplingNoteData {
account : usize ,
2019-09-09 10:29:51 -07:00
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 ,
2019-09-06 15:25:08 -07:00
pub note : Note < Bls12 > ,
2019-09-05 10:56:19 -07:00
witnesses : Vec < IncrementalWitness < Node > > ,
nullifier : [ u8 ; 32 ] ,
2019-09-09 10:52:07 -07:00
pub spent : Option < TxId > , // If this note was confirmed spent
pub unconfirmed_spent : Option < TxId > , // If this note was spent in a send, but has not yet been confirmed.
2019-09-06 15:25:08 -07:00
pub memo : Option < Memo > ,
pub is_change : bool ,
2019-09-09 10:52:07 -07:00
// TODO: We need to remove the unconfirmed_spent (i.e., set it to None) if the Tx has expired
2019-09-05 10:56:19 -07:00
}
2019-09-06 13:13:14 -07:00
/// 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 = < < Bls12 as JubjubEngine > ::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 < R : Read > ( mut reader : R ) -> io ::Result < ( u64 , Fs ) > {
let value = reader . read_u64 ::< LittleEndian > ( ) ? ;
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 ) )
}
2019-09-05 10:56:19 -07:00
impl SaplingNoteData {
2019-09-06 13:38:03 -07:00
fn serialized_version ( ) -> u64 {
1
}
2019-09-05 10:56:19 -07:00
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 ,
2019-09-06 13:13:14 -07:00
extfvk : extfvk . clone ( ) ,
2019-09-05 10:56:19 -07:00
diversifier : output . to . diversifier ,
note : output . note ,
witnesses : vec ! [ witness ] ,
nullifier : nf ,
spent : None ,
2019-09-09 10:52:07 -07:00
unconfirmed_spent : None ,
2019-09-06 15:25:08 -07:00
memo : None ,
is_change : output . is_change ,
2019-09-05 10:56:19 -07:00
}
}
2019-09-06 09:57:13 -07:00
2019-09-06 13:13:14 -07:00
// Reading a note also needs the corresponding address to read from.
pub fn read < R : Read > ( mut reader : R ) -> io ::Result < Self > {
let version = reader . read_u64 ::< LittleEndian > ( ) ? ;
2019-09-06 13:38:03 -07:00
assert_eq! ( version , SaplingNoteData ::serialized_version ( ) ) ;
2019-09-06 13:13:14 -07:00
let account = reader . read_u64 ::< LittleEndian > ( ) ? as usize ;
let extfvk = ExtendedFullViewingKey ::read ( & mut reader ) ? ;
let mut diversifier_bytes = [ 0 u8 ; 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 ::< Node > ::read ( r ) ) ? ;
let mut nullifier = [ 0 u8 ; 32 ] ;
reader . read_exact ( & mut nullifier ) ? ;
2019-09-09 10:52:07 -07:00
// 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.
2019-09-06 13:13:14 -07:00
let spent = Optional ::read ( & mut reader , | r | {
let mut txid_bytes = [ 0 u8 ; 32 ] ;
r . read_exact ( & mut txid_bytes ) ? ;
Ok ( TxId { 0 : txid_bytes } )
} ) ? ;
let memo = Optional ::read ( & mut reader , | r | {
let mut memo_bytes = [ 0 u8 ; 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 " ) )
}
} ) ? ;
2019-09-06 15:25:08 -07:00
let is_change : bool = reader . read_u8 ( ) ? > 0 ;
2019-09-06 13:13:14 -07:00
Ok ( SaplingNoteData {
account ,
extfvk ,
diversifier ,
note ,
witnesses ,
nullifier ,
spent ,
2019-09-09 10:52:07 -07:00
unconfirmed_spent : None ,
2019-09-06 15:25:08 -07:00
memo ,
is_change ,
2019-09-06 13:13:14 -07:00
} )
2019-09-06 09:57:13 -07:00
}
2019-09-05 10:56:19 -07:00
2019-09-06 13:13:14 -07:00
pub fn write < W : Write > ( & self , mut writer : W ) -> io ::Result < ( ) > {
// Write a version number first, so we can later upgrade this if needed.
2019-09-06 13:38:03 -07:00
writer . write_u64 ::< LittleEndian > ( SaplingNoteData ::serialized_version ( ) ) ? ;
2019-09-06 13:13:14 -07:00
writer . write_u64 ::< LittleEndian > ( 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 ::< LittleEndian > ( 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 ( ) ) ) ? ;
2019-09-06 15:25:08 -07:00
writer . write_u8 ( if self . is_change { 1 } else { 0 } ) ? ;
2019-09-06 13:13:14 -07:00
Ok ( ( ) )
}
2019-09-07 15:25:50 -07:00
pub fn note_address ( & self ) -> Option < String > {
match self . extfvk . fvk . vk . into_payment_address ( self . diversifier , & JUBJUB ) {
Some ( pa ) = > Some ( encode_payment_address ( HRP_SAPLING_PAYMENT_ADDRESS , & pa ) ) ,
None = > None
}
}
2019-09-06 13:13:14 -07:00
}
2019-09-11 17:05:14 -07:00
pub struct Utxo {
pub address : String ,
pub txid : TxId ,
pub output_index : u64 ,
pub script : Vec < u8 > ,
pub value : u64 ,
pub height : u64
}
2019-09-05 10:56:19 -07:00
pub struct WalletTx {
2019-09-06 15:25:08 -07:00
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. Note that some of these might be change notes.
2019-09-05 10:56:19 -07:00
pub notes : Vec < SaplingNoteData > ,
2019-09-06 15:25:08 -07:00
// Total shielded value spent in this Tx. Note that this is the value of notes spent,
// the change is returned in 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 ,
2019-09-05 10:56:19 -07:00
}
2019-09-06 13:13:14 -07:00
impl WalletTx {
2019-09-06 13:38:03 -07:00
pub fn serialized_version ( ) -> u64 {
return 1 ;
}
2019-09-06 15:25:08 -07:00
2019-09-06 13:13:14 -07:00
pub fn read < R : Read > ( mut reader : R ) -> io ::Result < Self > {
let version = reader . read_u64 ::< LittleEndian > ( ) ? ;
2019-09-06 13:38:03 -07:00
assert_eq! ( version , WalletTx ::serialized_version ( ) ) ;
2019-09-06 13:13:14 -07:00
let block = reader . read_i32 ::< LittleEndian > ( ) ? ;
2019-09-06 15:25:08 -07:00
let mut txid_bytes = [ 0 u8 ; 32 ] ;
reader . read_exact ( & mut txid_bytes ) ? ;
let txid = TxId { 0 : txid_bytes } ;
2019-09-06 13:13:14 -07:00
let notes = Vector ::read ( & mut reader , | r | SaplingNoteData ::read ( r ) ) ? ;
2019-09-06 15:25:08 -07:00
let total_shielded_value_spent = reader . read_u64 ::< LittleEndian > ( ) ? ;
2019-09-06 13:13:14 -07:00
Ok ( WalletTx {
block ,
2019-09-06 15:25:08 -07:00
txid ,
notes ,
total_shielded_value_spent
2019-09-06 13:13:14 -07:00
} )
}
pub fn write < W : Write > ( & self , mut writer : W ) -> io ::Result < ( ) > {
2019-09-06 13:38:03 -07:00
writer . write_u64 ::< LittleEndian > ( WalletTx ::serialized_version ( ) ) ? ;
2019-09-06 13:13:14 -07:00
writer . write_i32 ::< LittleEndian > ( self . block ) ? ;
2019-09-06 15:25:08 -07:00
writer . write_all ( & self . txid . 0 ) ? ;
2019-09-06 13:13:14 -07:00
Vector ::write ( & mut writer , & self . notes , | w , nd | nd . write ( w ) ) ? ;
2019-09-06 15:25:08 -07:00
writer . write_u64 ::< LittleEndian > ( self . total_shielded_value_spent ) ? ;
2019-09-06 13:13:14 -07:00
Ok ( ( ) )
}
}
2019-09-05 10:56:19 -07:00
struct SpendableNote {
txid : TxId ,
nullifier : [ u8 ; 32 ] ,
diversifier : Diversifier ,
note : Note < Bls12 > ,
witness : IncrementalWitness < Node > ,
}
impl SpendableNote {
fn from ( txid : TxId , nd : & SaplingNoteData , anchor_offset : usize ) -> Option < Self > {
2019-09-09 10:52:07 -07:00
// 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 ( ) {
2019-09-05 10:56:19 -07:00
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 {
2019-09-07 22:27:35 -07:00
seed : [ u8 ; 32 ] , // Seed phrase for this wallet.
// List of keys, actually in this wallet. This may include more
// than keys derived from the seed, for example, if user imports
// a private key
extsks : Vec < ExtendedSpendingKey > ,
extfvks : Vec < ExtendedFullViewingKey > ,
2019-09-08 15:03:24 -07:00
pub address : Vec < PaymentAddress < Bls12 > > ,
2019-09-07 22:27:35 -07:00
2019-09-05 10:56:19 -07:00
blocks : Arc < RwLock < Vec < BlockData > > > ,
pub txs : Arc < RwLock < HashMap < TxId , WalletTx > > > ,
}
impl LightWallet {
2019-09-06 13:38:03 -07:00
pub fn serialized_version ( ) -> u64 {
return 1 ;
}
2019-09-05 10:56:19 -07:00
2019-09-07 22:27:35 -07:00
fn get_pk_from_seed ( seed : & [ u8 ; 32 ] ) ->
( ExtendedSpendingKey , ExtendedFullViewingKey , PaymentAddress < Bls12 > ) {
let extsk : ExtendedSpendingKey = ExtendedSpendingKey ::from_path (
& ExtendedSpendingKey ::master ( seed ) ,
& [
ChildIndex ::Hardened ( 32 ) ,
ChildIndex ::Hardened ( 1 ) , // TODO: Cointype should be 133 for mainnet
ChildIndex ::Hardened ( 0 )
] ,
) ;
let extfvk = ExtendedFullViewingKey ::from ( & extsk ) ;
2019-09-05 10:56:19 -07:00
let address = extfvk . default_address ( ) . unwrap ( ) . 1 ;
2019-09-07 22:27:35 -07:00
( extsk , extfvk , address )
}
2019-09-08 19:52:25 -07:00
pub fn new ( seed_phrase : Option < & str > ) -> io ::Result < Self > {
2019-09-07 22:27:35 -07:00
use rand ::{ FromEntropy , ChaChaRng , Rng } ;
let mut seed_bytes = [ 0 u8 ; 32 ] ;
2019-09-08 19:52:25 -07:00
if seed_phrase . is_none ( ) {
// Create a random seed.
let mut system_rng = ChaChaRng ::from_entropy ( ) ;
system_rng . fill ( & mut seed_bytes ) ;
} else {
seed_bytes . copy_from_slice ( & Mnemonic ::from_phrase ( seed_phrase . expect ( " should have a seed phrase " ) ,
Language ::English ) . unwrap ( ) . entropy ( ) ) ;
}
2019-09-07 22:27:35 -07:00
// Derive only the first address
// TODO: We need to monitor addresses, and always keep 1 "free" address, so
// users can import a seed phrase and automatically get all used addresses
let ( extsk , extfvk , address ) = LightWallet ::get_pk_from_seed ( & seed_bytes ) ;
2019-09-08 19:52:25 -07:00
Ok ( LightWallet {
2019-09-07 22:27:35 -07:00
seed : seed_bytes ,
extsks : vec ! [ extsk ] ,
extfvks : vec ! [ extfvk ] ,
address : vec ! [ address ] ,
blocks : Arc ::new ( RwLock ::new ( vec! [ ] ) ) ,
txs : Arc ::new ( RwLock ::new ( HashMap ::new ( ) ) ) ,
2019-09-08 19:52:25 -07:00
} )
2019-09-05 10:56:19 -07:00
}
2019-09-06 13:13:14 -07:00
pub fn read < R : Read > ( mut reader : R ) -> io ::Result < Self > {
let version = reader . read_u64 ::< LittleEndian > ( ) ? ;
2019-09-06 13:38:03 -07:00
assert_eq! ( version , LightWallet ::serialized_version ( ) ) ;
2019-09-06 13:13:14 -07:00
2019-09-07 22:27:35 -07:00
// Seed
let mut seed_bytes = [ 0 u8 ; 32 ] ;
reader . read_exact ( & mut seed_bytes ) ? ;
// Read the spending keys
let extsks = Vector ::read ( & mut reader , | r | ExtendedSpendingKey ::read ( r ) ) ? ;
// Calculate the viewing keys
let extfvks = extsks . iter ( ) . map ( | sk | ExtendedFullViewingKey ::from ( sk ) )
. collect ::< Vec < ExtendedFullViewingKey > > ( ) ;
// Calculate the addresses
let addresses = extfvks . iter ( ) . map ( | fvk | fvk . default_address ( ) . unwrap ( ) . 1 )
. collect ::< Vec < PaymentAddress < Bls12 > > > ( ) ;
2019-09-06 13:13:14 -07:00
let blocks = Vector ::read ( & mut reader , | r | BlockData ::read ( r ) ) ? ;
let txs_tuples = Vector ::read ( & mut reader , | r | {
let mut txid_bytes = [ 0 u8 ; 32 ] ;
r . read_exact ( & mut txid_bytes ) ? ;
Ok ( ( TxId { 0 : txid_bytes } , WalletTx ::read ( r ) . unwrap ( ) ) )
} ) ? ;
let txs = txs_tuples . into_iter ( ) . collect ::< HashMap < TxId , WalletTx > > ( ) ;
Ok ( LightWallet {
2019-09-07 22:27:35 -07:00
seed : seed_bytes ,
extsks : extsks ,
extfvks : extfvks ,
address : addresses ,
blocks : Arc ::new ( RwLock ::new ( blocks ) ) ,
txs : Arc ::new ( RwLock ::new ( txs ) )
2019-09-06 13:13:14 -07:00
} )
}
pub fn write < W : Write > ( & self , mut writer : W ) -> io ::Result < ( ) > {
// Write the version
2019-09-06 13:38:03 -07:00
writer . write_u64 ::< LittleEndian > ( LightWallet ::serialized_version ( ) ) ? ;
2019-09-06 13:13:14 -07:00
2019-09-07 22:27:35 -07:00
// Write the seed
writer . write_all ( & self . seed ) ? ;
// Write all the spending keys
Vector ::write ( & mut writer , & self . extsks ,
| w , sk | sk . write ( w )
) ? ;
2019-09-06 13:13:14 -07:00
Vector ::write ( & mut writer , & self . blocks . read ( ) . unwrap ( ) , | w , b | b . write ( w ) ) ? ;
// The hashmap, write as a set of tuples
Vector ::write ( & mut writer , & self . txs . read ( ) . unwrap ( ) . iter ( ) . collect ::< Vec < ( & TxId , & WalletTx ) > > ( ) ,
| w , ( k , v ) | {
w . write_all ( & k . 0 ) ? ;
v . write ( w )
} ) ? ;
Ok ( ( ) )
}
2019-09-05 10:56:19 -07:00
pub fn set_initial_block ( & self , height : i32 , hash : & str , sapling_tree : & str ) -> bool {
let mut blocks = self . blocks . write ( ) . unwrap ( ) ;
if ! blocks . is_empty ( ) {
return false ;
}
let hash = match hex ::decode ( hash ) {
Ok ( hash ) = > {
let mut r = hash ;
r . reverse ( ) ;
BlockHash ::from_slice ( & r )
} ,
Err ( e ) = > {
eprintln! ( " {} " , e ) ;
return false ;
}
} ;
let sapling_tree = match hex ::decode ( sapling_tree ) {
Ok ( tree ) = > tree ,
Err ( e ) = > {
eprintln! ( " {} " , e ) ;
return false ;
}
} ;
if let Ok ( tree ) = CommitmentTree ::read ( & sapling_tree [ .. ] ) {
blocks . push ( BlockData { height , hash , tree } ) ;
true
} else {
false
}
}
pub fn last_scanned_height ( & self ) -> i32 {
self . blocks
. read ( )
. unwrap ( )
. last ( )
. map ( | block | block . height )
. unwrap_or ( SAPLING_ACTIVATION_HEIGHT - 1 )
}
/// Determines the target height for a transaction, and the offset from which to
/// select anchors, based on the current synchronised block chain.
fn get_target_height_and_anchor_offset ( & self ) -> Option < ( u32 , usize ) > {
match {
let blocks = self . blocks . read ( ) . unwrap ( ) ;
(
blocks . first ( ) . map ( | block | block . height as u32 ) ,
blocks . last ( ) . map ( | block | block . height as u32 ) ,
)
} {
( Some ( min_height ) , Some ( max_height ) ) = > {
let target_height = max_height + 1 ;
// Select an anchor ANCHOR_OFFSET back from the target block,
// unless that would be before the earliest block we have.
let anchor_height =
cmp ::max ( target_height . saturating_sub ( ANCHOR_OFFSET ) , min_height ) ;
Some ( ( target_height , ( target_height - anchor_height ) as usize ) )
}
_ = > None ,
}
}
2019-09-09 10:29:51 -07:00
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 ( ) )
2019-09-05 10:56:19 -07:00
}
2019-09-08 19:52:25 -07:00
pub fn get_seed_phrase ( & self ) -> String {
Mnemonic ::from_entropy ( & self . seed ,
Language ::English ,
) . unwrap ( ) . phrase ( ) . to_string ( )
}
2019-09-08 15:03:24 -07:00
pub fn balance ( & self , addr : Option < String > ) -> u64 {
2019-09-05 10:56:19 -07:00
self . txs
. read ( )
. unwrap ( )
. values ( )
. map ( | tx | {
tx . notes
. iter ( )
2019-09-08 15:03:24 -07:00
. filter ( | nd | { // TODO, this whole section is shared with verified_balance. Refactor it.
match addr . clone ( ) {
Some ( a ) = > a = = encode_payment_address (
HRP_SAPLING_PAYMENT_ADDRESS ,
& nd . extfvk . fvk . vk
. into_payment_address ( nd . diversifier , & JUBJUB ) . unwrap ( )
) ,
None = > true
}
} )
2019-09-05 10:56:19 -07:00
. map ( | nd | if nd . spent . is_none ( ) { nd . note . value } else { 0 } )
. sum ::< u64 > ( )
} )
. sum ::< u64 > ( )
}
2019-09-08 15:03:24 -07:00
pub fn verified_balance ( & self , addr : Option < String > ) -> u64 {
2019-09-05 10:56:19 -07:00
let anchor_height = match self . get_target_height_and_anchor_offset ( ) {
Some ( ( height , anchor_offset ) ) = > height - anchor_offset as u32 ,
None = > return 0 ,
} ;
self . txs
. read ( )
. unwrap ( )
. values ( )
. map ( | tx | {
if tx . block as u32 < = anchor_height {
tx . notes
. iter ( )
2019-09-08 15:03:24 -07:00
. filter ( | nd | { // TODO, this whole section is shared with verified_balance. Refactor it.
match addr . clone ( ) {
Some ( a ) = > a = = encode_payment_address (
HRP_SAPLING_PAYMENT_ADDRESS ,
& nd . extfvk . fvk . vk
. into_payment_address ( nd . diversifier , & JUBJUB ) . unwrap ( )
) ,
None = > true
}
} )
2019-09-09 10:52:07 -07:00
. map ( | nd | if nd . spent . is_none ( ) & & nd . unconfirmed_spent . is_none ( ) { nd . note . value } else { 0 } )
2019-09-05 10:56:19 -07:00
. sum ::< u64 > ( )
} else {
0
}
} )
. sum ::< u64 > ( )
}
pub fn scan_full_tx ( & self , tx : & Transaction ) {
for output in tx . shielded_outputs . iter ( ) {
let ivks : Vec < _ > = self . extfvks . iter ( ) . map ( | extfvk | extfvk . fvk . vk . ivk ( ) ) . collect ( ) ;
let cmu = output . cmu ;
let ct = output . enc_ciphertext ;
for ( _account , ivk ) in ivks . iter ( ) . enumerate ( ) {
let epk_prime = output . ephemeral_key . as_prime_order ( & JUBJUB ) . unwrap ( ) ;
let ( note , _to , memo ) = match try_sapling_note_decryption ( ivk , & epk_prime , & cmu , & ct ) {
Some ( ret ) = > ret ,
None = > continue ,
} ;
{
// Update the WalletTx
// Do it in a short scope because of the write lock.
let mut txs = self . txs . write ( ) . unwrap ( ) ;
txs . get_mut ( & tx . txid ( ) ) . unwrap ( )
. notes . iter_mut ( )
. find ( | nd | nd . note = = note ) . unwrap ( )
. memo = Some ( memo ) ;
}
}
}
}
pub fn scan_block ( & self , block : & [ u8 ] ) -> bool {
let block : CompactBlock = match parse_from_bytes ( block ) {
Ok ( block ) = > block ,
Err ( e ) = > {
eprintln! ( " Could not parse CompactBlock from bytes: {} " , e ) ;
return false ;
}
} ;
// Scanned blocks MUST be height-sequential.
let height = block . get_height ( ) as i32 ;
if height = = self . last_scanned_height ( ) {
// If the last scanned block is rescanned, check it still matches.
if let Some ( hash ) = self . blocks . read ( ) . unwrap ( ) . last ( ) . map ( | block | block . hash ) {
if block . hash ( ) ! = hash {
eprintln! ( " Block hash does not match for block {} . {} vs {} " , height , block . hash ( ) , hash ) ;
return false ;
}
}
return true ;
} else if height ! = ( self . last_scanned_height ( ) + 1 ) {
eprintln! (
" Block is not height-sequential (expected {}, found {}) " ,
self . last_scanned_height ( ) + 1 ,
height
) ;
return false ;
}
// Get the most recent scanned data.
let mut block_data = BlockData {
height ,
hash : block . hash ( ) ,
tree : self
. blocks
. read ( )
. unwrap ( )
. last ( )
. map ( | block | block . tree . clone ( ) )
. unwrap_or ( CommitmentTree ::new ( ) ) ,
} ;
let mut txs = self . txs . write ( ) . unwrap ( ) ;
// Create a Vec containing all unspent nullifiers.
2019-09-09 10:52:07 -07:00
// Include only the confirmed spent nullifiers, since unconfirmed ones still need to be included
// during scan_block below.
2019-09-05 10:56:19 -07:00
let nfs : Vec < _ > = txs
. iter ( )
. map ( | ( txid , tx ) | {
let txid = * txid ;
tx . notes . iter ( ) . filter_map ( move | nd | {
if nd . spent . is_none ( ) {
Some ( ( nd . nullifier , nd . account , txid ) )
} else {
None
}
} )
} )
. flatten ( )
. collect ( ) ;
// Prepare the note witnesses for updating
for tx in txs . values_mut ( ) {
for nd in tx . notes . iter_mut ( ) {
// Duplicate the most recent witness
if let Some ( witness ) = nd . witnesses . last ( ) {
let clone = witness . clone ( ) ;
nd . witnesses . push ( clone ) ;
}
// Trim the oldest witnesses
nd . witnesses = nd
. witnesses
. split_off ( nd . witnesses . len ( ) . saturating_sub ( 100 ) ) ;
}
}
let new_txs = {
let nf_refs : Vec < _ > = nfs . iter ( ) . map ( | ( nf , acc , _ ) | ( & nf [ .. ] , * acc ) ) . collect ( ) ;
// Create a single mutable slice of all the newly-added witnesses.
let mut witness_refs : Vec < _ > = txs
. values_mut ( )
. map ( | tx | tx . notes . iter_mut ( ) . filter_map ( | nd | nd . witnesses . last_mut ( ) ) )
. flatten ( )
. collect ( ) ;
scan_block (
block ,
& self . extfvks ,
& nf_refs [ .. ] ,
& mut block_data . tree ,
& mut witness_refs [ .. ] ,
)
} ;
for tx in new_txs {
// Mark notes as spent.
2019-09-06 15:25:08 -07:00
let mut total_shielded_value_spent : u64 = 0 ;
2019-09-05 10:56:19 -07:00
for spend in & tx . shielded_spends {
2019-09-06 15:25:08 -07:00
// TODO: Add up the spent value here and add it to the WalletTx as a Spent
2019-09-05 10:56:19 -07:00
let txid = nfs
. iter ( )
. find ( | ( nf , _ , _ ) | & nf [ .. ] = = & spend . nf [ .. ] )
. unwrap ( )
. 2 ;
let mut spent_note = txs
. get_mut ( & txid )
. unwrap ( )
. notes
. iter_mut ( )
. find ( | nd | & nd . nullifier [ .. ] = = & spend . nf [ .. ] )
. unwrap ( ) ;
2019-09-09 10:52:07 -07:00
// Mark the note as spent, and remove the unconfirmed part of it
2019-09-05 10:56:19 -07:00
spent_note . spent = Some ( tx . txid ) ;
2019-09-09 10:52:07 -07:00
spent_note . unconfirmed_spent = None ::< TxId > ;
2019-09-06 15:25:08 -07:00
total_shielded_value_spent + = spent_note . note . value ;
2019-09-05 10:56:19 -07:00
}
// Find the existing transaction entry, or create a new one.
if ! txs . contains_key ( & tx . txid ) {
let tx_entry = WalletTx {
block : block_data . height ,
2019-09-06 15:25:08 -07:00
txid : tx . txid ,
2019-09-05 10:56:19 -07:00
notes : vec ! [ ] ,
2019-09-06 21:59:01 -07:00
total_shielded_value_spent : 0
2019-09-05 10:56:19 -07:00
} ;
txs . insert ( tx . txid , tx_entry ) ;
}
let tx_entry = txs . get_mut ( & tx . txid ) . unwrap ( ) ;
2019-09-06 21:59:01 -07:00
tx_entry . total_shielded_value_spent = total_shielded_value_spent ;
2019-09-05 10:56:19 -07:00
// Save notes.
for output in tx
. shielded_outputs
. into_iter ( )
{
tx_entry . notes . push ( SaplingNoteData ::new (
& self . extfvks [ output . account ] ,
output
) ) ;
}
}
// Store scanned data for this block.
self . blocks . write ( ) . unwrap ( ) . push ( block_data ) ;
true
}
pub fn send_to_address (
& self ,
consensus_branch_id : u32 ,
spend_params : & [ u8 ] ,
output_params : & [ u8 ] ,
to : & str ,
value : u64 ,
memo : Option < String > ,
) -> Option < Box < [ u8 ] > > {
let start_time = now ( ) ;
println! (
" 0: Creating transaction sending {} tazoshis to {} " ,
value ,
to
) ;
let extsk = & self . extsks [ 0 ] ;
let extfvk = & self . extfvks [ 0 ] ;
let ovk = extfvk . fvk . ovk ;
let to = match address ::RecipientAddress ::from_str ( to ) {
Some ( to ) = > to ,
None = > {
eprintln! ( " Invalid recipient address " ) ;
return None ;
}
} ;
let value = Amount ::from_u64 ( value ) . unwrap ( ) ;
// Target the next block, assuming we are up-to-date.
let ( height , anchor_offset ) = match self . get_target_height_and_anchor_offset ( ) {
Some ( res ) = > res ,
None = > {
eprintln! ( " Cannot send funds before scanning any blocks " ) ;
return None ;
}
} ;
// Select notes to cover the target value
println! ( " {} : Selecting notes " , now ( ) - start_time ) ;
let target_value = value + DEFAULT_FEE ;
let notes : Vec < _ > = self
. txs
. read ( )
. unwrap ( )
. iter ( )
. map ( | ( txid , tx ) | tx . notes . iter ( ) . map ( move | note | ( * txid , note ) ) )
. flatten ( )
. filter_map ( | ( txid , note ) | SpendableNote ::from ( txid , note , anchor_offset ) )
. scan ( 0 , | running_total , spendable | {
let value = spendable . note . value ;
let ret = if * running_total < u64 ::from ( target_value ) {
Some ( spendable )
} else {
None
} ;
* running_total = * running_total + value ;
ret
} )
. collect ( ) ;
// Confirm we were able to select sufficient value
2019-09-10 14:20:19 -07:00
let selected_value = notes
. iter ( )
. map ( | selected | selected . note . value )
. sum ::< u64 > ( ) ;
if selected_value < u64 ::from ( target_value ) {
eprintln! (
" Insufficient funds (have {}, need {:?}) " ,
selected_value , target_value
) ;
return None ;
}
2019-09-05 10:56:19 -07:00
// Create the transaction
println! ( " {} : Adding {} inputs " , now ( ) - start_time , notes . len ( ) ) ;
let mut builder = Builder ::new ( height ) ;
2019-09-10 14:20:19 -07:00
for selected in notes . iter ( ) {
if let Err ( e ) = builder . add_sapling_spend (
extsk . clone ( ) ,
selected . diversifier ,
selected . note . clone ( ) ,
selected . witness . clone ( ) ,
) {
eprintln! ( " Error adding note: {:?} " , e ) ;
return None ;
}
}
2019-09-09 17:40:00 -07:00
// TODO: Temp - Add a transparent input manually for testing
2019-09-10 14:20:19 -07:00
// use zcash_primitives::transaction::components::{TxOut, OutPoint};
// use zcash_primitives::legacy::Script;
// let sk_bytes = "cVrKZtzs4xA58hkgR25uPVi7cV3x2o7fB5kZHFppSpqytD4fyHNz".from_base58check(&[0xEF], &[0x01]);
// println!("sk bytes {}", sk_bytes.len());
// let sk = secp256k1::SecretKey::from_slice(&sk_bytes).unwrap();
// let secp = secp256k1::Secp256k1::new();
// let pk = secp256k1::PublicKey::from_secret_key(&secp, &sk);
// // Address
// let mut hash160 = ripemd160::Ripemd160::new();
// hash160.input(sha2::Sha256::digest(&pk.serialize().to_vec()));
// let addr = hash160.result().to_base58check(&[0x1D, 0x25], &[]);
// println!("Address = {}", addr);
// let mut script_hash = [0u8; 32];
// script_hash.copy_from_slice(&hex::decode("92e600d49847b10d63cff7cf87a68e4664926f1c66c4d6ed9558d547c08927f0").unwrap()[0..32]);
// script_hash.reverse();
// let utxo = OutPoint::new(script_hash, 0);
// let script_pubkey = hex::decode("76a91455d60e3403d157326e3dd1341d481c197de1cf5a88ac").unwrap();
// let script = Script{0: script_pubkey};
// match script.address().unwrap() {
// PublicKey(p) => println!("{}", p.to_base58check(&[0x1D, 0x25], &[])),
// _ => {}
// };
2019-09-09 17:40:00 -07:00
2019-09-10 14:20:19 -07:00
// let coin = TxOut {
// value: Amount::from_u64(40000000).unwrap(),
// script_pubkey: script,
// };
2019-09-09 17:40:00 -07:00
2019-09-10 14:20:19 -07:00
// builder.add_transparent_input(sk, utxo, coin).unwrap();
2019-09-05 10:56:19 -07:00
// Compute memo if it exists
let encoded_memo = memo . map ( | s | Memo ::from_str ( & s ) . unwrap ( ) ) ;
println! ( " {} : Adding output " , now ( ) - start_time ) ;
if let Err ( e ) = match to {
address ::RecipientAddress ::Shielded ( to ) = > {
2019-09-09 10:29:51 -07:00
builder . add_sapling_output ( ovk , to . clone ( ) , value , encoded_memo )
2019-09-05 10:56:19 -07:00
}
address ::RecipientAddress ::Transparent ( to ) = > {
builder . add_transparent_output ( & to , value )
}
} {
eprintln! ( " Error adding output: {:?} " , e ) ;
return None ;
}
println! ( " {} : Building transaction " , now ( ) - start_time ) ;
let ( tx , _ ) = match builder . build (
consensus_branch_id ,
prover ::InMemTxProver ::new ( spend_params , output_params ) ,
) {
Ok ( res ) = > res ,
Err ( e ) = > {
eprintln! ( " Error creating transaction: {:?} " , e ) ;
return None ;
}
} ;
println! ( " {} : Transaction created " , now ( ) - start_time ) ;
println! ( " Transaction ID: {} " , tx . txid ( ) ) ;
// Mark notes as spent.
2019-09-09 10:52:07 -07:00
// TODO: This is only a non-confirmed spend, and the note should be marked as such.
2019-09-05 10:56:19 -07:00
let mut txs = self . txs . write ( ) . unwrap ( ) ;
for selected in notes {
let mut spent_note = txs
. get_mut ( & selected . txid )
. unwrap ( )
. notes
. iter_mut ( )
. find ( | nd | & nd . nullifier [ .. ] = = & selected . nullifier [ .. ] )
. unwrap ( ) ;
2019-09-09 10:52:07 -07:00
spent_note . unconfirmed_spent = Some ( tx . txid ( ) ) ;
2019-09-05 10:56:19 -07:00
}
// Return the encoded transaction, so the caller can send it.
let mut raw_tx = vec! [ ] ;
tx . write ( & mut raw_tx ) . unwrap ( ) ;
Some ( raw_tx . into_boxed_slice ( ) )
}
}