]> Untitled Git - bdk/commitdiff
Move everything in the root
authorAlekos Filini <alekos.filini@gmail.com>
Fri, 7 Feb 2020 11:27:10 +0000 (12:27 +0100)
committerAlekos Filini <alekos.filini@gmail.com>
Fri, 7 Feb 2020 11:27:10 +0000 (12:27 +0100)
29 files changed:
.gitignore
.travis.yml
Cargo.toml [new file with mode: 0644]
core/lib/.gitignore [deleted file]
core/lib/Cargo.toml [deleted file]
core/lib/examples/parse_descriptor.rs [deleted file]
core/lib/examples/psbt.rs [deleted file]
core/lib/src/database/keyvalue.rs [deleted file]
core/lib/src/database/mod.rs [deleted file]
core/lib/src/descriptor/error.rs [deleted file]
core/lib/src/descriptor/extended_key.rs [deleted file]
core/lib/src/descriptor/mod.rs [deleted file]
core/lib/src/error.rs [deleted file]
core/lib/src/lib.rs [deleted file]
core/lib/src/psbt.rs [deleted file]
core/lib/src/signer.rs [deleted file]
core/lib/src/types.rs [deleted file]
examples/parse_descriptor.rs [new file with mode: 0644]
examples/psbt.rs [new file with mode: 0644]
src/database/keyvalue.rs [new file with mode: 0644]
src/database/mod.rs [new file with mode: 0644]
src/descriptor/error.rs [new file with mode: 0644]
src/descriptor/extended_key.rs [new file with mode: 0644]
src/descriptor/mod.rs [new file with mode: 0644]
src/error.rs [new file with mode: 0644]
src/lib.rs [new file with mode: 0644]
src/psbt.rs [new file with mode: 0644]
src/signer.rs [new file with mode: 0644]
src/types.rs [new file with mode: 0644]

index 1377554ebea6f98a2c748183bc5a96852af12ac2..8b75e7bb889132463b2245eb338dc75975203ebe 100644 (file)
@@ -1 +1,4 @@
+/target
+Cargo.lock
+
 *.swp
index 24fa6c73b65e5ecb4e24eca3bbc9dd94ca03c0d1..28ec6fc891ed0b40fbb3c9cd4399e0a05bb58b59 100644 (file)
@@ -6,7 +6,6 @@ rust:
 before_script:
   - rustup component add rustfmt
 script:
-  - cd $TRAVIS_BUILD_DIR/core/lib/
   - cargo fmt -- --check --verbose
   - cargo build --verbose --all
   - cargo test --verbose --all
@@ -14,6 +13,6 @@ script:
 notifications:
   email: false
 
