| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715 |
- use async_std::sync::Arc;
- use std::collections::HashMap;
- use std::path::{Path, PathBuf};
- use ff::Field;
- use log::*;
- use rand::rngs::OsRng;
- use rusqlite::{named_params, params, Connection};
- use super::WalletApi;
- use crate::client::ClientFailed;
- use crate::crypto::{
- coin::Coin,
- merkle::IncrementalWitness,
- merkle_node::MerkleNode,
- note::Note,
- nullifier::Nullifier,
- types::{PublicKey, SecretKey, TokenId},
- OwnCoin, OwnCoins,
- };
- use crate::serial;
- use crate::{Error, Result};
- pub type WalletPtr = Arc<WalletDb>;
- #[derive(Debug, Clone)]
- pub struct Keypair {
- pub public: PublicKey,
- pub private: SecretKey,
- }
- #[derive(Debug, Clone)]
- pub struct Balance {
- pub token_id: TokenId,
- pub value: u64,
- pub nullifier: Nullifier,
- }
- #[derive(Debug, Clone)]
- pub struct Balances {
- pub list: Vec<Balance>,
- }
- impl Balances {
- pub fn add(&mut self, balance: &Balance) {
- if let Some(mut saved_balance) = self
- .list
- .iter_mut()
- .find(|b| b.token_id == balance.token_id)
- {
- saved_balance.value += balance.value;
- } else {
- self.list.push(balance.clone());
- }
- }
- }
- //#[derive(Clone)]
- pub struct WalletDb {
- pub path: PathBuf,
- pub password: String,
- }
- impl WalletApi for WalletDb {
- fn get_password(&self) -> String {
- self.password.to_owned()
- }
- fn get_path(&self) -> PathBuf {
- self.path.to_owned()
- }
- }
- impl WalletDb {
- pub fn new(path: &Path, password: String) -> Result<WalletPtr> {
- debug!(target: "WALLETDB", "new() Constructor called");
- Ok(Arc::new(Self {
- path: path.to_owned(),
- password,
- }))
- }
- pub fn init_db(&self) -> Result<()> {
- debug!(target: "WALLETDB", "Initialize...");
- if !self.password.trim().is_empty() {
- let contents = include_str!("../../sql/schema.sql");
- let conn = Connection::open(&self.path)?;
- debug!(target: "WALLETDB", "OPENED CONNECTION AT PATH {:?}", self.path);
- conn.pragma_update(None, "key", &self.password)?;
- conn.execute_batch(contents)?;
- } else {
- debug!(
- target: "WALLETDB",
- "Password is empty. You must set a password to use the wallet."
- );
- return Err(Error::from(ClientFailed::EmptyPassword));
- }
- Ok(())
- }
- pub fn key_gen(&self) -> Result<()> {
- debug!(target: "WALLETDB", "Attempting to generate keys...");
- let conn = Connection::open(&self.path)?;
- conn.pragma_update(None, "key", &self.password)?;
- let mut stmt = conn.prepare("SELECT * FROM keys WHERE key_id > ?")?;
- let key_check = stmt.exists(params!["0"])?;
- if !key_check {
- let secret: SecretKey = SecretKey::random(&mut OsRng);
- let public: PublicKey = zcash_primitives::constants::SPENDING_KEY_GENERATOR * secret;
- self.put_keypair(&public, &secret)?;
- } else {
- debug!(target: "WALLETDB", "Keys already exist.");
- return Err(Error::from(ClientFailed::KeyExists));
- }
- Ok(())
- }
- pub fn put_keypair(&self, key_public: &PublicKey, key_private: &SecretKey) -> Result<()> {
- let conn = Connection::open(&self.path)?;
- conn.pragma_update(None, "key", &self.password)?;
- let key_public = serial::serialize(key_public);
- let key_private = serial::serialize(key_private);
- conn.execute(
- "INSERT INTO keys(key_public, key_private) VALUES (?1, ?2)",
- params![key_public, key_private],
- )?;
- Ok(())
- }
- pub fn get_keypairs(&self) -> Result<Vec<Keypair>> {
- debug!(target: "WALLETDB", "Returning keypairs...");
- let conn = Connection::open(&self.path)?;
- conn.pragma_update(None, "key", &self.password)?;
- let mut stmt = conn.prepare("SELECT * FROM keys")?;
- // this just gets the first key. maybe we should randomize this
- let key_iter = stmt.query_map([], |row| Ok((row.get(1)?, row.get(2)?)))?;
- let mut keypairs = Vec::new();
- for key in key_iter {
- let key = key?;
- let public = key.0;
- let private = key.1;
- let public: PublicKey = self.get_value_deserialized(public)?;
- let private: SecretKey = self.get_value_deserialized(private)?;
- keypairs.push(Keypair { public, private });
- }
- Ok(keypairs)
- }
- pub fn get_own_coins(&self) -> Result<OwnCoins> {
- debug!(target: "WALLETDB", "Get own coins");
- let is_spent = 0;
- let conn = Connection::open(&self.path)?;
- // unlock database
- conn.pragma_update(None, "key", &self.password)?;
- let mut coins = conn.prepare("SELECT * FROM coins WHERE is_spent = :is_spent ;")?;
- let rows = coins.query_map(&[(":is_spent", &is_spent)], |row| {
- Ok((
- row.get(0)?,
- row.get(1)?,
- row.get(2)?,
- row.get(3)?,
- row.get(4)?,
- row.get(5)?,
- row.get(6)?,
- row.get(7)?,
- row.get(9)?,
- ))
- })?;
- let mut own_coins = Vec::new();
- for row in rows {
- let row = row?;
- let coin = self.get_value_deserialized(row.0)?;
- // note
- let serial = self.get_value_deserialized(row.1)?;
- let coin_blind = self.get_value_deserialized(row.2)?;
- let valcom_blind = self.get_value_deserialized(row.3)?;
- let value: u64 = row.4;
- let token_id = self.get_value_deserialized(row.5)?;
- let note = Note {
- serial,
- value,
- token_id,
- coin_blind,
- valcom_blind,
- };
- let witness = self.get_value_deserialized(row.6)?;
- let secret: SecretKey = self.get_value_deserialized(row.7)?;
- let nullifier: Nullifier = self.get_value_deserialized(row.8)?;
- let oc = OwnCoin {
- coin,
- note,
- secret,
- witness,
- nullifier,
- };
- own_coins.push(oc)
- }
- Ok(own_coins)
- }
- pub fn put_own_coins(&self, own_coin: OwnCoin) -> Result<()> {
- debug!(target: "WALLETDB", "Put own coins");
- // open connection
- let conn = Connection::open(&self.path)?;
- // unlock database
- conn.pragma_update(None, "key", &self.password)?;
- let coin = self.get_value_serialized(&own_coin.coin.repr)?;
- let serial = self.get_value_serialized(&own_coin.note.serial)?;
- let coin_blind = self.get_value_serialized(&own_coin.note.coin_blind)?;
- let valcom_blind = self.get_value_serialized(&own_coin.note.valcom_blind)?;
- let value: u64 = own_coin.note.value;
- let token_id = self.get_value_serialized(&own_coin.note.token_id)?;
- let witness = self.get_value_serialized(&own_coin.witness)?;
- let secret = self.get_value_serialized(&own_coin.secret)?;
- let is_spent = 0;
- let nullifier = self.get_value_serialized(&own_coin.nullifier)?;
- conn.execute(
- "INSERT OR REPLACE INTO coins
- (coin, serial, value, token_id, coin_blind,
- valcom_blind, witness, secret, is_spent, nullifier)
- VALUES
- (:coin, :serial, :value, :token_id, :coin_blind,
- :valcom_blind, :witness, :secret, :is_spent, :nullifier);",
- named_params! {
- ":coin": coin,
- ":serial": serial,
- ":value": value,
- ":token_id": token_id,
- ":coin_blind": coin_blind,
- ":valcom_blind": valcom_blind,
- ":witness": witness,
- ":secret": secret,
- ":is_spent": is_spent,
- ":nullifier": nullifier,
- },
- )?;
- Ok(())
- }
- pub fn remove_own_coins(&self) -> Result<()> {
- debug!(target: "WALLETDB", "Remove own coins");
- // open connection
- let conn = Connection::open(&self.path)?;
- // unlock database
- conn.pragma_update(None, "key", &self.password)?;
- conn.execute("DROP TABLE coins;", [])?;
- Ok(())
- }
- pub fn confirm_spend_coin(&self, coin: &Coin) -> Result<()> {
- debug!(target: "WALLETDB", "Confirm spend coin");
- let coin = self.get_value_serialized(coin)?;
- // open connection
- let conn = Connection::open(&self.path)?;
- // unlock database
- conn.pragma_update(None, "key", &self.password)?;
- let is_spent = 1;
- conn.execute(
- "UPDATE coins
- SET is_spent = ?1
- WHERE coin = ?2 ;",
- params![is_spent, coin],
- )?;
- Ok(())
- }
- pub fn get_witnesses(&self) -> Result<HashMap<Vec<u8>, IncrementalWitness<MerkleNode>>> {
- let conn = Connection::open(&self.path)?;
- conn.pragma_update(None, "key", &self.password)?;
- let is_spent = 0;
- let mut witnesses =
- conn.prepare("SELECT coin, witness FROM coins WHERE is_spent = :is_spent;")?;
- let rows = witnesses.query_map(&[(":is_spent", &is_spent)], |row| {
- Ok((row.get(0)?, row.get(1)?))
- })?;
- let mut witnesses = HashMap::new();
- for i in rows {
- let i = i?;
- let coin: Vec<u8> = i.0;
- let witness: IncrementalWitness<MerkleNode> = self.get_value_deserialized(i.1)?;
- witnesses.insert(coin, witness);
- }
- Ok(witnesses)
- }
- pub fn update_witnesses(
- &self,
- witnesses: HashMap<Vec<u8>, IncrementalWitness<MerkleNode>>,
- ) -> Result<()> {
- debug!(target: "WALLETDB", "Updating witness");
- let conn = Connection::open(&self.path)?;
- conn.pragma_update(None, "key", &self.password)?;
- for (coin, witness) in witnesses.iter() {
- let witness = self.get_value_serialized(witness)?;
- let is_spent = 0;
- conn.execute(
- "UPDATE coins SET witness = ?1 WHERE coin = ?2 AND is_spent = ?3",
- params![witness, coin, is_spent],
- )?;
- }
- Ok(())
- }
- pub fn get_balances(&self) -> Result<Balances> {
- debug!(target: "WALLETDB", "Get token and balances...");
- let conn = Connection::open(&self.path)?;
- conn.pragma_update(None, "key", &self.password)?;
- let is_spent = 0;
- let mut stmt = conn.prepare(
- "SELECT value, token_id, nullifier FROM coins WHERE is_spent = :is_spent ;",
- )?;
- let rows = stmt.query_map(&[(":is_spent", &is_spent)], |row| {
- Ok((row.get(0)?, row.get(1)?, row.get(2)?))
- })?;
- let mut balances = Balances { list: Vec::new() };
- for row in rows {
- let row = row?;
- let value: u64 = row.0;
- let token_id: TokenId = self.get_value_deserialized(row.1)?;
- let nullifier: Nullifier = self.get_value_deserialized(row.2)?;
- balances.add(&Balance {
- token_id,
- value,
- nullifier,
- });
- }
- Ok(balances)
- }
- pub fn get_token_id(&self) -> Result<Vec<TokenId>> {
- debug!(target: "WALLETDB", "Get token ID...");
- let conn = Connection::open(&self.path)?;
- conn.pragma_update(None, "key", &self.password)?;
- let is_spent = 0;
- let mut stmt = conn.prepare("SELECT token_id FROM coins WHERE is_spent = :is_spent ;")?;
- let rows = stmt.query_map(&[(":is_spent", &is_spent)], |row| row.get(0))?;
- let mut token_ids = Vec::new();
- for row in rows {
- let row = row?;
- let token_id = self.get_value_deserialized(row).unwrap();
- token_ids.push(token_id);
- }
- Ok(token_ids)
- }
- pub fn token_id_exists(&self, token_id: &TokenId) -> Result<bool> {
- debug!(target: "WALLETDB", "Check tokenID exists");
- let conn = Connection::open(&self.path)?;
- conn.pragma_update(None, "key", &self.password)?;
- let id = self.get_value_serialized(token_id)?;
- let is_spent = 0;
- let mut stmt = conn.prepare("SELECT * FROM coins WHERE token_id = ? AND is_spent = ? ;")?;
- let id_check = stmt.exists(params![id, is_spent])?;
- Ok(id_check)
- }
- pub fn test_wallet(&self) -> Result<()> {
- let conn = Connection::open(&self.path)?;
- conn.pragma_update(None, "key", &self.password)?;
- let mut stmt = conn.prepare("SELECT * FROM keys")?;
- let _rows = stmt.query([])?;
- Ok(())
- }
- }
- #[cfg(test)]
- mod tests {
- // TODO: Clean up, there's a lot of duplicated code here.
- use super::*;
- use crate::crypto::{
- coin::Coin,
- types::{derive_publickey, CoinBlind, NullifierSerial, ValueCommitBlind},
- OwnCoin,
- };
- use crate::util::join_config_path;
- use ff::PrimeField;
- pub fn init_db(path: &Path, password: String) -> Result<()> {
- if !password.trim().is_empty() {
- let contents = include_str!("../../sql/schema.sql");
- let conn = Connection::open(path)?;
- debug!(target: "WALLETDB", "OPENED CONNECTION AT PATH {:?}", path);
- conn.pragma_update(None, "key", &password)?;
- conn.execute_batch(contents)?;
- } else {
- debug!(
- target: "WALLETDB", "Password is empty. You must set a password to use the wallet."
- );
- return Err(Error::from(ClientFailed::EmptyPassword));
- }
- Ok(())
- }
- #[test]
- pub fn test_get_token_id() -> Result<()> {
- let walletdb_path = join_config_path(&PathBuf::from("test_wallet.db"))?;
- let password: String = "darkfi".into();
- let wallet = WalletDb::new(&walletdb_path, password.clone())?;
- init_db(&walletdb_path, password)?;
- let secret = SecretKey::random(&mut OsRng);
- let public = derive_publickey(secret);
- wallet.put_keypair(&public, &secret)?;
- let token_id = TokenId::random(&mut OsRng);
- let note = Note {
- serial: NullifierSerial::random(&mut OsRng),
- value: 110,
- token_id,
- coin_blind: CoinBlind::random(&mut OsRng),
- valcom_blind: ValueCommitBlind::random(&mut OsRng),
- };
- let coin = Coin::new(bls12_381::Scalar::random(&mut OsRng).to_repr());
- let mut tree = crate::crypto::merkle::CommitmentTree::empty();
- tree.append(MerkleNode::from_coin(&coin))?;
- let witness = IncrementalWitness::from_tree(&tree);
- let nullifier = Nullifier::new(coin.repr);
- let own_coin = OwnCoin {
- coin,
- note,
- secret,
- witness,
- nullifier,
- };
- wallet.put_own_coins(own_coin.clone())?;
- wallet.put_own_coins(own_coin.clone())?;
- wallet.put_own_coins(own_coin.clone())?;
- wallet.put_own_coins(own_coin)?;
- let id = wallet.get_token_id()?;
- assert_eq!(id.len(), 1);
- for i in id {
- assert_eq!(i, token_id);
- assert!(wallet.token_id_exists(&i)?);
- }
- std::fs::remove_file(walletdb_path)?;
- Ok(())
- }
- #[test]
- pub fn test_get_balances() -> Result<()> {
- let walletdb_path = join_config_path(&PathBuf::from("test2_wallet.db"))?;
- let password: String = "darkfi".into();
- let wallet = WalletDb::new(&walletdb_path, password.clone())?;
- init_db(&walletdb_path, password)?;
- let secret = SecretKey::random(&mut OsRng);
- let public = derive_publickey(secret);
- wallet.put_keypair(&public, &secret)?;
- let token_id = TokenId::random(&mut OsRng);
- let note = Note {
- serial: NullifierSerial::random(&mut OsRng),
- value: 110,
- token_id,
- coin_blind: CoinBlind::random(&mut OsRng),
- valcom_blind: ValueCommitBlind::random(&mut OsRng),
- };
- let coin = Coin::new(bls12_381::Scalar::random(&mut OsRng).to_repr());
- let mut tree = crate::crypto::merkle::CommitmentTree::empty();
- tree.append(MerkleNode::from_coin(&coin))?;
- let witness = IncrementalWitness::from_tree(&tree);
- let nullifier = Nullifier::new(coin.repr);
- let own_coin = OwnCoin {
- coin,
- note,
- secret,
- witness,
- nullifier,
- };
- wallet.put_own_coins(own_coin.clone())?;
- wallet.put_own_coins(own_coin.clone())?;
- wallet.put_own_coins(own_coin.clone())?;
- wallet.put_own_coins(own_coin)?;
- let balances = wallet.get_balances()?;
- assert_eq!(balances.list.len(), 1);
- assert_eq!(balances.list[0].value, 110);
- assert_eq!(balances.list[0].token_id, token_id);
- std::fs::remove_file(walletdb_path)?;
- Ok(())
- }
- #[test]
- pub fn test_save_and_load_keypair() -> Result<()> {
- let walletdb_path = join_config_path(&PathBuf::from("test3_wallet.db"))?;
- let password: String = "darkfi".into();
- let wallet = WalletDb::new(&walletdb_path, password.clone())?;
- init_db(&walletdb_path, password)?;
- let secret = SecretKey::random(&mut OsRng);
- let public = derive_publickey(secret);
- wallet.put_keypair(&public, &secret)?;
- let keypair = wallet.get_keypairs()?[0].clone();
- assert_eq!(public, keypair.public);
- assert_eq!(secret, keypair.private);
- std::fs::remove_file(walletdb_path)?;
- Ok(())
- }
- #[test]
- pub fn test_put_and_get_own_coins() -> Result<()> {
- let walletdb_path = join_config_path(&PathBuf::from("test4_wallet.db"))?;
- let password: String = "darkfi".into();
- let wallet = WalletDb::new(&walletdb_path, password.clone())?;
- init_db(&walletdb_path, password)?;
- let secret = SecretKey::random(&mut OsRng);
- let public = derive_publickey(secret);
- wallet.put_keypair(&public, &secret)?;
- let note = Note {
- serial: NullifierSerial::random(&mut OsRng),
- value: 110,
- token_id: TokenId::random(&mut OsRng),
- coin_blind: CoinBlind::random(&mut OsRng),
- valcom_blind: ValueCommitBlind::random(&mut OsRng),
- };
- let coin = Coin::new(bls12_381::Scalar::random(&mut OsRng).to_repr());
- let mut tree = crate::crypto::merkle::CommitmentTree::empty();
- tree.append(MerkleNode::from_coin(&coin))?;
- let witness = IncrementalWitness::from_tree(&tree);
- let coin_ser = crate::serial::serialize(&coin.repr);
- assert_eq!(coin, crate::serial::deserialize(&coin_ser)?);
- let nullifier = Nullifier::new(coin.repr);
- let own_coin = OwnCoin {
- coin,
- note: note.clone(),
- secret,
- witness: witness.clone(),
- nullifier: nullifier.clone(),
- };
- wallet.put_own_coins(own_coin)?;
- let own_coin = wallet.get_own_coins()?[0].clone();
- assert_eq!(&own_coin.note.valcom_blind, ¬e.valcom_blind);
- assert_eq!(&own_coin.note.coin_blind, ¬e.coin_blind);
- assert_eq!(own_coin.secret, secret);
- assert_eq!(own_coin.witness.root(), witness.root());
- assert_eq!(own_coin.witness.path(), witness.path());
- assert_eq!(own_coin.nullifier, nullifier);
- wallet.confirm_spend_coin(&own_coin.coin)?;
- let own_coins = wallet.get_own_coins()?;
- assert_eq!(own_coins.len(), 0);
- wallet.put_own_coins(own_coin)?;
- let own_coins = wallet.get_own_coins()?;
- assert_eq!(own_coins.len(), 1);
- wallet.remove_own_coins()?;
- std::fs::remove_file(walletdb_path)?;
- Ok(())
- }
- #[test]
- pub fn test_get_witnesses_and_update_them() -> Result<()> {
- let walletdb_path = join_config_path(&PathBuf::from("test5_wallet.db"))?;
- let password: String = "darkfi".into();
- let wallet = WalletDb::new(&walletdb_path, password.clone())?;
- init_db(&walletdb_path, password)?;
- let secret = SecretKey::random(&mut OsRng);
- let public = derive_publickey(secret);
- wallet.put_keypair(&public, &secret)?;
- let mut tree = crate::crypto::merkle::CommitmentTree::empty();
- let note = Note {
- serial: NullifierSerial::random(&mut OsRng),
- value: 110,
- token_id: TokenId::random(&mut OsRng),
- coin_blind: CoinBlind::random(&mut OsRng),
- valcom_blind: ValueCommitBlind::random(&mut OsRng),
- };
- let coin = Coin::new(bls12_381::Scalar::random(&mut OsRng).to_repr());
- let node = MerkleNode::from_coin(&coin);
- tree.append(node)?;
- tree.append(node)?;
- tree.append(node)?;
- tree.append(node)?;
- let witness = IncrementalWitness::from_tree(&tree);
- // for testing
- let nullifier = Nullifier::new(coin.repr);
- let own_coin = OwnCoin {
- coin,
- note,
- secret,
- witness,
- nullifier,
- };
- wallet.put_own_coins(own_coin.clone())?;
- wallet.put_own_coins(own_coin.clone())?;
- wallet.put_own_coins(own_coin.clone())?;
- wallet.put_own_coins(own_coin)?;
- let coin2 = Coin::new(bls12_381::Scalar::random(&mut OsRng).to_repr());
- let node2 = MerkleNode::from_coin(&coin2);
- tree.append(node2)?;
- let mut updated_witnesses = wallet.get_witnesses()?;
- updated_witnesses.iter_mut().for_each(|(_, witness)| {
- witness.append(node2).expect("Append to witness");
- });
- wallet.update_witnesses(updated_witnesses)?;
- for (_, witness) in wallet.get_witnesses()?.iter() {
- assert_eq!(tree.root(), witness.root());
- }
- std::fs::remove_file(walletdb_path)?;
- Ok(())
- }
- }
|