]> Untitled Git - bdk/commitdiff
Policy and contribution
authorAlekos Filini <alekos.filini@gmail.com>
Sat, 15 Feb 2020 20:27:51 +0000 (21:27 +0100)
committerAlekos Filini <alekos.filini@gmail.com>
Tue, 7 Apr 2020 09:17:46 +0000 (11:17 +0200)
13 files changed:
examples/repl.rs
src/database/keyvalue.rs
src/database/mod.rs
src/descriptor/checksum.rs [new file with mode: 0644]
src/descriptor/error.rs
src/descriptor/extended_key.rs
src/descriptor/mod.rs
src/descriptor/policy.rs
src/error.rs
src/psbt.rs [deleted file]
src/psbt/mod.rs [new file with mode: 0644]
src/psbt/utils.rs [new file with mode: 0644]
src/wallet/mod.rs

index 7ee163b223431ff63f2b56311a1c02c660a6cace..0430d962f776a5072708cadeb55cbe4053c217b2 100644 (file)
@@ -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<'_>| {
index a96577dd2e5d33faa0c3495282caadbe04d4d87f..80fdfb31dd47426b61525c694ac48da3e91ddcf3 100644 (file)
@@ -17,6 +17,7 @@ use crate::types::*;
 // rawtx                r<txid> -> tx
 // transactions         t<txid> -> tx details
 // deriv indexes        c{i,e} -> u32
