use magical_bitcoin_wallet::bitcoin;
use magical_bitcoin_wallet::sled;
use magical_bitcoin_wallet::types::ScriptType;
-use magical_bitcoin_wallet::{Client, ExtendedDescriptor, Wallet};
+use magical_bitcoin_wallet::{Client, Wallet};
fn prepare_home_dir() -> PathBuf {
let mut dir = PathBuf::new();
Some("testnet") | _ => Network::Testnet,
};
- let descriptor = matches
- .value_of("descriptor")
- .map(|x| ExtendedDescriptor::from_str(x).unwrap())
- .unwrap();
- let change_descriptor = matches
- .value_of("change_descriptor")
- .map(|x| ExtendedDescriptor::from_str(x).unwrap());
+ let descriptor = matches.value_of("descriptor").unwrap();
+ let change_descriptor = matches.value_of("change_descriptor");
debug!("descriptors: {:?} {:?}", descriptor, change_descriptor);
let database = sled::open(prepare_home_dir().to_str().unwrap()).unwrap();
debug!("database opened successfully");
let client = Client::new(matches.value_of("server").unwrap()).unwrap();
- let wallet = Wallet::new(descriptor, change_descriptor, network, tree, client);
+ let wallet = Wallet::new(descriptor, change_descriptor, network, tree, client).unwrap();
// TODO: print errors in a nice way
let handle_matches = |matches: ArgMatches<'_>| {
// rawtx r<txid> -> tx
// transactions t<txid> -> tx details
// deriv indexes c{i,e} -> u32
+// descriptor checksum d{i,e} -> vec<u8>
enum SledKey<'a> {
Path((Option<ScriptType>, Option<&'a DerivationPath>)),
RawTx(Option<&'a Txid>),
Transaction(Option<&'a Txid>),
LastIndex(ScriptType),
+ DescriptorChecksum(ScriptType),
}
impl SledKey<'_> {
SledKey::RawTx(_) => b"r".to_vec(),
SledKey::Transaction(_) => b"t".to_vec(),
SledKey::LastIndex(st) => [b"c", st.as_ref()].concat(),
+ SledKey::DescriptorChecksum(st) => [b"d", st.as_ref()].concat(),
}
}
}
impl Database for Tree {
+ fn check_descriptor_checksum<B: AsRef<[u8]>>(
+ &mut self,
+ script_type: ScriptType,
+ bytes: B,
+ ) -> Result<(), Error> {
+ let key = SledKey::DescriptorChecksum(script_type).as_sled_key();
+
+ let prev = self.get(&key)?.map(|x| x.to_vec());
+ if let Some(val) = prev {
+ if val == bytes.as_ref() {
+ Ok(())
+ } else {
+ Err(Error::ChecksumMismatch)
+ }
+ } else {
+ self.insert(&key, bytes.as_ref())?;
+ Ok(())
+ }
+ }
+
fn iter_script_pubkeys(&self, script_type: Option<ScriptType>) -> Result<Vec<Script>, Error> {
let key = SledKey::Path((script_type, None)).as_sled_key();
self.scan_prefix(key)
// inserts 0 if not present
fn increment_last_index(&mut self, script_type: ScriptType) -> Result<u32, Error> {
let key = SledKey::LastIndex(script_type).as_sled_key();
- self.fetch_and_update(key, |prev| {
+ self.update_and_fetch(key, |prev| {
let new = match prev {
Some(b) => {
let array: [u8; 4] = b.try_into().unwrap_or([0; 4]);
val + 1
}
- None => 1, // start from 1, we return 0 when the prev value was None
+ None => 0,
};
Some(new.to_be_bytes().to_vec())
}
pub trait Database: BatchOperations {
+ fn check_descriptor_checksum<B: AsRef<[u8]>>(
+ &mut self,
+ script_type: ScriptType,
+ bytes: B,
+ ) -> Result<(), Error>;
+
fn iter_script_pubkeys(&self, script_type: Option<ScriptType>) -> Result<Vec<Script>, Error>;
fn iter_utxos(&self) -> Result<Vec<UTXO>, Error>;
fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error>;
--- /dev/null
+use std::iter::FromIterator;
+
+use crate::descriptor::Error;
+
+const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
+const CHECKSUM_CHARSET: &str = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
+
+fn poly_mod(mut c: u64, val: u64) -> u64 {
+ let c0 = c >> 35;
+ c = ((c & 0x7ffffffff) << 5) ^ val;
+ if c0 & 1 > 0 {
+ c ^= 0xf5dee51989
+ };
+ if c0 & 2 > 0 {
+ c ^= 0xa9fdca3312
+ };
+ if c0 & 4 > 0 {
+ c ^= 0x1bab10e32d
+ };
+ if c0 & 8 > 0 {
+ c ^= 0x3706b1677a
+ };
+ if c0 & 16 > 0 {
+ c ^= 0x644d626ffd
+ };
+
+ c
+}
+
+pub fn get_checksum(desc: &str) -> Result<String, Error> {
+ let mut c = 1;
+ let mut cls = 0;
+ let mut clscount = 0;
+ for ch in desc.chars() {
+ let pos = INPUT_CHARSET
+ .find(ch)
+ .ok_or(Error::InvalidDescriptorCharacter(ch))? as u64;
+ c = poly_mod(c, pos & 31);
+ cls = cls * 3 + (pos >> 5);
+ clscount += 1;
+ if clscount == 3 {
+ c = poly_mod(c, cls);
+ cls = 0;
+ clscount = 0;
+ }
+ }
+ if clscount > 0 {
+ c = poly_mod(c, cls);
+ }
+ (0..8).for_each(|_| c = poly_mod(c, 0));
+ c ^= 1;
+
+ let mut chars = Vec::with_capacity(8);
+ for j in 0..8 {
+ chars.push(
+ CHECKSUM_CHARSET
+ .chars()
+ .nth(((c >> (5 * (7 - j))) & 31) as usize)
+ .unwrap(),
+ );
+ }
+
+ Ok(String::from_iter(chars))
+}
MalformedInput,
KeyParsingError(String),
+ InputIndexDoesntExist,
+ MissingPublicKey,
+ MissingDetails,
+
+ InvalidDescriptorCharacter(char),
+
+ CantDeriveWithMiniscript,
+
BIP32(bitcoin::util::bip32::Error),
Base58(bitcoin::util::base58::Error),
PK(bitcoin::util::key::Error),
impl DescriptorExtendedKey {
pub fn full_path(&self, index: u32) -> DerivationPath {
- let mut final_path: Vec<ChildNumber> = self.path.clone().into();
+ let mut final_path: Vec<ChildNumber> = Vec::new();
+ if let Some(path) = &self.master_derivation {
+ let path_as_vec: Vec<ChildNumber> = path.clone().into();
+ final_path.extend_from_slice(&path_as_vec);
+ }
+ let our_path: Vec<ChildNumber> = self.path.clone().into();
+ final_path.extend_from_slice(&our_path);
let other_path: Vec<ChildNumber> = self.final_index.as_path(index).into();
final_path.extend_from_slice(&other_path);
use std::fmt;
use std::str::FromStr;
-use bitcoin::blockdata::script::Script;
use bitcoin::hashes::{hash160, Hash};
use bitcoin::secp256k1::{All, Secp256k1};
use bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey, Fingerprint};
-use bitcoin::{PrivateKey, PublicKey};
+use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
+use bitcoin::{PrivateKey, PublicKey, Script};
-pub use miniscript::descriptor::Descriptor;
+pub use miniscript::{descriptor::Descriptor, Miniscript};
use serde::{Deserialize, Serialize};
+use crate::psbt::utils::PSBTUtils;
+
+pub mod checksum;
pub mod error;
pub mod extended_key;
pub mod policy;
+pub use self::checksum::get_checksum;
pub use self::error::Error;
pub use self::extended_key::{DerivationIndex, DescriptorExtendedKey};
-pub use self::policy::{ExtractPolicy, Policy};
+pub use self::policy::Policy;
trait MiniscriptExtractPolicy {
fn extract_policy(&self, lookup_map: &BTreeMap<String, Box<dyn Key>>) -> Option<Policy>;
}
+pub trait ExtractPolicy {
+ fn extract_policy(&self) -> Option<Policy>;
+}
+
#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord, Default)]
struct DummyKey();
fn psbt_redeem_script(&self) -> Option<Script> {
match self {
- Descriptor::ShWpkh(ref pk) => {
- let addr =
- bitcoin::Address::p2shwpkh(&pk.to_public_key(), bitcoin::Network::Bitcoin);
- Some(addr.script_pubkey())
- }
+ Descriptor::ShWpkh(_) => Some(self.witness_script()),
Descriptor::ShWsh(ref script) => Some(script.encode().to_v0_p2wsh()),
Descriptor::Sh(ref script) => Some(script.encode()),
_ => None,
fn xprv(&self) -> Option<ExtendedPrivKey>;
fn full_path(&self, index: u32) -> Option<DerivationPath>;
fn is_fixed(&self) -> bool;
+
+ fn has_secret(&self) -> bool {
+ self.xprv().is_some() || self.as_secret_key().is_some()
+ }
}
impl Key for PublicKey {
impl Key for DescriptorExtendedKey {
fn fingerprint(&self, secp: &Secp256k1<All>) -> Option<Fingerprint> {
- Some(self.root_xpub(secp).fingerprint())
+ if let Some(fing) = self.master_fingerprint {
+ Some(fing.clone())
+ } else {
+ Some(self.root_xpub(secp).fingerprint())
+ }
}
fn as_public_key(&self, secp: &Secp256k1<All>, index: Option<u32>) -> Result<PublicKey, Error> {
})
}
+ pub fn derive_with_miniscript(
+ &self,
+ miniscript: Miniscript<PublicKey>,
+ ) -> Result<DerivedDescriptor, Error> {
+ // TODO: make sure they are "equivalent"
+ match self.internal {
+ Descriptor::Bare(_) => Ok(Descriptor::Bare(miniscript)),
+ Descriptor::Sh(_) => Ok(Descriptor::Sh(miniscript)),
+ Descriptor::Wsh(_) => Ok(Descriptor::Wsh(miniscript)),
+ Descriptor::ShWsh(_) => Ok(Descriptor::ShWsh(miniscript)),
+ _ => Err(Error::CantDeriveWithMiniscript),
+ }
+ }
+
+ pub fn derive_from_psbt_input(
+ &self,
+ psbt: &PSBT,
+ input_index: usize,
+ ) -> Result<DerivedDescriptor, Error> {
+ let get_pk_from_partial_sigs = || {
+ // here we need the public key.. since it's a single sig, there are only two
+ // options: we can either find it in the `partial_sigs`, or we can't. if we
+ // can't, it means that we can't even satisfy the input, so we can exit knowing
+ // that we did our best to try to find it.
+ psbt.inputs[input_index]
+ .partial_sigs
+ .keys()
+ .nth(0)
+ .ok_or(Error::MissingPublicKey)
+ };
+
+ if let Some(wit_script) = &psbt.inputs[input_index].witness_script {
+ self.derive_with_miniscript(Miniscript::parse(wit_script)?)
+ } else if let Some(p2sh_script) = &psbt.inputs[input_index].redeem_script {
+ if p2sh_script.is_v0_p2wpkh() {
+ // wrapped p2wpkh
+ get_pk_from_partial_sigs().map(|pk| Descriptor::ShWpkh(*pk))
+ } else {
+ self.derive_with_miniscript(Miniscript::parse(p2sh_script)?)
+ }
+ } else if let Some(utxo) = psbt.get_utxo_for(input_index) {
+ if utxo.script_pubkey.is_p2pkh() {
+ get_pk_from_partial_sigs().map(|pk| Descriptor::Pkh(*pk))
+ } else if utxo.script_pubkey.is_p2pk() {
+ get_pk_from_partial_sigs().map(|pk| Descriptor::Pk(*pk))
+ } else if utxo.script_pubkey.is_v0_p2wpkh() {
+ get_pk_from_partial_sigs().map(|pk| Descriptor::Wpkh(*pk))
+ } else {
+ // try as bare script
+ self.derive_with_miniscript(Miniscript::parse(&utxo.script_pubkey)?)
+ }
+ } else {
+ Err(Error::MissingDetails)
+ }
+ }
+
pub fn derive(&self, index: u32) -> Result<DerivedDescriptor, Error> {
let translatefpk = |xpub: &String| {
self.keys
-use std::collections::BTreeMap;
+use std::collections::{BTreeMap, HashSet};
use serde::Serialize;
use bitcoin::hashes::*;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::util::bip32::Fingerprint;
+use bitcoin::util::psbt;
use bitcoin::PublicKey;
use miniscript::{Descriptor, Miniscript, Terminal};
fn from_key(k: &Box<dyn Key>) -> Self {
let secp = Secp256k1::gen_new();
+ let pubkey = k.as_public_key(&secp, None).unwrap();
if let Some(fing) = k.fingerprint(&secp) {
PKOrF {
fingerprint: Some(fing),
} else {
PKOrF {
fingerprint: None,
- pubkey: Some(k.as_public_key(&secp, None).unwrap()),
+ pubkey: Some(pubkey),
}
}
}
hash: hash160::Hash,
},
AbsoluteTimelock {
- height: u32,
+ value: u32,
},
RelativeTimelock {
- blocks: u32,
+ value: u32,
},
// Complex item
_ => true,
}
}
+
+ fn satisfy(&self, _input: &psbt::Input) -> Satisfaction {
+ Satisfaction::None
+ }
}
-#[derive(Debug, Serialize)]
-pub enum ItemSatisfier {
- Us,
- Other(Option<Fingerprint>),
- Timelock(Option<u32>), // remaining blocks. TODO: time-based timelocks
+#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
+#[serde(tag = "type", rename_all = "UPPERCASE")]
+pub enum Satisfaction {
+ Complete {
+ #[serde(skip_serializing_if = "PathRequirements::is_null")]
+ condition: PathRequirements,
+ },
+ Partial {
+ m: usize,
+ n: usize,
+ completed: HashSet<usize>,
+ },
+ None,
+}
+
+impl Satisfaction {
+ fn from_items_threshold(items: HashSet<usize>, threshold: usize) -> Satisfaction {
+ Satisfaction::Partial {
+ m: items.len(),
+ n: threshold,
+ completed: items,
+ }
+ }
+}
+
+impl<'a> std::ops::Add<&'a Satisfaction> for Satisfaction {
+ type Output = Satisfaction;
+
+ fn add(self, other: &'a Satisfaction) -> Satisfaction {
+ &self + other
+ }
+}
+
+impl<'a, 'b> std::ops::Add<&'b Satisfaction> for &'a Satisfaction {
+ type Output = Satisfaction;
+
+ fn add(self, other: &'b Satisfaction) -> Satisfaction {
+ match (self, other) {
+ // complete-complete
+ (
+ Satisfaction::Complete { condition: mut a },
+ Satisfaction::Complete { condition: b },
+ ) => {
+ a.merge(&b).unwrap();
+ Satisfaction::Complete { condition: a }
+ }
+ // complete-<any>
+ (Satisfaction::Complete { condition }, _) => Satisfaction::Complete {
+ condition: *condition,
+ },
+ (_, Satisfaction::Complete { condition }) => Satisfaction::Complete {
+ condition: *condition,
+ },
+
+ // none-<any>
+ (Satisfaction::None, any) => any.clone(),
+ (any, Satisfaction::None) => any.clone(),
+
+ // partial-partial
+ (
+ Satisfaction::Partial {
+ m: _,
+ n: a_n,
+ completed: a_items,
+ },
+ Satisfaction::Partial {
+ m: _,
+ n: _,
+ completed: b_items,
+ },
+ ) => {
+ let union: HashSet<_> = a_items.union(&b_items).cloned().collect();
+ Satisfaction::Partial {
+ m: union.len(),
+ n: *a_n,
+ completed: union,
+ }
+ }
+ }
+ }
}
#[derive(Debug, Serialize)]
pub struct Policy {
#[serde(flatten)]
item: SatisfiableItem,
- #[serde(skip_serializing_if = "Option::is_none")]
- satisfier: Option<ItemSatisfier>,
+ satisfaction: Satisfaction,
+ contribution: Satisfaction,
}
-#[derive(Debug, Default)]
+#[derive(Debug, Default, Eq, PartialEq, Clone, Copy, Serialize)]
pub struct PathRequirements {
+ #[serde(skip_serializing_if = "Option::is_none")]
pub csv: Option<u32>,
+ #[serde(skip_serializing_if = "Option::is_none")]
pub timelock: Option<u32>,
}
}?;
match (self.timelock, other.timelock) {
+ // TODO: we could actually set the timelock to the highest of the two, but we would
+ // have to first check that they are both in the same "unit" (blocks vs time)
(Some(old), Some(new)) if old != new => Err(PolicyError::DifferentTimelock(old, new)),
_ => {
self.timelock = self.timelock.or(other.timelock);
pub fn new(item: SatisfiableItem) -> Self {
Policy {
item,
- satisfier: None,
+ satisfaction: Satisfaction::None,
+ contribution: Satisfaction::None,
}
}
match (a, b) {
(None, None) => None,
(Some(x), None) | (None, Some(x)) => Some(x),
- (Some(a), Some(b)) => Some(
- SatisfiableItem::Thresh {
- items: vec![a, b],
- threshold: 2,
- }
- .into(),
- ),
+ (Some(a), Some(b)) => Self::make_thresh(vec![a, b], 2),
}
}
match (a, b) {
(None, None) => None,
(Some(x), None) | (None, Some(x)) => Some(x),
- (Some(a), Some(b)) => Some(
- SatisfiableItem::Thresh {
- items: vec![a, b],
- threshold: 1,
- }
- .into(),
- ),
+ (Some(a), Some(b)) => Self::make_thresh(vec![a, b], 1),
}
}
- pub fn make_thresh(items: Vec<Policy>, mut threshold: usize) -> Option<Policy> {
+ pub fn make_thresh(items: Vec<Policy>, threshold: usize) -> Option<Policy> {
if threshold == 0 {
return None;
}
- if threshold > items.len() {
- threshold = items.len();
- }
- Some(SatisfiableItem::Thresh { items, threshold }.into())
+ let contribution = items.iter().fold(
+ Satisfaction::Partial {
+ m: 0,
+ n: threshold,
+ completed: HashSet::new(),
+ },
+ |acc, x| acc + &x.contribution,
+ );
+ let mut policy: Policy = SatisfiableItem::Thresh { items, threshold }.into();
+ policy.contribution = contribution;
+
+ Some(policy)
}
- fn make_multisig(pubkeys: Vec<Option<&Box<dyn Key>>>, threshold: usize) -> Option<Policy> {
- let keys = pubkeys
- .into_iter()
- .map(|k| PKOrF::from_key(k.unwrap()))
+ fn make_multisig(keys: Vec<Option<&Box<dyn Key>>>, threshold: usize) -> Option<Policy> {
+ let parsed_keys = keys.iter().map(|k| PKOrF::from_key(k.unwrap())).collect();
+ let mut policy: Policy = SatisfiableItem::Multisig {
+ keys: parsed_keys,
+ threshold,
+ }
+ .into();
+ let our_keys = keys
+ .iter()
+ .enumerate()
+ .filter(|(_, x)| x.is_some() && x.unwrap().has_secret())
+ .map(|(k, _)| k)
.collect();
- Some(SatisfiableItem::Multisig { keys, threshold }.into())
+ policy.contribution = Satisfaction::from_items_threshold(our_keys, threshold);
+
+ Some(policy)
+ }
+
+ pub fn satisfy(&mut self, input: &psbt::Input) {
+ self.satisfaction = self.item.satisfy(input);
}
pub fn requires_path(&self) -> bool {
Ok(requirements)
}
_ if !selected.is_empty() => Err(PolicyError::TooManyItemsSelected(index)),
- SatisfiableItem::AbsoluteTimelock { height } => Ok(PathRequirements {
+ SatisfiableItem::AbsoluteTimelock { value } => Ok(PathRequirements {
csv: None,
- timelock: Some(*height),
+ timelock: Some(*value),
}),
- SatisfiableItem::RelativeTimelock { blocks } => Ok(PathRequirements {
- csv: Some(*blocks),
+ SatisfiableItem::RelativeTimelock { value } => Ok(PathRequirements {
+ csv: Some(*value),
timelock: None,
}),
_ => Ok(PathRequirements::default()),
}
}
-pub trait ExtractPolicy {
- fn extract_policy(&self) -> Option<Policy>;
-}
-
fn signature_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> {
- key.map(|k| SatisfiableItem::Signature(PKOrF::from_key(k)).into())
+ key.map(|k| {
+ let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key(k)).into();
+ policy.contribution = if k.has_secret() {
+ Satisfaction::Complete {
+ condition: Default::default(),
+ }
+ } else {
+ Satisfaction::None
+ };
+
+ policy
+ })
}
fn signature_key_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> {
let secp = Secp256k1::gen_new();
key.map(|k| {
- if let Some(fing) = k.fingerprint(&secp) {
+ let pubkey = k.as_public_key(&secp, None).unwrap();
+ let mut policy: Policy = if let Some(fing) = k.fingerprint(&secp) {
SatisfiableItem::SignatureKey {
fingerprint: Some(fing),
pubkey_hash: None,
} else {
SatisfiableItem::SignatureKey {
fingerprint: None,
- pubkey_hash: Some(hash160::Hash::hash(
- &k.as_public_key(&secp, None).unwrap().to_bytes(),
- )),
+ pubkey_hash: Some(hash160::Hash::hash(&pubkey.to_bytes())),
}
}
- .into()
+ .into();
+ policy.contribution = if k.has_secret() {
+ Satisfaction::Complete {
+ condition: Default::default(),
+ }
+ } else {
+ Satisfaction::None
+ };
+
+ policy
})
}
Terminal::True | Terminal::False => None,
Terminal::Pk(pubkey) => signature_from_string(lookup_map.get(pubkey)),
Terminal::PkH(pubkey_hash) => signature_key_from_string(lookup_map.get(pubkey_hash)),
- Terminal::After(height) => {
- Some(SatisfiableItem::AbsoluteTimelock { height: *height }.into())
+ Terminal::After(value) => {
+ let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into();
+ policy.contribution = Satisfaction::Complete {
+ condition: PathRequirements {
+ csv: None,
+ timelock: Some(*value),
+ },
+ };
+
+ Some(policy)
}
- Terminal::Older(blocks) => {
- Some(SatisfiableItem::RelativeTimelock { blocks: *blocks }.into())
+ Terminal::Older(value) => {
+ let mut policy: Policy = SatisfiableItem::RelativeTimelock { value: *value }.into();
+ policy.contribution = Satisfaction::Complete {
+ condition: PathRequirements {
+ csv: Some(*value),
+ timelock: None,
+ },
+ };
+
+ Some(policy)
}
Terminal::Sha256(hash) => Some(SatisfiableItem::SHA256Preimage { hash: *hash }.into()),
Terminal::Hash256(hash) => {
Terminal::Hash160(hash) => {
Some(SatisfiableItem::HASH160Preimage { hash: *hash }.into())
}
+ Terminal::ThreshM(k, pks) => {
+ Policy::make_multisig(pks.iter().map(|s| lookup_map.get(s)).collect(), *k)
+ }
// Identities
Terminal::Alt(inner)
| Terminal::Swap(inner)
Policy::make_thresh(mapped, threshold)
}
- Terminal::ThreshM(k, pks) => {
- Policy::make_multisig(pks.iter().map(|s| lookup_map.get(s)).collect(), *k)
- }
}
}
}
UnknownUTXO,
DifferentTransactions,
+ ChecksumMismatch,
+
SpendingPolicyRequired,
InvalidPolicyPathError(crate::descriptor::policy::PolicyError),
+++ /dev/null
-use std::collections::BTreeMap;
-
-use bitcoin::hashes::{hash160, Hash};
-use bitcoin::util::bip143::SighashComponents;
-use bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey, Fingerprint};
-use bitcoin::util::psbt;
-use bitcoin::{PrivateKey, PublicKey, Script, SigHashType, Transaction};
-
-use bitcoin::secp256k1::{self, All, Message, Secp256k1};
-
-#[allow(unused_imports)]
-use log::{debug, error, info, trace};
-
-use miniscript::{BitcoinSig, MiniscriptKey, Satisfier};
-
-use crate::descriptor::ExtendedDescriptor;
-use crate::error::Error;
-use crate::signer::Signer;
-
-pub struct PSBTSatisfier<'a> {
- input: &'a psbt::Input,
- create_height: Option<u32>,
- current_height: Option<u32>,
-}
-
-impl<'a> PSBTSatisfier<'a> {
- pub fn new(
- input: &'a psbt::Input,
- create_height: Option<u32>,
- current_height: Option<u32>,
- ) -> Self {
- PSBTSatisfier {
- input,
- create_height,
- current_height,
- }
- }
-}
-
-impl<'a> PSBTSatisfier<'a> {
- fn parse_sig(rawsig: &Vec<u8>) -> Option<BitcoinSig> {
- let (flag, sig) = rawsig.split_last().unwrap();
- let flag = bitcoin::SigHashType::from_u32(*flag as u32);
- let sig = match secp256k1::Signature::from_der(sig) {
- Ok(sig) => sig,
- Err(..) => return None,
- };
- Some((sig, flag))
- }
-}
-
-// TODO: also support hash preimages through the "unknown" section of PSBT
-impl<'a> Satisfier<bitcoin::PublicKey> for PSBTSatisfier<'a> {
- // from https://docs.rs/miniscript/0.12.0/src/miniscript/psbt/mod.rs.html#96
- fn lookup_sig(&self, pk: &bitcoin::PublicKey) -> Option<BitcoinSig> {
- debug!("lookup_sig: {}", pk);
-
- if let Some(rawsig) = self.input.partial_sigs.get(pk) {
- Self::parse_sig(&rawsig)
- } else {
- None
- }
- }
-
- fn lookup_pkh_pk(&self, hash: &hash160::Hash) -> Option<bitcoin::PublicKey> {
- debug!("lookup_pkh_pk: {}", hash);
-
- for (pk, _) in &self.input.partial_sigs {
- if &pk.to_pubkeyhash() == hash {
- return Some(*pk);
- }
- }
-
- None
- }
-
- fn lookup_pkh_sig(&self, hash: &hash160::Hash) -> Option<(bitcoin::PublicKey, BitcoinSig)> {
- debug!("lookup_pkh_sig: {}", hash);
-
- for (pk, sig) in &self.input.partial_sigs {
- if &pk.to_pubkeyhash() == hash {
- return match Self::parse_sig(&sig) {
- Some(bitcoinsig) => Some((*pk, bitcoinsig)),
- None => None,
- };
- }
- }
-
- None
- }
-
- fn check_older(&self, height: u32) -> bool {
- // TODO: also check if `nSequence` right
- debug!("check_older: {}", height);
-
- // TODO: test >= / >
- self.current_height.unwrap_or(0) >= self.create_height.unwrap_or(0) + height
- }
-
- fn check_after(&self, height: u32) -> bool {
- // TODO: also check if `nLockTime` is right
- debug!("check_older: {}", height);
-
- self.current_height.unwrap_or(0) > height
- }
-}
-
-#[derive(Debug)]
-pub struct PSBTSigner<'a> {
- tx: &'a Transaction,
- secp: Secp256k1<All>,
-
- // psbt: &'b psbt::PartiallySignedTransaction,
- extended_keys: BTreeMap<Fingerprint, ExtendedPrivKey>,
- private_keys: BTreeMap<PublicKey, PrivateKey>,
-}
-
-impl<'a> PSBTSigner<'a> {
- pub fn from_descriptor(tx: &'a Transaction, desc: &ExtendedDescriptor) -> Result<Self, Error> {
- let secp = Secp256k1::gen_new();
-
- let mut extended_keys = BTreeMap::new();
- for xprv in desc.get_xprv() {
- let fing = xprv.fingerprint(&secp);
- extended_keys.insert(fing, xprv);
- }
-
- let mut private_keys = BTreeMap::new();
- for privkey in desc.get_secret_keys() {
- let pubkey = privkey.public_key(&secp);
- private_keys.insert(pubkey, privkey);
- }
-
- Ok(PSBTSigner {
- tx,
- secp,
- extended_keys,
- private_keys,
- })
- }
-
- pub fn extend(&mut self, mut other: PSBTSigner) -> Result<(), Error> {
- if self.tx.txid() != other.tx.txid() {
- return Err(Error::DifferentTransactions);
- }
-
- self.extended_keys.append(&mut other.extended_keys);
- self.private_keys.append(&mut other.private_keys);
-
- Ok(())
- }
-
- // TODO: temporary
- pub fn all_public_keys(&self) -> impl IntoIterator<Item = &PublicKey> {
- self.private_keys.keys()
- }
-}
-
-impl<'a> Signer for PSBTSigner<'a> {
- fn sig_legacy_from_fingerprint(
- &self,
- index: usize,
- sighash: SigHashType,
- fingerprint: &Fingerprint,
- path: &DerivationPath,
- script: &Script,
- ) -> Result<Option<BitcoinSig>, Error> {
- self.extended_keys
- .get(fingerprint)
- .map_or(Ok(None), |xprv| {
- let privkey = xprv.derive_priv(&self.secp, path)?;
- // let derived_pubkey = secp256k1::PublicKey::from_secret_key(&self.secp, &privkey.private_key.key);
-
- let hash = self.tx.signature_hash(index, script, sighash.as_u32());
-
- let signature = self.secp.sign(
- &Message::from_slice(&hash.into_inner()[..])?,
- &privkey.private_key.key,
- );
-
- Ok(Some((signature, sighash)))
- })
- }
-
- fn sig_legacy_from_pubkey(
- &self,
- index: usize,
- sighash: SigHashType,
- public_key: &PublicKey,
- script: &Script,
- ) -> Result<Option<BitcoinSig>, Error> {
- self.private_keys
- .get(public_key)
- .map_or(Ok(None), |privkey| {
- let hash = self.tx.signature_hash(index, script, sighash.as_u32());
-
- let signature = self
- .secp
- .sign(&Message::from_slice(&hash.into_inner()[..])?, &privkey.key);
-
- Ok(Some((signature, sighash)))
- })
- }
-
- fn sig_segwit_from_fingerprint(
- &self,
- index: usize,
- sighash: SigHashType,
- fingerprint: &Fingerprint,
- path: &DerivationPath,
- script: &Script,
- value: u64,
- ) -> Result<Option<BitcoinSig>, Error> {
- self.extended_keys
- .get(fingerprint)
- .map_or(Ok(None), |xprv| {
- let privkey = xprv.derive_priv(&self.secp, path)?;
-
- let hash = SighashComponents::new(self.tx).sighash_all(
- &self.tx.input[index],
- script,
- value,
- );
-
- let signature = self.secp.sign(
- &Message::from_slice(&hash.into_inner()[..])?,
- &privkey.private_key.key,
- );
-
- Ok(Some((signature, sighash)))
- })
- }
-
- fn sig_segwit_from_pubkey(
- &self,
- index: usize,
- sighash: SigHashType,
- public_key: &PublicKey,
- script: &Script,
- value: u64,
- ) -> Result<Option<BitcoinSig>, Error> {
- self.private_keys
- .get(public_key)
- .map_or(Ok(None), |privkey| {
- let hash = SighashComponents::new(self.tx).sighash_all(
- &self.tx.input[index],
- script,
- value,
- );
-
- let signature = self
- .secp
- .sign(&Message::from_slice(&hash.into_inner()[..])?, &privkey.key);
-
- Ok(Some((signature, sighash)))
- })
- }
-}
--- /dev/null
+use std::collections::BTreeMap;
+
+use bitcoin::hashes::{hash160, Hash};
+use bitcoin::util::bip143::SighashComponents;
+use bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey, Fingerprint};
+use bitcoin::util::psbt;
+use bitcoin::{PrivateKey, PublicKey, Script, SigHashType, Transaction};
+
+use bitcoin::secp256k1::{self, All, Message, Secp256k1};
+
+#[allow(unused_imports)]
+use log::{debug, error, info, trace};
+
+use miniscript::{BitcoinSig, MiniscriptKey, Satisfier};
+
+use crate::descriptor::ExtendedDescriptor;
+use crate::error::Error;
+use crate::signer::Signer;
+
+pub mod utils;
+
+pub struct PSBTSatisfier<'a> {
+ input: &'a psbt::Input,
+ create_height: Option<u32>,
+ current_height: Option<u32>,
+}
+
+impl<'a> PSBTSatisfier<'a> {
+ pub fn new(
+ input: &'a psbt::Input,
+ create_height: Option<u32>,
+ current_height: Option<u32>,
+ ) -> Self {
+ PSBTSatisfier {
+ input,
+ create_height,
+ current_height,
+ }
+ }
+}
+
+impl<'a> PSBTSatisfier<'a> {
+ fn parse_sig(rawsig: &Vec<u8>) -> Option<BitcoinSig> {
+ let (flag, sig) = rawsig.split_last().unwrap();
+ let flag = bitcoin::SigHashType::from_u32(*flag as u32);
+ let sig = match secp256k1::Signature::from_der(sig) {
+ Ok(sig) => sig,
+ Err(..) => return None,
+ };
+ Some((sig, flag))
+ }
+}
+
+// TODO: also support hash preimages through the "unknown" section of PSBT
+impl<'a> Satisfier<bitcoin::PublicKey> for PSBTSatisfier<'a> {
+ // from https://docs.rs/miniscript/0.12.0/src/miniscript/psbt/mod.rs.html#96
+ fn lookup_sig(&self, pk: &bitcoin::PublicKey) -> Option<BitcoinSig> {
+ debug!("lookup_sig: {}", pk);
+
+ if let Some(rawsig) = self.input.partial_sigs.get(pk) {
+ Self::parse_sig(&rawsig)
+ } else {
+ None
+ }
+ }
+
+ fn lookup_pkh_pk(&self, hash: &hash160::Hash) -> Option<bitcoin::PublicKey> {
+ debug!("lookup_pkh_pk: {}", hash);
+
+ for (pk, _) in &self.input.partial_sigs {
+ if &pk.to_pubkeyhash() == hash {
+ return Some(*pk);
+ }
+ }
+
+ None
+ }
+
+ fn lookup_pkh_sig(&self, hash: &hash160::Hash) -> Option<(bitcoin::PublicKey, BitcoinSig)> {
+ debug!("lookup_pkh_sig: {}", hash);
+
+ for (pk, sig) in &self.input.partial_sigs {
+ if &pk.to_pubkeyhash() == hash {
+ return match Self::parse_sig(&sig) {
+ Some(bitcoinsig) => Some((*pk, bitcoinsig)),
+ None => None,
+ };
+ }
+ }
+
+ None
+ }
+
+ fn check_older(&self, height: u32) -> bool {
+ // TODO: also check if `nSequence` right
+ debug!("check_older: {}", height);
+
+ // TODO: test >= / >
+ self.current_height.unwrap_or(0) >= self.create_height.unwrap_or(0) + height
+ }
+
+ fn check_after(&self, height: u32) -> bool {
+ // TODO: also check if `nLockTime` is right
+ debug!("check_after: {}", height);
+
+ self.current_height.unwrap_or(0) > height
+ }
+}
+
+#[derive(Debug)]
+pub struct PSBTSigner<'a> {
+ tx: &'a Transaction,
+ secp: Secp256k1<All>,
+
+ // psbt: &'b psbt::PartiallySignedTransaction,
+ extended_keys: BTreeMap<Fingerprint, ExtendedPrivKey>,
+ private_keys: BTreeMap<PublicKey, PrivateKey>,
+}
+
+impl<'a> PSBTSigner<'a> {
+ pub fn from_descriptor(tx: &'a Transaction, desc: &ExtendedDescriptor) -> Result<Self, Error> {
+ let secp = Secp256k1::gen_new();
+
+ let mut extended_keys = BTreeMap::new();
+ for xprv in desc.get_xprv() {
+ let fing = xprv.fingerprint(&secp);
+ extended_keys.insert(fing, xprv);
+ }
+
+ let mut private_keys = BTreeMap::new();
+ for privkey in desc.get_secret_keys() {
+ let pubkey = privkey.public_key(&secp);
+ private_keys.insert(pubkey, privkey);
+ }
+
+ Ok(PSBTSigner {
+ tx,
+ secp,
+ extended_keys,
+ private_keys,
+ })
+ }
+
+ pub fn extend(&mut self, mut other: PSBTSigner) -> Result<(), Error> {
+ if self.tx.txid() != other.tx.txid() {
+ return Err(Error::DifferentTransactions);
+ }
+
+ self.extended_keys.append(&mut other.extended_keys);
+ self.private_keys.append(&mut other.private_keys);
+
+ Ok(())
+ }
+
+ // TODO: temporary
+ pub fn all_public_keys(&self) -> impl IntoIterator<Item = &PublicKey> {
+ self.private_keys.keys()
+ }
+}
+
+impl<'a> Signer for PSBTSigner<'a> {
+ fn sig_legacy_from_fingerprint(
+ &self,
+ index: usize,
+ sighash: SigHashType,
+ fingerprint: &Fingerprint,
+ path: &DerivationPath,
+ script: &Script,
+ ) -> Result<Option<BitcoinSig>, Error> {
+ self.extended_keys
+ .get(fingerprint)
+ .map_or(Ok(None), |xprv| {
+ let privkey = xprv.derive_priv(&self.secp, path)?;
+ // let derived_pubkey = secp256k1::PublicKey::from_secret_key(&self.secp, &privkey.private_key.key);
+
+ let hash = self.tx.signature_hash(index, script, sighash.as_u32());
+
+ let signature = self.secp.sign(
+ &Message::from_slice(&hash.into_inner()[..])?,
+ &privkey.private_key.key,
+ );
+
+ Ok(Some((signature, sighash)))
+ })
+ }
+
+ fn sig_legacy_from_pubkey(
+ &self,
+ index: usize,
+ sighash: SigHashType,
+ public_key: &PublicKey,
+ script: &Script,
+ ) -> Result<Option<BitcoinSig>, Error> {
+ self.private_keys
+ .get(public_key)
+ .map_or(Ok(None), |privkey| {
+ let hash = self.tx.signature_hash(index, script, sighash.as_u32());
+
+ let signature = self
+ .secp
+ .sign(&Message::from_slice(&hash.into_inner()[..])?, &privkey.key);
+
+ Ok(Some((signature, sighash)))
+ })
+ }
+
+ fn sig_segwit_from_fingerprint(
+ &self,
+ index: usize,
+ sighash: SigHashType,
+ fingerprint: &Fingerprint,
+ path: &DerivationPath,
+ script: &Script,
+ value: u64,
+ ) -> Result<Option<BitcoinSig>, Error> {
+ self.extended_keys
+ .get(fingerprint)
+ .map_or(Ok(None), |xprv| {
+ let privkey = xprv.derive_priv(&self.secp, path)?;
+
+ let hash = SighashComponents::new(self.tx).sighash_all(
+ &self.tx.input[index],
+ script,
+ value,
+ );
+
+ let signature = self.secp.sign(
+ &Message::from_slice(&hash.into_inner()[..])?,
+ &privkey.private_key.key,
+ );
+
+ Ok(Some((signature, sighash)))
+ })
+ }
+
+ fn sig_segwit_from_pubkey(
+ &self,
+ index: usize,
+ sighash: SigHashType,
+ public_key: &PublicKey,
+ script: &Script,
+ value: u64,
+ ) -> Result<Option<BitcoinSig>, Error> {
+ self.private_keys
+ .get(public_key)
+ .map_or(Ok(None), |privkey| {
+ let hash = SighashComponents::new(self.tx).sighash_all(
+ &self.tx.input[index],
+ script,
+ value,
+ );
+
+ let signature = self
+ .secp
+ .sign(&Message::from_slice(&hash.into_inner()[..])?, &privkey.key);
+
+ Ok(Some((signature, sighash)))
+ })
+ }
+}
--- /dev/null
+use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
+use bitcoin::TxOut;
+
+pub trait PSBTUtils {
+ fn get_utxo_for(&self, input_index: usize) -> Option<TxOut>;
+}
+
+impl PSBTUtils for PSBT {
+ fn get_utxo_for(&self, input_index: usize) -> Option<TxOut> {
+ let tx = &self.global.unsigned_tx;
+
+ if input_index >= tx.input.len() {
+ return None;
+ }
+
+ if let Some(input) = self.inputs.get(input_index) {
+ if let Some(wit_utxo) = &input.witness_utxo {
+ Some(wit_utxo.clone())
+ } else if let Some(in_tx) = &input.non_witness_utxo {
+ Some(in_tx.output[tx.input[input_index].previous_output.vout as usize].clone())
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
+}
use std::collections::{BTreeMap, HashSet, VecDeque};
use std::convert::TryFrom;
use std::io::{Read, Write};
+use std::str::FromStr;
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use bitcoin::blockdata::opcodes;
use self::utils::{ChunksIterator, IsDust};
use crate::database::{BatchDatabase, BatchOperations};
-use crate::descriptor::{
- DerivedDescriptor, DescriptorMeta, ExtendedDescriptor, ExtractPolicy, Policy,
-};
+use crate::descriptor::{get_checksum, DescriptorMeta, ExtendedDescriptor, ExtractPolicy, Policy};
use crate::error::Error;
-use crate::psbt::{PSBTSatisfier, PSBTSigner};
+use crate::psbt::{utils::PSBTUtils, PSBTSatisfier, PSBTSigner};
use crate::signer::Signer;
use crate::types::*;
#[cfg(not(any(feature = "electrum", feature = "default")))]
use std::marker::PhantomData as Client;
-// TODO: force descriptor and change_descriptor to have the same policies?
pub struct Wallet<S: Read + Write, D: BatchDatabase> {
descriptor: ExtendedDescriptor,
change_descriptor: Option<ExtendedDescriptor>,
D: BatchDatabase,
{
pub fn new_offline(
- descriptor: ExtendedDescriptor,
- change_descriptor: Option<ExtendedDescriptor>,
+ descriptor: &str,
+ change_descriptor: Option<&str>,
network: Network,
- database: D,
- ) -> Self {
- Wallet {
+ mut database: D,
+ ) -> Result<Self, Error> {
+ database.check_descriptor_checksum(
+ ScriptType::External,
+ get_checksum(descriptor)?.as_bytes(),
+ )?;
+ let descriptor = ExtendedDescriptor::from_str(descriptor)?;
+ let change_descriptor = match change_descriptor {
+ Some(desc) => {
+ database.check_descriptor_checksum(
+ ScriptType::Internal,
+ get_checksum(desc)?.as_bytes(),
+ )?;
+ Some(ExtendedDescriptor::from_str(desc)?)
+ }
+ None => None,
+ };
+
+ // TODO: make sure that both descriptor have the same structure
+
+ Ok(Wallet {
descriptor,
change_descriptor,
network,
client: None,
database: RefCell::new(database),
_secp: Secp256k1::gen_new(),
- }
+ })
}
pub fn get_new_address(&self) -> Result<Address, Error> {
utxos: Option<Vec<OutPoint>>,
unspendable: Option<Vec<OutPoint>>,
) -> Result<(PSBT, TransactionDetails), Error> {
- // TODO: run before deriving the descriptor
let policy = self.descriptor.extract_policy().unwrap();
if policy.requires_path() && policy_path.is_none() {
return Err(Error::SpendingPolicyRequired);
// TODO: define an enum for signing errors
pub fn sign(&self, mut psbt: PSBT) -> Result<(PSBT, bool), Error> {
- let mut derived_descriptors = BTreeMap::new();
-
let tx = &psbt.global.unsigned_tx;
+ let mut input_utxos = Vec::with_capacity(psbt.inputs.len());
+ for n in 0..psbt.inputs.len() {
+ input_utxos.push(psbt.get_utxo_for(n).clone());
+ }
// try to add hd_keypaths if we've already seen the output
- for (n, psbt_input) in psbt.inputs.iter_mut().enumerate() {
- let out = match (&psbt_input.witness_utxo, &psbt_input.non_witness_utxo) {
- (Some(wit_out), _) => Some(wit_out),
- (_, Some(in_tx))
- if (tx.input[n].previous_output.vout as usize) < in_tx.output.len() =>
- {
- Some(&in_tx.output[tx.input[n].previous_output.vout as usize])
- }
- _ => None,
- };
-
+ for (psbt_input, out) in psbt.inputs.iter_mut().zip(input_utxos.iter()) {
debug!("searching hd_keypaths for out: {:?}", out);
if let Some(out) = out {
None => 0,
};
- let desc = self.get_descriptor_for(script_type);
- let derived_descriptor = desc.derive(index)?;
- derived_descriptors.insert(n, derived_descriptor);
-
// merge hd_keypaths
+ let desc = self.get_descriptor_for(script_type);
let mut hd_keypaths = desc.get_hd_keypaths(index)?;
psbt_input.hd_keypaths.append(&mut hd_keypaths);
}
}
// attempt to finalize
- let finalized = self.finalize_psbt(tx.clone(), &mut psbt, derived_descriptors);
+ let finalized = self.finalize_psbt(tx.clone(), &mut psbt);
Ok((psbt, finalized))
}
Ok((answer, paths, selected_amount, fee_val))
}
- fn finalize_psbt(
- &self,
- mut tx: Transaction,
- psbt: &mut PSBT,
- derived_descriptors: BTreeMap<usize, DerivedDescriptor>,
- ) -> bool {
+ fn finalize_psbt(&self, mut tx: Transaction, psbt: &mut PSBT) -> bool {
for (n, input) in tx.input.iter_mut().enumerate() {
- debug!("getting descriptor for {}", n);
-
- let desc = match derived_descriptors.get(&n) {
- None => return false,
- Some(desc) => desc,
+ // safe to run only on the descriptor because we assume the change descriptor also has
+ // the same structure
+ let desc = self.descriptor.derive_from_psbt_input(psbt, n);
+ debug!("reconstructed descriptor is {:?}", desc);
+
+ let desc = match desc {
+ Err(_) => return false,
+ Ok(desc) => desc,
};
// TODO: use height once we sync headers
D: BatchDatabase,
{
pub fn new(
- descriptor: ExtendedDescriptor,
- change_descriptor: Option<ExtendedDescriptor>,
+ descriptor: &str,
+ change_descriptor: Option<&str>,
network: Network,
- database: D,
+ mut database: D,
client: Client<S>,
- ) -> Self {
- Wallet {
+ ) -> Result<Self, Error> {
+ database.check_descriptor_checksum(
+ ScriptType::External,
+ get_checksum(descriptor)?.as_bytes(),
+ )?;
+ let descriptor = ExtendedDescriptor::from_str(descriptor)?;
+ let change_descriptor = match change_descriptor {
+ Some(desc) => {
+ database.check_descriptor_checksum(
+ ScriptType::Internal,
+ get_checksum(desc)?.as_bytes(),
+ )?;
+ Some(ExtendedDescriptor::from_str(desc)?)
+ }
+ None => None,
+ };
+
+ // TODO: make sure that both descriptor have the same structure
+
+ Ok(Wallet {
descriptor,
change_descriptor,
network,
client: Some(RefCell::new(client)),
database: RefCell::new(database),
_secp: Secp256k1::gen_new(),
- }
+ })
}
fn get_previous_output(&self, outpoint: &OutPoint) -> Option<TxOut> {