Wallet encryption commands
This commit is contained in:
@@ -91,7 +91,14 @@ impl ToBase58Check for [u8] {
|
||||
}
|
||||
|
||||
pub struct LightWallet {
|
||||
locked: bool, // Is the wallet's spending keys locked?
|
||||
// Is the wallet encrypted? If it is, then when writing to disk, the seed is always encrypted
|
||||
// and the individual spending keys are not written
|
||||
encrypted: bool,
|
||||
|
||||
// In memory only (i.e, this field is not written to disk). Is the wallet unlocked and are
|
||||
// the spending keys present to allow spending from this wallet?
|
||||
unlocked: bool,
|
||||
|
||||
enc_seed: [u8; 48], // If locked, this contains the encrypted seed
|
||||
nonce: Vec<u8>, // Nonce used to encrypt the wallet.
|
||||
|
||||
@@ -184,7 +191,8 @@ impl LightWallet {
|
||||
= LightWallet::get_zaddr_from_bip39seed(&config, &bip39_seed.as_bytes(), 0);
|
||||
|
||||
Ok(LightWallet {
|
||||
locked: false,
|
||||
encrypted: false,
|
||||
unlocked: true,
|
||||
enc_seed: [0u8; 48],
|
||||
nonce: vec![],
|
||||
seed: seed_bytes,
|
||||
@@ -210,7 +218,7 @@ impl LightWallet {
|
||||
|
||||
info!("Reading wallet version {}", version);
|
||||
|
||||
let locked = if version >= 4 {
|
||||
let encrypted = if version >= 4 {
|
||||
reader.read_u8()? > 0
|
||||
} else {
|
||||
false
|
||||
@@ -281,7 +289,8 @@ impl LightWallet {
|
||||
let birthday = reader.read_u64::<LittleEndian>()?;
|
||||
|
||||
Ok(LightWallet{
|
||||
locked: locked,
|
||||
encrypted: encrypted,
|
||||
unlocked: !encrypted, // When reading from disk, if wallet is encrypted, it starts off locked.
|
||||
enc_seed: enc_seed,
|
||||
nonce: nonce,
|
||||
seed: seed_bytes,
|
||||
@@ -298,11 +307,16 @@ impl LightWallet {
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
|
||||
if self.encrypted && self.unlocked {
|
||||
return Err(Error::new(ErrorKind::InvalidInput,
|
||||
format!("Cannot write while wallet is unlocked while encrypted.")));
|
||||
}
|
||||
|
||||
// Write the version
|
||||
writer.write_u64::<LittleEndian>(LightWallet::serialized_version())?;
|
||||
|
||||
// Write if it is locked
|
||||
writer.write_u8(if self.locked {1} else {0})?;
|
||||
writer.write_u8(if self.encrypted {1} else {0})?;
|
||||
|
||||
// Write the encrypted seed bytes
|
||||
writer.write_all(&self.enc_seed)?;
|
||||
@@ -400,6 +414,10 @@ impl LightWallet {
|
||||
/// at the next position and add it to the wallet.
|
||||
/// NOTE: This does NOT rescan
|
||||
pub fn add_zaddr(&self) -> String {
|
||||
if !self.unlocked {
|
||||
return "".to_string();
|
||||
}
|
||||
|
||||
let pos = self.extsks.read().unwrap().len() as u32;
|
||||
let bip39_seed = bip39::Seed::new(&Mnemonic::from_entropy(&self.seed, Language::English).unwrap(), "");
|
||||
|
||||
@@ -418,6 +436,10 @@ impl LightWallet {
|
||||
/// at the next position.
|
||||
/// NOTE: This is not rescan the wallet
|
||||
pub fn add_taddr(&self) -> String {
|
||||
if !self.unlocked {
|
||||
return "".to_string();
|
||||
}
|
||||
|
||||
let pos = self.tkeys.read().unwrap().len() as u32;
|
||||
let bip39_seed = bip39::Seed::new(&Mnemonic::from_entropy(&self.seed, Language::English).unwrap(), "");
|
||||
|
||||
@@ -562,16 +584,20 @@ impl LightWallet {
|
||||
}
|
||||
|
||||
pub fn get_seed_phrase(&self) -> String {
|
||||
if !self.unlocked {
|
||||
return "".to_string();
|
||||
}
|
||||
|
||||
Mnemonic::from_entropy(&self.seed,
|
||||
Language::English,
|
||||
).unwrap().phrase().to_string()
|
||||
}
|
||||
|
||||
pub fn lock(&mut self, passwd: String) -> io::Result<()> {
|
||||
pub fn encrypt(&mut self, passwd: String) -> io::Result<()> {
|
||||
use sodiumoxide::crypto::secretbox;
|
||||
|
||||
if self.locked {
|
||||
return Err(io::Error::new(ErrorKind::AlreadyExists, "Wallet is already locked"));
|
||||
if self.encrypted && !self.unlocked {
|
||||
return Err(io::Error::new(ErrorKind::AlreadyExists, "Wallet is already encrypted and locked"));
|
||||
}
|
||||
|
||||
// Get the doublesha256 of the password, which is the right length
|
||||
@@ -584,20 +610,32 @@ impl LightWallet {
|
||||
self.nonce = vec![];
|
||||
self.nonce.extend_from_slice(nonce.as_ref());
|
||||
|
||||
self.encrypted = true;
|
||||
self.lock()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn lock(&mut self) -> io::Result<()> {
|
||||
// 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;
|
||||
self.unlocked = false;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unlock(&mut self, passwd: String) -> io::Result<()> {
|
||||
use sodiumoxide::crypto::secretbox;
|
||||
use sodiumoxide::crypto::secretbox;
|
||||
|
||||
if !self.locked {
|
||||
return Err(io::Error::new(ErrorKind::AlreadyExists, "Wallet is not locked"));
|
||||
if !self.encrypted {
|
||||
return Err(Error::new(ErrorKind::AlreadyExists, "Wallet is not encrypted"));
|
||||
}
|
||||
|
||||
if self.encrypted && self.unlocked {
|
||||
return Err(Error::new(ErrorKind::AlreadyExists, "Wallet is already unlocked"));
|
||||
}
|
||||
|
||||
// Get the doublesha256 of the password, which is the right length
|
||||
@@ -650,18 +688,45 @@ impl LightWallet {
|
||||
tkeys.push(sk);
|
||||
}
|
||||
|
||||
// Everything checks out, so we'll update our wallet with the unlocked values
|
||||
// Everything checks out, so we'll update our wallet with the decrypted 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;
|
||||
|
||||
self.encrypted = true;
|
||||
self.unlocked = true;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Removing encryption means unlocking it and setting the self.encrypted = false,
|
||||
// permanantly removing the encryption
|
||||
pub fn remove_encryption(&mut self, passwd: String) -> io::Result<()> {
|
||||
if !self.encrypted {
|
||||
return Err(Error::new(ErrorKind::AlreadyExists, "Wallet is not encrypted"));
|
||||
}
|
||||
|
||||
// Unlock the wallet if it's locked
|
||||
if !self.unlocked {
|
||||
self.unlock(passwd)?;
|
||||
}
|
||||
|
||||
// Permanantly remove the encryption
|
||||
self.encrypted = false;
|
||||
self.nonce = vec![];
|
||||
self.enc_seed.copy_from_slice(&[0u8; 48]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_encrypted(&self) -> bool {
|
||||
return self.encrypted;
|
||||
}
|
||||
|
||||
pub fn is_unlocked_for_spending(&self) -> bool {
|
||||
return self.unlocked;
|
||||
}
|
||||
|
||||
pub fn zbalance(&self, addr: Option<String>) -> u64 {
|
||||
self.txs.read().unwrap()
|
||||
.values()
|
||||
@@ -1245,6 +1310,10 @@ impl LightWallet {
|
||||
output_params: &[u8],
|
||||
tos: Vec<(&str, u64, Option<String>)>
|
||||
) -> Result<Box<[u8]>, String> {
|
||||
if !self.unlocked {
|
||||
return Err("Cannot spend while wallet is locked".to_string());
|
||||
}
|
||||
|
||||
let start_time = now();
|
||||
|
||||
let total_value = tos.iter().map(|to| to.1).sum::<u64>();
|
||||
@@ -3083,10 +3152,10 @@ pub mod tests {
|
||||
|
||||
let seed = wallet.seed;
|
||||
|
||||
wallet.lock("somepassword".to_string()).unwrap();
|
||||
wallet.encrypt("somepassword".to_string()).unwrap();
|
||||
|
||||
// Locking a locked wallet should fail
|
||||
assert!(wallet.lock("somepassword".to_string()).is_err());
|
||||
// Encrypting an already encrypted wallet should fail
|
||||
assert!(wallet.encrypt("somepassword".to_string()).is_err());
|
||||
|
||||
// Serialize a locked wallet
|
||||
let mut serialized_data = vec![];
|
||||
@@ -3120,6 +3189,12 @@ pub mod tests {
|
||||
// Unlocking an already unlocked wallet should fail
|
||||
assert!(wallet.unlock("somepassword".to_string()).is_err());
|
||||
|
||||
// Trying to serialize a encrypted but unlocked wallet should fail
|
||||
assert!(wallet.write(&mut vec![]).is_err());
|
||||
|
||||
// ...but if we lock it again, it should serialize
|
||||
wallet.lock().unwrap();
|
||||
wallet.write(&mut vec![]).expect("Serialize wallet");
|
||||
|
||||
// Try from a deserialized, locked wallet
|
||||
let mut wallet2 = LightWallet::read(&serialized_data[..], &config).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user