use std::cell::RefCell; use std::cmp; use std::collections::{BTreeMap, HashSet, VecDeque}; use std::convert::TryFrom; use std::io::{Read, Write}; use std::str::FromStr; use std::time::{Instant, SystemTime, UNIX_EPOCH}; use bitcoin::blockdata::opcodes; use bitcoin::blockdata::script::Builder; use bitcoin::consensus::encode::serialize; use bitcoin::secp256k1::{All, Secp256k1}; use bitcoin::util::bip32::{ChildNumber, DerivationPath}; use bitcoin::util::psbt::PartiallySignedTransaction as PSBT; use bitcoin::{ Address, Network, OutPoint, PublicKey, Script, SigHashType, Transaction, TxIn, TxOut, Txid, }; use miniscript::BitcoinSig; #[allow(unused_imports)] use log::{debug, error, info, trace}; pub mod offline_stream; pub mod utils; use self::utils::{ChunksIterator, IsDust}; use crate::database::{BatchDatabase, BatchOperations}; use crate::descriptor::{get_checksum, DescriptorMeta, ExtendedDescriptor, ExtractPolicy, Policy}; use crate::error::Error; use crate::psbt::{utils::PSBTUtils, PSBTSatisfier, PSBTSigner}; use crate::signer::Signer; use crate::types::*; #[cfg(any(feature = "electrum", feature = "default"))] use electrum_client::types::*; #[cfg(any(feature = "electrum", feature = "default"))] use electrum_client::Client; #[cfg(not(any(feature = "electrum", feature = "default")))] use std::marker::PhantomData as Client; pub struct Wallet { descriptor: ExtendedDescriptor, change_descriptor: Option, network: Network, client: Option>>, database: RefCell, // TODO: save descriptor checksum and check when loading _secp: Secp256k1, } // offline actions, always available impl Wallet where S: Read + Write, D: BatchDatabase, { pub fn new_offline( descriptor: &str, change_descriptor: Option<&str>, network: Network, mut database: D, ) -> Result { database.check_descriptor_checksum( ScriptType::External, get_checksum(descriptor)?.as_bytes(), )?; let descriptor = ExtendedDescriptor::from_str(descriptor)?; let change_descriptor = match change_descriptor { Some(desc) => { database.check_descriptor_checksum( ScriptType::Internal, get_checksum(desc)?.as_bytes(), )?; Some(ExtendedDescriptor::from_str(desc)?) } None => None, }; // TODO: make sure that both descriptor have the same structure Ok(Wallet { descriptor, change_descriptor, network, client: None, database: RefCell::new(database), _secp: Secp256k1::gen_new(), }) } pub fn get_new_address(&self) -> Result { let index = self .database .borrow_mut() .increment_last_index(ScriptType::External)?; // TODO: refill the address pool if index is close to the last cached addr self.descriptor .derive(index)? .address(self.network) .ok_or(Error::ScriptDoesntHaveAddressForm) } pub fn is_mine(&self, script: &Script) -> Result { self.get_path(script).map(|x| x.is_some()) } pub fn list_unspent(&self) -> Result, Error> { self.database.borrow().iter_utxos() } pub fn list_transactions(&self, include_raw: bool) -> Result, Error> { self.database.borrow().iter_txs(include_raw) } pub fn get_balance(&self) -> Result { Ok(self .list_unspent()? .iter() .fold(0, |sum, i| sum + i.txout.value)) } // TODO: add a flag to ignore change in coin selection pub fn create_tx( &self, addressees: Vec<(Address, u64)>, send_all: bool, fee_perkb: f32, policy_path: Option>>, utxos: Option>, unspendable: Option>, ) -> Result<(PSBT, TransactionDetails), Error> { let policy = self.descriptor.extract_policy().unwrap(); if policy.requires_path() && policy_path.is_none() { return Err(Error::SpendingPolicyRequired); } let requirements = policy_path.map_or(Ok(Default::default()), |path| { policy.get_requirements(&path) })?; debug!("requirements: {:?}", requirements); let mut tx = Transaction { version: 2, lock_time: requirements.timelock.unwrap_or(0), input: vec![], output: vec![], }; let fee_rate = fee_perkb * 100_000.0; if send_all && addressees.len() != 1 { return Err(Error::SendAllMultipleOutputs); } // we keep it as a float while we accumulate it, and only round it at the end let mut fee_val: f32 = 0.0; let mut outgoing: u64 = 0; let mut received: u64 = 0; let calc_fee_bytes = |wu| (wu as f32) * fee_rate / 4.0; fee_val += calc_fee_bytes(tx.get_weight()); for (index, (address, satoshi)) in addressees.iter().enumerate() { let value = match send_all { true => 0, false if satoshi.is_dust() => return Err(Error::OutputBelowDustLimit(index)), false => *satoshi, }; // TODO: check address network if self.is_mine(&address.script_pubkey())? { received += value; } let new_out = TxOut { script_pubkey: address.script_pubkey(), value, }; fee_val += calc_fee_bytes(serialize(&new_out).len() * 4); tx.output.push(new_out); outgoing += value; } // TODO: assumes same weight to spend external and internal let input_witness_weight = self.descriptor.max_satisfaction_weight(); let (available_utxos, use_all_utxos) = self.get_available_utxos(&utxos, &unspendable, send_all)?; let (mut inputs, paths, selected_amount, mut fee_val) = self.coin_select( available_utxos, use_all_utxos, fee_rate, outgoing, input_witness_weight, fee_val, )?; inputs .iter_mut() .for_each(|i| i.sequence = requirements.csv.unwrap_or(0xFFFFFFFF)); tx.input.append(&mut inputs); // prepare the change output let change_output = match send_all { true => None, false => { let change_script = self.get_change_address()?; let change_output = TxOut { script_pubkey: change_script, value: 0, }; // take the change into account for fees fee_val += calc_fee_bytes(serialize(&change_output).len() * 4); Some(change_output) } }; let change_val = selected_amount - outgoing - (fee_val.ceil() as u64); if !send_all && !change_val.is_dust() { let mut change_output = change_output.unwrap(); change_output.value = change_val; received += change_val; tx.output.push(change_output); } else if send_all && !change_val.is_dust() { // set the outgoing value to whatever we've put in outgoing = selected_amount; // there's only one output, send everything to it tx.output[0].value = change_val; // send_all to our address if self.is_mine(&tx.output[0].script_pubkey)? { received = change_val; } } else if send_all { // send_all but the only output would be below dust limit return Err(Error::InsufficientFunds); // TODO: or OutputBelowDustLimit? } // TODO: shuffle the outputs let txid = tx.txid(); let mut psbt = PSBT::from_unsigned_tx(tx)?; // add metadata for the inputs for ((psbt_input, (script_type, path)), input) in psbt .inputs .iter_mut() .zip(paths.into_iter()) .zip(psbt.global.unsigned_tx.input.iter()) { let path: Vec = path.into(); let index = match path.last() { Some(ChildNumber::Normal { index }) => *index, Some(ChildNumber::Hardened { index }) => *index, None => 0, }; let desc = self.get_descriptor_for(script_type); psbt_input.hd_keypaths = desc.get_hd_keypaths(index).unwrap(); let derived_descriptor = desc.derive(index).unwrap(); // TODO: figure out what do redeem_script and witness_script mean psbt_input.redeem_script = derived_descriptor.psbt_redeem_script(); psbt_input.witness_script = derived_descriptor.psbt_witness_script(); let prev_output = input.previous_output; let prev_tx = self .database .borrow() .get_raw_tx(&prev_output.txid)? .unwrap(); // TODO: remove unwrap if derived_descriptor.is_witness() { psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone()); } else { psbt_input.non_witness_utxo = Some(prev_tx); }; // we always sign with SIGHASH_ALL psbt_input.sighash_type = Some(SigHashType::All); } // TODO: add metadata for the outputs, like derivation paths for change addrs /*for psbt_output in psbt.outputs.iter_mut().zip(psbt.global.unsigned_tx.output.iter()) { }*/ let transaction_details = TransactionDetails { transaction: None, txid: txid, timestamp: Self::get_timestamp(), received, sent: outgoing, height: None, }; Ok((psbt, transaction_details)) } // TODO: define an enum for signing errors pub fn sign(&self, mut psbt: PSBT) -> Result<(PSBT, bool), Error> { let tx = &psbt.global.unsigned_tx; let mut input_utxos = Vec::with_capacity(psbt.inputs.len()); for n in 0..psbt.inputs.len() { input_utxos.push(psbt.get_utxo_for(n).clone()); } // try to add hd_keypaths if we've already seen the output for (psbt_input, out) in psbt.inputs.iter_mut().zip(input_utxos.iter()) { debug!("searching hd_keypaths for out: {:?}", out); if let Some(out) = out { let option_path = self .database .borrow() .get_path_from_script_pubkey(&out.script_pubkey)?; debug!("found descriptor path {:?}", option_path); let (script_type, path) = match option_path { None => continue, Some((script_type, path)) => (script_type, path), }; // TODO: this is duplicated code let index = match path.into_iter().last() { Some(ChildNumber::Normal { index }) => *index, Some(ChildNumber::Hardened { index }) => *index, None => 0, }; // merge hd_keypaths let desc = self.get_descriptor_for(script_type); let mut hd_keypaths = desc.get_hd_keypaths(index)?; psbt_input.hd_keypaths.append(&mut hd_keypaths); } } let mut signer = PSBTSigner::from_descriptor(&psbt.global.unsigned_tx, &self.descriptor)?; if let Some(desc) = &self.change_descriptor { let change_signer = PSBTSigner::from_descriptor(&psbt.global.unsigned_tx, desc)?; signer.extend(change_signer)?; } // sign everything we can. TODO: ideally we should only sign with the keys in the policy // path selected, if present for (i, input) in psbt.inputs.iter_mut().enumerate() { let sighash = input.sighash_type.unwrap_or(SigHashType::All); let prevout = tx.input[i].previous_output; let mut partial_sigs = BTreeMap::new(); { let mut push_sig = |pubkey: &PublicKey, opt_sig: Option| { if let Some((signature, sighash)) = opt_sig { let mut concat_sig = Vec::new(); concat_sig.extend_from_slice(&signature.serialize_der()); concat_sig.extend_from_slice(&[sighash as u8]); //input.partial_sigs.insert(*pubkey, concat_sig); partial_sigs.insert(*pubkey, concat_sig); } }; if let Some(non_wit_utxo) = &input.non_witness_utxo { if non_wit_utxo.txid() != prevout.txid { return Err(Error::InputTxidMismatch((non_wit_utxo.txid(), prevout))); } let prev_script = &non_wit_utxo.output [psbt.global.unsigned_tx.input[i].previous_output.vout as usize] .script_pubkey; // return (signature, sighash) from here let sign_script = if let Some(redeem_script) = &input.redeem_script { if &redeem_script.to_p2sh() != prev_script { return Err(Error::InputRedeemScriptMismatch(( prev_script.clone(), redeem_script.clone(), ))); } redeem_script } else { prev_script }; for (pubkey, (fing, path)) in &input.hd_keypaths { push_sig( pubkey, signer.sig_legacy_from_fingerprint( i, sighash, fing, path, sign_script, )?, ); } // TODO: this sucks, we sign with every key for pubkey in signer.all_public_keys() { push_sig( pubkey, signer.sig_legacy_from_pubkey(i, sighash, pubkey, sign_script)?, ); } } else if let Some(witness_utxo) = &input.witness_utxo { let value = witness_utxo.value; let script = match &input.redeem_script { Some(script) if script.to_p2sh() != witness_utxo.script_pubkey => { return Err(Error::InputRedeemScriptMismatch(( witness_utxo.script_pubkey.clone(), script.clone(), ))) } Some(script) => script, None => &witness_utxo.script_pubkey, }; let sign_script = if script.is_v0_p2wpkh() { self.to_p2pkh(&script.as_bytes()[2..]) } else if script.is_v0_p2wsh() { match &input.witness_script { None => Err(Error::InputMissingWitnessScript(i)), Some(witness_script) if script != &witness_script.to_v0_p2wsh() => { Err(Error::InputRedeemScriptMismatch(( script.clone(), witness_script.clone(), ))) } Some(witness_script) => Ok(witness_script), }? .clone() } else { return Err(Error::InputUnknownSegwitScript(script.clone())); }; for (pubkey, (fing, path)) in &input.hd_keypaths { push_sig( pubkey, signer.sig_segwit_from_fingerprint( i, sighash, fing, path, &sign_script, value, )?, ); } // TODO: this sucks, we sign with every key for pubkey in signer.all_public_keys() { push_sig( pubkey, signer.sig_segwit_from_pubkey( i, sighash, pubkey, &sign_script, value, )?, ); } } else { return Err(Error::MissingUTXO); } } // push all the signatures into the psbt input.partial_sigs.append(&mut partial_sigs); } // attempt to finalize let finalized = self.finalize_psbt(tx.clone(), &mut psbt); Ok((psbt, finalized)) } pub fn policies(&self, script_type: ScriptType) -> Result, Error> { match (script_type, self.change_descriptor.as_ref()) { (ScriptType::External, _) => Ok(self.descriptor.extract_policy()), (ScriptType::Internal, None) => Ok(None), (ScriptType::Internal, Some(desc)) => Ok(desc.extract_policy()), } } // Internals fn get_timestamp() -> u64 { SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs() } fn get_path(&self, script: &Script) -> Result, Error> { self.database.borrow().get_path_from_script_pubkey(script) } fn get_descriptor_for(&self, script_type: ScriptType) -> &ExtendedDescriptor { let desc = match script_type { ScriptType::External => &self.descriptor, ScriptType::Internal => &self.change_descriptor.as_ref().unwrap_or(&self.descriptor), }; desc } fn to_p2pkh(&self, pubkey_hash: &[u8]) -> Script { Builder::new() .push_opcode(opcodes::all::OP_DUP) .push_opcode(opcodes::all::OP_HASH160) .push_slice(pubkey_hash) .push_opcode(opcodes::all::OP_EQUALVERIFY) .push_opcode(opcodes::all::OP_CHECKSIG) .into_script() } fn get_change_address(&self) -> Result { let (desc, script_type) = if self.change_descriptor.is_none() { (&self.descriptor, ScriptType::External) } else { ( self.change_descriptor.as_ref().unwrap(), ScriptType::Internal, ) }; // TODO: refill the address pool if index is close to the last cached addr let index = self .database .borrow_mut() .increment_last_index(script_type)?; Ok(desc.derive(index)?.script_pubkey()) } fn get_available_utxos( &self, utxo: &Option>, unspendable: &Option>, send_all: bool, ) -> Result<(Vec, bool), Error> { // TODO: should we consider unconfirmed received rbf txs as "unspendable" too by default? let unspendable_set = match unspendable { None => HashSet::new(), Some(vec) => vec.into_iter().collect(), }; match utxo { // with manual coin selection we always want to spend all the selected utxos, no matter // what (even if they are marked as unspendable) Some(raw_utxos) => { // TODO: unwrap to remove let full_utxos: Vec<_> = raw_utxos .iter() .map(|u| self.database.borrow().get_utxo(&u).unwrap()) .collect(); if !full_utxos.iter().all(|u| u.is_some()) { return Err(Error::UnknownUTXO); } Ok((full_utxos.into_iter().map(|x| x.unwrap()).collect(), true)) } // otherwise limit ourselves to the spendable utxos and the `send_all` setting None => Ok(( self.list_unspent()? .into_iter() .filter(|u| !unspendable_set.contains(&u.outpoint)) .collect(), send_all, )), } } fn coin_select( &self, mut utxos: Vec, use_all_utxos: bool, fee_rate: f32, outgoing: u64, input_witness_weight: usize, mut fee_val: f32, ) -> Result<(Vec, Vec<(ScriptType, DerivationPath)>, u64, f32), Error> { let mut answer = Vec::new(); let mut paths = Vec::new(); let calc_fee_bytes = |wu| (wu as f32) * fee_rate / 4.0; debug!( "coin select: outgoing = `{}`, fee_val = `{}`, fee_rate = `{}`", outgoing, fee_val, fee_rate ); // sort so that we pick them starting from the larger. TODO: proper coin selection utxos.sort_by(|a, b| a.txout.value.partial_cmp(&b.txout.value).unwrap()); let mut selected_amount: u64 = 0; while use_all_utxos || selected_amount < outgoing + (fee_val.ceil() as u64) { let utxo = match utxos.pop() { Some(utxo) => utxo, None if selected_amount < outgoing + (fee_val.ceil() as u64) => { return Err(Error::InsufficientFunds) } None if use_all_utxos => break, None => return Err(Error::InsufficientFunds), }; let new_in = TxIn { previous_output: utxo.outpoint, script_sig: Script::default(), sequence: 0xFFFFFFFD, // TODO: change according to rbf/csv witness: vec![], }; fee_val += calc_fee_bytes(serialize(&new_in).len() * 4 + input_witness_weight); debug!("coin select new fee_val = `{}`", fee_val); answer.push(new_in); selected_amount += utxo.txout.value; let path = self .database .borrow() .get_path_from_script_pubkey(&utxo.txout.script_pubkey)? .unwrap(); // TODO: remove unrwap paths.push(path); } Ok((answer, paths, selected_amount, fee_val)) } fn finalize_psbt(&self, mut tx: Transaction, psbt: &mut PSBT) -> bool { for (n, input) in tx.input.iter_mut().enumerate() { // safe to run only on the descriptor because we assume the change descriptor also has // the same structure let desc = self.descriptor.derive_from_psbt_input(psbt, n); debug!("reconstructed descriptor is {:?}", desc); let desc = match desc { Err(_) => return false, Ok(desc) => desc, }; // TODO: use height once we sync headers let satisfier = PSBTSatisfier::new(&psbt.inputs[n], None, None); match desc.satisfy(input, satisfier) { Ok(_) => continue, Err(e) => { debug!("satisfy error {:?} for input {}", e, n); return false; } } } // consume tx to extract its input's script_sig and witnesses and move them into the psbt for (input, psbt_input) in tx.input.into_iter().zip(psbt.inputs.iter_mut()) { psbt_input.final_script_sig = Some(input.script_sig); psbt_input.final_script_witness = Some(input.witness); } true } } #[cfg(any(feature = "electrum", feature = "default"))] impl Wallet where S: Read + Write, D: BatchDatabase, { pub fn new( descriptor: &str, change_descriptor: Option<&str>, network: Network, mut database: D, client: Client, ) -> Result { database.check_descriptor_checksum( ScriptType::External, get_checksum(descriptor)?.as_bytes(), )?; let descriptor = ExtendedDescriptor::from_str(descriptor)?; let change_descriptor = match change_descriptor { Some(desc) => { database.check_descriptor_checksum( ScriptType::Internal, get_checksum(desc)?.as_bytes(), )?; Some(ExtendedDescriptor::from_str(desc)?) } None => None, }; // TODO: make sure that both descriptor have the same structure Ok(Wallet { descriptor, change_descriptor, network, client: Some(RefCell::new(client)), database: RefCell::new(database), _secp: Secp256k1::gen_new(), }) } fn get_previous_output(&self, outpoint: &OutPoint) -> Option { // the fact that we visit addresses in a BFS fashion starting from the external addresses // should ensure that this query is always consistent (i.e. when we get to call this all // the transactions at a lower depth have already been indexed, so if an outpoint is ours // we are guaranteed to have it in the db). self.database .borrow() .get_raw_tx(&outpoint.txid) .unwrap() .map(|previous_tx| previous_tx.output[outpoint.vout as usize].clone()) } fn check_tx_and_descendant( &self, txid: &Txid, height: Option, cur_script: &Script, change_max_deriv: &mut u32, ) -> Result, Error> { debug!( "check_tx_and_descendant of {}, height: {:?}, script: {}", txid, height, cur_script ); let mut updates = self.database.borrow().begin_batch(); let tx = match self.database.borrow().get_tx(&txid, true)? { // TODO: do we need the raw? Some(mut saved_tx) => { // update the height if it's different (in case of reorg) if saved_tx.height != height { info!( "updating height from {:?} to {:?} for tx {}", saved_tx.height, height, txid ); saved_tx.height = height; updates.set_tx(&saved_tx)?; } debug!("already have {} in db, returning the cached version", txid); // unwrap since we explicitly ask for the raw_tx, if it's not present something // went wrong saved_tx.transaction.unwrap() } None => self .client .as_ref() .unwrap() .borrow_mut() .transaction_get(&txid)?, }; let mut incoming: u64 = 0; let mut outgoing: u64 = 0; // look for our own inputs for (i, input) in tx.input.iter().enumerate() { if let Some(previous_output) = self.get_previous_output(&input.previous_output) { if self.is_mine(&previous_output.script_pubkey)? { outgoing += previous_output.value; debug!("{} input #{} is mine, removing from utxo", txid, i); updates.del_utxo(&input.previous_output)?; } } } let mut to_check_later = vec![]; for (i, output) in tx.output.iter().enumerate() { // this output is ours, we have a path to derive it if let Some((script_type, path)) = self.get_path(&output.script_pubkey)? { debug!("{} output #{} is mine, adding utxo", txid, i); updates.set_utxo(&UTXO { outpoint: OutPoint::new(tx.txid(), i as u32), txout: output.clone(), })?; incoming += output.value; if output.script_pubkey != *cur_script { debug!("{} output #{} script {} was not current script, adding script to be checked later", txid, i, output.script_pubkey); to_check_later.push(output.script_pubkey.clone()) } // derive as many change addrs as external addresses that we've seen if script_type == ScriptType::Internal && u32::from(path.as_ref()[0]) > *change_max_deriv { *change_max_deriv = u32::from(path.as_ref()[0]); } } } let tx = TransactionDetails { txid: tx.txid(), transaction: Some(tx), received: incoming, sent: outgoing, height, timestamp: 0, }; info!("Saving tx {}", txid); updates.set_tx(&tx)?; self.database.borrow_mut().commit_batch(updates)?; Ok(to_check_later) } fn check_history( &self, script_pubkey: Script, txs: Vec, change_max_deriv: &mut u32, ) -> Result, Error> { let mut to_check_later = Vec::new(); debug!( "history of {} script {} has {} tx", Address::from_script(&script_pubkey, self.network).unwrap(), script_pubkey, txs.len() ); for tx in txs { let height: Option = match tx.height { 0 | -1 => None, x => u32::try_from(x).ok(), }; to_check_later.extend_from_slice(&self.check_tx_and_descendant( &tx.tx_hash, height, &script_pubkey, change_max_deriv, )?); } Ok(to_check_later) } pub fn sync( &self, max_address: Option, batch_query_size: Option, ) -> Result<(), Error> { debug!("begin sync..."); // TODO: consider taking an RwLock as writere here to prevent other "read-only" calls to // break because the db is in an inconsistent state let max_address = if self.descriptor.is_fixed() { 0 } else { max_address.unwrap_or(100) }; let batch_query_size = batch_query_size.unwrap_or(20); let stop_gap = batch_query_size; let path = DerivationPath::from(vec![ChildNumber::Normal { index: max_address }]); let last_addr = self .database .borrow() .get_script_pubkey_from_path(ScriptType::External, &path)?; // cache a few of our addresses if last_addr.is_none() { let mut address_batch = self.database.borrow().begin_batch(); let start = Instant::now(); for i in 0..=max_address { let derived = self.descriptor.derive(i).unwrap(); let full_path = DerivationPath::from(vec![ChildNumber::Normal { index: i }]); address_batch.set_script_pubkey( &derived.script_pubkey(), ScriptType::External, &full_path, )?; } if self.change_descriptor.is_some() { for i in 0..=max_address { let derived = self.change_descriptor.as_ref().unwrap().derive(i).unwrap(); let full_path = DerivationPath::from(vec![ChildNumber::Normal { index: i }]); address_batch.set_script_pubkey( &derived.script_pubkey(), ScriptType::Internal, &full_path, )?; } } info!( "derivation of {} addresses, took {} ms", max_address, start.elapsed().as_millis() ); self.database.borrow_mut().commit_batch(address_batch)?; } // check unconfirmed tx, delete so they are retrieved later let mut del_batch = self.database.borrow().begin_batch(); for tx in self.database.borrow().iter_txs(false)? { if tx.height.is_none() { del_batch.del_tx(&tx.txid, false)?; } } self.database.borrow_mut().commit_batch(del_batch)?; // maximum derivation index for a change address that we've seen during sync let mut change_max_deriv = 0; let mut already_checked: HashSet