cryptonote-wallet 0.1.6

base58 for cryptonote
Documentation
use chrono::{DateTime, Utc};
use cryptonote_account::Address;
use cryptonote_raw_crypto::{generate_secret_key, secret_to_public, Chacha, ChachaIV, ChachaKey};
use cryptonote_varint as varint;
use ed25519_dalek::{Keypair, PublicKey};

use hex;
use std::fs::File;
use std::io::{BufReader, BufWriter, Read, Write};

type Keys = ([u8; 32], [u8; 32]);
pub struct Wallet {
  pub version: u64,
  pub spend_keys: Keys,
  pub view_keys: Keys,
  pub createtime: u64,
  pub iv: [u8; 8],
  loaded: bool,
}

impl Wallet {
  pub fn new() -> Wallet {
    Wallet {
      version: 1,
      spend_keys: ([0; 32], [0; 32]),
      view_keys: ([0; 32], [0; 32]),
      createtime: 0,
      iv: [0; 8],
      loaded: false,
    }
  }

  fn timestamp() -> u64 {
    let now: DateTime<Utc> = Utc::now();
    let now = now.timestamp() as u64;
    now
  }

  pub fn parse_key(r: &mut Read) -> [u8; 32] {
    let mut key: [u8; 32] = [0; 32];
    r.read_exact(&mut key).unwrap();
    key
  }

  pub fn check(secret: [u8; 32], public: [u8; 32]) -> bool {
    let generated_pub = secret_to_public(&secret);
    return public == generated_pub;
  }

  pub fn to_key_pair(secret_bytes: [u8; 32], public_bytes: [u8; 32]) -> Keypair {
    let mut pair: Vec<u8> = vec![];
    pair.extend(&public_bytes);
    pair.extend(&secret_bytes);
    let key_pair: Keypair = Keypair::from_bytes(&pair).expect("Error Key Pair");
    key_pair
  }

  pub fn to_address(&self, prefix: u64) -> Address {
    let spend_pubk: PublicKey = PublicKey::from_bytes(&self.spend_keys.1).unwrap();
    let view_pubk: PublicKey = PublicKey::from_bytes(&self.view_keys.1).unwrap();
    let address = Address::new(prefix, spend_pubk, view_pubk);
    address
  }

  pub fn prase(&mut self, buffer: &[u8]) {
    let mut buffered = BufReader::new(buffer);
    let createtime: u64 = varint::read(&mut buffered);
    self.createtime = createtime;
    let spend_public_key = Wallet::parse_key(&mut buffered);
    let spend_private_key = Wallet::parse_key(&mut buffered);
    if !Wallet::check(spend_private_key, spend_public_key) {
      panic!("Wrong spend keys!");
    }

    let view_public_key = Wallet::parse_key(&mut buffered);
    let view_private_key = Wallet::parse_key(&mut buffered);

    if !Wallet::check(spend_private_key, spend_public_key) {
      panic!("Wrong view keys!");
    }

    self.spend_keys = (spend_private_key, spend_public_key);
    self.view_keys = (view_private_key, view_public_key);
  }

  pub fn create_keys() -> ([u8; 32], [u8; 32]) {
    let secret: [u8; 32] = generate_secret_key();
    let public: [u8; 32] = secret_to_public(&secret);
    return (secret, public);
  }

  pub fn create() -> Wallet {
    let chacha_iv = ChachaIV::new();
    Wallet {
      version: 1,
      spend_keys: Wallet::create_keys(),
      view_keys: Wallet::create_keys(),
      createtime: Wallet::timestamp(),
      iv: chacha_iv.data,
      loaded: true,
    }
  }

  pub fn load(&mut self, file: String, password: String) {
    let input = File::open(file).expect("File not found!");
    let mut buffered = BufReader::new(input);
    let version: u64 = varint::read(&mut buffered);
    self.version = version;
    let mut iv: [u8; 8] = [0; 8];
    buffered
      .read_exact(&mut iv[..])
      .expect("Failed to read iv!");
    self.iv = iv;
    let cipher_len: u64 = varint::read(&mut buffered);
    let mut cipher = vec![0; cipher_len as usize];
    buffered
      .read_exact(&mut cipher[..])
      .expect("Failed to read cipher!");
    let key = ChachaKey::generate(password.to_string());

    let iv0 = ChachaIV::from(iv);
    let chacha = Chacha::new(key, iv0);
    let dec = chacha.encrypt(&cipher);
    self.prase(&dec);
    self.loaded = true;
  }

  pub fn save(&self, file: String, password: String) {
    let output = File::create(file).expect("Fail to create file!");
    let mut buffered = BufWriter::new(output);
    varint::write(&mut buffered, self.version);
    buffered.write(&self.iv).expect("Error write iv!");
    let mut plain: Vec<u8> = vec![];
    varint::write(&mut plain, self.createtime);
    for item in [
      self.spend_keys.1,
      self.spend_keys.0,
      self.view_keys.1,
      self.view_keys.0,
    ]
    .into_iter()
    {
      plain.extend(item);
    }
    plain.extend(vec![0]);
    varint::write(&mut plain, 0 as u64);
    let key = ChachaKey::generate(password.to_string());
    let iv = ChachaIV::from(self.iv);
    let chacha = Chacha::new(key, iv);
    let cipher = chacha.encrypt(&plain);
    varint::write(&mut buffered, cipher.len() as u64);
    buffered.write(&cipher).expect("Error write cipher!");
  }

