diff --git a/examples/repl.rs b/examples/repl.rs index 7ee163b2..0430d962 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -25,7 +25,7 @@ use bitcoin::{Address, Network, OutPoint}; use magical_bitcoin_wallet::bitcoin; use magical_bitcoin_wallet::sled; use magical_bitcoin_wallet::types::ScriptType; -use magical_bitcoin_wallet::{Client, ExtendedDescriptor, Wallet}; +use magical_bitcoin_wallet::{Client, Wallet}; fn prepare_home_dir() -> PathBuf { let mut dir = PathBuf::new(); @@ -232,13 +232,8 @@ fn main() { Some("testnet") | _ => Network::Testnet, }; - let descriptor = matches - .value_of("descriptor") - .map(|x| ExtendedDescriptor::from_str(x).unwrap()) - .unwrap(); - let change_descriptor = matches - .value_of("change_descriptor") - .map(|x| ExtendedDescriptor::from_str(x).unwrap()); + let descriptor = matches.value_of("descriptor").unwrap(); + let change_descriptor = matches.value_of("change_descriptor"); debug!("descriptors: {:?} {:?}", descriptor, change_descriptor); let database = sled::open(prepare_home_dir().to_str().unwrap()).unwrap(); @@ -248,7 +243,7 @@ fn main() { debug!("database opened successfully"); let client = Client::new(matches.value_of("server").unwrap()).unwrap(); - let wallet = Wallet::new(descriptor, change_descriptor, network, tree, client); + let wallet = Wallet::new(descriptor, change_descriptor, network, tree, client).unwrap(); // TODO: print errors in a nice way let handle_matches = |matches: ArgMatches<'_>| { diff --git a/src/database/keyvalue.rs b/src/database/keyvalue.rs index a96577dd..80fdfb31 100644 --- a/src/database/keyvalue.rs +++ b/src/database/keyvalue.rs @@ -17,6 +17,7 @@ use crate::types::*; // rawtx r -> tx // transactions t -> tx details // deriv indexes c{i,e} -> u32 +// descriptor checksum d{i,e} -> vec enum SledKey<'a> { Path((Option, Option<&'a DerivationPath>)), @@ -25,6 +26,7 @@ enum SledKey<'a> { RawTx(Option<&'a Txid>), Transaction(Option<&'a Txid>), LastIndex(ScriptType), + DescriptorChecksum(ScriptType), } impl SledKey<'_> { @@ -42,6 +44,7 @@ impl SledKey<'_> { SledKey::RawTx(_) => b"r".to_vec(), SledKey::Transaction(_) => b"t".to_vec(), SledKey::LastIndex(st) => [b"c", st.as_ref()].concat(), + SledKey::DescriptorChecksum(st) => [b"d", st.as_ref()].concat(), } } @@ -235,6 +238,26 @@ impl BatchOperations for Batch { } impl Database for Tree { + fn check_descriptor_checksum>( + &mut self, + script_type: ScriptType, + bytes: B, + ) -> Result<(), Error> { + let key = SledKey::DescriptorChecksum(script_type).as_sled_key(); + + let prev = self.get(&key)?.map(|x| x.to_vec()); + if let Some(val) = prev { + if val == bytes.as_ref() { + Ok(()) + } else { + Err(Error::ChecksumMismatch) + } + } else { + self.insert(&key, bytes.as_ref())?; + Ok(()) + } + } + fn iter_script_pubkeys(&self, script_type: Option) -> Result, Error> { let key = SledKey::Path((script_type, None)).as_sled_key(); self.scan_prefix(key) @@ -358,7 +381,7 @@ impl Database for Tree { // inserts 0 if not present fn increment_last_index(&mut self, script_type: ScriptType) -> Result { let key = SledKey::LastIndex(script_type).as_sled_key(); - self.fetch_and_update(key, |prev| { + self.update_and_fetch(key, |prev| { let new = match prev { Some(b) => { let array: [u8; 4] = b.try_into().unwrap_or([0; 4]); @@ -366,7 +389,7 @@ impl Database for Tree { val + 1 } - None => 1, // start from 1, we return 0 when the prev value was None + None => 0, }; Some(new.to_be_bytes().to_vec()) diff --git a/src/database/mod.rs b/src/database/mod.rs index 52dc83b8..2a078fce 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -40,6 +40,12 @@ pub trait BatchOperations { } pub trait Database: BatchOperations { + fn check_descriptor_checksum>( + &mut self, + script_type: ScriptType, + bytes: B, + ) -> Result<(), Error>; + fn iter_script_pubkeys(&self, script_type: Option) -> Result, Error>; fn iter_utxos(&self) -> Result, Error>; fn iter_raw_txs(&self) -> Result, Error>; diff --git a/src/descriptor/checksum.rs b/src/descriptor/checksum.rs new file mode 100644 index 00000000..002a5038 --- /dev/null +++ b/src/descriptor/checksum.rs @@ -0,0 +1,64 @@ +use std::iter::FromIterator; + +use crate::descriptor::Error; + +const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ "; +const CHECKSUM_CHARSET: &str = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; + +fn poly_mod(mut c: u64, val: u64) -> u64 { + let c0 = c >> 35; + c = ((c & 0x7ffffffff) << 5) ^ val; + if c0 & 1 > 0 { + c ^= 0xf5dee51989 + }; + if c0 & 2 > 0 { + c ^= 0xa9fdca3312 + }; + if c0 & 4 > 0 { + c ^= 0x1bab10e32d + }; + if c0 & 8 > 0 { + c ^= 0x3706b1677a + }; + if c0 & 16 > 0 { + c ^= 0x644d626ffd + }; + + c +} + +pub fn get_checksum(desc: &str) -> Result { + let mut c = 1; + let mut cls = 0; + let mut clscount = 0; + for ch in desc.chars() { + let pos = INPUT_CHARSET + .find(ch) + .ok_or(Error::InvalidDescriptorCharacter(ch))? as u64; + c = poly_mod(c, pos & 31); + cls = cls * 3 + (pos >> 5); + clscount += 1; + if clscount == 3 { + c = poly_mod(c, cls); + cls = 0; + clscount = 0; + } + } + if clscount > 0 { + c = poly_mod(c, cls); + } + (0..8).for_each(|_| c = poly_mod(c, 0)); + c ^= 1; + + let mut chars = Vec::with_capacity(8); + for j in 0..8 { + chars.push( + CHECKSUM_CHARSET + .chars() + .nth(((c >> (5 * (7 - j))) & 31) as usize) + .unwrap(), + ); + } + + Ok(String::from_iter(chars)) +} diff --git a/src/descriptor/error.rs b/src/descriptor/error.rs index f9a10870..0a58ab3d 100644 --- a/src/descriptor/error.rs +++ b/src/descriptor/error.rs @@ -6,6 +6,14 @@ pub enum Error { MalformedInput, KeyParsingError(String), + InputIndexDoesntExist, + MissingPublicKey, + MissingDetails, + + InvalidDescriptorCharacter(char), + + CantDeriveWithMiniscript, + BIP32(bitcoin::util::bip32::Error), Base58(bitcoin::util::base58::Error), PK(bitcoin::util::key::Error), diff --git a/src/descriptor/extended_key.rs b/src/descriptor/extended_key.rs index 0f7bd9e9..9e6fc10a 100644 --- a/src/descriptor/extended_key.rs +++ b/src/descriptor/extended_key.rs @@ -54,7 +54,13 @@ pub struct DescriptorExtendedKey { impl DescriptorExtendedKey { pub fn full_path(&self, index: u32) -> DerivationPath { - let mut final_path: Vec = self.path.clone().into(); + let mut final_path: Vec = Vec::new(); + if let Some(path) = &self.master_derivation { + let path_as_vec: Vec = path.clone().into(); + final_path.extend_from_slice(&path_as_vec); + } + let our_path: Vec = self.path.clone().into(); + final_path.extend_from_slice(&our_path); let other_path: Vec = self.final_index.as_path(index).into(); final_path.extend_from_slice(&other_path); diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 72fe5982..bca3ddd7 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -4,28 +4,36 @@ use std::convert::{Into, TryFrom}; use std::fmt; use std::str::FromStr; -use bitcoin::blockdata::script::Script; use bitcoin::hashes::{hash160, Hash}; use bitcoin::secp256k1::{All, Secp256k1}; use bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey, Fingerprint}; -use bitcoin::{PrivateKey, PublicKey}; +use bitcoin::util::psbt::PartiallySignedTransaction as PSBT; +use bitcoin::{PrivateKey, PublicKey, Script}; -pub use miniscript::descriptor::Descriptor; +pub use miniscript::{descriptor::Descriptor, Miniscript}; use serde::{Deserialize, Serialize}; +use crate::psbt::utils::PSBTUtils; + +pub mod checksum; pub mod error; pub mod extended_key; pub mod policy; +pub use self::checksum::get_checksum; pub use self::error::Error; pub use self::extended_key::{DerivationIndex, DescriptorExtendedKey}; -pub use self::policy::{ExtractPolicy, Policy}; +pub use self::policy::Policy; trait MiniscriptExtractPolicy { fn extract_policy(&self, lookup_map: &BTreeMap>) -> Option; } +pub trait ExtractPolicy { + fn extract_policy(&self) -> Option; +} + #[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord, Default)] struct DummyKey(); @@ -78,11 +86,7 @@ where fn psbt_redeem_script(&self) -> Option