Browse Source

Add lock/unlock API

checkpoints
Aditya Kulkarni 5 years ago
parent
commit
72548e077e
  1. 1
      lib/Cargo.toml
  2. 219
      lib/src/lightwallet.rs

1
lib/Cargo.toml

@ -34,6 +34,7 @@ webpki-roots = "0.16.0"
tower-h2 = { git = "https://github.com/tower-rs/tower-h2" }
rust-embed = { version = "5.1.0", features = ["debug-embed"] }
rand = "0.7.2"
sodiumoxide = "0.2.5"
[dependencies.bellman]
git = "https://github.com/adityapk00/librustzcash.git"

219
lib/src/lightwallet.rs

@ -90,9 +90,11 @@ impl ToBase58Check for [u8] {
}
pub struct LightWallet {
locked: bool, // Is the wallet's spending keys locked?
locked: bool, // Is the wallet's spending keys locked?
enc_seed: [u8; 48], // If locked, this contains the encrypted seed
nonce: Vec<u8>, // Nonce used to encrypt the wallet.
seed: [u8; 32], // Seed phrase for this wallet. If wallet is locked, this is encrypted
seed: [u8; 32], // Seed phrase for this wallet. If wallet is locked, this is 0
// List of keys, actually in this wallet. If the wallet is locked, the `extsks` will be
// encrypted (but the fvks are not encrpyted)
@ -123,6 +125,8 @@ impl LightWallet {
}
fn get_taddr_from_bip39seed(config: &LightClientConfig, bip39_seed: &[u8], pos: u32) -> SecretKey {
assert_eq!(bip39_seed.len(), 64);
let ext_t_key = ExtendedPrivKey::with_seed(bip39_seed).unwrap();
ext_t_key
.derive_private_key(KeyIndex::hardened_from_normalize_index(44).unwrap()).unwrap()
@ -134,10 +138,12 @@ impl LightWallet {
}
fn get_zaddr_from_bip39seed(config: &LightClientConfig, bip39seed: &[u8], pos: u32) ->
fn get_zaddr_from_bip39seed(config: &LightClientConfig, bip39_seed: &[u8], pos: u32) ->
(ExtendedSpendingKey, ExtendedFullViewingKey, PaymentAddress<Bls12>) {
assert_eq!(bip39_seed.len(), 64);
let extsk: ExtendedSpendingKey = ExtendedSpendingKey::from_path(
&ExtendedSpendingKey::master(bip39seed),
&ExtendedSpendingKey::master(bip39_seed),
&[
ChildIndex::Hardened(32),
ChildIndex::Hardened(config.get_coin_type()),
@ -178,6 +184,8 @@ impl LightWallet {
Ok(LightWallet {
locked: false,
enc_seed: [0u8; 48],
nonce: vec![],
seed: seed_bytes,
extsks: Arc::new(RwLock::new(vec![extsk])),
extfvks: Arc::new(RwLock::new(vec![extfvk])),
@ -202,6 +210,17 @@ impl LightWallet {
false
};
let mut enc_seed = [0u8; 48];
if version >= 4 {
reader.read_exact(&mut enc_seed)?;
}
let nonce = if version >= 4 {
Vector::read(&mut reader, |r| r.read_u8())?
} else {
vec![]
};
// Seed
let mut seed_bytes = [0u8; 32];
reader.read_exact(&mut seed_bytes)?;
@ -257,6 +276,8 @@ impl LightWallet {
Ok(LightWallet{
locked: locked,
enc_seed: enc_seed,
nonce: nonce,
seed: seed_bytes,
extsks: Arc::new(RwLock::new(extsks)),
extfvks: Arc::new(RwLock::new(extfvks)),
@ -277,6 +298,12 @@ impl LightWallet {
// Write if it is locked
writer.write_u8(if self.locked {1} else {0})?;
// Write the encrypted seed bytes
writer.write_all(&self.enc_seed)?;
// Write the nonce
Vector::write(&mut writer, &self.nonce, |w, b| w.write_u8(*b))?;
// Write the seed
writer.write_all(&self.seed)?;
@ -368,8 +395,10 @@ impl LightWallet {
/// NOTE: This does NOT rescan
pub fn add_zaddr(&self) -> String {
let pos = self.extsks.read().unwrap().len() as u32;
let bip39_seed = bip39::Seed::new(&Mnemonic::from_entropy(&self.seed, Language::English).unwrap(), "");
let (extsk, extfvk, address) =
LightWallet::get_zaddr_from_bip39seed(&self.config, &self.seed, pos);
LightWallet::get_zaddr_from_bip39seed(&self.config, &bip39_seed.as_bytes(), pos);
let zaddr = encode_payment_address(self.config.hrp_sapling_address(), &address);
self.extsks.write().unwrap().push(extsk);
@ -384,8 +413,9 @@ impl LightWallet {
/// NOTE: This is not rescan the wallet
pub fn add_taddr(&self) -> String {
let pos = self.tkeys.read().unwrap().len() as u32;
let bip39_seed = bip39::Seed::new(&Mnemonic::from_entropy(&self.seed, Language::English).unwrap(), "");
let sk = LightWallet::get_taddr_from_bip39seed(&self.config, &self.seed, pos);
let sk = LightWallet::get_taddr_from_bip39seed(&self.config, &bip39_seed.as_bytes(), pos);
let address = self.address_from_sk(&sk);
self.tkeys.write().unwrap().push(sk);
@ -531,6 +561,102 @@ impl LightWallet {
).unwrap().phrase().to_string()
}
pub fn lock(&mut self, passwd: String) -> io::Result<()> {
use sodiumoxide::crypto::secretbox;
if self.locked {
return Err(io::Error::new(ErrorKind::AlreadyExists, "Wallet is already locked"));
}
// Get the doublesha256 of the password, which is the right length
let key = secretbox::Key::from_slice(&double_sha256(passwd.as_bytes())).unwrap();
let nonce = secretbox::gen_nonce();
let cipher = secretbox::seal(&self.seed, &nonce, &key);
self.enc_seed.copy_from_slice(&cipher);
self.nonce = vec![];
self.nonce.extend_from_slice(nonce.as_ref());
// Empty the seed and the secret keys
self.seed.copy_from_slice(&[0u8; 32]);
self.tkeys = Arc::new(RwLock::new(vec![]));
self.extsks = Arc::new(RwLock::new(vec![]));
self.locked = true;
Ok(())
}
pub fn unlock(&mut self, passwd: String) -> io::Result<()> {
use sodiumoxide::crypto::secretbox;
if !self.locked {
return Err(io::Error::new(ErrorKind::AlreadyExists, "Wallet is not locked"));
}
// Get the doublesha256 of the password, which is the right length
let key = secretbox::Key::from_slice(&double_sha256(passwd.as_bytes())).unwrap();
let nonce = secretbox::Nonce::from_slice(&self.nonce).unwrap();
let seed = match secretbox::open(&self.enc_seed, &nonce, &key) {
Ok(s) => s,
Err(_) => {return Err(io::Error::new(ErrorKind::InvalidData, "Decryption failed. Is your password correct?"));}
};
// Now that we have the seed, we'll generate the extsks and tkeys, and verify the fvks and addresses
// respectively match
// The seed bytes is the raw entropy. To pass it to HD wallet generation,
// we need to get the 64 byte bip39 entropy
let bip39_seed = bip39::Seed::new(&Mnemonic::from_entropy(&seed, Language::English).unwrap(), "");
// Sapling keys
let mut extsks = vec![];
for pos in 0..self.zaddress.read().unwrap().len() {
let (extsk, extfvk, address) =
LightWallet::get_zaddr_from_bip39seed(&self.config, &bip39_seed.as_bytes(), pos as u32);
if address != self.zaddress.read().unwrap()[pos] {
return Err(io::Error::new(ErrorKind::InvalidData,
format!("zaddress mismatch at {}. {:?} vs {:?}", pos, address, self.zaddress.read().unwrap()[pos])));
}
if extfvk != self.extfvks.read().unwrap()[pos] {
return Err(io::Error::new(ErrorKind::InvalidData,
format!("fvk mismatch at {}. {:?} vs {:?}", pos, extfvk, self.extfvks.read().unwrap()[pos])));
}
// Don't add it to self yet, we'll do that at the end when everything is verified
extsks.push(extsk);
}
// Transparent keys
let mut tkeys = vec![];
for pos in 0..self.taddresses.read().unwrap().len() {
let sk = LightWallet::get_taddr_from_bip39seed(&self.config, &bip39_seed.as_bytes(), pos as u32);
let address = self.address_from_sk(&sk);
if address != self.taddresses.read().unwrap()[pos] {
return Err(io::Error::new(ErrorKind::InvalidData,
format!("taddress mismatch at {}. {} vs {}", pos, address, self.taddresses.read().unwrap()[pos])));
}
tkeys.push(sk);
}
// Everything checks out, so we'll update our wallet with the unlocked values
self.extsks = Arc::new(RwLock::new(extsks));
self.tkeys = Arc::new(RwLock::new(tkeys));
self.seed.copy_from_slice(&seed);
self.nonce = vec![];
self.enc_seed.copy_from_slice(&[0u8; 48]);
self.locked = false;
Ok(())
}
pub fn zbalance(&self, addr: Option<String>) -> u64 {
self.txs.read().unwrap()
.values()
@ -2927,6 +3053,87 @@ pub mod tests {
assert_eq!(seed_phrase, Some(wallet.get_seed_phrase()));
}
#[test]
fn test_lock_unlock() {
const AMOUNT: u64 = 500000;
let (mut wallet, _, _) = get_test_wallet(AMOUNT);
let config = wallet.config.clone();
// Add some addresses
let zaddr0 = encode_payment_address(config.hrp_sapling_address(),
&wallet.extfvks.read().unwrap()[0].default_address().unwrap().1);
let zaddr1 = wallet.add_zaddr();
let zaddr2 = wallet.add_zaddr();
let taddr0 = wallet.address_from_sk(&wallet.tkeys.read().unwrap()[0]);
let taddr1 = wallet.add_taddr();
let taddr2 = wallet.add_taddr();
let seed = wallet.seed;
wallet.lock("somepassword".to_string()).unwrap();
// Locking a locked wallet should fail
assert!(wallet.lock("somepassword".to_string()).is_err());
// Serialize a locked wallet
let mut serialized_data = vec![];
wallet.write(&mut serialized_data).expect("Serialize wallet");
// Should fail when there's a wrong password
assert!(wallet.unlock("differentpassword".to_string()).is_err());
// Properly unlock
wallet.unlock("somepassword".to_string()).unwrap();
assert_eq!(seed, wallet.seed);
{
let extsks = wallet.extsks.read().unwrap();
let tkeys = wallet.tkeys.read().unwrap();
assert_eq!(extsks.len(), 3);
assert_eq!(tkeys.len(), 3);
assert_eq!(zaddr0, encode_payment_address(config.hrp_sapling_address(),
&ExtendedFullViewingKey::from(&extsks[0]).default_address().unwrap().1));
assert_eq!(zaddr1, encode_payment_address(config.hrp_sapling_address(),
&ExtendedFullViewingKey::from(&extsks[1]).default_address().unwrap().1));
assert_eq!(zaddr2, encode_payment_address(config.hrp_sapling_address(),
&ExtendedFullViewingKey::from(&extsks[2]).default_address().unwrap().1));
assert_eq!(taddr0, wallet.address_from_sk(&tkeys[0]));
assert_eq!(taddr1, wallet.address_from_sk(&tkeys[1]));
assert_eq!(taddr2, wallet.address_from_sk(&tkeys[2]));
}
// Unlocking an already unlocked wallet should fail
assert!(wallet.unlock("somepassword".to_string()).is_err());
// Try from a deserialized, locked wallet
let mut wallet2 = LightWallet::read(&serialized_data[..], &config).unwrap();
wallet2.unlock("somepassword".to_string()).unwrap();
assert_eq!(seed, wallet2.seed);
{
let extsks = wallet2.extsks.read().unwrap();
let tkeys = wallet2.tkeys.read().unwrap();
assert_eq!(extsks.len(), 3);
assert_eq!(tkeys.len(), 3);
assert_eq!(zaddr0, encode_payment_address(wallet2.config.hrp_sapling_address(),
&ExtendedFullViewingKey::from(&extsks[0]).default_address().unwrap().1));
assert_eq!(zaddr1, encode_payment_address(wallet2.config.hrp_sapling_address(),
&ExtendedFullViewingKey::from(&extsks[1]).default_address().unwrap().1));
assert_eq!(zaddr2, encode_payment_address(wallet2.config.hrp_sapling_address(),
&ExtendedFullViewingKey::from(&extsks[2]).default_address().unwrap().1));
assert_eq!(taddr0, wallet2.address_from_sk(&tkeys[0]));
assert_eq!(taddr1, wallet2.address_from_sk(&tkeys[1]));
assert_eq!(taddr2, wallet2.address_from_sk(&tkeys[2]));
}
}
#[test]
fn test_invalid_scan_blocks() {
const AMOUNT: u64 = 500000;

Loading…
Cancel
Save