  pub fn update_secret_keys(&mut self, spend_str: String, view_str: String) {
    let spend_slice = hex::decode(spend_str).expect("Wrong spend str!");
    let view_slice = hex::decode(view_str).expect("Wrong view str");
    let spend: [u8; 32] = Wallet::to_fixed_key(&spend_slice[..]);
    let view: [u8; 32] = Wallet::to_fixed_key(&view_slice[..]);
    let spend_pub = secret_to_public(&spend);
    let view_pub = secret_to_public(&view);
    self.spend_keys = (spend, spend_pub);
    self.view_keys = (view, view_pub);
  }

  pub fn from_secret_keys(spend: [u8; 32], view: [u8; 32]) -> Wallet {
    let spend_pub = secret_to_public(&spend);
    let view_pub = secret_to_public(&view);
    Wallet::from_pair((spend, spend_pub), (view, view_pub))
  }

  fn to_fixed_key(bytes: &[u8]) -> [u8; 32] {
    let mut key: [u8; 32] = [0; 32];
    for i in 0..32 {
      key[i] = bytes[i];
    }
    key
  }

  pub fn from_secret_string(spend_str: String, view_str: String) -> Wallet {
    let spend_slice = hex::decode(spend_str).expect("Wrong spend str!");
    let view_slice = hex::decode(view_str).expect("Wrong view str");
    let spend: [u8; 32] = Wallet::to_fixed_key(&spend_slice[..]);
    let view: [u8; 32] = Wallet::to_fixed_key(&view_slice[..]);
    Wallet::from_secret_keys(spend, view)
  }

  pub fn from_pair(spend_keys: Keys, view_keys: Keys) -> Wallet {
    let chacha_iv = ChachaIV::new();
    let iv = chacha_iv.data;
    Wallet {
      version: 1,
      createtime: Wallet::timestamp(),
      loaded: true,
      iv,
      spend_keys,
      view_keys,
    }
  }
}

#[cfg(test)]
mod tests {
  use super::Wallet;

  #[test]
  fn should_read() {
    let prefix: u64 = 0x3d;
    let mut wallet = Wallet::new();
    wallet.load(String::from("tests/vig.wallet"), String::from(""));
    let address = wallet.to_address(prefix);

    let mut wallet1 = Wallet::new();
    wallet1.load(
      String::from("tests/vig-enc.wallet"),
      String::from("abcd$1234"),
    );
    let address1 = wallet1.to_address(prefix);
    assert!(address.get() == address1.get());

    wallet.save(
      String::from("tests/vig-new.wallet"),
      String::from("abcd$1234"),
    );
    wallet1.save(String::from("tests/vig-enc-new.wallet"), String::from(""));

    let mut wallet2 = Wallet::new();
    wallet2.load(String::from("tests/vig-enc-new.wallet"), String::from(""));

    let mut wallet2 = Wallet::new();
    wallet2.load(
      String::from("tests/vig-new.wallet"),
      String::from("abcd$1234"),
    );

    assert!(wallet.version == wallet2.version);
    assert!(wallet.spend_keys == wallet2.spend_keys);
    assert!(wallet.view_keys == wallet2.view_keys);
    assert!(wallet.createtime == wallet2.createtime);

    let spend = wallet.spend_keys.0;
    let view = wallet.view_keys.0;

    let wallet3 = Wallet::from_secret_keys(spend, view);
    let address3 = wallet3.to_address(prefix);
    assert!(address3.get() == address1.get());
    let spend_str = "f644de91c7defae58ff9136dcc8b03a2059fda3294865065f86554d3aaeb310c";
    let view_str = "3dd9d71a6fe2b909e1603c9ac325f13f2c6ac965e7e1ec98e5e666ed84b4d40c";
    println!("before from secret string");
    let wallet = Wallet::from_secret_string(String::from(spend_str), String::from(view_str));
    let address = wallet.to_address(prefix);
    assert!(address.get() == "BM5A1ACoB4Af9ZuaJwTjHE37zowNmSp2nP2FjUZkm4u2LVo2UPXvMnW7xRhf9C7mJcBcLu5n9W3ArU69SKBS6azrMfn6NBH");

    let mut wallet10 = Wallet::new();
    let spend_str_10 = "f644de91c7defae58ff9136dcc8b03a2059fda3294865065f86554d3aaeb310c";
    let view_str_10 = "3dd9d71a6fe2b909e1603c9ac325f13f2c6ac965e7e1ec98e5e666ed84b4d40c";
    wallet10.update_secret_keys(String::from(spend_str_10), String::from(view_str_10));

    let address10 = wallet10.to_address(prefix);
    assert!(address10.get() == "BM5A1ACoB4Af9ZuaJwTjHE37zowNmSp2nP2FjUZkm4u2LVo2UPXvMnW7xRhf9C7mJcBcLu5n9W3ArU69SKBS6azrMfn6NBH");
  }

  #[test]
  fn should_create() {
    let prefix: u64 = 0x3d;
    let wallet = Wallet::create();
    wallet.save(String::from("tests/created.wallet"), String::from(""));
    let address = wallet.to_address(prefix);

    let mut wallet = Wallet::new();
    wallet.load(String::from("tests/created.wallet"), String::from(""));
    let address1 = wallet.to_address(prefix);

    assert!(address.get() == address1.get());
  }

  #[test]
  #[should_panic]
  fn test_wrong_load() {
    let mut wallet0 = Wallet::new();
    wallet0.load(String::from("tests/vig.wallet"), String::from("aaaa"));
  }

  #[test]
  #[should_panic]
  fn test_wrong_file() {
    let mut wallet0 = Wallet::new();
    wallet0.load(String::from("tests/vig1.wallet"), String::from("sssss"));
  }
}