-before_cache:
-#  - rm -rf "$TRAVIS_HOME/.cargo/registry/src"
-#cache: cargo
+before_cache:
+  - rm -rf "$TRAVIS_HOME/.cargo/registry/src"
+cache: cargo
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644 (file)
index 0000000..81e268f
--- /dev/null
@@ -0,0 +1,22 @@
+[package]
+name = "magical-bitcoin-wallet"
+version = "0.1.0"
+authors = ["Riccardo Casatta <riccardo@casatta.it>", "Alekos Filini <alekos.filini@gmail.com>"]
+
+[dependencies]
+log = "^0.4"
+bitcoin = { version = "0.23", features = ["use-serde"] }
+miniscript = { version = "0.12" }
+serde = { version = "^1.0", features = ["derive"] }
+serde_json = { version = "^1.0" }
+base64 = "^0.11"
+
+# Optional dependencies
+sled = { version = "0.31.0", optional = true }
+
+[features]
+default = ["sled"]
+key-value-db = ["sled"]
+
+[dev-dependencies]
+lazy_static = "1.4.0"
diff --git a/core/lib/.gitignore b/core/lib/.gitignore
deleted file mode 100644 (file)
index 96ef6c0..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/target
-Cargo.lock
diff --git a/core/lib/Cargo.toml b/core/lib/Cargo.toml
deleted file mode 100644 (file)
index 81e268f..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-[package]
-name = "magical-bitcoin-wallet"
-version = "0.1.0"
-authors = ["Riccardo Casatta <riccardo@casatta.it>", "Alekos Filini <alekos.filini@gmail.com>"]
-
-[dependencies]
-log = "^0.4"
-bitcoin = { version = "0.23", features = ["use-serde"] }
-miniscript = { version = "0.12" }
-serde = { version = "^1.0", features = ["derive"] }
-serde_json = { version = "^1.0" }
-base64 = "^0.11"
-
-# Optional dependencies
-sled = { version = "0.31.0", optional = true }
-
-[features]
-default = ["sled"]
-key-value-db = ["sled"]
-
-[dev-dependencies]
-lazy_static = "1.4.0"
diff --git a/core/lib/examples/parse_descriptor.rs b/core/lib/examples/parse_descriptor.rs
deleted file mode 100644 (file)
index 2af42b3..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-extern crate magical_bitcoin_wallet;
-
-use std::str::FromStr;
-
-use magical_bitcoin_wallet::bitcoin::*;
-use magical_bitcoin_wallet::descriptor::*;
-
-fn main() {
-    let desc = "sh(wsh(or_d(\
-                    thresh_m(\
-                      2,[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*,tprv8ZgxMBicQKsPduL5QnGihpprdHyypMGi4DhimjtzYemu7se5YQNcZfAPLqXRuGHb5ZX2eTQj62oNqMnyxJ7B7wz54Uzswqw8fFqMVdcmVF7/1/*\
-                    ),\
-                    and_v(vc:pk_h(cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy),older(1000))\
-                   )))";
-
-    let extended_desc = ExtendedDescriptor::from_str(desc).unwrap();
-    println!("{:?}", extended_desc);
-
-    let derived_desc = extended_desc.derive(42).unwrap();
-    println!("{:?}", derived_desc);
-
-    let addr = derived_desc.address(Network::Testnet).unwrap();
-    println!("{}", addr);
-
-    let script = derived_desc.witness_script();
-    println!("{:?}", script);
-}
diff --git a/core/lib/examples/psbt.rs b/core/lib/examples/psbt.rs
deleted file mode 100644 (file)
index ed0281f..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-extern crate base64;
-extern crate magical_bitcoin_wallet;
-
-use std::str::FromStr;
-
-use magical_bitcoin_wallet::bitcoin;
-use magical_bitcoin_wallet::descriptor::*;
-use magical_bitcoin_wallet::psbt::*;
-use magical_bitcoin_wallet::signer::Signer;
-
-use bitcoin::consensus::encode::{deserialize, serialize};
-use bitcoin::util::psbt::PartiallySignedTransaction;
-use bitcoin::SigHashType;
-
-fn main() {
-    let desc = "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/*)";
-
-    let extended_desc = ExtendedDescriptor::from_str(desc).unwrap();
-
-    let psbt_str = "cHNidP8BAFMCAAAAAd9SiQfxXZ+CKjgjRNonWXsnlA84aLvjxtwCmMfRc0ZbAQAAAAD+////ASjS9QUAAAAAF6kUYJR3oB0lS1M0W1RRMMiENSX45IuHAAAAAAABAPUCAAAAA9I7/OqeFeOFdr5VTLnj3UI/CNRw2eWmMPf7qDv6uIF6AAAAABcWABTG+kgr0g44V0sK9/9FN9oG/CxMK/7///+d0ffphPcV6FE9J/3ZPKWu17YxBnWWTJQyRJs3HUo1gwEAAAAA/v///835mYd9DmnjVnUKd2421MDoZmIxvB4XyJluN3SPUV9hAAAAABcWABRfvwFGp+x/yWdXeNgFs9v0duyeS/7///8CFbH+AAAAAAAXqRSEnTOAjJN/X6ZgR9ftKmwisNSZx4cA4fUFAAAAABl2qRTs6pS4x17MSQ4yNs/1GPsfdlv2NIisAAAAACIGApVE9PPtkcqp8Da43yrXGv4nLOotZdyxwJoTWQxuLxIuCAxfmh4JAAAAAAA=";
-    let psbt_buf = base64::decode(psbt_str).unwrap();
-    let mut psbt: PartiallySignedTransaction = deserialize(&psbt_buf).unwrap();
-
-    let signer = PSBTSigner::from_descriptor(&psbt.global.unsigned_tx, &extended_desc).unwrap();
-
-    for (index, input) in psbt.inputs.iter_mut().enumerate() {
-        for (pubkey, (fing, path)) in &input.hd_keypaths {
-            let sighash = input.sighash_type.unwrap_or(SigHashType::All);
-
-            // Ignore the "witness_utxo" case because we know this psbt is a legacy tx
-            if let Some(non_wit_utxo) = &input.non_witness_utxo {
-                let prev_script = &non_wit_utxo.output
-                    [psbt.global.unsigned_tx.input[index].previous_output.vout as usize]
-                    .script_pubkey;
-                let (signature, sighash) = signer
-                    .sig_legacy_from_fingerprint(index, sighash, fing, path, prev_script)
-                    .unwrap()
-                    .unwrap();
-
-                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);
-            }
-        }
-    }
-
-    println!("signed: {}", base64::encode(&serialize(&psbt)));
-}
diff --git a/core/lib/src/database/keyvalue.rs b/core/lib/src/database/keyvalue.rs
deleted file mode 100644 (file)
index d34ac9f..0000000
+++ /dev/null
@@ -1,638 +0,0 @@
-use std::convert::{From, TryInto};
-
-use sled::{Batch, Tree};
-
-use bitcoin::consensus::encode::{deserialize, serialize};
-use bitcoin::hash_types::Txid;
-use bitcoin::util::bip32::{ChildNumber, DerivationPath};
-use bitcoin::{OutPoint, Script, Transaction};
-
-use crate::database::{BatchDatabase, BatchOperations, Database};
-use crate::error::Error;
-use crate::types::*;
-
-// path -> script       p{i,e}<path> -> script
-// script -> path       s<script> -> {i,e}<path>
-// outpoint             u<outpoint> -> txout
-// rawtx                r<txid> -> tx
-// transactions         t<txid> -> tx details
-// deriv indexes        c{i,e} -> u32
-
-enum SledKey<'a> {
-    Path((Option<ScriptType>, Option<&'a DerivationPath>)),
-    Script(Option<&'a Script>),
-    UTXO(Option<&'a OutPoint>),
-    RawTx(Option<&'a Txid>),
-    Transaction(Option<&'a Txid>),
-    LastIndex(ScriptType),
-}
-
-impl SledKey<'_> {
-    pub fn as_prefix(&self) -> Vec<u8> {
-        match self {
-            SledKey::Path((st, _)) => {
-                let mut v = b"p".to_vec();
-                if let Some(st) = st {
-                    v.push(st.as_byte());
-                }
-                v
-            }
-            SledKey::Script(_) => b"s".to_vec(),
-            SledKey::UTXO(_) => b"u".to_vec(),
-            SledKey::RawTx(_) => b"r".to_vec(),
-            SledKey::Transaction(_) => b"t".to_vec(),
-            SledKey::LastIndex(st) => [b"c", st.as_ref()].concat(),
-        }
-    }
-
-    fn serialize_content(&self) -> Vec<u8> {
-        match self {
-            SledKey::Path((_, Some(path))) => {
-                let mut res = vec![];
-                for val in *path {
-                    res.extend(&u32::from(*val).to_be_bytes());
-                }
-                res
-            }
-            SledKey::Script(Some(s)) => serialize(*s),
-            SledKey::UTXO(Some(s)) => serialize(*s),
-            SledKey::RawTx(Some(s)) => serialize(*s),
-            SledKey::Transaction(Some(s)) => serialize(*s),
-            _ => vec![],
-        }
-    }
-
-    pub fn as_sled_key(&self) -> Vec<u8> {
-        let mut v = self.as_prefix();
-        v.extend_from_slice(&self.serialize_content());
-
-        v
-    }
-}
-
-macro_rules! impl_batch_operations {
-    ( { $($after_insert:tt)* }, $process_delete:ident ) => {
-        fn set_script_pubkey<P: AsRef<[ChildNumber]>>(&mut self, script: &Script, script_type: ScriptType, path: &P) -> Result<(), Error> {
-            let deriv_path = DerivationPath::from(path.as_ref());
-            let key = SledKey::Path((Some(script_type), Some(&deriv_path))).as_sled_key();
-            self.insert(key, serialize(script))$($after_insert)*;
-
-            let key = SledKey::Script(Some(script)).as_sled_key();
-            let value = json!({
-                "t": script_type,
-                "p": deriv_path,
-            });
-            self.insert(key, serde_json::to_vec(&value)?)$($after_insert)*;
-
-            Ok(())
-        }
-
-        fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error> {
-            let key = SledKey::UTXO(Some(&utxo.outpoint)).as_sled_key();
-            let value = serialize(&utxo.txout);
-            self.insert(key, value)$($after_insert)*;
-
-            Ok(())
-        }
-
-        fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error> {
-            let key = SledKey::RawTx(Some(&transaction.txid())).as_sled_key();
-            let value = serialize(transaction);
-            self.insert(key, value)$($after_insert)*;
-
-            Ok(())
-        }
-
-        fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error> {
-            let key = SledKey::Transaction(Some(&transaction.txid)).as_sled_key();
-
-            // remove the raw tx from the serialized version
-            let mut value = serde_json::to_value(transaction)?;
-            value["transaction"] = serde_json::Value::Null;
-            let value = serde_json::to_vec(&value)?;
-
-            self.insert(key, value)$($after_insert)*;
-
-            // insert the raw_tx if present
-            if let Some(ref tx) = transaction.transaction {
-                self.set_raw_tx(tx)?;
-            }
-
-            Ok(())
-        }
-
-        fn set_last_index(&mut self, script_type: ScriptType, value: u32) -> Result<(), Error> {
-            let key = SledKey::LastIndex(script_type).as_sled_key();
-            self.insert(key, &value.to_be_bytes())$($after_insert)*;
-
-            Ok(())
-        }
-
-        fn del_script_pubkey_from_path<P: AsRef<[ChildNumber]>>(&mut self, script_type: ScriptType, path: &P) -> Result<Option<Script>, Error> {
-            let deriv_path = DerivationPath::from(path.as_ref());
-            let key = SledKey::Path((Some(script_type), Some(&deriv_path))).as_sled_key();
-            let res = self.remove(key);
-            let res = $process_delete!(res);
-
-            Ok(res.map_or(Ok(None), |x| Some(deserialize(&x)).transpose())?)
-        }
-
-        fn del_path_from_script_pubkey(&mut self, script: &Script) -> Result<Option<(ScriptType, DerivationPath)>, Error> {
-            let key = SledKey::Script(Some(script)).as_sled_key();
-            let res = self.remove(key);
-            let res = $process_delete!(res);
-
-            match res {
-                None => Ok(None),
-                Some(b) => {
-                    let mut val: serde_json::Value = serde_json::from_slice(&b)?;
-                    let st = serde_json::from_value(val["t"].take())?;
-                    let path = serde_json::from_value(val["p"].take())?;
-
-                    Ok(Some((st, path)))
-                }
-            }
-        }
-
-        fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> {
-            let key = SledKey::UTXO(Some(outpoint)).as_sled_key();
-            let res = self.remove(key);
-            let res = $process_delete!(res);
-
-            match res {
-                None => Ok(None),
-                Some(b) => {
-                    let txout = deserialize(&b)?;
-                    Ok(Some(UTXO { outpoint: outpoint.clone(), txout }))
-                }
-            }
-        }
-
-        fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-            let key = SledKey::RawTx(Some(txid)).as_sled_key();
-            let res = self.remove(key);
-            let res = $process_delete!(res);
-
-            Ok(res.map_or(Ok(None), |x| Some(deserialize(&x)).transpose())?)
-        }
-
-        fn del_tx(&mut self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error> {
-            let raw_tx = if include_raw {
-                self.del_raw_tx(txid)?
-            } else {
-                None
-            };
-
-            let key = SledKey::Transaction(Some(txid)).as_sled_key();
-            let res = self.remove(key);
-            let res = $process_delete!(res);
-
-            match res {
-                None => Ok(None),
-                Some(b) => {
-                    let mut val: TransactionDetails = serde_json::from_slice(&b)?;
-                    val.transaction = raw_tx;
-
-                    Ok(Some(val))
-                }
-            }
-        }
-
-        fn del_last_index(&mut self, script_type: ScriptType) -> Result<Option<u32>, Error> {
-            let key = SledKey::LastIndex(script_type).as_sled_key();
-            let res = self.remove(key);
-            let res = $process_delete!(res);
-
-            match res {
-                None => Ok(None),
-                Some(b) => {
-                    let array: [u8; 4] = b.as_ref().try_into().map_err(|_| Error::InvalidU32Bytes(b.to_vec()))?;
-                    let val = u32::from_be_bytes(array);
-                    Ok(Some(val))
-                }
-            }
-        }
-    }
-}
-
-macro_rules! process_delete_tree {
-    ($res:expr) => {
-        $res?
-    };
-}
-impl BatchOperations for Tree {
-    impl_batch_operations!({?}, process_delete_tree);
-}
-
-macro_rules! process_delete_batch {
-    ($res:expr) => {
-        None as Option<sled::IVec>
-    };
-}
-#[allow(unused_variables)]
-impl BatchOperations for Batch {
-    impl_batch_operations!({}, process_delete_batch);
-}
-
-impl Database for Tree {
-    fn iter_script_pubkeys(&self, script_type: Option<ScriptType>) -> Vec<Result<Script, Error>> {
-        let key = SledKey::Path((script_type, None)).as_sled_key();
-        self.scan_prefix(key)
-            .map(|x| -> Result<_, Error> {
-                let (_, v) = x?;
-                Ok(deserialize(&v)?)
-            })
-            .collect()
-    }
-
-    fn iter_utxos(&self) -> Vec<Result<UTXO, Error>> {
-        let key = SledKey::UTXO(None).as_sled_key();
-        self.scan_prefix(key)
-            .map(|x| -> Result<_, Error> {
-                let (k, v) = x?;
-                let outpoint = deserialize(&k[1..])?;
-                let txout = deserialize(&v)?;
-                Ok(UTXO { outpoint, txout })
-            })
-            .collect()
-    }
-
-    fn iter_raw_txs(&self) -> Vec<Result<Transaction, Error>> {
-        let key = SledKey::RawTx(None).as_sled_key();
-        self.scan_prefix(key)
-            .map(|x| -> Result<_, Error> {
-                let (_, v) = x?;
-                Ok(deserialize(&v)?)
-            })
-            .collect()
-    }
-
-    fn iter_txs(&self, include_raw: bool) -> Vec<Result<TransactionDetails, Error>> {
-        let key = SledKey::RawTx(None).as_sled_key();
-        self.scan_prefix(key)
-            .map(|x| -> Result<_, Error> {
-                let (k, v) = x?;
-                let mut txdetails: TransactionDetails = serde_json::from_slice(&v)?;
-                if include_raw {
-                    let txid = deserialize(&k[1..])?;
-                    txdetails.transaction = self.get_raw_tx(&txid)?;
-                }
-
-                Ok(txdetails)
-            })
-            .collect()
-    }
-
-    fn get_script_pubkey_from_path<P: AsRef<[ChildNumber]>>(
-        &self,
-        script_type: ScriptType,
-        path: &P,
-    ) -> Result<Option<Script>, Error> {
-        let deriv_path = DerivationPath::from(path.as_ref());
-        let key = SledKey::Path((Some(script_type), Some(&deriv_path))).as_sled_key();
-        Ok(self.get(key)?.map(|b| deserialize(&b)).transpose()?)
-    }
-
-    fn get_path_from_script_pubkey(
-        &self,
-        script: &Script,
-    ) -> Result<Option<(ScriptType, DerivationPath)>, Error> {
-        let key = SledKey::Script(Some(script)).as_sled_key();
-        self.get(key)?
-            .map(|b| -> Result<_, Error> {
-                let mut val: serde_json::Value = serde_json::from_slice(&b)?;
-                let st = serde_json::from_value(val["t"].take())?;
-                let path = serde_json::from_value(val["p"].take())?;
-
-                Ok((st, path))
-            })
-            .transpose()
-    }
-
-    fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> {
-        let key = SledKey::UTXO(Some(outpoint)).as_sled_key();
-        self.get(key)?
-            .map(|b| -> Result<_, Error> {
-                let txout = deserialize(&b)?;
-                Ok(UTXO {
-                    outpoint: outpoint.clone(),
-                    txout,
-                })
-            })
-            .transpose()
-    }
-
-    fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-        let key = SledKey::RawTx(Some(txid)).as_sled_key();
-        Ok(self.get(key)?.map(|b| deserialize(&b)).transpose()?)
-    }
-
-    fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error> {
-        let key = SledKey::Transaction(Some(txid)).as_sled_key();
-        self.get(key)?
-            .map(|b| -> Result<_, Error> {
-                let mut txdetails: TransactionDetails = serde_json::from_slice(&b)?;
-                if include_raw {
-                    txdetails.transaction = self.get_raw_tx(&txid)?;
-                }
-
-                Ok(txdetails)
-            })
-            .transpose()
-    }
-
-    fn get_last_index(&self, script_type: ScriptType) -> Result<Option<u32>, Error> {
-        let key = SledKey::LastIndex(script_type).as_sled_key();
-        self.get(key)?
-            .map(|b| -> Result<_, Error> {
-                let array: [u8; 4] = b
-                    .as_ref()
-                    .try_into()
-                    .map_err(|_| Error::InvalidU32Bytes(b.to_vec()))?;
-                let val = u32::from_be_bytes(array);
-                Ok(val)
-            })
-            .transpose()
-    }
-
-    // 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| {
-            let new = match prev {
-                Some(b) => {
-                    let array: [u8; 4] = b.try_into().unwrap_or([0; 4]);
-                    let val = u32::from_be_bytes(array);
-
-                    val + 1
-                }
-                None => 1, // start from 1, we return 0 when the prev value was None
-            };
-
-            Some(new.to_be_bytes().to_vec())
-        })?
-        .map_or(Ok(0), |b| -> Result<_, Error> {
-            let array: [u8; 4] = b
-                .as_ref()
-                .try_into()
-                .map_err(|_| Error::InvalidU32Bytes(b.to_vec()))?;
-            let val = u32::from_be_bytes(array);
-            Ok(val)
-        })
-    }
-}
-
-impl BatchDatabase for Tree {
-    type Batch = sled::Batch;
-
-    fn begin_batch(&self) -> Self::Batch {
-        sled::Batch::default()
-    }
-
-    fn commit_batch(&mut self, batch: Self::Batch) -> Result<(), Error> {
-        Ok(self.apply_batch(batch)?)
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use std::str::FromStr;
-    use std::sync::{Arc, Condvar, Mutex, Once};
-    use std::time::{SystemTime, UNIX_EPOCH};
-
-    use sled::{Db, Tree};
-
-    use bitcoin::consensus::encode::deserialize;
-    use bitcoin::hashes::hex::*;
-    use bitcoin::*;
-
-    use crate::database::*;
-
-    static mut COUNT: usize = 0;
-
-    lazy_static! {
-        static ref DB: Arc<(Mutex<Option<Db>>, Condvar)> =
-            Arc::new((Mutex::new(None), Condvar::new()));
-        static ref INIT: Once = Once::new();
-    }
-
-    fn get_tree() -> Tree {
-        unsafe {
-            let cloned = DB.clone();
-            let (mutex, cvar) = &*cloned;
-
-            INIT.call_once(|| {
-                let mut db = mutex.lock().unwrap();
-
-                let time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
-                let mut dir = std::env::temp_dir();
-                dir.push(format!("mbw_{}", time.as_nanos()));
-
-                *db = Some(sled::open(dir).unwrap());
-                cvar.notify_all();
-            });
-
-            let mut db = mutex.lock().unwrap();
-            while !db.is_some() {
-                db = cvar.wait(db).unwrap();
-            }
-
-            COUNT += 1;
-
-            db.as_ref()
-                .unwrap()
-                .open_tree(format!("tree_{}", COUNT))
-                .unwrap()
-        }
-    }
-
-    #[test]
-    fn test_script_pubkey() {
-        let mut tree = get_tree();
-
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let path = DerivationPath::from_str("m/0/1/2/3").unwrap();
-        let script_type = ScriptType::External;
-
-        tree.set_script_pubkey(&script, script_type, &path).unwrap();
-
-        assert_eq!(
-            tree.get_script_pubkey_from_path(script_type, &path)
-                .unwrap(),
-            Some(script.clone())
-        );
-        assert_eq!(
-            tree.get_path_from_script_pubkey(&script).unwrap(),
-            Some((script_type, path.clone()))
-        );
-    }
-
-    #[test]
-    fn test_batch_script_pubkey() {
-        let mut tree = get_tree();
-        let mut batch = tree.begin_batch();
-
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let path = DerivationPath::from_str("m/0/1/2/3").unwrap();
-        let script_type = ScriptType::External;
-
-        batch
-            .set_script_pubkey(&script, script_type, &path)
-            .unwrap();
-
-        assert_eq!(
-            tree.get_script_pubkey_from_path(script_type, &path)
-                .unwrap(),
-            None
-        );
-        assert_eq!(tree.get_path_from_script_pubkey(&script).unwrap(), None);
-
-        tree.commit_batch(batch).unwrap();
-
-        assert_eq!(
-            tree.get_script_pubkey_from_path(script_type, &path)
-                .unwrap(),
-            Some(script.clone())
-        );
-        assert_eq!(
-            tree.get_path_from_script_pubkey(&script).unwrap(),
-            Some((script_type, path.clone()))
-        );
-    }
-
-    #[test]
-    fn test_iter_script_pubkey() {
-        let mut tree = get_tree();
-
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let path = DerivationPath::from_str("m/0/1/2/3").unwrap();
-        let script_type = ScriptType::External;
-
-        tree.set_script_pubkey(&script, script_type, &path).unwrap();
-
-        assert_eq!(tree.iter_script_pubkeys(None).len(), 1);
-    }
-
-    #[test]
-    fn test_del_script_pubkey() {
-        let mut tree = get_tree();
-
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let path = DerivationPath::from_str("m/0/1/2/3").unwrap();
-        let script_type = ScriptType::External;
-
-        tree.set_script_pubkey(&script, script_type, &path).unwrap();
-        assert_eq!(tree.iter_script_pubkeys(None).len(), 1);
-
-        tree.del_script_pubkey_from_path(script_type, &path)
-            .unwrap();
-        assert_eq!(tree.iter_script_pubkeys(None).len(), 0);
-    }
-
-    #[test]
-    fn test_utxo() {
-        let mut tree = get_tree();
-
-        let outpoint = OutPoint::from_str(
-            "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:0",
-        )
-        .unwrap();
-        let script = Script::from(
-            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
-        );
-        let txout = TxOut {
-            value: 133742,
-            script_pubkey: script,
-        };
-        let utxo = UTXO { txout, outpoint };
-
-        tree.set_utxo(&utxo).unwrap();
-
-        assert_eq!(tree.get_utxo(&outpoint).unwrap(), Some(utxo));
-    }
-
-    #[test]
-    fn test_raw_tx() {
-        let mut tree = get_tree();
-
-        let hex_tx = Vec::<u8>::from_hex("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000").unwrap();
-        let tx: Transaction = deserialize(&hex_tx).unwrap();
-
-        tree.set_raw_tx(&tx).unwrap();
-
-        let txid = tx.txid();
-
-        assert_eq!(tree.get_raw_tx(&txid).unwrap(), Some(tx));
-    }
-
-    #[test]
-    fn test_tx() {
-        let mut tree = get_tree();
-
-        let hex_tx = Vec::<u8>::from_hex("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000").unwrap();
-        let tx: Transaction = deserialize(&hex_tx).unwrap();
-        let txid = tx.txid();
-        let mut tx_details = TransactionDetails {
-            transaction: Some(tx),
-            txid,
-            timestamp: 123456,
-            received: 1337,
-            sent: 420420,
-            height: Some(1000),
-        };
-
-        tree.set_tx(&tx_details).unwrap();
-
-        // get with raw tx too
-        assert_eq!(
-            tree.get_tx(&tx_details.txid, true).unwrap(),
-            Some(tx_details.clone())
-        );
-        // get only raw_tx
-        assert_eq!(
-            tree.get_raw_tx(&tx_details.txid).unwrap(),
-            tx_details.transaction
-        );
-
-        // now get without raw_tx
-        tx_details.transaction = None;
-        assert_eq!(
-            tree.get_tx(&tx_details.txid, false).unwrap(),
-            Some(tx_details)
-        );
-    }
-
-    #[test]
-    fn test_last_index() {
-        let mut tree = get_tree();
-
-        tree.set_last_index(ScriptType::External, 1337).unwrap();
-
-        assert_eq!(
-            tree.get_last_index(ScriptType::External).unwrap(),
-            Some(1337)
-        );
-        assert_eq!(tree.get_last_index(ScriptType::Internal).unwrap(), None);
-
-        let res = tree.increment_last_index(ScriptType::External).unwrap();
-        assert_eq!(res, 1337);
-        let res = tree.increment_last_index(ScriptType::Internal).unwrap();
-        assert_eq!(res, 0);
-
-        assert_eq!(
-            tree.get_last_index(ScriptType::External).unwrap(),
-            Some(1338)
-        );
-        assert_eq!(tree.get_last_index(ScriptType::Internal).unwrap(), Some(1));
-    }
-
-    // TODO: more tests...
-}
diff --git a/core/lib/src/database/mod.rs b/core/lib/src/database/mod.rs
deleted file mode 100644 (file)
index 0e28b78..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-use bitcoin::hash_types::Txid;
-use bitcoin::util::bip32::{ChildNumber, DerivationPath};
-use bitcoin::{OutPoint, Script, Transaction};
-
-use crate::error::Error;
-use crate::types::*;
-
-#[cfg(any(feature = "key-value-db", feature = "default"))]
-pub mod keyvalue;
-
-pub trait BatchOperations {
-    fn set_script_pubkey<P: AsRef<[ChildNumber]>>(
-        &mut self,
-        script: &Script,
-        script_type: ScriptType,
-        path: &P,
-    ) -> Result<(), Error>;
-    fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error>;
-    fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error>;
-    fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error>;
-    fn set_last_index(&mut self, script_type: ScriptType, value: u32) -> Result<(), Error>;
-
-    fn del_script_pubkey_from_path<P: AsRef<[ChildNumber]>>(
-        &mut self,
-        script_type: ScriptType,
-        path: &P,
-    ) -> Result<Option<Script>, Error>;
-    fn del_path_from_script_pubkey(
-        &mut self,
-        script: &Script,
-    ) -> Result<Option<(ScriptType, DerivationPath)>, Error>;
-    fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error>;
-    fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error>;
-    fn del_tx(
-        &mut self,
-        txid: &Txid,
-        include_raw: bool,
-    ) -> Result<Option<TransactionDetails>, Error>;
-    fn del_last_index(&mut self, script_type: ScriptType) -> Result<Option<u32>, Error>;
-}
-
-pub trait Database: BatchOperations {
-    fn iter_script_pubkeys(&self, script_type: Option<ScriptType>) -> Vec<Result<Script, Error>>;
-    fn iter_utxos(&self) -> Vec<Result<UTXO, Error>>;
-    fn iter_raw_txs(&self) -> Vec<Result<Transaction, Error>>;
-    fn iter_txs(&self, include_raw: bool) -> Vec<Result<TransactionDetails, Error>>;
-
-    fn get_script_pubkey_from_path<P: AsRef<[ChildNumber]>>(
-        &self,
-        script_type: ScriptType,
-        path: &P,
-    ) -> Result<Option<Script>, Error>;
-    fn get_path_from_script_pubkey(
-        &self,
-        script: &Script,
-    ) -> Result<Option<(ScriptType, DerivationPath)>, Error>;
-    fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error>;
-    fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>;
-    fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error>;
-    fn get_last_index(&self, script_type: ScriptType) -> Result<Option<u32>, Error>;
-
-    // inserts 0 if not present
-    fn increment_last_index(&mut self, script_type: ScriptType) -> Result<u32, Error>;
-}
-
-pub trait BatchDatabase: Database {
-    type Batch: BatchOperations;
-
-    fn begin_batch(&self) -> Self::Batch;
-    fn commit_batch(&mut self, batch: Self::Batch) -> Result<(), Error>;
-}
diff --git a/core/lib/src/descriptor/error.rs b/core/lib/src/descriptor/error.rs
deleted file mode 100644 (file)
index f9a1087..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-#[derive(Debug)]
-pub enum Error {
-    InternalError,
-    InvalidPrefix(Vec<u8>),
-    HardenedDerivationOnXpub,
-    MalformedInput,
-    KeyParsingError(String),
-
-    BIP32(bitcoin::util::bip32::Error),
-    Base58(bitcoin::util::base58::Error),
-    PK(bitcoin::util::key::Error),
-    Miniscript(miniscript::Error),
-    Hex(bitcoin::hashes::hex::Error),
-}
-
-impl std::fmt::Display for Error {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{:?}", self)
-    }
-}
-
-impl_error!(bitcoin::util::bip32::Error, BIP32);
-impl_error!(bitcoin::util::base58::Error, Base58);
-impl_error!(bitcoin::util::key::Error, PK);
-impl_error!(miniscript::Error, Miniscript);
-impl_error!(bitcoin::hashes::hex::Error, Hex);
diff --git a/core/lib/src/descriptor/extended_key.rs b/core/lib/src/descriptor/extended_key.rs
deleted file mode 100644 (file)
index 0f7bd9e..0000000
+++ /dev/null
@@ -1,349 +0,0 @@
-use std::fmt::{self, Display};
-use std::str::FromStr;
-
-use bitcoin::hashes::hex::{FromHex, ToHex};
-use bitcoin::secp256k1;
-use bitcoin::util::base58;
-use bitcoin::util::bip32::{
-    ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, Fingerprint,
-};
-use bitcoin::PublicKey;
-
-#[allow(unused_imports)]
-use log::{debug, error, info, trace};
-
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-pub enum DerivationIndex {
-    Fixed,
-    Normal,
-    Hardened,
-}
-
-impl DerivationIndex {
-    fn as_path(&self, index: u32) -> DerivationPath {
-        match self {
-            DerivationIndex::Fixed => vec![],
-            DerivationIndex::Normal => vec![ChildNumber::Normal { index }],
-            DerivationIndex::Hardened => vec![ChildNumber::Hardened { index }],
-        }
-        .into()
-    }
-}
-
-impl Display for DerivationIndex {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let chars = match *self {
-            Self::Fixed => "",
-            Self::Normal => "/*",
-            Self::Hardened => "/*'",
-        };
-
-        write!(f, "{}", chars)
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct DescriptorExtendedKey {
-    pub master_fingerprint: Option<Fingerprint>,
-    pub master_derivation: Option<DerivationPath>,
-    pub pubkey: ExtendedPubKey,
-    pub secret: Option<ExtendedPrivKey>,
-    pub path: DerivationPath,
-    pub final_index: DerivationIndex,
-}
-
-impl DescriptorExtendedKey {
-    pub fn full_path(&self, index: u32) -> DerivationPath {
-        let mut final_path: Vec<ChildNumber> = self.path.clone().into();
-        let other_path: Vec<ChildNumber> = self.final_index.as_path(index).into();
-        final_path.extend_from_slice(&other_path);
-
-        final_path.into()
-    }
-
-    pub fn derive<C: secp256k1::Verification + secp256k1::Signing>(
-        &self,
-        ctx: &secp256k1::Secp256k1<C>,
-        index: u32,
-    ) -> Result<PublicKey, super::Error> {
-        Ok(self.derive_xpub(ctx, index)?.public_key)
-    }
-
-    pub fn derive_xpub<C: secp256k1::Verification + secp256k1::Signing>(
-        &self,
-        ctx: &secp256k1::Secp256k1<C>,
-        index: u32,
-    ) -> Result<ExtendedPubKey, super::Error> {
-        if let Some(xprv) = self.secret {
-            let derive_priv = xprv.derive_priv(ctx, &self.full_path(index))?;
-            Ok(ExtendedPubKey::from_private(ctx, &derive_priv))
-        } else {
-            Ok(self.pubkey.derive_pub(ctx, &self.full_path(index))?)
-        }
-    }
-
-    pub fn root_xpub<C: secp256k1::Verification + secp256k1::Signing>(
-        &self,
-        ctx: &secp256k1::Secp256k1<C>,
-    ) -> ExtendedPubKey {
-        if let Some(ref xprv) = self.secret {
-            ExtendedPubKey::from_private(ctx, xprv)
-        } else {
-            self.pubkey
-        }
-    }
-}
-
-impl Display for DescriptorExtendedKey {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        if let Some(ref fingerprint) = self.master_fingerprint {
-            write!(f, "[{}", fingerprint.to_hex())?;
-            if let Some(ref path) = self.master_derivation {
-                write!(f, "{}", &path.to_string()[1..])?;
-            }
-            write!(f, "]")?;
-        }
-
-        if let Some(xprv) = self.secret {
-            write!(f, "{}", xprv)?
-        } else {
-            write!(f, "{}", self.pubkey)?
-        }
-
-        write!(f, "{}{}", &self.path.to_string()[1..], self.final_index)
-    }
-}
-
-impl FromStr for DescriptorExtendedKey {
-    type Err = super::Error;
-
-    fn from_str(inp: &str) -> Result<DescriptorExtendedKey, Self::Err> {
-        let len = inp.len();
-
-        let (master_fingerprint, master_derivation, offset) = match inp.starts_with("[") {
-            false => (None, None, 0),
-            true => {
-                if inp.len() < 9 {
-                    return Err(super::Error::MalformedInput);
-                }
-
-                let master_fingerprint = &inp[1..9];
-                let close_bracket_index =
-                    &inp[9..].find("]").ok_or(super::Error::MalformedInput)?;
-                let path = if *close_bracket_index > 0 {
-                    Some(DerivationPath::from_str(&format!(
-                        "m{}",
-                        &inp[9..9 + *close_bracket_index]
-                    ))?)
-                } else {
-                    None
-                };
-
-                (
-                    Some(Fingerprint::from_hex(master_fingerprint)?),
-                    path,
-                    9 + *close_bracket_index + 1,
-                )
-            }
-        };
-
-        let (key_range, offset) = match &inp[offset..].find("/") {
-            Some(index) => (offset..offset + *index, offset + *index),
-            None => (offset..len, len),
-        };
-        let data = base58::from_check(&inp[key_range.clone()])?;
-        let secp = secp256k1::Secp256k1::new();
-        let (pubkey, secret) = match &data[0..4] {
-            [0x04u8, 0x88, 0xB2, 0x1E] | [0x04u8, 0x35, 0x87, 0xCF] => {
-                (ExtendedPubKey::from_str(&inp[key_range])?, None)
-            }
-            [0x04u8, 0x88, 0xAD, 0xE4] | [0x04u8, 0x35, 0x83, 0x94] => {
-                let private = ExtendedPrivKey::from_str(&inp[key_range])?;
-                (ExtendedPubKey::from_private(&secp, &private), Some(private))
-            }
-            data => return Err(super::Error::InvalidPrefix(data.into())),
-        };
-
-        let (path, final_index, _) = match &inp[offset..].starts_with("/") {
-            false => (DerivationPath::from(vec![]), DerivationIndex::Fixed, offset),
-            true => {
-                let (all, skip) = match &inp[len - 2..len] {
-                    "/*" => (DerivationIndex::Normal, 2),
-                    "*'" | "*h" => (DerivationIndex::Hardened, 3),
-                    _ => (DerivationIndex::Fixed, 0),
-                };
-
-                if all == DerivationIndex::Hardened && secret.is_none() {
-                    return Err(super::Error::HardenedDerivationOnXpub);
-                }
-
-                (
-                    DerivationPath::from_str(&format!("m{}", &inp[offset..len - skip]))?,
-                    all,
-                    len,
-                )
-            }
-        };
-
-        Ok(DescriptorExtendedKey {
-            master_fingerprint,
-            master_derivation,
-            pubkey,
-            secret,
-            path,
-            final_index,
-        })
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use std::str::FromStr;
-
-    use bitcoin::hashes::hex::FromHex;
-    use bitcoin::util::bip32::{ChildNumber, DerivationPath};
-
-    use crate::descriptor::*;
-
-    macro_rules! hex_fingerprint {
-        ($hex:expr) => {
-            Fingerprint::from_hex($hex).unwrap()
-        };
-    }
-
-    macro_rules! deriv_path {
-        ($str:expr) => {
-            DerivationPath::from_str($str).unwrap()
-        };
-
-        () => {
-            DerivationPath::from(vec![])
-        };
-    }
-
-    #[test]
-    fn test_derivation_index_fixed() {
-        let index = DerivationIndex::Fixed;
-        assert_eq!(index.as_path(1337), DerivationPath::from(vec![]));
-        assert_eq!(format!("{}", index), "");
-    }
-
-    #[test]
-    fn test_derivation_index_normal() {
-        let index = DerivationIndex::Normal;
-        assert_eq!(
-            index.as_path(1337),
-            DerivationPath::from(vec![ChildNumber::Normal { index: 1337 }])
-        );
-        assert_eq!(format!("{}", index), "/*");
-    }
-
-    #[test]
-    fn test_derivation_index_hardened() {
-        let index = DerivationIndex::Hardened;
-        assert_eq!(
-            index.as_path(1337),
-            DerivationPath::from(vec![ChildNumber::Hardened { index: 1337 }])
-        );
-        assert_eq!(format!("{}", index), "/*'");
-    }
-
-    #[test]
-    fn test_parse_xpub_no_path_fixed() {
-        let key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL";
-        let ek = DescriptorExtendedKey::from_str(key).unwrap();
-        assert_eq!(ek.pubkey.fingerprint(), hex_fingerprint!("31a507b8"));
-        assert_eq!(ek.path, deriv_path!());
-        assert_eq!(ek.final_index, DerivationIndex::Fixed);
-    }
-
-    #[test]
-    fn test_parse_xpub_with_path_fixed() {
-        let key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/2/3";
-        let ek = DescriptorExtendedKey::from_str(key).unwrap();
-        assert_eq!(ek.pubkey.fingerprint(), hex_fingerprint!("31a507b8"));
-        assert_eq!(ek.path, deriv_path!("m/1/2/3"));
-        assert_eq!(ek.final_index, DerivationIndex::Fixed);
-    }
-
-    #[test]
-    fn test_parse_xpub_with_path_normal() {
-        let key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/2/3/*";
-        let ek = DescriptorExtendedKey::from_str(key).unwrap();
-        assert_eq!(ek.pubkey.fingerprint(), hex_fingerprint!("31a507b8"));
-        assert_eq!(ek.path, deriv_path!("m/1/2/3"));
-        assert_eq!(ek.final_index, DerivationIndex::Normal);
-    }
-
-    #[test]
-    #[should_panic(expected = "HardenedDerivationOnXpub")]
-    fn test_parse_xpub_with_path_hardened() {
-        let key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*'";
-        let ek = DescriptorExtendedKey::from_str(key).unwrap();
-        assert_eq!(ek.pubkey.fingerprint(), hex_fingerprint!("31a507b8"));
-        assert_eq!(ek.path, deriv_path!("m/1/2/3"));
-        assert_eq!(ek.final_index, DerivationIndex::Fixed);
-    }
-
-    #[test]
-    fn test_parse_tprv_with_path_hardened() {
-        let key = "tprv8ZgxMBicQKsPduL5QnGihpprdHyypMGi4DhimjtzYemu7se5YQNcZfAPLqXRuGHb5ZX2eTQj62oNqMnyxJ7B7wz54Uzswqw8fFqMVdcmVF7/1/2/3/*'";
-        let ek = DescriptorExtendedKey::from_str(key).unwrap();
-        assert!(ek.secret.is_some());
-        assert_eq!(ek.pubkey.fingerprint(), hex_fingerprint!("5ea4190e"));
-        assert_eq!(ek.path, deriv_path!("m/1/2/3"));
-        assert_eq!(ek.final_index, DerivationIndex::Hardened);
-    }
-
-    #[test]
-    fn test_parse_xpub_master_details() {
-        let key = "[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL";
-        let ek = DescriptorExtendedKey::from_str(key).unwrap();
-        assert_eq!(ek.master_fingerprint, Some(hex_fingerprint!("d34db33f")));
-        assert_eq!(ek.master_derivation, Some(deriv_path!("m/44'/0'/0'")));
-    }
-
-    #[test]
-    fn test_parse_xpub_master_details_empty_derivation() {
-        let key = "[d34db33f]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL";
-        let ek = DescriptorExtendedKey::from_str(key).unwrap();
-        assert_eq!(ek.master_fingerprint, Some(hex_fingerprint!("d34db33f")));
-        assert_eq!(ek.master_derivation, None);
-    }
-
-    #[test]
-    #[should_panic(expected = "MalformedInput")]
-    fn test_parse_xpub_short_input() {
-        let key = "[d34d";
-        DescriptorExtendedKey::from_str(key).unwrap();
-    }
-
-    #[test]
-    #[should_panic(expected = "MalformedInput")]
-    fn test_parse_xpub_missing_closing_bracket() {
-        let key = "[d34db33fxpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL";
-        DescriptorExtendedKey::from_str(key).unwrap();
-    }
-
-    #[test]
-    #[should_panic(expected = "InvalidChar")]
-    fn test_parse_xpub_invalid_fingerprint() {
-        let key = "[d34db33z]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL";
-        DescriptorExtendedKey::from_str(key).unwrap();
-    }
-
-    #[test]
-    fn test_xpub_normal_full_path() {
-        let key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/2/*";
-        let ek = DescriptorExtendedKey::from_str(key).unwrap();
-        assert_eq!(ek.full_path(42), deriv_path!("m/1/2/42"));
-    }
-
-    #[test]
-    fn test_xpub_fixed_full_path() {
-        let key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/2";
-        let ek = DescriptorExtendedKey::from_str(key).unwrap();
-        assert_eq!(ek.full_path(42), deriv_path!("m/1/2"));
-        assert_eq!(ek.full_path(1337), deriv_path!("m/1/2"));
-    }
-}
diff --git a/core/lib/src/descriptor/mod.rs b/core/lib/src/descriptor/mod.rs
deleted file mode 100644 (file)
index 01c6a0a..0000000
+++ /dev/null
@@ -1,476 +0,0 @@
-use std::cell::RefCell;
-use std::collections::BTreeMap;
-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};
-
-pub use miniscript::descriptor::Descriptor;
-
-use serde::{Deserialize, Serialize};
-
-pub mod error;
-pub mod extended_key;
-
-pub use self::error::Error;
-pub use self::extended_key::{DerivationIndex, DescriptorExtendedKey};
-
-#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord, Default)]
-struct DummyKey();
-
-impl fmt::Display for DummyKey {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "DummyKey")
-    }
-}
-
-impl std::str::FromStr for DummyKey {
-    type Err = ();
-
-    fn from_str(_: &str) -> Result<Self, Self::Err> {
-        Ok(DummyKey::default())
-    }
-}
-
-impl miniscript::MiniscriptKey for DummyKey {
-    type Hash = DummyKey;
-
-    fn to_pubkeyhash(&self) -> DummyKey {
-        DummyKey::default()
-    }
-}
-
-pub type DerivedDescriptor = Descriptor<PublicKey>;
-pub type StringDescriptor = Descriptor<String>;
-
-pub trait DescriptorMeta {
-    fn is_witness(&self) -> bool;
-    fn psbt_redeem_script(&self) -> Option<Script>;
-    fn psbt_witness_script(&self) -> Option<Script>;
-}
-
-impl<T> DescriptorMeta for Descriptor<T>
-where
-    T: miniscript::MiniscriptKey + miniscript::ToPublicKey,
-{
-    fn is_witness(&self) -> bool {
-        match self {
-            Descriptor::Bare(_) | Descriptor::Pk(_) | Descriptor::Pkh(_) | Descriptor::Sh(_) => {
-                false
-            }
-            Descriptor::Wpkh(_)
-            | Descriptor::ShWpkh(_)
-            | Descriptor::Wsh(_)
-            | Descriptor::ShWsh(_) => true,
-        }
-    }
-
-    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::ShWsh(ref script) => Some(script.encode().to_v0_p2wsh()),
-            Descriptor::Sh(ref script) => Some(script.encode()),
-            _ => None,
-        }
-    }
-
-    fn psbt_witness_script(&self) -> Option<Script> {
-        match self {
-            Descriptor::Wsh(ref script) => Some(script.encode()),
-            _ => None,
-        }
-    }
-}
-
-trait Key: std::fmt::Debug {
-    fn fingerprint(&self, secp: &Secp256k1<All>) -> Option<Fingerprint>;
-    fn as_public_key(&self, secp: &Secp256k1<All>, index: Option<u32>) -> Result<PublicKey, Error>;
-    fn as_secret_key(&self) -> Option<PrivateKey>;
-    fn xprv(&self) -> Option<ExtendedPrivKey>;
-    fn full_path(&self, index: u32) -> Option<DerivationPath>;
-    fn is_fixed(&self) -> bool;
-}
-
-impl Key for PublicKey {
-    fn fingerprint(&self, _secp: &Secp256k1<All>) -> Option<Fingerprint> {
-        None
-    }
-
-    fn as_public_key(
-        &self,
-        _secp: &Secp256k1<All>,
-        _index: Option<u32>,
-    ) -> Result<PublicKey, Error> {
-        Ok(PublicKey::clone(self))
-    }
-
-    fn as_secret_key(&self) -> Option<PrivateKey> {
-        None
-    }
-
-    fn xprv(&self) -> Option<ExtendedPrivKey> {
-        None
-    }
-
-    fn full_path(&self, _index: u32) -> Option<DerivationPath> {
-        None
-    }
-
-    fn is_fixed(&self) -> bool {
-        true
-    }
-}
-
-impl Key for PrivateKey {
-    fn fingerprint(&self, _secp: &Secp256k1<All>) -> Option<Fingerprint> {
-        None
-    }
-
-    fn as_public_key(
-        &self,
-        secp: &Secp256k1<All>,
-        _index: Option<u32>,
-    ) -> Result<PublicKey, Error> {
-        Ok(self.public_key(secp))
-    }
-
-    fn as_secret_key(&self) -> Option<PrivateKey> {
-        Some(PrivateKey::clone(self))
-    }
-
-    fn xprv(&self) -> Option<ExtendedPrivKey> {
-        None
-    }
-
-    fn full_path(&self, _index: u32) -> Option<DerivationPath> {
-        None
-    }
-
-    fn is_fixed(&self) -> bool {
-        true
-    }
-}
-
-impl Key for DescriptorExtendedKey {
-    fn fingerprint(&self, secp: &Secp256k1<All>) -> Option<Fingerprint> {
-        Some(self.root_xpub(secp).fingerprint())
-    }
-
-    fn as_public_key(&self, secp: &Secp256k1<All>, index: Option<u32>) -> Result<PublicKey, Error> {
-        Ok(self.derive_xpub(secp, index.unwrap_or(0))?.public_key)
-    }
-
-    fn as_secret_key(&self) -> Option<PrivateKey> {
-        None
-    }
-
-    fn xprv(&self) -> Option<ExtendedPrivKey> {
-        self.secret
-    }
-
-    fn full_path(&self, index: u32) -> Option<DerivationPath> {
-        Some(self.full_path(index))
-    }
-
-    fn is_fixed(&self) -> bool {
-        self.final_index == DerivationIndex::Fixed
-    }
-}
-
-#[serde(try_from = "&str", into = "String")]
-#[derive(Debug, Serialize, Deserialize)]
-pub struct ExtendedDescriptor {
-    #[serde(flatten)]
-    internal: StringDescriptor,
-
-    #[serde(skip)]
-    keys: BTreeMap<String, Box<dyn Key>>,
-
-    #[serde(skip)]
-    ctx: Secp256k1<All>,
-}
-
-impl std::clone::Clone for ExtendedDescriptor {
-    fn clone(&self) -> Self {
-        Self {
-            internal: self.internal.clone(),
-            ctx: self.ctx.clone(),
-            keys: BTreeMap::new(),
-        }
-    }
-}
-
-impl ExtendedDescriptor {
-    fn parse_string(string: &str) -> Result<(String, Box<dyn Key>), Error> {
-        if let Ok(pk) = PublicKey::from_str(string) {
-            return Ok((string.to_string(), Box::new(pk)));
-        } else if let Ok(sk) = PrivateKey::from_wif(string) {
-            return Ok((string.to_string(), Box::new(sk)));
-        } else if let Ok(ext_key) = DescriptorExtendedKey::from_str(string) {
-            return Ok((string.to_string(), Box::new(ext_key)));
-        }
-
-        return Err(Error::KeyParsingError(string.to_string()));
-    }
-
-    fn new(sd: StringDescriptor) -> Result<Self, Error> {
-        let ctx = Secp256k1::gen_new();
-        let keys: RefCell<BTreeMap<String, Box<dyn Key>>> = RefCell::new(BTreeMap::new());
-
-        let translatefpk = |string: &String| -> Result<_, Error> {
-            let (key, parsed) = Self::parse_string(string)?;
-            keys.borrow_mut().insert(key, parsed);
-
-            Ok(DummyKey::default())
-        };
-        let translatefpkh = |string: &String| -> Result<_, Error> {
-            let (key, parsed) = Self::parse_string(string)?;
-            keys.borrow_mut().insert(key, parsed);
-
-            Ok(DummyKey::default())
-        };
-
-        sd.translate_pk(translatefpk, translatefpkh)?;
-
-        Ok(ExtendedDescriptor {
-            internal: sd,
-            keys: keys.into_inner(),
-            ctx,
-        })
-    }
-
-    pub fn derive(&self, index: u32) -> Result<DerivedDescriptor, Error> {
-        let translatefpk = |xpub: &String| {
-            self.keys
-                .get(xpub)
-                .unwrap()
-                .as_public_key(&self.ctx, Some(index))
-        };
-        let translatefpkh =
-            |xpub: &String| Ok(hash160::Hash::hash(&translatefpk(xpub)?.to_bytes()));
-
-        Ok(self.internal.translate_pk(translatefpk, translatefpkh)?)
-    }
-
-    pub fn get_xprv(&self) -> Vec<ExtendedPrivKey> {
-        self.keys
-            .iter()
-            .filter(|(_, v)| v.xprv().is_some())
-            .map(|(_, v)| v.xprv().unwrap())
-            .collect()
-    }
-
-    pub fn get_secret_keys(&self) -> Vec<PrivateKey> {
-        self.keys
-            .iter()
-            .filter(|(_, v)| v.as_secret_key().is_some())
-            .map(|(_, v)| v.as_secret_key().unwrap())
-            .collect()
-    }
-
-    pub fn get_hd_keypaths(
-        &self,
-        index: u32,
-    ) -> Result<BTreeMap<PublicKey, (Fingerprint, DerivationPath)>, Error> {
-        let mut answer = BTreeMap::new();
-
-        for (_, key) in &self.keys {
-            if let Some(fingerprint) = key.fingerprint(&self.ctx) {
-                let derivation_path = key.full_path(index).unwrap();
-                let pubkey = key.as_public_key(&self.ctx, Some(index))?;
-
-                answer.insert(pubkey, (fingerprint, derivation_path));
-            }
-        }
-
-        Ok(answer)
-    }
-
-    pub fn max_satisfaction_weight(&self) -> usize {
-        let fake_pk = PublicKey::from_slice(&[
-            2, 140, 40, 169, 123, 248, 41, 139, 192, 210, 61, 140, 116, 148, 82, 163, 46, 105, 75,
-            101, 227, 10, 148, 114, 163, 149, 74, 179, 15, 229, 50, 76, 170,
-        ])
-        .unwrap();
-        let translated: Descriptor<PublicKey> = self
-            .internal
-            .translate_pk(
-                |_| -> Result<_, ()> { Ok(fake_pk.clone()) },
-                |_| -> Result<_, ()> { Ok(Default::default()) },
-            )
-            .unwrap();
-
-        translated.max_satisfaction_weight()
-    }
-
-    pub fn is_fixed(&self) -> bool {
-        self.keys.iter().all(|(_, key)| key.is_fixed())
-    }
-}
-
-impl TryFrom<&str> for ExtendedDescriptor {
-    type Error = Error;
-
-    fn try_from(value: &str) -> Result<Self, Self::Error> {
-        let internal = StringDescriptor::from_str(value)?;
-        ExtendedDescriptor::new(internal)
-    }
-}
-
-impl TryFrom<StringDescriptor> for ExtendedDescriptor {
-    type Error = Error;
-
-    fn try_from(other: StringDescriptor) -> Result<Self, Self::Error> {
-        ExtendedDescriptor::new(other)
-    }
-}
-
-impl FromStr for ExtendedDescriptor {
-    type Err = Error;
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        Self::try_from(s)
-    }
-}
-
-impl Into<String> for ExtendedDescriptor {
-    fn into(self) -> String {
-        format!("{}", self.internal)
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use std::str::FromStr;
-
-    use bitcoin::hashes::hex::FromHex;
-    use bitcoin::{Network, PublicKey};
-
-    use crate::descriptor::*;
-
-    macro_rules! hex_fingerprint {
-        ($hex:expr) => {
-            Fingerprint::from_hex($hex).unwrap()
-        };
-    }
-
-    macro_rules! hex_pubkey {
-        ($hex:expr) => {
-            PublicKey::from_str($hex).unwrap()
-        };
-    }
-
-    macro_rules! deriv_path {
-        ($str:expr) => {
-            DerivationPath::from_str($str).unwrap()
-        };
-
-        () => {
-            DerivationPath::from(vec![])
-        };
-    }
-
-    #[test]
-    fn test_descriptor_parse_wif() {
-        let string = "pkh(cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy)";
-        let desc = ExtendedDescriptor::from_str(string).unwrap();
-        assert!(desc.is_fixed());
-        assert_eq!(
-            desc.derive(0)
-                .unwrap()
-                .address(Network::Testnet)
-                .unwrap()
-                .to_string(),
-            "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx"
-        );
-        assert_eq!(
-            desc.derive(42)
-                .unwrap()
-                .address(Network::Testnet)
-                .unwrap()
-                .to_string(),
-            "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx"
-        );
-        assert_eq!(desc.get_secret_keys().len(), 1);
-    }
-
-    #[test]
-    fn test_descriptor_parse_pubkey() {
-        let string = "pkh(039b6347398505f5ec93826dc61c19f47c66c0283ee9be980e29ce325a0f4679ef)";
-        let desc = ExtendedDescriptor::from_str(string).unwrap();
-        assert!(desc.is_fixed());
-        assert_eq!(
-            desc.derive(0)
-                .unwrap()
-                .address(Network::Testnet)
-                .unwrap()
-                .to_string(),
-            "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx"
-        );
-        assert_eq!(
-            desc.derive(42)
-                .unwrap()
-                .address(Network::Testnet)
-                .unwrap()
-                .to_string(),
-            "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx"
-        );
-        assert_eq!(desc.get_secret_keys().len(), 0);
-    }
-
-    #[test]
-    fn test_descriptor_parse_xpub() {
-        let string = "pkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/*)";
-        let desc = ExtendedDescriptor::from_str(string).unwrap();
-        assert!(!desc.is_fixed());
-        assert_eq!(
-            desc.derive(0)
-                .unwrap()
-                .address(Network::Testnet)
-                .unwrap()
-                .to_string(),
-            "mxbXpnVkwARGtYXk5yeGYf59bGWuPpdE4X"
-        );
-        assert_eq!(
-            desc.derive(42)
-                .unwrap()
-                .address(Network::Testnet)
-                .unwrap()
-                .to_string(),
-            "mhtuS1QaEV4HPcK4bWk4Wvpd64SUjiC5Zt"
-        );
-        assert_eq!(desc.get_xprv().len(), 0);
-    }
-
-    #[test]
-    #[should_panic(expected = "KeyParsingError")]
-    fn test_descriptor_parse_fail() {
-        let string = "pkh(this_is_not_a_valid_key)";
-        ExtendedDescriptor::from_str(string).unwrap();
-    }
-
-    #[test]
-    fn test_descriptor_hd_keypaths() {
-        let string = "pkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/*)";
-        let desc = ExtendedDescriptor::from_str(string).unwrap();
-        let keypaths = desc.get_hd_keypaths(0).unwrap();
-        assert!(keypaths.contains_key(&hex_pubkey!(
-            "025d5fc65ebb8d44a5274b53bac21ff8307fec2334a32df05553459f8b1f7fe1b6"
-        )));
-        assert_eq!(
-            keypaths.get(&hex_pubkey!(
-                "025d5fc65ebb8d44a5274b53bac21ff8307fec2334a32df05553459f8b1f7fe1b6"
-            )),
-            Some(&(hex_fingerprint!("31a507b8"), deriv_path!("m/0")))
-        )
-    }
-}
diff --git a/core/lib/src/error.rs b/core/lib/src/error.rs
deleted file mode 100644 (file)
index 80b72c1..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-#[derive(Debug)]
-pub enum Error {
-    KeyMismatch(bitcoin::secp256k1::PublicKey, bitcoin::secp256k1::PublicKey),
-    MissingInputUTXO(usize),
-    InvalidU32Bytes(Vec<u8>),
-    Generic(String),
-
-    Encode(bitcoin::consensus::encode::Error),
-    BIP32(bitcoin::util::bip32::Error),
-    Secp256k1(bitcoin::secp256k1::Error),
-    JSON(serde_json::Error),
-
-    #[cfg(any(feature = "key-value-db", feature = "default"))]
-    Sled(sled::Error),
-}
-
-macro_rules! impl_error {
-    ( $from:ty, $to:ident ) => {
-        impl std::convert::From<$from> for Error {
-            fn from(err: $from) -> Self {
-                Error::$to(err)
-            }
-        }
-    };
-}
-
-impl_error!(bitcoin::consensus::encode::Error, Encode);
-impl_error!(bitcoin::util::bip32::Error, BIP32);
-impl_error!(bitcoin::secp256k1::Error, Secp256k1);
-impl_error!(serde_json::Error, JSON);
-
-#[cfg(any(feature = "key-value-db", feature = "default"))]
-impl_error!(sled::Error, Sled);
diff --git a/core/lib/src/lib.rs b/core/lib/src/lib.rs
deleted file mode 100644 (file)
index 4805a44..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-pub extern crate bitcoin;
-extern crate log;
-pub extern crate miniscript;
-extern crate serde;
-#[macro_use]
-extern crate serde_json;
-
-#[cfg(test)]
-#[macro_use]
-extern crate lazy_static;
-
-#[cfg(any(feature = "key-value-db", feature = "default"))]
-extern crate sled;
-
-#[macro_use]
-pub mod error;
-pub mod database;
-pub mod descriptor;
-pub mod psbt;
-pub mod signer;
-pub mod types;
diff --git a/core/lib/src/psbt.rs b/core/lib/src/psbt.rs
deleted file mode 100644 (file)
index 2326e75..0000000
+++ /dev/null
@@ -1,198 +0,0 @@
-use std::collections::BTreeMap;
-
-use bitcoin::hashes::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};
-
-use miniscript::{BitcoinSig, 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,
-        }
-    }
-}
-
-// 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> {
-        if let Some(rawsig) = self.input.partial_sigs.get(pk) {
-            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))
-        } else {
-            None
-        }
-    }
-
-    fn check_older(&self, height: u32) -> bool {
-        // TODO: test >= / >
-        self.current_height.unwrap_or(0) >= self.create_height.unwrap_or(0) + height
-    }
-
-    fn check_after(&self, height: u32) -> bool {
-        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,
-        })
-    }
-}
-
-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/core/lib/src/signer.rs b/core/lib/src/signer.rs
deleted file mode 100644 (file)
index 8f6f8b6..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-use bitcoin::util::bip32::{DerivationPath, Fingerprint};
-use bitcoin::{PublicKey, Script, SigHashType};
-
-use miniscript::miniscript::satisfy::BitcoinSig;
-
-use crate::error::Error;
-
-pub trait Signer {
-    fn sig_legacy_from_fingerprint(
-        &self,
-        index: usize,
-        sighash: SigHashType,
-        fingerprint: &Fingerprint,
-        path: &DerivationPath,
-        script: &Script,
-    ) -> Result<Option<BitcoinSig>, Error>;
-    fn sig_legacy_from_pubkey(
-        &self,
-        index: usize,
-        sighash: SigHashType,
-        public_key: &PublicKey,
-        script: &Script,
-    ) -> Result<Option<BitcoinSig>, Error>;
-
-    fn sig_segwit_from_fingerprint(
-        &self,
-        index: usize,
-        sighash: SigHashType,
-        fingerprint: &Fingerprint,
-        path: &DerivationPath,
-        script: &Script,
-        value: u64,
-    ) -> Result<Option<BitcoinSig>, Error>;
-    fn sig_segwit_from_pubkey(
-        &self,
-        index: usize,
-        sighash: SigHashType,
-        public_key: &PublicKey,
-        script: &Script,
-        value: u64,
-    ) -> Result<Option<BitcoinSig>, Error>;
-}
-
-#[allow(dead_code)]
-impl dyn Signer {
-    fn sig_legacy_from_fingerprint(
-        &self,
-        _index: usize,
-        _sighash: SigHashType,
-        _fingerprint: &Fingerprint,
-        _path: &DerivationPath,
-        _script: &Script,
-    ) -> Result<Option<BitcoinSig>, Error> {
-        Ok(None)
-    }
-    fn sig_legacy_from_pubkey(
-        &self,
-        _index: usize,
-        _sighash: SigHashType,
-        _public_key: &PublicKey,
-        _script: &Script,
-    ) -> Result<Option<BitcoinSig>, Error> {
-        Ok(None)
-    }
-
-    fn sig_segwit_from_fingerprint(
-        &self,
-        _index: usize,
-        _sighash: SigHashType,
-        _fingerprint: &Fingerprint,
-        _path: &DerivationPath,
-        _script: &Script,
-        _value: u64,
-    ) -> Result<Option<BitcoinSig>, Error> {
-        Ok(None)
-    }
-    fn sig_segwit_from_pubkey(
-        &self,
-        _index: usize,
-        _sighash: SigHashType,
-        _public_key: &PublicKey,
-        _script: &Script,
-        _value: u64,
-    ) -> Result<Option<BitcoinSig>, Error> {
-        Ok(None)
-    }
-}
diff --git a/core/lib/src/types.rs b/core/lib/src/types.rs
deleted file mode 100644 (file)
index e6ed00d..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-use std::convert::AsRef;
-
-use bitcoin::blockdata::transaction::{OutPoint, Transaction, TxOut};
-use bitcoin::hash_types::Txid;
-
-use serde::{Deserialize, Serialize};
-
-// TODO serde flatten?
-#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
-pub enum ScriptType {
-    External = 0,
-    Internal = 1,
-}
-
-impl ScriptType {
-    pub fn as_byte(&self) -> u8 {
-        match self {
-            ScriptType::External => 'e' as u8,
-            ScriptType::Internal => 'i' as u8,
-        }
-    }
-}
-
-impl AsRef<[u8]> for ScriptType {
-    fn as_ref(&self) -> &[u8] {
-        match self {
-            ScriptType::External => b"e",
-            ScriptType::Internal => b"i",
-        }
-    }
-}
-
-#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
-pub struct UTXO {
-    pub outpoint: OutPoint,
-    pub txout: TxOut,
-}
-
-#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
-pub struct TransactionDetails {
-    pub transaction: Option<Transaction>,
-    pub txid: Txid,
-    pub timestamp: u64,
-    pub received: u64,
-    pub sent: u64,
-    pub height: Option<u32>,
-}
diff --git a/examples/parse_descriptor.rs b/examples/parse_descriptor.rs
new file mode 100644 (file)
index 0000000..2af42b3
--- /dev/null
@@ -0,0 +1,27 @@
+extern crate magical_bitcoin_wallet;
+
+use std::str::FromStr;
+
+use magical_bitcoin_wallet::bitcoin::*;
+use magical_bitcoin_wallet::descriptor::*;
+
+fn main() {
+    let desc = "sh(wsh(or_d(\
+                    thresh_m(\
+                      2,[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*,tprv8ZgxMBicQKsPduL5QnGihpprdHyypMGi4DhimjtzYemu7se5YQNcZfAPLqXRuGHb5ZX2eTQj62oNqMnyxJ7B7wz54Uzswqw8fFqMVdcmVF7/1/*\
+                    ),\
+                    and_v(vc:pk_h(cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy),older(1000))\
+                   )))";
+
+    let extended_desc = ExtendedDescriptor::from_str(desc).unwrap();
+    println!("{:?}", extended_desc);
+
+    let derived_desc = extended_desc.derive(42).unwrap();
+    println!("{:?}", derived_desc);
+
+    let addr = derived_desc.address(Network::Testnet).unwrap();
+    println!("{}", addr);
+
+    let script = derived_desc.witness_script();
+    println!("{:?}", script);
+}
diff --git a/examples/psbt.rs b/examples/psbt.rs
new file mode 100644 (file)
index 0000000..ed0281f
--- /dev/null
@@ -0,0 +1,50 @@
+extern crate base64;
+extern crate magical_bitcoin_wallet;
+
+use std::str::FromStr;
+
+use magical_bitcoin_wallet::bitcoin;
+use magical_bitcoin_wallet::descriptor::*;
+use magical_bitcoin_wallet::psbt::*;
+use magical_bitcoin_wallet::signer::Signer;
+
+use bitcoin::consensus::encode::{deserialize, serialize};
+use bitcoin::util::psbt::PartiallySignedTransaction;
+use bitcoin::SigHashType;
+
+fn main() {
+    let desc = "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/*)";
+
+    let extended_desc = ExtendedDescriptor::from_str(desc).unwrap();
+
+    let psbt_str = "cHNidP8BAFMCAAAAAd9SiQfxXZ+CKjgjRNonWXsnlA84aLvjxtwCmMfRc0ZbAQAAAAD+////ASjS9QUAAAAAF6kUYJR3oB0lS1M0W1RRMMiENSX45IuHAAAAAAABAPUCAAAAA9I7/OqeFeOFdr5VTLnj3UI/CNRw2eWmMPf7qDv6uIF6AAAAABcWABTG+kgr0g44V0sK9/9FN9oG/CxMK/7///+d0ffphPcV6FE9J/3ZPKWu17YxBnWWTJQyRJs3HUo1gwEAAAAA/v///835mYd9DmnjVnUKd2421MDoZmIxvB4XyJluN3SPUV9hAAAAABcWABRfvwFGp+x/yWdXeNgFs9v0duyeS/7///8CFbH+AAAAAAAXqRSEnTOAjJN/X6ZgR9ftKmwisNSZx4cA4fUFAAAAABl2qRTs6pS4x17MSQ4yNs/1GPsfdlv2NIisAAAAACIGApVE9PPtkcqp8Da43yrXGv4nLOotZdyxwJoTWQxuLxIuCAxfmh4JAAAAAAA=";
+    let psbt_buf = base64::decode(psbt_str).unwrap();
+    let mut psbt: PartiallySignedTransaction = deserialize(&psbt_buf).unwrap();
+
+    let signer = PSBTSigner::from_descriptor(&psbt.global.unsigned_tx, &extended_desc).unwrap();
+
+    for (index, input) in psbt.inputs.iter_mut().enumerate() {
+        for (pubkey, (fing, path)) in &input.hd_keypaths {
+            let sighash = input.sighash_type.unwrap_or(SigHashType::All);
+
+            // Ignore the "witness_utxo" case because we know this psbt is a legacy tx
+            if let Some(non_wit_utxo) = &input.non_witness_utxo {
+                let prev_script = &non_wit_utxo.output
+                    [psbt.global.unsigned_tx.input[index].previous_output.vout as usize]
+                    .script_pubkey;
+                let (signature, sighash) = signer
+                    .sig_legacy_from_fingerprint(index, sighash, fing, path, prev_script)
+                    .unwrap()
+                    .unwrap();
+
+                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);
+            }
+        }
+    }
+
+    println!("signed: {}", base64::encode(&serialize(&psbt)));
+}
diff --git a/src/database/keyvalue.rs b/src/database/keyvalue.rs
new file mode 100644 (file)
index 0000000..d34ac9f
--- /dev/null
@@ -0,0 +1,638 @@
+use std::convert::{From, TryInto};
+
+use sled::{Batch, Tree};
+
+use bitcoin::consensus::encode::{deserialize, serialize};
+use bitcoin::hash_types::Txid;
+use bitcoin::util::bip32::{ChildNumber, DerivationPath};
+use bitcoin::{OutPoint, Script, Transaction};
+
+use crate::database::{BatchDatabase, BatchOperations, Database};
+use crate::error::Error;
+use crate::types::*;
+
+// path -> script       p{i,e}<path> -> script
+// script -> path       s<script> -> {i,e}<path>
+// outpoint             u<outpoint> -> txout
+// rawtx                r<txid> -> tx
+// transactions         t<txid> -> tx details
+// deriv indexes        c{i,e} -> u32
+
+enum SledKey<'a> {
+    Path((Option<ScriptType>, Option<&'a DerivationPath>)),
+    Script(Option<&'a Script>),
+    UTXO(Option<&'a OutPoint>),
+    RawTx(Option<&'a Txid>),
+    Transaction(Option<&'a Txid>),
+    LastIndex(ScriptType),
+}
+
+impl SledKey<'_> {
+    pub fn as_prefix(&self) -> Vec<u8> {
+        match self {
+            SledKey::Path((st, _)) => {
+                let mut v = b"p".to_vec();
+                if let Some(st) = st {
+                    v.push(st.as_byte());
+                }
+                v
+            }
+            SledKey::Script(_) => b"s".to_vec(),
+            SledKey::UTXO(_) => b"u".to_vec(),
+            SledKey::RawTx(_) => b"r".to_vec(),
+            SledKey::Transaction(_) => b"t".to_vec(),
+            SledKey::LastIndex(st) => [b"c", st.as_ref()].concat(),
+        }
+    }
+
+    fn serialize_content(&self) -> Vec<u8> {
+        match self {
+            SledKey::Path((_, Some(path))) => {
+                let mut res = vec![];
+                for val in *path {
+                    res.extend(&u32::from(*val).to_be_bytes());
+                }
+                res
+            }
+            SledKey::Script(Some(s)) => serialize(*s),
+            SledKey::UTXO(Some(s)) => serialize(*s),
+            SledKey::RawTx(Some(s)) => serialize(*s),
+            SledKey::Transaction(Some(s)) => serialize(*s),
+            _ => vec![],
+        }
+    }
+
+    pub fn as_sled_key(&self) -> Vec<u8> {
+        let mut v = self.as_prefix();
+        v.extend_from_slice(&self.serialize_content());
+
+        v
+    }
+}
+
+macro_rules! impl_batch_operations {
+    ( { $($after_insert:tt)* }, $process_delete:ident ) => {
+        fn set_script_pubkey<P: AsRef<[ChildNumber]>>(&mut self, script: &Script, script_type: ScriptType, path: &P) -> Result<(), Error> {
+            let deriv_path = DerivationPath::from(path.as_ref());
+            let key = SledKey::Path((Some(script_type), Some(&deriv_path))).as_sled_key();
+            self.insert(key, serialize(script))$($after_insert)*;
+
+            let key = SledKey::Script(Some(script)).as_sled_key();
+            let value = json!({
+                "t": script_type,
+                "p": deriv_path,
+            });
+            self.insert(key, serde_json::to_vec(&value)?)$($after_insert)*;
+
+            Ok(())
+        }
+
+        fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error> {
+            let key = SledKey::UTXO(Some(&utxo.outpoint)).as_sled_key();
+            let value = serialize(&utxo.txout);
+            self.insert(key, value)$($after_insert)*;
+
+            Ok(())
+        }
+
+        fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error> {
+            let key = SledKey::RawTx(Some(&transaction.txid())).as_sled_key();
+            let value = serialize(transaction);
+            self.insert(key, value)$($after_insert)*;
+
+            Ok(())
+        }
+
+        fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error> {
+            let key = SledKey::Transaction(Some(&transaction.txid)).as_sled_key();
+
+            // remove the raw tx from the serialized version
+            let mut value = serde_json::to_value(transaction)?;
+            value["transaction"] = serde_json::Value::Null;
+            let value = serde_json::to_vec(&value)?;
+
+            self.insert(key, value)$($after_insert)*;
+
+            // insert the raw_tx if present
+            if let Some(ref tx) = transaction.transaction {
+                self.set_raw_tx(tx)?;
+            }
+
+            Ok(())
+        }
+
+        fn set_last_index(&mut self, script_type: ScriptType, value: u32) -> Result<(), Error> {
+            let key = SledKey::LastIndex(script_type).as_sled_key();
+            self.insert(key, &value.to_be_bytes())$($after_insert)*;
+
+            Ok(())
+        }
+
+        fn del_script_pubkey_from_path<P: AsRef<[ChildNumber]>>(&mut self, script_type: ScriptType, path: &P) -> Result<Option<Script>, Error> {
+            let deriv_path = DerivationPath::from(path.as_ref());
+            let key = SledKey::Path((Some(script_type), Some(&deriv_path))).as_sled_key();
+            let res = self.remove(key);
+            let res = $process_delete!(res);
+
+            Ok(res.map_or(Ok(None), |x| Some(deserialize(&x)).transpose())?)
+        }
+
+        fn del_path_from_script_pubkey(&mut self, script: &Script) -> Result<Option<(ScriptType, DerivationPath)>, Error> {
+            let key = SledKey::Script(Some(script)).as_sled_key();
+            let res = self.remove(key);
+            let res = $process_delete!(res);
+
+            match res {
+                None => Ok(None),
+                Some(b) => {
+                    let mut val: serde_json::Value = serde_json::from_slice(&b)?;
+                    let st = serde_json::from_value(val["t"].take())?;
+                    let path = serde_json::from_value(val["p"].take())?;
+
+                    Ok(Some((st, path)))
+                }
+            }
+        }
+
+        fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> {
+            let key = SledKey::UTXO(Some(outpoint)).as_sled_key();
+            let res = self.remove(key);
+            let res = $process_delete!(res);
+
+            match res {
+                None => Ok(None),
+                Some(b) => {
+                    let txout = deserialize(&b)?;
+                    Ok(Some(UTXO { outpoint: outpoint.clone(), txout }))
+                }
+            }
+        }
+
+        fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error> {
+            let key = SledKey::RawTx(Some(txid)).as_sled_key();
+            let res = self.remove(key);
+            let res = $process_delete!(res);
+
+            Ok(res.map_or(Ok(None), |x| Some(deserialize(&x)).transpose())?)
+        }
+
+        fn del_tx(&mut self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error> {
+            let raw_tx = if include_raw {
+                self.del_raw_tx(txid)?
+            } else {
+                None
+            };
+
+            let key = SledKey::Transaction(Some(txid)).as_sled_key();
+            let res = self.remove(key);
+            let res = $process_delete!(res);
+
+            match res {
+                None => Ok(None),
+                Some(b) => {
+                    let mut val: TransactionDetails = serde_json::from_slice(&b)?;
+                    val.transaction = raw_tx;
+
+                    Ok(Some(val))
+                }
+            }
+        }
+
+        fn del_last_index(&mut self, script_type: ScriptType) -> Result<Option<u32>, Error> {
+            let key = SledKey::LastIndex(script_type).as_sled_key();
+            let res = self.remove(key);
+            let res = $process_delete!(res);
+
+            match res {
+                None => Ok(None),
+                Some(b) => {
+                    let array: [u8; 4] = b.as_ref().try_into().map_err(|_| Error::InvalidU32Bytes(b.to_vec()))?;
+                    let val = u32::from_be_bytes(array);
+                    Ok(Some(val))
+                }
+            }
+        }
+    }
+}
+
+macro_rules! process_delete_tree {
+    ($res:expr) => {
+        $res?
+    };
+}
+impl BatchOperations for Tree {
+    impl_batch_operations!({?}, process_delete_tree);
+}
+
+macro_rules! process_delete_batch {
+    ($res:expr) => {
+        None as Option<sled::IVec>
+    };
+}
+#[allow(unused_variables)]
+impl BatchOperations for Batch {
+    impl_batch_operations!({}, process_delete_batch);
+}
+
+impl Database for Tree {
+    fn iter_script_pubkeys(&self, script_type: Option<ScriptType>) -> Vec<Result<Script, Error>> {
+        let key = SledKey::Path((script_type, None)).as_sled_key();
+        self.scan_prefix(key)
+            .map(|x| -> Result<_, Error> {
+                let (_, v) = x?;
+                Ok(deserialize(&v)?)
+            })
+            .collect()
+    }
+
+    fn iter_utxos(&self) -> Vec<Result<UTXO, Error>> {
+        let key = SledKey::UTXO(None).as_sled_key();
+        self.scan_prefix(key)
+            .map(|x| -> Result<_, Error> {
+                let (k, v) = x?;
+                let outpoint = deserialize(&k[1..])?;
+                let txout = deserialize(&v)?;
+                Ok(UTXO { outpoint, txout })
+            })
+            .collect()
+    }
+
+    fn iter_raw_txs(&self) -> Vec<Result<Transaction, Error>> {
+        let key = SledKey::RawTx(None).as_sled_key();
+        self.scan_prefix(key)
+            .map(|x| -> Result<_, Error> {
+                let (_, v) = x?;
+                Ok(deserialize(&v)?)
+            })
+            .collect()
+    }
+
+    fn iter_txs(&self, include_raw: bool) -> Vec<Result<TransactionDetails, Error>> {
+        let key = SledKey::RawTx(None).as_sled_key();
+        self.scan_prefix(key)
+            .map(|x| -> Result<_, Error> {
+                let (k, v) = x?;
+                let mut txdetails: TransactionDetails = serde_json::from_slice(&v)?;
+                if include_raw {
+                    let txid = deserialize(&k[1..])?;
+                    txdetails.transaction = self.get_raw_tx(&txid)?;
+                }
+
+                Ok(txdetails)
+            })
+            .collect()
+    }
+
+    fn get_script_pubkey_from_path<P: AsRef<[ChildNumber]>>(
+        &self,
+        script_type: ScriptType,
+        path: &P,
+    ) -> Result<Option<Script>, Error> {
+        let deriv_path = DerivationPath::from(path.as_ref());
+        let key = SledKey::Path((Some(script_type), Some(&deriv_path))).as_sled_key();
+        Ok(self.get(key)?.map(|b| deserialize(&b)).transpose()?)
+    }
+
+    fn get_path_from_script_pubkey(
+        &self,
+        script: &Script,
+    ) -> Result<Option<(ScriptType, DerivationPath)>, Error> {
+        let key = SledKey::Script(Some(script)).as_sled_key();
+        self.get(key)?
+            .map(|b| -> Result<_, Error> {
+                let mut val: serde_json::Value = serde_json::from_slice(&b)?;
+                let st = serde_json::from_value(val["t"].take())?;
+                let path = serde_json::from_value(val["p"].take())?;
+
+                Ok((st, path))
+            })
+            .transpose()
+    }
+
+    fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> {
+        let key = SledKey::UTXO(Some(outpoint)).as_sled_key();
+        self.get(key)?
+            .map(|b| -> Result<_, Error> {
+                let txout = deserialize(&b)?;
+                Ok(UTXO {
+                    outpoint: outpoint.clone(),
+                    txout,
+                })
+            })
+            .transpose()
+    }
+
+    fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
+        let key = SledKey::RawTx(Some(txid)).as_sled_key();
+        Ok(self.get(key)?.map(|b| deserialize(&b)).transpose()?)
+    }
+
+    fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error> {
+        let key = SledKey::Transaction(Some(txid)).as_sled_key();
+        self.get(key)?
+            .map(|b| -> Result<_, Error> {
+                let mut txdetails: TransactionDetails = serde_json::from_slice(&b)?;
+                if include_raw {
+                    txdetails.transaction = self.get_raw_tx(&txid)?;
+                }
+
+                Ok(txdetails)
+            })
+            .transpose()
+    }
+
+    fn get_last_index(&self, script_type: ScriptType) -> Result<Option<u32>, Error> {
+        let key = SledKey::LastIndex(script_type).as_sled_key();
+        self.get(key)?
+            .map(|b| -> Result<_, Error> {
+                let array: [u8; 4] = b
+                    .as_ref()
+                    .try_into()
+                    .map_err(|_| Error::InvalidU32Bytes(b.to_vec()))?;
+                let val = u32::from_be_bytes(array);
+                Ok(val)
+            })
+            .transpose()
+    }
+
+    // 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| {
+            let new = match prev {
+                Some(b) => {
+                    let array: [u8; 4] = b.try_into().unwrap_or([0; 4]);
+                    let val = u32::from_be_bytes(array);
+
+                    val + 1
+                }
+                None => 1, // start from 1, we return 0 when the prev value was None
+            };
+
+            Some(new.to_be_bytes().to_vec())
+        })?
+        .map_or(Ok(0), |b| -> Result<_, Error> {
+            let array: [u8; 4] = b
+                .as_ref()
+                .try_into()
+                .map_err(|_| Error::InvalidU32Bytes(b.to_vec()))?;
+            let val = u32::from_be_bytes(array);
+            Ok(val)
+        })
+    }
+}
+
+impl BatchDatabase for Tree {
+    type Batch = sled::Batch;
+
+    fn begin_batch(&self) -> Self::Batch {
+        sled::Batch::default()
+    }
+
+    fn commit_batch(&mut self, batch: Self::Batch) -> Result<(), Error> {
+        Ok(self.apply_batch(batch)?)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use std::str::FromStr;
+    use std::sync::{Arc, Condvar, Mutex, Once};
+    use std::time::{SystemTime, UNIX_EPOCH};
+
+    use sled::{Db, Tree};
+
+    use bitcoin::consensus::encode::deserialize;
+    use bitcoin::hashes::hex::*;
+    use bitcoin::*;
+
+    use crate::database::*;
+
+    static mut COUNT: usize = 0;
+
+    lazy_static! {
+        static ref DB: Arc<(Mutex<Option<Db>>, Condvar)> =
+            Arc::new((Mutex::new(None), Condvar::new()));
+        static ref INIT: Once = Once::new();
+    }
+
+    fn get_tree() -> Tree {
+        unsafe {
+            let cloned = DB.clone();
+            let (mutex, cvar) = &*cloned;
+
+            INIT.call_once(|| {
+                let mut db = mutex.lock().unwrap();
+
+                let time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
+                let mut dir = std::env::temp_dir();
+                dir.push(format!("mbw_{}", time.as_nanos()));
+
+                *db = Some(sled::open(dir).unwrap());
+                cvar.notify_all();
+            });
+
+            let mut db = mutex.lock().unwrap();
+            while !db.is_some() {
+                db = cvar.wait(db).unwrap();
+            }
+
+            COUNT += 1;
+
+            db.as_ref()
+                .unwrap()
+                .open_tree(format!("tree_{}", COUNT))
+                .unwrap()
+        }
+    }
+
+    #[test]
+    fn test_script_pubkey() {
+        let mut tree = get_tree();
+
+        let script = Script::from(
+            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
+        );
+        let path = DerivationPath::from_str("m/0/1/2/3").unwrap();
+        let script_type = ScriptType::External;
+
+        tree.set_script_pubkey(&script, script_type, &path).unwrap();
+
+        assert_eq!(
+            tree.get_script_pubkey_from_path(script_type, &path)
+                .unwrap(),
+            Some(script.clone())
+        );
+        assert_eq!(
+            tree.get_path_from_script_pubkey(&script).unwrap(),
+            Some((script_type, path.clone()))
+        );
+    }
+
+    #[test]
+    fn test_batch_script_pubkey() {
+        let mut tree = get_tree();
+        let mut batch = tree.begin_batch();
+
+        let script = Script::from(
+            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
+        );
+        let path = DerivationPath::from_str("m/0/1/2/3").unwrap();
+        let script_type = ScriptType::External;
+
+        batch
+            .set_script_pubkey(&script, script_type, &path)
+            .unwrap();
+
+        assert_eq!(
+            tree.get_script_pubkey_from_path(script_type, &path)
+                .unwrap(),
+            None
+        );
+        assert_eq!(tree.get_path_from_script_pubkey(&script).unwrap(), None);
+
+        tree.commit_batch(batch).unwrap();
+
+        assert_eq!(
+            tree.get_script_pubkey_from_path(script_type, &path)
+                .unwrap(),
+            Some(script.clone())
+        );
+        assert_eq!(
+            tree.get_path_from_script_pubkey(&script).unwrap(),
+            Some((script_type, path.clone()))
+        );
+    }
+
+    #[test]
+    fn test_iter_script_pubkey() {
+        let mut tree = get_tree();
+
+        let script = Script::from(
+            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
+        );
+        let path = DerivationPath::from_str("m/0/1/2/3").unwrap();
+        let script_type = ScriptType::External;
+
+        tree.set_script_pubkey(&script, script_type, &path).unwrap();
+
+        assert_eq!(tree.iter_script_pubkeys(None).len(), 1);
+    }
+
+    #[test]
+    fn test_del_script_pubkey() {
+        let mut tree = get_tree();
+
+        let script = Script::from(
+            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
+        );
+        let path = DerivationPath::from_str("m/0/1/2/3").unwrap();
+        let script_type = ScriptType::External;
+
+        tree.set_script_pubkey(&script, script_type, &path).unwrap();
+        assert_eq!(tree.iter_script_pubkeys(None).len(), 1);
+
+        tree.del_script_pubkey_from_path(script_type, &path)
+            .unwrap();
+        assert_eq!(tree.iter_script_pubkeys(None).len(), 0);
+    }
+
+    #[test]
+    fn test_utxo() {
+        let mut tree = get_tree();
+
+        let outpoint = OutPoint::from_str(
+            "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:0",
+        )
+        .unwrap();
+        let script = Script::from(
+            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
+        );
+        let txout = TxOut {
+            value: 133742,
+            script_pubkey: script,
+        };
+        let utxo = UTXO { txout, outpoint };
+
+        tree.set_utxo(&utxo).unwrap();
+
+        assert_eq!(tree.get_utxo(&outpoint).unwrap(), Some(utxo));
+    }
+
+    #[test]
+    fn test_raw_tx() {
+        let mut tree = get_tree();
+
+        let hex_tx = Vec::<u8>::from_hex("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000").unwrap();
+        let tx: Transaction = deserialize(&hex_tx).unwrap();
+
+        tree.set_raw_tx(&tx).unwrap();
+
+        let txid = tx.txid();
+
+        assert_eq!(tree.get_raw_tx(&txid).unwrap(), Some(tx));
+    }
+
+    #[test]
+    fn test_tx() {
+        let mut tree = get_tree();
+
+        let hex_tx = Vec::<u8>::from_hex("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000").unwrap();
+        let tx: Transaction = deserialize(&hex_tx).unwrap();
+        let txid = tx.txid();
+        let mut tx_details = TransactionDetails {
+            transaction: Some(tx),
+            txid,
+            timestamp: 123456,
+            received: 1337,
+            sent: 420420,
+            height: Some(1000),
+        };
+
+        tree.set_tx(&tx_details).unwrap();
+
+        // get with raw tx too
+        assert_eq!(
+            tree.get_tx(&tx_details.txid, true).unwrap(),
+            Some(tx_details.clone())
+        );
+        // get only raw_tx
+        assert_eq!(
+            tree.get_raw_tx(&tx_details.txid).unwrap(),
+            tx_details.transaction
+        );
+
+        // now get without raw_tx
+        tx_details.transaction = None;
+        assert_eq!(
+            tree.get_tx(&tx_details.txid, false).unwrap(),
+            Some(tx_details)
+        );
+    }
+
+    #[test]
+    fn test_last_index() {
+        let mut tree = get_tree();
+
+        tree.set_last_index(ScriptType::External, 1337).unwrap();
+
+        assert_eq!(
+            tree.get_last_index(ScriptType::External).unwrap(),
+            Some(1337)
+        );
+        assert_eq!(tree.get_last_index(ScriptType::Internal).unwrap(), None);
+
+        let res = tree.increment_last_index(ScriptType::External).unwrap();
+        assert_eq!(res, 1337);
+        let res = tree.increment_last_index(ScriptType::Internal).unwrap();
+        assert_eq!(res, 0);
+
+        assert_eq!(
+            tree.get_last_index(ScriptType::External).unwrap(),
+            Some(1338)
+        );
+        assert_eq!(tree.get_last_index(ScriptType::Internal).unwrap(), Some(1));
+    }
+
+    // TODO: more tests...
+}
diff --git a/src/database/mod.rs b/src/database/mod.rs
new file mode 100644 (file)
index 0000000..0e28b78
--- /dev/null
@@ -0,0 +1,71 @@
+use bitcoin::hash_types::Txid;
+use bitcoin::util::bip32::{ChildNumber, DerivationPath};
+use bitcoin::{OutPoint, Script, Transaction};
+
+use crate::error::Error;
+use crate::types::*;
+
+#[cfg(any(feature = "key-value-db", feature = "default"))]
+pub mod keyvalue;
+
+pub trait BatchOperations {
+    fn set_script_pubkey<P: AsRef<[ChildNumber]>>(
+        &mut self,
+        script: &Script,
+        script_type: ScriptType,
+        path: &P,
+    ) -> Result<(), Error>;
+    fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error>;
+    fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error>;
+    fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error>;
+    fn set_last_index(&mut self, script_type: ScriptType, value: u32) -> Result<(), Error>;
+
+    fn del_script_pubkey_from_path<P: AsRef<[ChildNumber]>>(
+        &mut self,
+        script_type: ScriptType,
+        path: &P,
+    ) -> Result<Option<Script>, Error>;
+    fn del_path_from_script_pubkey(
+        &mut self,
+        script: &Script,
+    ) -> Result<Option<(ScriptType, DerivationPath)>, Error>;
+    fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error>;
+    fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error>;
+    fn del_tx(
+        &mut self,
+        txid: &Txid,
+        include_raw: bool,
+    ) -> Result<Option<TransactionDetails>, Error>;
+    fn del_last_index(&mut self, script_type: ScriptType) -> Result<Option<u32>, Error>;
+}
+
+pub trait Database: BatchOperations {
+    fn iter_script_pubkeys(&self, script_type: Option<ScriptType>) -> Vec<Result<Script, Error>>;
+    fn iter_utxos(&self) -> Vec<Result<UTXO, Error>>;
+    fn iter_raw_txs(&self) -> Vec<Result<Transaction, Error>>;
+    fn iter_txs(&self, include_raw: bool) -> Vec<Result<TransactionDetails, Error>>;
+
+    fn get_script_pubkey_from_path<P: AsRef<[ChildNumber]>>(
+        &self,
+        script_type: ScriptType,
+        path: &P,
+    ) -> Result<Option<Script>, Error>;
+    fn get_path_from_script_pubkey(
+        &self,
+        script: &Script,
+    ) -> Result<Option<(ScriptType, DerivationPath)>, Error>;
+    fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error>;
+    fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>;
+    fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error>;
+    fn get_last_index(&self, script_type: ScriptType) -> Result<Option<u32>, Error>;
+
+    // inserts 0 if not present
+    fn increment_last_index(&mut self, script_type: ScriptType) -> Result<u32, Error>;
+}
+
+pub trait BatchDatabase: Database {
+    type Batch: BatchOperations;
+
+    fn begin_batch(&self) -> Self::Batch;
+    fn commit_batch(&mut self, batch: Self::Batch) -> Result<(), Error>;
+}
diff --git a/src/descriptor/error.rs b/src/descriptor/error.rs
new file mode 100644 (file)
index 0000000..f9a1087
--- /dev/null
@@ -0,0 +1,26 @@
+#[derive(Debug)]
+pub enum Error {
+    InternalError,
+    InvalidPrefix(Vec<u8>),
+    HardenedDerivationOnXpub,
+    MalformedInput,
+    KeyParsingError(String),
+
+    BIP32(bitcoin::util::bip32::Error),
+    Base58(bitcoin::util::base58::Error),
+    PK(bitcoin::util::key::Error),
+    Miniscript(miniscript::Error),
+    Hex(bitcoin::hashes::hex::Error),
+}
+
+impl std::fmt::Display for Error {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{:?}", self)
+    }
+}
+
+impl_error!(bitcoin::util::bip32::Error, BIP32);
+impl_error!(bitcoin::util::base58::Error, Base58);
+impl_error!(bitcoin::util::key::Error, PK);
+impl_error!(miniscript::Error, Miniscript);
+impl_error!(bitcoin::hashes::hex::Error, Hex);
diff --git a/src/descriptor/extended_key.rs b/src/descriptor/extended_key.rs
new file mode 100644 (file)
index 0000000..0f7bd9e
--- /dev/null
@@ -0,0 +1,349 @@
+use std::fmt::{self, Display};
+use std::str::FromStr;
+
+use bitcoin::hashes::hex::{FromHex, ToHex};
+use bitcoin::secp256k1;
+use bitcoin::util::base58;
+use bitcoin::util::bip32::{
+    ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, Fingerprint,
+};
+use bitcoin::PublicKey;
+
+#[allow(unused_imports)]
+use log::{debug, error, info, trace};
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum DerivationIndex {
+    Fixed,
+    Normal,
+    Hardened,
+}
+
+impl DerivationIndex {
+    fn as_path(&self, index: u32) -> DerivationPath {
+        match self {
+            DerivationIndex::Fixed => vec![],
+            DerivationIndex::Normal => vec![ChildNumber::Normal { index }],
+            DerivationIndex::Hardened => vec![ChildNumber::Hardened { index }],
+        }
+        .into()
+    }
+}
+
+impl Display for DerivationIndex {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let chars = match *self {
+            Self::Fixed => "",
+            Self::Normal => "/*",
+            Self::Hardened => "/*'",
+        };
+
+        write!(f, "{}", chars)
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct DescriptorExtendedKey {
+    pub master_fingerprint: Option<Fingerprint>,
+    pub master_derivation: Option<DerivationPath>,
+    pub pubkey: ExtendedPubKey,
+    pub secret: Option<ExtendedPrivKey>,
+    pub path: DerivationPath,
+    pub final_index: DerivationIndex,
+}
+
+impl DescriptorExtendedKey {
+    pub fn full_path(&self, index: u32) -> DerivationPath {
+        let mut final_path: Vec<ChildNumber> = self.path.clone().into();
+        let other_path: Vec<ChildNumber> = self.final_index.as_path(index).into();
+        final_path.extend_from_slice(&other_path);
+
+        final_path.into()
+    }
+
+    pub fn derive<C: secp256k1::Verification + secp256k1::Signing>(
+        &self,
+        ctx: &secp256k1::Secp256k1<C>,
+        index: u32,
+    ) -> Result<PublicKey, super::Error> {
+        Ok(self.derive_xpub(ctx, index)?.public_key)
+    }
+
+    pub fn derive_xpub<C: secp256k1::Verification + secp256k1::Signing>(
+        &self,
+        ctx: &secp256k1::Secp256k1<C>,
+        index: u32,
+    ) -> Result<ExtendedPubKey, super::Error> {
+        if let Some(xprv) = self.secret {
+            let derive_priv = xprv.derive_priv(ctx, &self.full_path(index))?;
+            Ok(ExtendedPubKey::from_private(ctx, &derive_priv))
+        } else {
+            Ok(self.pubkey.derive_pub(ctx, &self.full_path(index))?)
+        }
+    }
+
+    pub fn root_xpub<C: secp256k1::Verification + secp256k1::Signing>(
+        &self,
+        ctx: &secp256k1::Secp256k1<C>,
+    ) -> ExtendedPubKey {
+        if let Some(ref xprv) = self.secret {
+            ExtendedPubKey::from_private(ctx, xprv)
+        } else {
+            self.pubkey
+        }
+    }
+}
+
+impl Display for DescriptorExtendedKey {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        if let Some(ref fingerprint) = self.master_fingerprint {
+            write!(f, "[{}", fingerprint.to_hex())?;
+            if let Some(ref path) = self.master_derivation {
+                write!(f, "{}", &path.to_string()[1..])?;
+            }
+            write!(f, "]")?;
+        }
+
+        if let Some(xprv) = self.secret {
+            write!(f, "{}", xprv)?
+        } else {
+            write!(f, "{}", self.pubkey)?
+        }
+
+        write!(f, "{}{}", &self.path.to_string()[1..], self.final_index)
+    }
+}
+
+impl FromStr for DescriptorExtendedKey {
+    type Err = super::Error;
+
+    fn from_str(inp: &str) -> Result<DescriptorExtendedKey, Self::Err> {
+        let len = inp.len();
+
+        let (master_fingerprint, master_derivation, offset) = match inp.starts_with("[") {
+            false => (None, None, 0),
+            true => {
+                if inp.len() < 9 {
+                    return Err(super::Error::MalformedInput);
+                }
+
+                let master_fingerprint = &inp[1..9];
+                let close_bracket_index =
+                    &inp[9..].find("]").ok_or(super::Error::MalformedInput)?;
+                let path = if *close_bracket_index > 0 {
+                    Some(DerivationPath::from_str(&format!(
+                        "m{}",
+                        &inp[9..9 + *close_bracket_index]
+                    ))?)
+                } else {
+                    None
+                };
+
+                (
+                    Some(Fingerprint::from_hex(master_fingerprint)?),
+                    path,
+                    9 + *close_bracket_index + 1,
+                )
+            }
+        };
+
+        let (key_range, offset) = match &inp[offset..].find("/") {
+            Some(index) => (offset..offset + *index, offset + *index),
+            None => (offset..len, len),
+        };
+        let data = base58::from_check(&inp[key_range.clone()])?;
+        let secp = secp256k1::Secp256k1::new();
+        let (pubkey, secret) = match &data[0..4] {
+            [0x04u8, 0x88, 0xB2, 0x1E] | [0x04u8, 0x35, 0x87, 0xCF] => {
+                (ExtendedPubKey::from_str(&inp[key_range])?, None)
+            }
+            [0x04u8, 0x88, 0xAD, 0xE4] | [0x04u8, 0x35, 0x83, 0x94] => {
+                let private = ExtendedPrivKey::from_str(&inp[key_range])?;
+                (ExtendedPubKey::from_private(&secp, &private), Some(private))
+            }
+            data => return Err(super::Error::InvalidPrefix(data.into())),
+        };
+
+        let (path, final_index, _) = match &inp[offset..].starts_with("/") {
+            false => (DerivationPath::from(vec![]), DerivationIndex::Fixed, offset),
+            true => {
+                let (all, skip) = match &inp[len - 2..len] {
+                    "/*" => (DerivationIndex::Normal, 2),
+                    "*'" | "*h" => (DerivationIndex::Hardened, 3),
+                    _ => (DerivationIndex::Fixed, 0),
+                };
+
+                if all == DerivationIndex::Hardened && secret.is_none() {
+                    return Err(super::Error::HardenedDerivationOnXpub);
+                }
+
+                (
+                    DerivationPath::from_str(&format!("m{}", &inp[offset..len - skip]))?,
+                    all,
+                    len,
+                )
+            }
+        };
+
+        Ok(DescriptorExtendedKey {
+            master_fingerprint,
+            master_derivation,
+            pubkey,
+            secret,
+            path,
+            final_index,
+        })
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use std::str::FromStr;
+
+    use bitcoin::hashes::hex::FromHex;
+    use bitcoin::util::bip32::{ChildNumber, DerivationPath};
+
+    use crate::descriptor::*;
+
+    macro_rules! hex_fingerprint {
+        ($hex:expr) => {
+            Fingerprint::from_hex($hex).unwrap()
+        };
+    }
+
+    macro_rules! deriv_path {
+        ($str:expr) => {
+            DerivationPath::from_str($str).unwrap()
+        };
+
+        () => {
+            DerivationPath::from(vec![])
+        };
+    }
+
+    #[test]
+    fn test_derivation_index_fixed() {
+        let index = DerivationIndex::Fixed;
+        assert_eq!(index.as_path(1337), DerivationPath::from(vec![]));
+        assert_eq!(format!("{}", index), "");
+    }
+
+    #[test]
+    fn test_derivation_index_normal() {
+        let index = DerivationIndex::Normal;
+        assert_eq!(
+            index.as_path(1337),
+            DerivationPath::from(vec![ChildNumber::Normal { index: 1337 }])
+        );
+        assert_eq!(format!("{}", index), "/*");
+    }
+
+    #[test]
+    fn test_derivation_index_hardened() {
+        let index = DerivationIndex::Hardened;
+        assert_eq!(
+            index.as_path(1337),
+            DerivationPath::from(vec![ChildNumber::Hardened { index: 1337 }])
+        );
+        assert_eq!(format!("{}", index), "/*'");
+    }
+
+    #[test]
+    fn test_parse_xpub_no_path_fixed() {
+        let key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL";
+        let ek = DescriptorExtendedKey::from_str(key).unwrap();
+        assert_eq!(ek.pubkey.fingerprint(), hex_fingerprint!("31a507b8"));
+        assert_eq!(ek.path, deriv_path!());
+        assert_eq!(ek.final_index, DerivationIndex::Fixed);
+    }
+
+    #[test]
+    fn test_parse_xpub_with_path_fixed() {
+        let key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/2/3";
+        let ek = DescriptorExtendedKey::from_str(key).unwrap();
+        assert_eq!(ek.pubkey.fingerprint(), hex_fingerprint!("31a507b8"));
+        assert_eq!(ek.path, deriv_path!("m/1/2/3"));
+        assert_eq!(ek.final_index, DerivationIndex::Fixed);
+    }
+
+    #[test]
+    fn test_parse_xpub_with_path_normal() {
+        let key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/2/3/*";
+        let ek = DescriptorExtendedKey::from_str(key).unwrap();
+        assert_eq!(ek.pubkey.fingerprint(), hex_fingerprint!("31a507b8"));
+        assert_eq!(ek.path, deriv_path!("m/1/2/3"));
+        assert_eq!(ek.final_index, DerivationIndex::Normal);
+    }
+
+    #[test]
+    #[should_panic(expected = "HardenedDerivationOnXpub")]
+    fn test_parse_xpub_with_path_hardened() {
+        let key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*'";
+        let ek = DescriptorExtendedKey::from_str(key).unwrap();
+        assert_eq!(ek.pubkey.fingerprint(), hex_fingerprint!("31a507b8"));
+        assert_eq!(ek.path, deriv_path!("m/1/2/3"));
+        assert_eq!(ek.final_index, DerivationIndex::Fixed);
+    }
+
+    #[test]
+    fn test_parse_tprv_with_path_hardened() {
+        let key = "tprv8ZgxMBicQKsPduL5QnGihpprdHyypMGi4DhimjtzYemu7se5YQNcZfAPLqXRuGHb5ZX2eTQj62oNqMnyxJ7B7wz54Uzswqw8fFqMVdcmVF7/1/2/3/*'";
+        let ek = DescriptorExtendedKey::from_str(key).unwrap();
+        assert!(ek.secret.is_some());
+        assert_eq!(ek.pubkey.fingerprint(), hex_fingerprint!("5ea4190e"));
+        assert_eq!(ek.path, deriv_path!("m/1/2/3"));
+        assert_eq!(ek.final_index, DerivationIndex::Hardened);
+    }
+
+    #[test]
+    fn test_parse_xpub_master_details() {
+        let key = "[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL";
+        let ek = DescriptorExtendedKey::from_str(key).unwrap();
+        assert_eq!(ek.master_fingerprint, Some(hex_fingerprint!("d34db33f")));
+        assert_eq!(ek.master_derivation, Some(deriv_path!("m/44'/0'/0'")));
+    }
+
+    #[test]
+    fn test_parse_xpub_master_details_empty_derivation() {
+        let key = "[d34db33f]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL";
+        let ek = DescriptorExtendedKey::from_str(key).unwrap();
+        assert_eq!(ek.master_fingerprint, Some(hex_fingerprint!("d34db33f")));
+        assert_eq!(ek.master_derivation, None);
+    }
+
+    #[test]
+    #[should_panic(expected = "MalformedInput")]
+    fn test_parse_xpub_short_input() {
+        let key = "[d34d";
+        DescriptorExtendedKey::from_str(key).unwrap();
+    }
+
+    #[test]
+    #[should_panic(expected = "MalformedInput")]
+    fn test_parse_xpub_missing_closing_bracket() {
+        let key = "[d34db33fxpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL";
+        DescriptorExtendedKey::from_str(key).unwrap();
+    }
+
+    #[test]
+    #[should_panic(expected = "InvalidChar")]
+    fn test_parse_xpub_invalid_fingerprint() {
+        let key = "[d34db33z]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL";
+        DescriptorExtendedKey::from_str(key).unwrap();
+    }
+
+    #[test]
+    fn test_xpub_normal_full_path() {
+        let key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/2/*";
+        let ek = DescriptorExtendedKey::from_str(key).unwrap();
+        assert_eq!(ek.full_path(42), deriv_path!("m/1/2/42"));
+    }
+
+    #[test]
+    fn test_xpub_fixed_full_path() {
+        let key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/2";
+        let ek = DescriptorExtendedKey::from_str(key).unwrap();
+        assert_eq!(ek.full_path(42), deriv_path!("m/1/2"));
+        assert_eq!(ek.full_path(1337), deriv_path!("m/1/2"));
+    }
+}
diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs
new file mode 100644 (file)
index 0000000..01c6a0a
--- /dev/null
@@ -0,0 +1,476 @@
+use std::cell::RefCell;
+use std::collections::BTreeMap;
+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};
+
+pub use miniscript::descriptor::Descriptor;
+
+use serde::{Deserialize, Serialize};
+
+pub mod error;
+pub mod extended_key;
+
+pub use self::error::Error;
+pub use self::extended_key::{DerivationIndex, DescriptorExtendedKey};
+
+#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord, Default)]
+struct DummyKey();
+
+impl fmt::Display for DummyKey {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "DummyKey")
+    }
+}
+
+impl std::str::FromStr for DummyKey {
+    type Err = ();
+
+    fn from_str(_: &str) -> Result<Self, Self::Err> {
+        Ok(DummyKey::default())
+    }
+}
+
+impl miniscript::MiniscriptKey for DummyKey {
+    type Hash = DummyKey;
+
+    fn to_pubkeyhash(&self) -> DummyKey {
+        DummyKey::default()
+    }
+}
+
+pub type DerivedDescriptor = Descriptor<PublicKey>;
+pub type StringDescriptor = Descriptor<String>;
+
+pub trait DescriptorMeta {
+    fn is_witness(&self) -> bool;
+    fn psbt_redeem_script(&self) -> Option<Script>;
+    fn psbt_witness_script(&self) -> Option<Script>;
+}
+
+impl<T> DescriptorMeta for Descriptor<T>
+where
+    T: miniscript::MiniscriptKey + miniscript::ToPublicKey,
+{
+    fn is_witness(&self) -> bool {
+        match self {
+            Descriptor::Bare(_) | Descriptor::Pk(_) | Descriptor::Pkh(_) | Descriptor::Sh(_) => {
+                false
+            }
+            Descriptor::Wpkh(_)
+            | Descriptor::ShWpkh(_)
+            | Descriptor::Wsh(_)
+            | Descriptor::ShWsh(_) => true,
+        }
+    }
+
+    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::ShWsh(ref script) => Some(script.encode().to_v0_p2wsh()),
+            Descriptor::Sh(ref script) => Some(script.encode()),
+            _ => None,
+        }
+    }
+
+    fn psbt_witness_script(&self) -> Option<Script> {
+        match self {
+            Descriptor::Wsh(ref script) => Some(script.encode()),
+            _ => None,
+        }
+    }
+}
+
+trait Key: std::fmt::Debug {
+    fn fingerprint(&self, secp: &Secp256k1<All>) -> Option<Fingerprint>;
+    fn as_public_key(&self, secp: &Secp256k1<All>, index: Option<u32>) -> Result<PublicKey, Error>;
+    fn as_secret_key(&self) -> Option<PrivateKey>;
+    fn xprv(&self) -> Option<ExtendedPrivKey>;
+    fn full_path(&self, index: u32) -> Option<DerivationPath>;
+    fn is_fixed(&self) -> bool;
+}
+
+impl Key for PublicKey {
+    fn fingerprint(&self, _secp: &Secp256k1<All>) -> Option<Fingerprint> {
+        None
+    }
+
+    fn as_public_key(
+        &self,
+        _secp: &Secp256k1<All>,
+        _index: Option<u32>,
+    ) -> Result<PublicKey, Error> {
+        Ok(PublicKey::clone(self))
+    }
+
+    fn as_secret_key(&self) -> Option<PrivateKey> {
+        None
+    }
+
+    fn xprv(&self) -> Option<ExtendedPrivKey> {
+        None
+    }
+
+    fn full_path(&self, _index: u32) -> Option<DerivationPath> {
+        None
+    }
+
+    fn is_fixed(&self) -> bool {
+        true
+    }
+}
+
+impl Key for PrivateKey {
+    fn fingerprint(&self, _secp: &Secp256k1<All>) -> Option<Fingerprint> {
+        None
+    }
+
+    fn as_public_key(
+        &self,
+        secp: &Secp256k1<All>,
+        _index: Option<u32>,
+    ) -> Result<PublicKey, Error> {
+        Ok(self.public_key(secp))
+    }
+
+    fn as_secret_key(&self) -> Option<PrivateKey> {
+        Some(PrivateKey::clone(self))
+    }
+
+    fn xprv(&self) -> Option<ExtendedPrivKey> {
+        None
+    }
+
+    fn full_path(&self, _index: u32) -> Option<DerivationPath> {
+        None
+    }
+
+    fn is_fixed(&self) -> bool {
+        true
+    }
+}
+
+impl Key for DescriptorExtendedKey {
+    fn fingerprint(&self, secp: &Secp256k1<All>) -> Option<Fingerprint> {
+        Some(self.root_xpub(secp).fingerprint())
+    }
+
+    fn as_public_key(&self, secp: &Secp256k1<All>, index: Option<u32>) -> Result<PublicKey, Error> {
+        Ok(self.derive_xpub(secp, index.unwrap_or(0))?.public_key)
+    }
+
+    fn as_secret_key(&self) -> Option<PrivateKey> {
+        None
+    }
+
+    fn xprv(&self) -> Option<ExtendedPrivKey> {
+        self.secret
+    }
+
+    fn full_path(&self, index: u32) -> Option<DerivationPath> {
+        Some(self.full_path(index))
+    }
+
+    fn is_fixed(&self) -> bool {
+        self.final_index == DerivationIndex::Fixed
+    }
+}
+
+#[serde(try_from = "&str", into = "String")]
+#[derive(Debug, Serialize, Deserialize)]
+pub struct ExtendedDescriptor {
+    #[serde(flatten)]
+    internal: StringDescriptor,
+
+    #[serde(skip)]
+    keys: BTreeMap<String, Box<dyn Key>>,
+
+    #[serde(skip)]
+    ctx: Secp256k1<All>,
+}
+
+impl std::clone::Clone for ExtendedDescriptor {
+    fn clone(&self) -> Self {
+        Self {
+            internal: self.internal.clone(),
+            ctx: self.ctx.clone(),
+            keys: BTreeMap::new(),
+        }
+    }
+}
+
+impl ExtendedDescriptor {
+    fn parse_string(string: &str) -> Result<(String, Box<dyn Key>), Error> {
+        if let Ok(pk) = PublicKey::from_str(string) {
+            return Ok((string.to_string(), Box::new(pk)));
+        } else if let Ok(sk) = PrivateKey::from_wif(string) {
+            return Ok((string.to_string(), Box::new(sk)));
+        } else if let Ok(ext_key) = DescriptorExtendedKey::from_str(string) {
+            return Ok((string.to_string(), Box::new(ext_key)));
+        }
+
+        return Err(Error::KeyParsingError(string.to_string()));
+    }
+
+    fn new(sd: StringDescriptor) -> Result<Self, Error> {
+        let ctx = Secp256k1::gen_new();
+        let keys: RefCell<BTreeMap<String, Box<dyn Key>>> = RefCell::new(BTreeMap::new());
+
+        let translatefpk = |string: &String| -> Result<_, Error> {
+            let (key, parsed) = Self::parse_string(string)?;
+            keys.borrow_mut().insert(key, parsed);
+
+            Ok(DummyKey::default())
+        };
+        let translatefpkh = |string: &String| -> Result<_, Error> {
+            let (key, parsed) = Self::parse_string(string)?;
+            keys.borrow_mut().insert(key, parsed);
+
+            Ok(DummyKey::default())
+        };
+
+        sd.translate_pk(translatefpk, translatefpkh)?;
+
+        Ok(ExtendedDescriptor {
+            internal: sd,
+            keys: keys.into_inner(),
+            ctx,
+        })
+    }
+
+    pub fn derive(&self, index: u32) -> Result<DerivedDescriptor, Error> {
+        let translatefpk = |xpub: &String| {
+            self.keys
+                .get(xpub)
+                .unwrap()
+                .as_public_key(&self.ctx, Some(index))
+        };
+        let translatefpkh =
+            |xpub: &String| Ok(hash160::Hash::hash(&translatefpk(xpub)?.to_bytes()));
+
+        Ok(self.internal.translate_pk(translatefpk, translatefpkh)?)
+    }
+
+    pub fn get_xprv(&self) -> Vec<ExtendedPrivKey> {
+        self.keys
+            .iter()
+            .filter(|(_, v)| v.xprv().is_some())
+            .map(|(_, v)| v.xprv().unwrap())
+            .collect()
+    }
+
+    pub fn get_secret_keys(&self) -> Vec<PrivateKey> {
+        self.keys
+            .iter()
+            .filter(|(_, v)| v.as_secret_key().is_some())
+            .map(|(_, v)| v.as_secret_key().unwrap())
+            .collect()
+    }
+
+    pub fn get_hd_keypaths(
+        &self,
+        index: u32,
+    ) -> Result<BTreeMap<PublicKey, (Fingerprint, DerivationPath)>, Error> {
+        let mut answer = BTreeMap::new();
+
+        for (_, key) in &self.keys {
+            if let Some(fingerprint) = key.fingerprint(&self.ctx) {
+                let derivation_path = key.full_path(index).unwrap();
+                let pubkey = key.as_public_key(&self.ctx, Some(index))?;
+
+                answer.insert(pubkey, (fingerprint, derivation_path));
+            }
+        }
+
+        Ok(answer)
+    }
+
+    pub fn max_satisfaction_weight(&self) -> usize {
+        let fake_pk = PublicKey::from_slice(&[
+            2, 140, 40, 169, 123, 248, 41, 139, 192, 210, 61, 140, 116, 148, 82, 163, 46, 105, 75,
+            101, 227, 10, 148, 114, 163, 149, 74, 179, 15, 229, 50, 76, 170,
+        ])
+        .unwrap();
+        let translated: Descriptor<PublicKey> = self
+            .internal
+            .translate_pk(
+                |_| -> Result<_, ()> { Ok(fake_pk.clone()) },
+                |_| -> Result<_, ()> { Ok(Default::default()) },
+            )
+            .unwrap();
+
+        translated.max_satisfaction_weight()
+    }
+
+    pub fn is_fixed(&self) -> bool {
+        self.keys.iter().all(|(_, key)| key.is_fixed())
+    }
+}
+
+impl TryFrom<&str> for ExtendedDescriptor {
+    type Error = Error;
+
+    fn try_from(value: &str) -> Result<Self, Self::Error> {
+        let internal = StringDescriptor::from_str(value)?;
+        ExtendedDescriptor::new(internal)
+    }
+}
+
+impl TryFrom<StringDescriptor> for ExtendedDescriptor {
+    type Error = Error;
+
+    fn try_from(other: StringDescriptor) -> Result<Self, Self::Error> {
+        ExtendedDescriptor::new(other)
+    }
+}
+
+impl FromStr for ExtendedDescriptor {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        Self::try_from(s)
+    }
+}
+
+impl Into<String> for ExtendedDescriptor {
+    fn into(self) -> String {
+        format!("{}", self.internal)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use std::str::FromStr;
+
+    use bitcoin::hashes::hex::FromHex;
+    use bitcoin::{Network, PublicKey};
+
+    use crate::descriptor::*;
+
+    macro_rules! hex_fingerprint {
+        ($hex:expr) => {
+            Fingerprint::from_hex($hex).unwrap()
+        };
+    }
+
+    macro_rules! hex_pubkey {
+        ($hex:expr) => {
+            PublicKey::from_str($hex).unwrap()
+        };
+    }
+
+    macro_rules! deriv_path {
+        ($str:expr) => {
+            DerivationPath::from_str($str).unwrap()
+        };
+
+        () => {
+            DerivationPath::from(vec![])
+        };
+    }
+
+    #[test]
+    fn test_descriptor_parse_wif() {
+        let string = "pkh(cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy)";
+        let desc = ExtendedDescriptor::from_str(string).unwrap();
+        assert!(desc.is_fixed());
+        assert_eq!(
+            desc.derive(0)
+                .unwrap()
+                .address(Network::Testnet)
+                .unwrap()
+                .to_string(),
+            "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx"
+        );
+        assert_eq!(
+            desc.derive(42)
+                .unwrap()
+                .address(Network::Testnet)
+                .unwrap()
+                .to_string(),
+            "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx"
+        );
+        assert_eq!(desc.get_secret_keys().len(), 1);
+    }
+
+    #[test]
+    fn test_descriptor_parse_pubkey() {
+        let string = "pkh(039b6347398505f5ec93826dc61c19f47c66c0283ee9be980e29ce325a0f4679ef)";
+        let desc = ExtendedDescriptor::from_str(string).unwrap();
+        assert!(desc.is_fixed());
+        assert_eq!(
+            desc.derive(0)
+                .unwrap()
+                .address(Network::Testnet)
+                .unwrap()
+                .to_string(),
+            "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx"
+        );
+        assert_eq!(
+            desc.derive(42)
+                .unwrap()
+                .address(Network::Testnet)
+                .unwrap()
+                .to_string(),
+            "mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx"
+        );
+        assert_eq!(desc.get_secret_keys().len(), 0);
+    }
+
+    #[test]
+    fn test_descriptor_parse_xpub() {
+        let string = "pkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/*)";
+        let desc = ExtendedDescriptor::from_str(string).unwrap();
+        assert!(!desc.is_fixed());
+        assert_eq!(
+            desc.derive(0)
+                .unwrap()
+                .address(Network::Testnet)
+                .unwrap()
+                .to_string(),
+            "mxbXpnVkwARGtYXk5yeGYf59bGWuPpdE4X"
+        );
+        assert_eq!(
+            desc.derive(42)
+                .unwrap()
+                .address(Network::Testnet)
+                .unwrap()
+                .to_string(),
+            "mhtuS1QaEV4HPcK4bWk4Wvpd64SUjiC5Zt"
+        );
+        assert_eq!(desc.get_xprv().len(), 0);
+    }
+
+    #[test]
+    #[should_panic(expected = "KeyParsingError")]
+    fn test_descriptor_parse_fail() {
+        let string = "pkh(this_is_not_a_valid_key)";
+        ExtendedDescriptor::from_str(string).unwrap();
+    }
+
+    #[test]
+    fn test_descriptor_hd_keypaths() {
+        let string = "pkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/*)";
+        let desc = ExtendedDescriptor::from_str(string).unwrap();
+        let keypaths = desc.get_hd_keypaths(0).unwrap();
+        assert!(keypaths.contains_key(&hex_pubkey!(
+            "025d5fc65ebb8d44a5274b53bac21ff8307fec2334a32df05553459f8b1f7fe1b6"
+        )));
+        assert_eq!(
+            keypaths.get(&hex_pubkey!(
+                "025d5fc65ebb8d44a5274b53bac21ff8307fec2334a32df05553459f8b1f7fe1b6"
+            )),
+            Some(&(hex_fingerprint!("31a507b8"), deriv_path!("m/0")))
+        )
+    }
+}
diff --git a/src/error.rs b/src/error.rs
new file mode 100644 (file)
index 0000000..80b72c1
--- /dev/null
@@ -0,0 +1,33 @@
+#[derive(Debug)]
+pub enum Error {
+    KeyMismatch(bitcoin::secp256k1::PublicKey, bitcoin::secp256k1::PublicKey),
+    MissingInputUTXO(usize),
+    InvalidU32Bytes(Vec<u8>),
+    Generic(String),
+
+    Encode(bitcoin::consensus::encode::Error),
+    BIP32(bitcoin::util::bip32::Error),
+    Secp256k1(bitcoin::secp256k1::Error),
+    JSON(serde_json::Error),
+
+    #[cfg(any(feature = "key-value-db", feature = "default"))]
+    Sled(sled::Error),
+}
+
+macro_rules! impl_error {
+    ( $from:ty, $to:ident ) => {
+        impl std::convert::From<$from> for Error {
+            fn from(err: $from) -> Self {
+                Error::$to(err)
+            }
+        }
+    };
+}
+
+impl_error!(bitcoin::consensus::encode::Error, Encode);
+impl_error!(bitcoin::util::bip32::Error, BIP32);
+impl_error!(bitcoin::secp256k1::Error, Secp256k1);
+impl_error!(serde_json::Error, JSON);
+
+#[cfg(any(feature = "key-value-db", feature = "default"))]
+impl_error!(sled::Error, Sled);
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644 (file)
index 0000000..4805a44
--- /dev/null
@@ -0,0 +1,21 @@
+pub extern crate bitcoin;
+extern crate log;
+pub extern crate miniscript;
+extern crate serde;
+#[macro_use]
+extern crate serde_json;
+
+#[cfg(test)]
+#[macro_use]
+extern crate lazy_static;
+
+#[cfg(any(feature = "key-value-db", feature = "default"))]
+extern crate sled;
+
+#[macro_use]
+pub mod error;
+pub mod database;
+pub mod descriptor;
+pub mod psbt;
+pub mod signer;
+pub mod types;
diff --git a/src/psbt.rs b/src/psbt.rs
new file mode 100644 (file)
index 0000000..2326e75
--- /dev/null
@@ -0,0 +1,198 @@
+use std::collections::BTreeMap;
+
+use bitcoin::hashes::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};
+
+use miniscript::{BitcoinSig, 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,
+        }
+    }
+}
+
+// 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> {
+        if let Some(rawsig) = self.input.partial_sigs.get(pk) {
+            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))
+        } else {
+            None
+        }
+    }
+
+    fn check_older(&self, height: u32) -> bool {
+        // TODO: test >= / >
+        self.current_height.unwrap_or(0) >= self.create_height.unwrap_or(0) + height
+    }
+
+    fn check_after(&self, height: u32) -> bool {
+        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,
+        })
+    }
+}
+
+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/signer.rs b/src/signer.rs
new file mode 100644 (file)
index 0000000..8f6f8b6
--- /dev/null
@@ -0,0 +1,87 @@
+use bitcoin::util::bip32::{DerivationPath, Fingerprint};
+use bitcoin::{PublicKey, Script, SigHashType};
+
+use miniscript::miniscript::satisfy::BitcoinSig;
+
+use crate::error::Error;
+
+pub trait Signer {
+    fn sig_legacy_from_fingerprint(
+        &self,
+        index: usize,
+        sighash: SigHashType,
+        fingerprint: &Fingerprint,
+        path: &DerivationPath,
+        script: &Script,
+    ) -> Result<Option<BitcoinSig>, Error>;
+    fn sig_legacy_from_pubkey(
+        &self,
+        index: usize,
+        sighash: SigHashType,
+        public_key: &PublicKey,
+        script: &Script,
+    ) -> Result<Option<BitcoinSig>, Error>;
+
+    fn sig_segwit_from_fingerprint(
+        &self,
+        index: usize,
+        sighash: SigHashType,
+        fingerprint: &Fingerprint,
+        path: &DerivationPath,
+        script: &Script,
+        value: u64,
+    ) -> Result<Option<BitcoinSig>, Error>;
+    fn sig_segwit_from_pubkey(
+        &self,
+        index: usize,
+        sighash: SigHashType,
+        public_key: &PublicKey,
+        script: &Script,
+        value: u64,
+    ) -> Result<Option<BitcoinSig>, Error>;
+}
+
+#[allow(dead_code)]
+impl dyn Signer {
+    fn sig_legacy_from_fingerprint(
+        &self,
+        _index: usize,
+        _sighash: SigHashType,
+        _fingerprint: &Fingerprint,
+        _path: &DerivationPath,
+        _script: &Script,
+    ) -> Result<Option<BitcoinSig>, Error> {
+        Ok(None)
+    }
+    fn sig_legacy_from_pubkey(
+        &self,
+        _index: usize,
+        _sighash: SigHashType,
+        _public_key: &PublicKey,
+        _script: &Script,
+    ) -> Result<Option<BitcoinSig>, Error> {
+        Ok(None)
+    }
+
+    fn sig_segwit_from_fingerprint(
+        &self,
+        _index: usize,
+        _sighash: SigHashType,
+        _fingerprint: &Fingerprint,
+        _path: &DerivationPath,
+        _script: &Script,
+        _value: u64,
+    ) -> Result<Option<BitcoinSig>, Error> {
+        Ok(None)
+    }
+    fn sig_segwit_from_pubkey(
+        &self,
+        _index: usize,
+        _sighash: SigHashType,
+        _public_key: &PublicKey,
+        _script: &Script,
+        _value: u64,
+    ) -> Result<Option<BitcoinSig>, Error> {
+        Ok(None)
+    }
+}
diff --git a/src/types.rs b/src/types.rs
new file mode 100644 (file)
index 0000000..e6ed00d
--- /dev/null
@@ -0,0 +1,47 @@
+use std::convert::AsRef;
+
+use bitcoin::blockdata::transaction::{OutPoint, Transaction, TxOut};
+use bitcoin::hash_types::Txid;
+
+use serde::{Deserialize, Serialize};
+
+// TODO serde flatten?
+#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ScriptType {
+    External = 0,
+    Internal = 1,
+}
+
+impl ScriptType {
+    pub fn as_byte(&self) -> u8 {
+        match self {
+            ScriptType::External => 'e' as u8,
+            ScriptType::Internal => 'i' as u8,
+        }
+    }
+}
+
+impl AsRef<[u8]> for ScriptType {
+    fn as_ref(&self) -> &[u8] {
+        match self {
+            ScriptType::External => b"e",
+            ScriptType::Internal => b"i",
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
+pub struct UTXO {
+    pub outpoint: OutPoint,
+    pub txout: TxOut,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
+pub struct TransactionDetails {
+    pub transaction: Option<Transaction>,
+    pub txid: Txid,
+    pub timestamp: u64,
+    pub received: u64,
+    pub sent: u64,
+    pub height: Option<u32>,
+}