+// descriptor checksum  d{i,e} -> vec<u8>
 
 enum SledKey<'a> {
     Path((Option<ScriptType>, 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<B: AsRef<[u8]>>(
+        &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<ScriptType>) -> Result<Vec<Script>, 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<u32, Error> {
         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())
index 52dc83b86782a34e82e051723384ba920dbd6d9a..2a078fce1c1d766f9a31afd287fa97de535d1739 100644 (file)
@@ -40,6 +40,12 @@ pub trait BatchOperations {
 }
 
 pub trait Database: BatchOperations {
+    fn check_descriptor_checksum<B: AsRef<[u8]>>(
+        &mut self,
+        script_type: ScriptType,
+        bytes: B,
+    ) -> Result<(), Error>;
+
     fn iter_script_pubkeys(&self, script_type: Option<ScriptType>) -> Result<Vec<Script>, Error>;
     fn iter_utxos(&self) -> Result<Vec<UTXO>, Error>;
     fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error>;
diff --git a/src/descriptor/checksum.rs b/src/descriptor/checksum.rs
new file mode 100644 (file)
index 0000000..002a503
--- /dev/null
@@ -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<String, Error> {
+    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))
+}
index f9a1087080bd55082802ae9423e6866e61df4fa0..0a58ab3dcd8e76a8997f612c156ea73176ebbca3 100644 (file)
@@ -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),
index 0f7bd9e979380d6b8d69964e0536a9bd7eaa4f5b..9e6fc10aa2028c9fc46874639f98d19be218362a 100644 (file)
@@ -54,7 +54,13 @@ pub struct DescriptorExtendedKey {
 
 impl DescriptorExtendedKey {
     pub fn full_path(&self, index: u32) -> DerivationPath {
-        let mut final_path: Vec<ChildNumber> = self.path.clone().into();
+        let mut final_path: Vec<ChildNumber> = Vec::new();
+        if let Some(path) = &self.master_derivation {
+            let path_as_vec: Vec<ChildNumber> = path.clone().into();
+            final_path.extend_from_slice(&path_as_vec);
+        }
+        let our_path: Vec<ChildNumber> = self.path.clone().into();
+        final_path.extend_from_slice(&our_path);
         let other_path: Vec<ChildNumber> = self.final_index.as_path(index).into();
         final_path.extend_from_slice(&other_path);
 
index 72fe598276a6e0fc8a238935af1c72df4739c768..bca3ddd78e71405adf99f6a4f410f5bf437059b2 100644 (file)
@@ -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<String, Box<dyn Key>>) -> Option<Policy>;
 }
 
+pub trait ExtractPolicy {
+    fn extract_policy(&self) -> Option<Policy>;
+}
+
 #[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord, Default)]
 struct DummyKey();
 
@@ -78,11 +86,7 @@ where
 
     fn psbt_redeem_script(&self) -> Option<Script> {
         match self {
-            Descriptor::ShWpkh(ref pk) => {
-                let addr =
-                    bitcoin::Address::p2shwpkh(&pk.to_public_key(), bitcoin::Network::Bitcoin);
-                Some(addr.script_pubkey())
-            }
+            Descriptor::ShWpkh(_) => Some(self.witness_script()),
             Descriptor::ShWsh(ref script) => Some(script.encode().to_v0_p2wsh()),
             Descriptor::Sh(ref script) => Some(script.encode()),
             _ => None,
@@ -105,6 +109,10 @@ trait Key: std::fmt::Debug {
     fn xprv(&self) -> Option<ExtendedPrivKey>;
     fn full_path(&self, index: u32) -> Option<DerivationPath>;
     fn is_fixed(&self) -> bool;
+
+    fn has_secret(&self) -> bool {
+        self.xprv().is_some() || self.as_secret_key().is_some()
+    }
 }
 
 impl Key for PublicKey {
@@ -169,7 +177,11 @@ impl Key for PrivateKey {
 
 impl Key for DescriptorExtendedKey {
     fn fingerprint(&self, secp: &Secp256k1<All>) -> Option<Fingerprint> {
-        Some(self.root_xpub(secp).fingerprint())
+        if let Some(fing) = self.master_fingerprint {
+            Some(fing.clone())
+        } else {
+            Some(self.root_xpub(secp).fingerprint())
+        }
     }
 
     fn as_public_key(&self, secp: &Secp256k1<All>, index: Option<u32>) -> Result<PublicKey, Error> {
@@ -255,6 +267,62 @@ impl ExtendedDescriptor {
         })
     }
 
+    pub fn derive_with_miniscript(
+        &self,
+        miniscript: Miniscript<PublicKey>,
+    ) -> Result<DerivedDescriptor, Error> {
+        // TODO: make sure they are "equivalent"
+        match self.internal {
+            Descriptor::Bare(_) => Ok(Descriptor::Bare(miniscript)),
+            Descriptor::Sh(_) => Ok(Descriptor::Sh(miniscript)),
+            Descriptor::Wsh(_) => Ok(Descriptor::Wsh(miniscript)),
+            Descriptor::ShWsh(_) => Ok(Descriptor::ShWsh(miniscript)),
+            _ => Err(Error::CantDeriveWithMiniscript),
+        }
+    }
+
+    pub fn derive_from_psbt_input(
+        &self,
+        psbt: &PSBT,
+        input_index: usize,
+    ) -> Result<DerivedDescriptor, Error> {
+        let get_pk_from_partial_sigs = || {
+            // here we need the public key.. since it's a single sig, there are only two
+            // options: we can either find it in the `partial_sigs`, or we can't. if we
+            // can't, it means that we can't even satisfy the input, so we can exit knowing
+            // that we did our best to try to find it.
+            psbt.inputs[input_index]
+                .partial_sigs
+                .keys()
+                .nth(0)
+                .ok_or(Error::MissingPublicKey)
+        };
+
+        if let Some(wit_script) = &psbt.inputs[input_index].witness_script {
+            self.derive_with_miniscript(Miniscript::parse(wit_script)?)
+        } else if let Some(p2sh_script) = &psbt.inputs[input_index].redeem_script {
+            if p2sh_script.is_v0_p2wpkh() {
+                // wrapped p2wpkh
+                get_pk_from_partial_sigs().map(|pk| Descriptor::ShWpkh(*pk))
+            } else {
+                self.derive_with_miniscript(Miniscript::parse(p2sh_script)?)
+            }
+        } else if let Some(utxo) = psbt.get_utxo_for(input_index) {
+            if utxo.script_pubkey.is_p2pkh() {
+                get_pk_from_partial_sigs().map(|pk| Descriptor::Pkh(*pk))
+            } else if utxo.script_pubkey.is_p2pk() {
+                get_pk_from_partial_sigs().map(|pk| Descriptor::Pk(*pk))
+            } else if utxo.script_pubkey.is_v0_p2wpkh() {
+                get_pk_from_partial_sigs().map(|pk| Descriptor::Wpkh(*pk))
+            } else {
+                // try as bare script
+                self.derive_with_miniscript(Miniscript::parse(&utxo.script_pubkey)?)
+            }
+        } else {
+            Err(Error::MissingDetails)
+        }
+    }
+
     pub fn derive(&self, index: u32) -> Result<DerivedDescriptor, Error> {
         let translatefpk = |xpub: &String| {
             self.keys
index 5ad638dccb1f6da18c0d12179fe4dacb7646769c..b8b59c878ec18f3c709b9f1581fbfdd313e742eb 100644 (file)
@@ -1,10 +1,11 @@
-use std::collections::BTreeMap;
+use std::collections::{BTreeMap, HashSet};
 
 use serde::Serialize;
 
 use bitcoin::hashes::*;
 use bitcoin::secp256k1::Secp256k1;
 use bitcoin::util::bip32::Fingerprint;
+use bitcoin::util::psbt;
 use bitcoin::PublicKey;
 
 use miniscript::{Descriptor, Miniscript, Terminal};
@@ -23,6 +24,7 @@ impl PKOrF {
     fn from_key(k: &Box<dyn Key>) -> Self {
         let secp = Secp256k1::gen_new();
 
+        let pubkey = k.as_public_key(&secp, None).unwrap();
         if let Some(fing) = k.fingerprint(&secp) {
             PKOrF {
                 fingerprint: Some(fing),
@@ -31,7 +33,7 @@ impl PKOrF {
         } else {
             PKOrF {
                 fingerprint: None,
-                pubkey: Some(k.as_public_key(&secp, None).unwrap()),
+                pubkey: Some(pubkey),
             }
         }
     }
@@ -61,10 +63,10 @@ pub enum SatisfiableItem {
         hash: hash160::Hash,
     },
     AbsoluteTimelock {
-        height: u32,
+        value: u32,
     },
     RelativeTimelock {
-        blocks: u32,
+        value: u32,
     },
 
     // Complex item
@@ -88,26 +90,107 @@ impl SatisfiableItem {
             _ => true,
         }
     }
+
+    fn satisfy(&self, _input: &psbt::Input) -> Satisfaction {
+        Satisfaction::None
+    }
 }
 
-#[derive(Debug, Serialize)]
-pub enum ItemSatisfier {
-    Us,
-    Other(Option<Fingerprint>),
-    Timelock(Option<u32>), // remaining blocks. TODO: time-based timelocks
+#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
+#[serde(tag = "type", rename_all = "UPPERCASE")]
+pub enum Satisfaction {
+    Complete {
+        #[serde(skip_serializing_if = "PathRequirements::is_null")]
+        condition: PathRequirements,
+    },
+    Partial {
+        m: usize,
+        n: usize,
+        completed: HashSet<usize>,
+    },
+    None,
+}
+
+impl Satisfaction {
+    fn from_items_threshold(items: HashSet<usize>, threshold: usize) -> Satisfaction {
+        Satisfaction::Partial {
+            m: items.len(),
+            n: threshold,
+            completed: items,
+        }
+    }
+}
+
+impl<'a> std::ops::Add<&'a Satisfaction> for Satisfaction {
+    type Output = Satisfaction;
+
+    fn add(self, other: &'a Satisfaction) -> Satisfaction {
+        &self + other
+    }
+}
+
+impl<'a, 'b> std::ops::Add<&'b Satisfaction> for &'a Satisfaction {
+    type Output = Satisfaction;
+
+    fn add(self, other: &'b Satisfaction) -> Satisfaction {
+        match (self, other) {
+            // complete-complete
+            (
+                Satisfaction::Complete { condition: mut a },
+                Satisfaction::Complete { condition: b },
+            ) => {
+                a.merge(&b).unwrap();
+                Satisfaction::Complete { condition: a }
+            }
+            // complete-<any>
+            (Satisfaction::Complete { condition }, _) => Satisfaction::Complete {
+                condition: *condition,
+            },
+            (_, Satisfaction::Complete { condition }) => Satisfaction::Complete {
+                condition: *condition,
+            },
+
+            // none-<any>
+            (Satisfaction::None, any) => any.clone(),
+            (any, Satisfaction::None) => any.clone(),
+
+            // partial-partial
+            (
+                Satisfaction::Partial {
+                    m: _,
+                    n: a_n,
+                    completed: a_items,
+                },
+                Satisfaction::Partial {
+                    m: _,
+                    n: _,
+                    completed: b_items,
+                },
+            ) => {
+                let union: HashSet<_> = a_items.union(&b_items).cloned().collect();
+                Satisfaction::Partial {
+                    m: union.len(),
+                    n: *a_n,
+                    completed: union,
+                }
+            }
+        }
+    }
 }
 
 #[derive(Debug, Serialize)]
 pub struct Policy {
     #[serde(flatten)]
     item: SatisfiableItem,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    satisfier: Option<ItemSatisfier>,
+    satisfaction: Satisfaction,
+    contribution: Satisfaction,
 }
 
-#[derive(Debug, Default)]
+#[derive(Debug, Default, Eq, PartialEq, Clone, Copy, Serialize)]
 pub struct PathRequirements {
+    #[serde(skip_serializing_if = "Option::is_none")]
     pub csv: Option<u32>,
+    #[serde(skip_serializing_if = "Option::is_none")]
     pub timelock: Option<u32>,
 }
 
@@ -126,6 +209,8 @@ impl PathRequirements {
         }?;
 
         match (self.timelock, other.timelock) {
+            // TODO: we could actually set the timelock to the highest of the two, but we would
+            // have to first check that they are both in the same "unit" (blocks vs time)
             (Some(old), Some(new)) if old != new => Err(PolicyError::DifferentTimelock(old, new)),
             _ => {
                 self.timelock = self.timelock.or(other.timelock);
@@ -154,7 +239,8 @@ impl Policy {
     pub fn new(item: SatisfiableItem) -> Self {
         Policy {
             item,
-            satisfier: None,
+            satisfaction: Satisfaction::None,
+            contribution: Satisfaction::None,
         }
     }
 
@@ -162,13 +248,7 @@ impl Policy {
         match (a, b) {
             (None, None) => None,
             (Some(x), None) | (None, Some(x)) => Some(x),
-            (Some(a), Some(b)) => Some(
-                SatisfiableItem::Thresh {
-                    items: vec![a, b],
-                    threshold: 2,
-                }
-                .into(),
-            ),
+            (Some(a), Some(b)) => Self::make_thresh(vec![a, b], 2),
         }
     }
 
@@ -176,33 +256,49 @@ impl Policy {
         match (a, b) {
             (None, None) => None,
             (Some(x), None) | (None, Some(x)) => Some(x),
-            (Some(a), Some(b)) => Some(
-                SatisfiableItem::Thresh {
-                    items: vec![a, b],
-                    threshold: 1,
-                }
-                .into(),
-            ),
+            (Some(a), Some(b)) => Self::make_thresh(vec![a, b], 1),
         }
     }
 
-    pub fn make_thresh(items: Vec<Policy>, mut threshold: usize) -> Option<Policy> {
+    pub fn make_thresh(items: Vec<Policy>, threshold: usize) -> Option<Policy> {
         if threshold == 0 {
             return None;
         }
-        if threshold > items.len() {
-            threshold = items.len();
-        }
 
-        Some(SatisfiableItem::Thresh { items, threshold }.into())
+        let contribution = items.iter().fold(
+            Satisfaction::Partial {
+                m: 0,
+                n: threshold,
+                completed: HashSet::new(),
+            },
+            |acc, x| acc + &x.contribution,
+        );
+        let mut policy: Policy = SatisfiableItem::Thresh { items, threshold }.into();
+        policy.contribution = contribution;
+
+        Some(policy)
     }
 
-    fn make_multisig(pubkeys: Vec<Option<&Box<dyn Key>>>, threshold: usize) -> Option<Policy> {
-        let keys = pubkeys
-            .into_iter()
-            .map(|k| PKOrF::from_key(k.unwrap()))
+    fn make_multisig(keys: Vec<Option<&Box<dyn Key>>>, threshold: usize) -> Option<Policy> {
+        let parsed_keys = keys.iter().map(|k| PKOrF::from_key(k.unwrap())).collect();
+        let mut policy: Policy = SatisfiableItem::Multisig {
+            keys: parsed_keys,
+            threshold,
+        }
+        .into();
+        let our_keys = keys
+            .iter()
+            .enumerate()
+            .filter(|(_, x)| x.is_some() && x.unwrap().has_secret())
+            .map(|(k, _)| k)
             .collect();
-        Some(SatisfiableItem::Multisig { keys, threshold }.into())
+        policy.contribution = Satisfaction::from_items_threshold(our_keys, threshold);
+
+        Some(policy)
+    }
+
+    pub fn satisfy(&mut self, input: &psbt::Input) {
+        self.satisfaction = self.item.satisfy(input);
     }
 
     pub fn requires_path(&self) -> bool {
@@ -267,12 +363,12 @@ impl Policy {
                 Ok(requirements)
             }
             _ if !selected.is_empty() => Err(PolicyError::TooManyItemsSelected(index)),
-            SatisfiableItem::AbsoluteTimelock { height } => Ok(PathRequirements {
+            SatisfiableItem::AbsoluteTimelock { value } => Ok(PathRequirements {
                 csv: None,
-                timelock: Some(*height),
+                timelock: Some(*value),
             }),
-            SatisfiableItem::RelativeTimelock { blocks } => Ok(PathRequirements {
-                csv: Some(*blocks),
+            SatisfiableItem::RelativeTimelock { value } => Ok(PathRequirements {
+                csv: Some(*value),
                 timelock: None,
             }),
             _ => Ok(PathRequirements::default()),
@@ -286,19 +382,27 @@ impl From<SatisfiableItem> for Policy {
     }
 }
 
-pub trait ExtractPolicy {
-    fn extract_policy(&self) -> Option<Policy>;
-}
-
 fn signature_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> {
-    key.map(|k| SatisfiableItem::Signature(PKOrF::from_key(k)).into())
+    key.map(|k| {
+        let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key(k)).into();
+        policy.contribution = if k.has_secret() {
+            Satisfaction::Complete {
+                condition: Default::default(),
+            }
+        } else {
+            Satisfaction::None
+        };
+
+        policy
+    })
 }
 
 fn signature_key_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> {
     let secp = Secp256k1::gen_new();
 
     key.map(|k| {
-        if let Some(fing) = k.fingerprint(&secp) {
+        let pubkey = k.as_public_key(&secp, None).unwrap();
+        let mut policy: Policy = if let Some(fing) = k.fingerprint(&secp) {
             SatisfiableItem::SignatureKey {
                 fingerprint: Some(fing),
                 pubkey_hash: None,
@@ -306,12 +410,19 @@ fn signature_key_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> {
         } else {
             SatisfiableItem::SignatureKey {
                 fingerprint: None,
-                pubkey_hash: Some(hash160::Hash::hash(
-                    &k.as_public_key(&secp, None).unwrap().to_bytes(),
-                )),
+                pubkey_hash: Some(hash160::Hash::hash(&pubkey.to_bytes())),
             }
         }
-        .into()
+        .into();
+        policy.contribution = if k.has_secret() {
+            Satisfaction::Complete {
+                condition: Default::default(),
+            }
+        } else {
+            Satisfaction::None
+        };
+
+        policy
     })
 }
 
@@ -322,11 +433,27 @@ impl MiniscriptExtractPolicy for Miniscript<String> {
             Terminal::True | Terminal::False => None,
             Terminal::Pk(pubkey) => signature_from_string(lookup_map.get(pubkey)),
             Terminal::PkH(pubkey_hash) => signature_key_from_string(lookup_map.get(pubkey_hash)),
-            Terminal::After(height) => {
-                Some(SatisfiableItem::AbsoluteTimelock { height: *height }.into())
+            Terminal::After(value) => {
+                let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into();
+                policy.contribution = Satisfaction::Complete {
+                    condition: PathRequirements {
+                        csv: None,
+                        timelock: Some(*value),
+                    },
+                };
+
+                Some(policy)
             }
-            Terminal::Older(blocks) => {
-                Some(SatisfiableItem::RelativeTimelock { blocks: *blocks }.into())
+            Terminal::Older(value) => {
+                let mut policy: Policy = SatisfiableItem::RelativeTimelock { value: *value }.into();
+                policy.contribution = Satisfaction::Complete {
+                    condition: PathRequirements {
+                        csv: Some(*value),
+                        timelock: None,
+                    },
+                };
+
+                Some(policy)
             }
             Terminal::Sha256(hash) => Some(SatisfiableItem::SHA256Preimage { hash: *hash }.into()),
             Terminal::Hash256(hash) => {
@@ -338,6 +465,9 @@ impl MiniscriptExtractPolicy for Miniscript<String> {
             Terminal::Hash160(hash) => {
                 Some(SatisfiableItem::HASH160Preimage { hash: *hash }.into())
             }
+            Terminal::ThreshM(k, pks) => {
+                Policy::make_multisig(pks.iter().map(|s| lookup_map.get(s)).collect(), *k)
+            }
             // Identities
             Terminal::Alt(inner)
             | Terminal::Swap(inner)
@@ -376,9 +506,6 @@ impl MiniscriptExtractPolicy for Miniscript<String> {
 
                 Policy::make_thresh(mapped, threshold)
             }
-            Terminal::ThreshM(k, pks) => {
-                Policy::make_multisig(pks.iter().map(|s| lookup_map.get(s)).collect(), *k)
-            }
         }
     }
 }
index fe5d02914f7e89e39e839e6ffe0c75d6d3eba113..cee77b8ddae5555f8f0bba38a5f20d4f963c9b6d 100644 (file)
@@ -13,6 +13,8 @@ pub enum Error {
     UnknownUTXO,
     DifferentTransactions,
 
+    ChecksumMismatch,
+
     SpendingPolicyRequired,
     InvalidPolicyPathError(crate::descriptor::policy::PolicyError),
 
diff --git a/src/psbt.rs b/src/psbt.rs
deleted file mode 100644 (file)
index e482ff7..0000000
+++ /dev/null
@@ -1,258 +0,0 @@
-use std::collections::BTreeMap;
-
-use bitcoin::hashes::{hash160, Hash};
-use bitcoin::util::bip143::SighashComponents;
-use bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey, Fingerprint};
-use bitcoin::util::psbt;
-use bitcoin::{PrivateKey, PublicKey, Script, SigHashType, Transaction};
-
-use bitcoin::secp256k1::{self, All, Message, Secp256k1};
-
-#[allow(unused_imports)]
-use log::{debug, error, info, trace};
-
-use miniscript::{BitcoinSig, MiniscriptKey, Satisfier};
-
-use crate::descriptor::ExtendedDescriptor;
-use crate::error::Error;
-use crate::signer::Signer;
-
-pub struct PSBTSatisfier<'a> {
-    input: &'a psbt::Input,
-    create_height: Option<u32>,
-    current_height: Option<u32>,
-}
-
-impl<'a> PSBTSatisfier<'a> {
-    pub fn new(
-        input: &'a psbt::Input,
-        create_height: Option<u32>,
-        current_height: Option<u32>,
-    ) -> Self {
-        PSBTSatisfier {
-            input,
-            create_height,
-            current_height,
-        }
-    }
-}
-
-impl<'a> PSBTSatisfier<'a> {
-    fn parse_sig(rawsig: &Vec<u8>) -> Option<BitcoinSig> {
-        let (flag, sig) = rawsig.split_last().unwrap();
-        let flag = bitcoin::SigHashType::from_u32(*flag as u32);
-        let sig = match secp256k1::Signature::from_der(sig) {
-            Ok(sig) => sig,
-            Err(..) => return None,
-        };
-        Some((sig, flag))
-    }
-}
-
-// TODO: also support hash preimages through the "unknown" section of PSBT
-impl<'a> Satisfier<bitcoin::PublicKey> for PSBTSatisfier<'a> {
-    // from https://docs.rs/miniscript/0.12.0/src/miniscript/psbt/mod.rs.html#96
-    fn lookup_sig(&self, pk: &bitcoin::PublicKey) -> Option<BitcoinSig> {
-        debug!("lookup_sig: {}", pk);
-
-        if let Some(rawsig) = self.input.partial_sigs.get(pk) {
-            Self::parse_sig(&rawsig)
-        } else {
-            None
-        }
-    }
-
-    fn lookup_pkh_pk(&self, hash: &hash160::Hash) -> Option<bitcoin::PublicKey> {
-        debug!("lookup_pkh_pk: {}", hash);
-
-        for (pk, _) in &self.input.partial_sigs {
-            if &pk.to_pubkeyhash() == hash {
-                return Some(*pk);
-            }
-        }
-
-        None
-    }
-
-    fn lookup_pkh_sig(&self, hash: &hash160::Hash) -> Option<(bitcoin::PublicKey, BitcoinSig)> {
-        debug!("lookup_pkh_sig: {}", hash);
-
-        for (pk, sig) in &self.input.partial_sigs {
-            if &pk.to_pubkeyhash() == hash {
-                return match Self::parse_sig(&sig) {
-                    Some(bitcoinsig) => Some((*pk, bitcoinsig)),
-                    None => None,
-                };
-            }
-        }
-
-        None
-    }
-
-    fn check_older(&self, height: u32) -> bool {
-        // TODO: also check if `nSequence` right
-        debug!("check_older: {}", height);
-
-        // TODO: test >= / >
-        self.current_height.unwrap_or(0) >= self.create_height.unwrap_or(0) + height
-    }
-
-    fn check_after(&self, height: u32) -> bool {
-        // TODO: also check if `nLockTime` is right
-        debug!("check_older: {}", height);
-
-        self.current_height.unwrap_or(0) > height
-    }
-}
-
-#[derive(Debug)]
-pub struct PSBTSigner<'a> {
-    tx: &'a Transaction,
-    secp: Secp256k1<All>,
-
-    // psbt: &'b psbt::PartiallySignedTransaction,
-    extended_keys: BTreeMap<Fingerprint, ExtendedPrivKey>,
-    private_keys: BTreeMap<PublicKey, PrivateKey>,
-}
-
-impl<'a> PSBTSigner<'a> {
-    pub fn from_descriptor(tx: &'a Transaction, desc: &ExtendedDescriptor) -> Result<Self, Error> {
-        let secp = Secp256k1::gen_new();
-
-        let mut extended_keys = BTreeMap::new();
-        for xprv in desc.get_xprv() {
-            let fing = xprv.fingerprint(&secp);
-            extended_keys.insert(fing, xprv);
-        }
-
-        let mut private_keys = BTreeMap::new();
-        for privkey in desc.get_secret_keys() {
-            let pubkey = privkey.public_key(&secp);
-            private_keys.insert(pubkey, privkey);
-        }
-
-        Ok(PSBTSigner {
-            tx,
-            secp,
-            extended_keys,
-            private_keys,
-        })
-    }
-
-    pub fn extend(&mut self, mut other: PSBTSigner) -> Result<(), Error> {
-        if self.tx.txid() != other.tx.txid() {
-            return Err(Error::DifferentTransactions);
-        }
-
-        self.extended_keys.append(&mut other.extended_keys);
-        self.private_keys.append(&mut other.private_keys);
-
-        Ok(())
-    }
-
-    // TODO: temporary
-    pub fn all_public_keys(&self) -> impl IntoIterator<Item = &PublicKey> {
-        self.private_keys.keys()
-    }
-}
-
-impl<'a> Signer for PSBTSigner<'a> {
-    fn sig_legacy_from_fingerprint(
-        &self,
-        index: usize,
-        sighash: SigHashType,
-        fingerprint: &Fingerprint,
-        path: &DerivationPath,
-        script: &Script,
-    ) -> Result<Option<BitcoinSig>, Error> {
-        self.extended_keys
-            .get(fingerprint)
-            .map_or(Ok(None), |xprv| {
-                let privkey = xprv.derive_priv(&self.secp, path)?;
-                // let derived_pubkey = secp256k1::PublicKey::from_secret_key(&self.secp, &privkey.private_key.key);
-
-                let hash = self.tx.signature_hash(index, script, sighash.as_u32());
-
-                let signature = self.secp.sign(
-                    &Message::from_slice(&hash.into_inner()[..])?,
-                    &privkey.private_key.key,
-                );
-
-                Ok(Some((signature, sighash)))
-            })
-    }
-
-    fn sig_legacy_from_pubkey(
-        &self,
-        index: usize,
-        sighash: SigHashType,
-        public_key: &PublicKey,
-        script: &Script,
-    ) -> Result<Option<BitcoinSig>, Error> {
-        self.private_keys
-            .get(public_key)
-            .map_or(Ok(None), |privkey| {
-                let hash = self.tx.signature_hash(index, script, sighash.as_u32());
-
-                let signature = self
-                    .secp
-                    .sign(&Message::from_slice(&hash.into_inner()[..])?, &privkey.key);
-
-                Ok(Some((signature, sighash)))
-            })
-    }
-
-    fn sig_segwit_from_fingerprint(
-        &self,
-        index: usize,
-        sighash: SigHashType,
-        fingerprint: &Fingerprint,
-        path: &DerivationPath,
-        script: &Script,
-        value: u64,
-    ) -> Result<Option<BitcoinSig>, Error> {
-        self.extended_keys
-            .get(fingerprint)
-            .map_or(Ok(None), |xprv| {
-                let privkey = xprv.derive_priv(&self.secp, path)?;
-
-                let hash = SighashComponents::new(self.tx).sighash_all(
-                    &self.tx.input[index],
-                    script,
-                    value,
-                );
-
-                let signature = self.secp.sign(
-                    &Message::from_slice(&hash.into_inner()[..])?,
-                    &privkey.private_key.key,
-                );
-
-                Ok(Some((signature, sighash)))
-            })
-    }
-
-    fn sig_segwit_from_pubkey(
-        &self,
-        index: usize,
-        sighash: SigHashType,
-        public_key: &PublicKey,
-        script: &Script,
-        value: u64,
-    ) -> Result<Option<BitcoinSig>, Error> {
-        self.private_keys
-            .get(public_key)
-            .map_or(Ok(None), |privkey| {
-                let hash = SighashComponents::new(self.tx).sighash_all(
-                    &self.tx.input[index],
-                    script,
-                    value,
-                );
-
-                let signature = self
-                    .secp
-                    .sign(&Message::from_slice(&hash.into_inner()[..])?, &privkey.key);
-
-                Ok(Some((signature, sighash)))
-            })
-    }
-}
diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs
new file mode 100644 (file)
index 0000000..d7eb43f
--- /dev/null
@@ -0,0 +1,260 @@
+use std::collections::BTreeMap;
+
+use bitcoin::hashes::{hash160, Hash};
+use bitcoin::util::bip143::SighashComponents;
+use bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey, Fingerprint};
+use bitcoin::util::psbt;
+use bitcoin::{PrivateKey, PublicKey, Script, SigHashType, Transaction};
+
+use bitcoin::secp256k1::{self, All, Message, Secp256k1};
+
+#[allow(unused_imports)]
+use log::{debug, error, info, trace};
+
+use miniscript::{BitcoinSig, MiniscriptKey, Satisfier};
+
+use crate::descriptor::ExtendedDescriptor;
+use crate::error::Error;
+use crate::signer::Signer;
+
+pub mod utils;
+
+pub struct PSBTSatisfier<'a> {
+    input: &'a psbt::Input,
+    create_height: Option<u32>,
+    current_height: Option<u32>,
+}
+
+impl<'a> PSBTSatisfier<'a> {
+    pub fn new(
+        input: &'a psbt::Input,
+        create_height: Option<u32>,
+        current_height: Option<u32>,
+    ) -> Self {
+        PSBTSatisfier {
+            input,
+            create_height,
+            current_height,
+        }
+    }
+}
+
+impl<'a> PSBTSatisfier<'a> {
+    fn parse_sig(rawsig: &Vec<u8>) -> Option<BitcoinSig> {
+        let (flag, sig) = rawsig.split_last().unwrap();
+        let flag = bitcoin::SigHashType::from_u32(*flag as u32);
+        let sig = match secp256k1::Signature::from_der(sig) {
+            Ok(sig) => sig,
+            Err(..) => return None,
+        };
+        Some((sig, flag))
+    }
+}
+
+// TODO: also support hash preimages through the "unknown" section of PSBT
+impl<'a> Satisfier<bitcoin::PublicKey> for PSBTSatisfier<'a> {
+    // from https://docs.rs/miniscript/0.12.0/src/miniscript/psbt/mod.rs.html#96
+    fn lookup_sig(&self, pk: &bitcoin::PublicKey) -> Option<BitcoinSig> {
+        debug!("lookup_sig: {}", pk);
+
+        if let Some(rawsig) = self.input.partial_sigs.get(pk) {
+            Self::parse_sig(&rawsig)
+        } else {
+            None
+        }
+    }
+
+    fn lookup_pkh_pk(&self, hash: &hash160::Hash) -> Option<bitcoin::PublicKey> {
+        debug!("lookup_pkh_pk: {}", hash);
+
+        for (pk, _) in &self.input.partial_sigs {
+            if &pk.to_pubkeyhash() == hash {
+                return Some(*pk);
+            }
+        }
+
+        None
+    }
+
+    fn lookup_pkh_sig(&self, hash: &hash160::Hash) -> Option<(bitcoin::PublicKey, BitcoinSig)> {
+        debug!("lookup_pkh_sig: {}", hash);
+
+        for (pk, sig) in &self.input.partial_sigs {
+            if &pk.to_pubkeyhash() == hash {
+                return match Self::parse_sig(&sig) {
+                    Some(bitcoinsig) => Some((*pk, bitcoinsig)),
+                    None => None,
+                };
+            }
+        }
+
+        None
+    }
+
+    fn check_older(&self, height: u32) -> bool {
+        // TODO: also check if `nSequence` right
+        debug!("check_older: {}", height);
+
+        // TODO: test >= / >
+        self.current_height.unwrap_or(0) >= self.create_height.unwrap_or(0) + height
+    }
+
+    fn check_after(&self, height: u32) -> bool {
+        // TODO: also check if `nLockTime` is right
+        debug!("check_after: {}", height);
+
+        self.current_height.unwrap_or(0) > height
+    }
+}
+
+#[derive(Debug)]
+pub struct PSBTSigner<'a> {
+    tx: &'a Transaction,
+    secp: Secp256k1<All>,
+
+    // psbt: &'b psbt::PartiallySignedTransaction,
+    extended_keys: BTreeMap<Fingerprint, ExtendedPrivKey>,
+    private_keys: BTreeMap<PublicKey, PrivateKey>,
+}
+
+impl<'a> PSBTSigner<'a> {
+    pub fn from_descriptor(tx: &'a Transaction, desc: &ExtendedDescriptor) -> Result<Self, Error> {
+        let secp = Secp256k1::gen_new();
+
+        let mut extended_keys = BTreeMap::new();
+        for xprv in desc.get_xprv() {
+            let fing = xprv.fingerprint(&secp);
+            extended_keys.insert(fing, xprv);
+        }
+
+        let mut private_keys = BTreeMap::new();
+        for privkey in desc.get_secret_keys() {
+            let pubkey = privkey.public_key(&secp);
+            private_keys.insert(pubkey, privkey);
+        }
+
+        Ok(PSBTSigner {
+            tx,
+            secp,
+            extended_keys,
+            private_keys,
+        })
+    }
+
+    pub fn extend(&mut self, mut other: PSBTSigner) -> Result<(), Error> {
+        if self.tx.txid() != other.tx.txid() {
+            return Err(Error::DifferentTransactions);
+        }
+
+        self.extended_keys.append(&mut other.extended_keys);
+        self.private_keys.append(&mut other.private_keys);
+
+        Ok(())
+    }
+
+    // TODO: temporary
+    pub fn all_public_keys(&self) -> impl IntoIterator<Item = &PublicKey> {
+        self.private_keys.keys()
+    }
+}
+
+impl<'a> Signer for PSBTSigner<'a> {
+    fn sig_legacy_from_fingerprint(
+        &self,
+        index: usize,
+        sighash: SigHashType,
+        fingerprint: &Fingerprint,
+        path: &DerivationPath,
+        script: &Script,
+    ) -> Result<Option<BitcoinSig>, Error> {
+        self.extended_keys
+            .get(fingerprint)
+            .map_or(Ok(None), |xprv| {
+                let privkey = xprv.derive_priv(&self.secp, path)?;
+                // let derived_pubkey = secp256k1::PublicKey::from_secret_key(&self.secp, &privkey.private_key.key);
+
+                let hash = self.tx.signature_hash(index, script, sighash.as_u32());
+
+                let signature = self.secp.sign(
+                    &Message::from_slice(&hash.into_inner()[..])?,
+                    &privkey.private_key.key,
+                );
+
+                Ok(Some((signature, sighash)))
+            })
+    }
+
+    fn sig_legacy_from_pubkey(
+        &self,
+        index: usize,
+        sighash: SigHashType,
+        public_key: &PublicKey,
+        script: &Script,
+    ) -> Result<Option<BitcoinSig>, Error> {
+        self.private_keys
+            .get(public_key)
+            .map_or(Ok(None), |privkey| {
+                let hash = self.tx.signature_hash(index, script, sighash.as_u32());
+
+                let signature = self
+                    .secp
+                    .sign(&Message::from_slice(&hash.into_inner()[..])?, &privkey.key);
+
+                Ok(Some((signature, sighash)))
+            })
+    }
+
+    fn sig_segwit_from_fingerprint(
+        &self,
+        index: usize,
+        sighash: SigHashType,
+        fingerprint: &Fingerprint,
+        path: &DerivationPath,
+        script: &Script,
+        value: u64,
+    ) -> Result<Option<BitcoinSig>, Error> {
+        self.extended_keys
+            .get(fingerprint)
+            .map_or(Ok(None), |xprv| {
+                let privkey = xprv.derive_priv(&self.secp, path)?;
+
+                let hash = SighashComponents::new(self.tx).sighash_all(
+                    &self.tx.input[index],
+                    script,
+                    value,
+                );
+
+                let signature = self.secp.sign(
+                    &Message::from_slice(&hash.into_inner()[..])?,
+                    &privkey.private_key.key,
+                );
+
+                Ok(Some((signature, sighash)))
+            })
+    }
+
+    fn sig_segwit_from_pubkey(
+        &self,
+        index: usize,
+        sighash: SigHashType,
+        public_key: &PublicKey,
+        script: &Script,
+        value: u64,
+    ) -> Result<Option<BitcoinSig>, Error> {
+        self.private_keys
+            .get(public_key)
+            .map_or(Ok(None), |privkey| {
+                let hash = SighashComponents::new(self.tx).sighash_all(
+                    &self.tx.input[index],
+                    script,
+                    value,
+                );
+
+                let signature = self
+                    .secp
+                    .sign(&Message::from_slice(&hash.into_inner()[..])?, &privkey.key);
+
+                Ok(Some((signature, sighash)))
+            })
+    }
+}
diff --git a/src/psbt/utils.rs b/src/psbt/utils.rs
new file mode 100644 (file)
index 0000000..35552c2
--- /dev/null
@@ -0,0 +1,28 @@
+use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
+use bitcoin::TxOut;
+
+pub trait PSBTUtils {
+    fn get_utxo_for(&self, input_index: usize) -> Option<TxOut>;
+}
+
+impl PSBTUtils for PSBT {
+    fn get_utxo_for(&self, input_index: usize) -> Option<TxOut> {
+        let tx = &self.global.unsigned_tx;
+
+        if input_index >= tx.input.len() {
+            return None;
+        }
+
+        if let Some(input) = self.inputs.get(input_index) {
+            if let Some(wit_utxo) = &input.witness_utxo {
+                Some(wit_utxo.clone())
+            } else if let Some(in_tx) = &input.non_witness_utxo {
+                Some(in_tx.output[tx.input[input_index].previous_output.vout as usize].clone())
+            } else {
+                None
+            }
+        } else {
+            None
+        }
+    }
+}
index 7dc4869e21e81883fad1b1267cd9033a1fcdb726..9c63b67bf2ad3be6d7e850be05deee7417acac3d 100644 (file)
@@ -3,6 +3,7 @@ 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;
@@ -25,11 +26,9 @@ pub mod utils;
 
 use self::utils::{ChunksIterator, IsDust};
 use crate::database::{BatchDatabase, BatchOperations};
-use crate::descriptor::{
-    DerivedDescriptor, DescriptorMeta, ExtendedDescriptor, ExtractPolicy, Policy,
-};
+use crate::descriptor::{get_checksum, DescriptorMeta, ExtendedDescriptor, ExtractPolicy, Policy};
 use crate::error::Error;
-use crate::psbt::{PSBTSatisfier, PSBTSigner};
+use crate::psbt::{utils::PSBTUtils, PSBTSatisfier, PSBTSigner};
 use crate::signer::Signer;
 use crate::types::*;
 
@@ -40,7 +39,6 @@ use electrum_client::Client;
 #[cfg(not(any(feature = "electrum", feature = "default")))]
 use std::marker::PhantomData as Client;
 
-// TODO: force descriptor and change_descriptor to have the same policies?
 pub struct Wallet<S: Read + Write, D: BatchDatabase> {
     descriptor: ExtendedDescriptor,
     change_descriptor: Option<ExtendedDescriptor>,
@@ -58,12 +56,30 @@ where
     D: BatchDatabase,
 {
     pub fn new_offline(
-        descriptor: ExtendedDescriptor,
-        change_descriptor: Option<ExtendedDescriptor>,
+        descriptor: &str,
+        change_descriptor: Option<&str>,
         network: Network,
-        database: D,
-    ) -> Self {
-        Wallet {
+        mut database: D,
+    ) -> Result<Self, Error> {
+        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,
@@ -71,7 +87,7 @@ where
             client: None,
             database: RefCell::new(database),
             _secp: Secp256k1::gen_new(),
-        }
+        })
     }
 
     pub fn get_new_address(&self) -> Result<Address, Error> {
@@ -116,7 +132,6 @@ where
         utxos: Option<Vec<OutPoint>>,
         unspendable: Option<Vec<OutPoint>>,
     ) -> Result<(PSBT, TransactionDetails), Error> {
-        // TODO: run before deriving the descriptor
         let policy = self.descriptor.extract_policy().unwrap();
         if policy.requires_path() && policy_path.is_none() {
             return Err(Error::SpendingPolicyRequired);
@@ -287,22 +302,14 @@ where
 
     // TODO: define an enum for signing errors
     pub fn sign(&self, mut psbt: PSBT) -> Result<(PSBT, bool), Error> {
-        let mut derived_descriptors = BTreeMap::new();
-
         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 (n, psbt_input) in psbt.inputs.iter_mut().enumerate() {
-            let out = match (&psbt_input.witness_utxo, &psbt_input.non_witness_utxo) {
-                (Some(wit_out), _) => Some(wit_out),
-                (_, Some(in_tx))
-                    if (tx.input[n].previous_output.vout as usize) < in_tx.output.len() =>
-                {
-                    Some(&in_tx.output[tx.input[n].previous_output.vout as usize])
-                }
-                _ => None,
-            };
-
+        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 {
@@ -325,11 +332,8 @@ where
                     None => 0,
                 };
 
-                let desc = self.get_descriptor_for(script_type);
-                let derived_descriptor = desc.derive(index)?;
-                derived_descriptors.insert(n, derived_descriptor);
-
                 // 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);
             }
@@ -469,7 +473,7 @@ where
         }
 
         // attempt to finalize
-        let finalized = self.finalize_psbt(tx.clone(), &mut psbt, derived_descriptors);
+        let finalized = self.finalize_psbt(tx.clone(), &mut psbt);
 
         Ok((psbt, finalized))
     }
@@ -626,18 +630,16 @@ where
         Ok((answer, paths, selected_amount, fee_val))
     }
 
-    fn finalize_psbt(
-        &self,
-        mut tx: Transaction,
-        psbt: &mut PSBT,
-        derived_descriptors: BTreeMap<usize, DerivedDescriptor>,
-    ) -> bool {
+    fn finalize_psbt(&self, mut tx: Transaction, psbt: &mut PSBT) -> bool {
         for (n, input) in tx.input.iter_mut().enumerate() {
-            debug!("getting descriptor for {}", n);
-
-            let desc = match derived_descriptors.get(&n) {
-                None => return false,
-                Some(desc) => desc,
+            // 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
@@ -669,13 +671,31 @@ where
     D: BatchDatabase,
 {
     pub fn new(
-        descriptor: ExtendedDescriptor,
-        change_descriptor: Option<ExtendedDescriptor>,
+        descriptor: &str,
+        change_descriptor: Option<&str>,
         network: Network,
-        database: D,
+        mut database: D,
         client: Client<S>,
-    ) -> Self {
-        Wallet {
+    ) -> Result<Self, Error> {
+        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,
@@ -683,7 +703,7 @@ where
             client: Some(RefCell::new(client)),
             database: RefCell::new(database),
             _secp: Secp256k1::gen_new(),
-        }
+        })
     }
 
     fn get_previous_output(&self, outpoint: &OutPoint) -> Option<TxOut> {