This allows us to remove all our custom "ExtendedDescriptor" implementation since that is
now built directly in miniscript.
name = "magical-bitcoin-wallet"
version = "0.1.0"
edition = "2018"
-authors = ["Riccardo Casatta <riccardo@casatta.it>", "Alekos Filini <alekos.filini@gmail.com>"]
+authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
[dependencies]
magical-macros = { path = "./macros" }
socks = { version = "0.3", optional = true }
lazy_static = { version = "1.4", optional = true }
+[patch.crates-io]
+bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin/", rev = "478e091" }
+miniscript = { git = "https://github.com/MagicalBitcoin/rust-miniscript", branch = "descriptor-public-key" }
+
# Platform-specific dependencies
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "0.2", features = ["rt-core"] }
extern crate magical_bitcoin_wallet;
extern crate serde_json;
-use std::str::FromStr;
+use std::sync::Arc;
+use magical_bitcoin_wallet::bitcoin::util::bip32::ChildNumber;
use magical_bitcoin_wallet::bitcoin::*;
use magical_bitcoin_wallet::descriptor::*;
and_v(vc:pk_h(cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy),older(1000))\
))";
- let extended_desc = ExtendedDescriptor::from_str(desc).unwrap();
+ let (extended_desc, key_map) = ExtendedDescriptor::parse_secret(desc).unwrap();
println!("{:?}", extended_desc);
- let policy = extended_desc.extract_policy().unwrap();
+ let signers = Arc::new(key_map.into());
+ let policy = extended_desc.extract_policy(signers).unwrap();
println!("policy: {}", serde_json::to_string(&policy).unwrap());
- let derived_desc = extended_desc.derive(42).unwrap();
+ let derived_desc = extended_desc.derive(&[ChildNumber::from_normal_idx(42).unwrap()]);
println!("{:?}", derived_desc);
let addr = derived_desc.address(Network::Testnet).unwrap();
use log::{debug, error, info, trace};
use bitcoin::network::message_blockdata::Inventory;
-use bitcoin::{BitcoinHash, OutPoint, Transaction, Txid};
+use bitcoin::{OutPoint, Transaction, Txid};
use rocksdb::{Options, SliceTransform, DB};
let block_height = headers.get_height_for(block_hash)?.unwrap_or(0);
let saved_correct_block = match headers.get_full_block(block_height)? {
- Some(block) if &block.bitcoin_hash() == block_hash => true,
+ Some(block) if &block.block_hash() == block_hash => true,
_ => false,
};
use bitcoin::hashes::hex::FromHex;
use bitcoin::hashes::{sha256d, Hash};
use bitcoin::util::bip158::BlockFilter;
-use bitcoin::util::hash::BitcoinHash;
use bitcoin::util::uint::Uint256;
use bitcoin::Block;
use bitcoin::BlockHash;
);
batch.put_cf(
cf_handle,
- StoreEntry::BlockHeaderIndex(Some(genesis.bitcoin_hash())).get_key(),
+ StoreEntry::BlockHeaderIndex(Some(genesis.block_hash())).get_key(),
&0usize.to_be_bytes(),
);
store.write(batch)?;
.get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(index)).get_key())?
.unwrap(),
)?;
- answer.push((header.bitcoin_hash(), index));
+ answer.push((header.block_hash(), index));
if let Some(new_index) = index.checked_sub(step) {
index = new_index;
let mut batch = WriteBatch::default();
batch.put_cf(
new_cf_handle,
- StoreEntry::BlockHeaderIndex(Some(header.bitcoin_hash())).get_key(),
+ StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
&from.to_be_bytes(),
);
batch.put_cf(
batch.delete_cf(
cf_handle,
- StoreEntry::BlockHeaderIndex(Some(header.bitcoin_hash())).get_key(),
+ StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
);
}
.map(|data| {
let (header, _): (BlockHeader, Uint256) =
deserialize(&data).map_err(|_| CompactFiltersError::DataCorruption)?;
- Ok::<_, CompactFiltersError>(header.bitcoin_hash())
+ Ok::<_, CompactFiltersError>(header.block_hash())
})
.transpose()?)
}
.map(|(_, v)| -> Result<_, CompactFiltersError> {
let (header, _): (BlockHeader, Uint256) = SerializeDb::deserialize(&v)?;
- Ok(header.bitcoin_hash())
+ Ok(header.block_hash())
})
.transpose()?)
}
.get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(from)).get_key())?
.map(|result| {
let (header, work): (BlockHeader, Uint256) = SerializeDb::deserialize(&result)?;
- Ok::<_, CompactFiltersError>((header.bitcoin_hash(), work))
+ Ok::<_, CompactFiltersError>((header.block_hash(), work))
})
.transpose()?
.ok_or(CompactFiltersError::DataCorruption)?;
return Err(CompactFiltersError::InvalidHeaders);
}
- last_hash = header.bitcoin_hash();
+ last_hash = header.block_hash();
accumulated_work = accumulated_work + header.work();
let height = from + index + 1;
batch.put_cf(
cf_handle,
- StoreEntry::BlockHeaderIndex(Some(header.bitcoin_hash())).get_key(),
+ StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
&(height).to_be_bytes(),
);
batch.put_cf(
filter_hash: FilterHash,
}
-impl BitcoinHash<FilterHeaderHash> for FilterHeader {
- fn bitcoin_hash(&self) -> FilterHeaderHash {
+impl FilterHeader {
+ fn header_hash(&self) -> FilterHeaderHash {
let mut hash_data = self.filter_hash.into_inner().to_vec();
hash_data.extend_from_slice(&self.prev_header_hash);
sha256d::Hash::hash(&hash_data).into()
prev_header_hash: last_hash,
filter_hash,
};
- last_hash = filter_header.bitcoin_hash();
+ last_hash = filter_header.header_hash();
filter_header
})
+++ /dev/null
-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> = 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_with_index(index).into();
- final_path.extend_from_slice(&our_path);
-
- final_path.into()
- }
-
- pub fn path_with_index(&self, index: u32) -> DerivationPath {
- let mut final_path: Vec<ChildNumber> = Vec::new();
- 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);
-
- 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.path_with_index(index))?;
- Ok(ExtendedPubKey::from_private(ctx, &derive_priv))
- } else {
- Ok(self.pubkey.derive_pub(ctx, &self.path_with_index(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,
- )
- }
- };
-
- if secret.is_none()
- && path.into_iter().any(|child| match child {
- ChildNumber::Hardened { .. } => true,
- _ => false,
- })
- {
- return Err(super::Error::HardenedDerivationOnXpub);
- }
-
- 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"));
- }
-}
+++ /dev/null
-use bitcoin::secp256k1::{All, Secp256k1};
-use bitcoin::{PrivateKey, PublicKey};
-
-use bitcoin::util::bip32::{
- ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, Fingerprint,
-};
-
-use super::error::Error;
-use super::extended_key::DerivationIndex;
-use super::DescriptorExtendedKey;
-
-pub(super) trait Key: std::fmt::Debug + std::fmt::Display {
- 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;
-
- fn has_secret(&self) -> bool {
- self.xprv().is_some() || self.as_secret_key().is_some()
- }
-
- fn public(&self, secp: &Secp256k1<All>) -> Result<Box<dyn Key>, Error> {
- Ok(Box::new(self.as_public_key(secp, None)?))
- }
-}
-
-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> {
- 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> {
- Ok(self.derive_xpub(secp, index.unwrap_or(0))?.public_key)
- }
-
- fn public(&self, secp: &Secp256k1<All>) -> Result<Box<dyn Key>, Error> {
- if self.final_index == DerivationIndex::Hardened {
- return Err(Error::HardenedDerivationOnXpub);
- }
-
- if self.xprv().is_none() {
- return Ok(Box::new(self.clone()));
- }
-
- // copy the part of the path that can be derived on the xpub
- let path = self
- .path
- .into_iter()
- .rev()
- .take_while(|child| match child {
- ChildNumber::Normal { .. } => true,
- _ => false,
- })
- .cloned()
- .collect::<Vec<_>>();
- // take the prefix that has to be derived on the xprv
- let master_derivation_add = self
- .path
- .into_iter()
- .take(self.path.as_ref().len() - path.len())
- .cloned()
- .collect::<Vec<_>>();
- let has_derived = !master_derivation_add.is_empty();
-
- let derived_xprv = self
- .secret
- .as_ref()
- .unwrap()
- .derive_priv(secp, &master_derivation_add)?;
- let pubkey = ExtendedPubKey::from_private(secp, &derived_xprv);
-
- let master_derivation = self
- .master_derivation
- .as_ref()
- .map_or(vec![], |path| path.as_ref().to_vec())
- .into_iter()
- .chain(master_derivation_add.into_iter())
- .collect::<Vec<_>>();
- let master_derivation = match &master_derivation[..] {
- &[] => None,
- child_vec => Some(child_vec.into()),
- };
-
- let master_fingerprint = match self.master_fingerprint {
- Some(desc) => Some(desc.clone()),
- None if has_derived => Some(self.fingerprint(secp).unwrap()),
- _ => None,
- };
-
- Ok(Box::new(DescriptorExtendedKey {
- master_fingerprint,
- master_derivation,
- pubkey,
- secret: None,
- path: path.into(),
- final_index: self.final_index,
- }))
- }
-
- 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
- }
-}
-use std::cell::RefCell;
-use std::collections::BTreeMap;
-use std::convert::{Into, TryFrom};
+use std::collections::{BTreeMap, HashMap};
use std::fmt;
-use std::str::FromStr;
+use std::sync::Arc;
-use bitcoin::hashes::{hash160, Hash};
-use bitcoin::secp256k1::{All, Secp256k1};
-use bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey, Fingerprint};
-use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
-use bitcoin::{PrivateKey, PublicKey, Script};
+use bitcoin::hashes::hash160;
+use bitcoin::secp256k1::Secp256k1;
+use bitcoin::util::bip32::{ChildNumber, DerivationPath, Fingerprint};
+use bitcoin::util::psbt;
+use bitcoin::{PublicKey, Script, TxOut};
+use miniscript::descriptor::{DescriptorPublicKey, DescriptorXKey, InnerXKey};
pub use miniscript::{
- Descriptor, Legacy, Miniscript, MiniscriptKey, ScriptContext, Segwitv0, Terminal,
+ Descriptor, Legacy, Miniscript, MiniscriptKey, ScriptContext, Segwitv0, Terminal, ToPublicKey,
};
-use serde::{Deserialize, Serialize};
-
-use crate::psbt::utils::PSBTUtils;
-
pub mod checksum;
pub mod error;
-pub mod extended_key;
-mod keys;
pub mod policy;
+// use crate::wallet::utils::AddressType;
+use crate::wallet::signer::SignersContainer;
+
pub use self::checksum::get_checksum;
use self::error::Error;
-pub use self::extended_key::{DerivationIndex, DescriptorExtendedKey};
pub use self::policy::Policy;
-use self::keys::Key;
+pub type ExtendedDescriptor = Descriptor<DescriptorPublicKey>;
+type HDKeyPaths = BTreeMap<PublicKey, (Fingerprint, DerivationPath)>;
-trait MiniscriptExtractPolicy {
+pub trait ExtractPolicy {
fn extract_policy(
&self,
- lookup_map: &BTreeMap<String, Box<dyn Key>>,
+ signers: Arc<SignersContainer<DescriptorPublicKey>>,
) -> Result<Option<Policy>, Error>;
}
-pub trait ExtractPolicy {
- fn extract_policy(&self) -> Result<Option<Policy>, Error>;
+pub trait XKeyUtils {
+ fn full_path(&self, append: &[ChildNumber]) -> DerivationPath;
+ fn root_fingerprint(&self) -> Fingerprint;
}
-#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord, Default)]
-struct DummyKey();
+impl<K: InnerXKey> XKeyUtils for DescriptorXKey<K> {
+ fn full_path(&self, append: &[ChildNumber]) -> DerivationPath {
+ let full_path = match &self.source {
+ &Some((_, ref path)) => path
+ .into_iter()
+ .chain(self.derivation_path.into_iter())
+ .cloned()
+ .collect(),
+ &None => self.derivation_path.clone(),
+ };
-impl fmt::Display for DummyKey {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "DummyKey")
+ if self.is_wildcard {
+ full_path
+ .into_iter()
+ .chain(append.into_iter())
+ .cloned()
+ .collect()
+ } else {
+ full_path
+ }
}
-}
-
-impl std::str::FromStr for DummyKey {
- type Err = ();
- fn from_str(_: &str) -> Result<Self, Self::Err> {
- Ok(DummyKey::default())
+ fn root_fingerprint(&self) -> Fingerprint {
+ match &self.source {
+ &Some((fingerprint, _)) => fingerprint.clone(),
+ &None => self.xkey.xkey_fingerprint(),
+ }
}
}
-impl miniscript::MiniscriptKey for DummyKey {
- type Hash = DummyKey;
-
- fn to_pubkeyhash(&self) -> DummyKey {
- DummyKey::default()
- }
+pub trait DescriptorMeta: Sized {
+ fn is_witness(&self) -> bool;
+ fn get_hd_keypaths(&self, index: u32) -> Result<HDKeyPaths, Error>;
+ fn is_fixed(&self) -> bool;
+ fn derive_from_hd_keypaths(&self, hd_keypaths: &HDKeyPaths) -> Option<Self>;
+ fn derive_from_psbt_input(&self, psbt_input: &psbt::Input, utxo: Option<TxOut>)
+ -> Option<Self>;
+ // fn address_type(&self) -> Option<AddressType>;
}
-pub type DerivedDescriptor = Descriptor<PublicKey>;
-pub type StringDescriptor = Descriptor<String>;
-
-pub trait DescriptorMeta {
- fn is_witness(&self) -> bool;
+pub trait DescriptorScripts {
fn psbt_redeem_script(&self) -> Option<Script>;
fn psbt_witness_script(&self) -> Option<Script>;
}
-impl<T> DescriptorMeta for Descriptor<T>
+impl<T> DescriptorScripts 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(_) => Some(self.witness_script()),
Descriptor::ShWsh(ref script) => Some(script.encode().to_v0_p2wsh()),
Descriptor::Sh(ref script) => Some(script.encode()),
+ Descriptor::Bare(ref script) => Some(script.encode()),
_ => None,
}
}
}
}
-#[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 fmt::Display for ExtendedDescriptor {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{}", self.internal)
- }
-}
-
-impl std::clone::Clone for ExtendedDescriptor {
- fn clone(&self) -> Self {
- Self {
- internal: self.internal.clone(),
- ctx: self.ctx.clone(),
- keys: BTreeMap::new(),
- }
- }
-}
-
-impl std::convert::AsRef<StringDescriptor> for ExtendedDescriptor {
- fn as_ref(&self) -> &StringDescriptor {
- &self.internal
- }
-}
-
-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)));
+impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
+ 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,
}
-
- 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);
+ fn get_hd_keypaths(&self, index: u32) -> Result<HDKeyPaths, Error> {
+ let mut answer = BTreeMap::new();
- Ok(DummyKey::default())
- };
- let translatefpkh = |string: &String| -> Result<_, Error> {
- let (key, parsed) = Self::parse_string(string)?;
- keys.borrow_mut().insert(key, parsed);
+ let translatefpk = |key: &DescriptorPublicKey| -> Result<_, Error> {
+ match key {
+ DescriptorPublicKey::PubKey(_) => {}
+ DescriptorPublicKey::XPub(xpub) => {
+ let derive_path = if xpub.is_wildcard {
+ xpub.derivation_path
+ .into_iter()
+ .chain([ChildNumber::from_normal_idx(index)?].iter())
+ .cloned()
+ .collect()
+ } else {
+ xpub.derivation_path.clone()
+ };
+ let derived_pubkey = xpub
+ .xkey
+ .derive_pub(&Secp256k1::verification_only(), &derive_path)?;
+
+ answer.insert(
+ derived_pubkey.public_key,
+ (
+ xpub.root_fingerprint(),
+ xpub.full_path(&[ChildNumber::from_normal_idx(index)?]),
+ ),
+ );
+ }
+ }
Ok(DummyKey::default())
};
+ let translatefpkh = |_: &hash160::Hash| -> Result<_, Error> { Ok(DummyKey::default()) };
- sd.translate_pk(translatefpk, translatefpkh)?;
+ self.translate_pk(translatefpk, translatefpkh)?;
- Ok(ExtendedDescriptor {
- internal: sd,
- keys: keys.into_inner(),
- ctx,
- })
+ Ok(answer)
}
- pub fn derive_with_miniscript_legacy(
- &self,
- miniscript: Miniscript<PublicKey, Legacy>,
- ) -> Result<DerivedDescriptor, Error> {
- let derived_desc = match self.internal {
- Descriptor::Bare(_) => Descriptor::Bare(miniscript),
- Descriptor::Sh(_) => Descriptor::Sh(miniscript),
- _ => return Err(Error::CantDeriveWithMiniscript),
- };
+ fn is_fixed(&self) -> bool {
+ let mut found_wildcard = false;
- // if !self.same_structure(&derived_desc) {
- // Err(Error::CantDeriveWithMiniscript)
- // } else {
- Ok(derived_desc)
- // }
- }
+ let translatefpk = |key: &DescriptorPublicKey| -> Result<_, Error> {
+ match key {
+ DescriptorPublicKey::PubKey(_) => {}
+ DescriptorPublicKey::XPub(xpub) => {
+ if xpub.is_wildcard {
+ found_wildcard = true;
+ }
+ }
+ }
- pub fn derive_with_miniscript_segwit_v0(
- &self,
- miniscript: Miniscript<PublicKey, Segwitv0>,
- ) -> Result<DerivedDescriptor, Error> {
- let derived_desc = match self.internal {
- Descriptor::Wsh(_) => Descriptor::Wsh(miniscript),
- Descriptor::ShWsh(_) => Descriptor::ShWsh(miniscript),
- _ => return Err(Error::CantDeriveWithMiniscript),
+ Ok(DummyKey::default())
};
+ let translatefpkh = |_: &hash160::Hash| -> Result<_, Error> { Ok(DummyKey::default()) };
+
+ self.translate_pk(translatefpk, translatefpkh).unwrap();
- // if !self.same_structure(&derived_desc) {
- // Err(Error::CantDeriveWithMiniscript)
- // } else {
- Ok(derived_desc)
- // }
+ !found_wildcard
}
- 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)
- };
+ fn derive_from_hd_keypaths(&self, hd_keypaths: &HDKeyPaths) -> Option<Self> {
+ let index: HashMap<_, _> = hd_keypaths.values().cloned().collect();
- if let Some(wit_script) = &psbt.inputs[input_index].witness_script {
- self.derive_with_miniscript_segwit_v0(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_legacy(Miniscript::parse(p2sh_script)?)
+ let mut derive_path = None::<DerivationPath>;
+ let translatefpk = |key: &DescriptorPublicKey| -> Result<_, Error> {
+ if derive_path.is_some() {
+ // already found a matching path, we are done
+ return Ok(DummyKey::default());
}
- } 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_legacy(Miniscript::parse(&utxo.script_pubkey)?)
+
+ if let DescriptorPublicKey::XPub(xpub) = key {
+ // Check if the key matches one entry in our `index`. If it does, `matches()` will
+ // return the "prefix" that matched, so we remove that prefix from the full path
+ // found in `index` and save it in `derive_path`
+ let root_fingerprint = xpub.root_fingerprint();
+ derive_path = index
+ .get_key_value(&root_fingerprint)
+ .and_then(|(fingerprint, path)| xpub.matches(*fingerprint, path))
+ .map(|prefix_path| prefix_path.into_iter().cloned().collect::<Vec<_>>())
+ .map(|prefix| {
+ index
+ .get(&xpub.root_fingerprint())
+ .unwrap()
+ .into_iter()
+ .skip(prefix.len())
+ .cloned()
+ .collect()
+ });
}
- } else {
- Err(Error::MissingDetails)
- }
- }
- 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))
+ Ok(DummyKey::default())
};
- let translatefpkh =
- |xpub: &String| Ok(hash160::Hash::hash(&translatefpk(xpub)?.to_bytes()));
+ let translatefpkh = |_: &hash160::Hash| -> Result<_, Error> { Ok(DummyKey::default()) };
- Ok(self.internal.translate_pk(translatefpk, translatefpkh)?)
- }
+ self.translate_pk(translatefpk, translatefpkh).unwrap();
- pub fn get_xprv(&self) -> impl IntoIterator<Item = ExtendedPrivKey> + '_ {
- self.keys
- .iter()
- .filter(|(_, v)| v.xprv().is_some())
- .map(|(_, v)| v.xprv().unwrap())
+ derive_path.map(|path| self.derive(path.as_ref()))
}
- pub fn get_secret_keys(&self) -> impl IntoIterator<Item = PrivateKey> + '_ {
- self.keys
- .iter()
- .filter(|(_, v)| v.as_secret_key().is_some())
- .map(|(_, v)| v.as_secret_key().unwrap())
- }
-
- pub fn get_hd_keypaths(
+ fn derive_from_psbt_input(
&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));
- }
+ psbt_input: &psbt::Input,
+ utxo: Option<TxOut>,
+ ) -> Option<Self> {
+ if let Some(derived) = self.derive_from_hd_keypaths(&psbt_input.hd_keypaths) {
+ return Some(derived);
+ } else if !self.is_fixed() {
+ // If the descriptor is not fixed we can't brute-force the derivation address, so just
+ // exit here
+ return None;
}
- 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())
- }
-
- pub fn same_structure<K: MiniscriptKey>(&self, other: &Descriptor<K>) -> bool {
- // Translate all the public keys to () and then check if the two descriptors are equal.
- // TODO: translate hashes to their default value before checking for ==
-
- let func_string = |_string: &String| -> Result<_, Error> { Ok(DummyKey::default()) };
-
- let func_generic_pk = |_data: &K| -> Result<_, Error> { Ok(DummyKey::default()) };
- let func_generic_pkh =
- |_data: &<K as MiniscriptKey>::Hash| -> Result<_, Error> { Ok(DummyKey::default()) };
-
- let translated_a = self.internal.translate_pk(func_string, func_string);
- let translated_b = other.translate_pk(func_generic_pk, func_generic_pkh);
-
- match (translated_a, translated_b) {
- (Ok(a), Ok(b)) => a == b,
- _ => false,
+ match self {
+ Descriptor::Pk(_)
+ | Descriptor::Pkh(_)
+ | Descriptor::Wpkh(_)
+ | Descriptor::ShWpkh(_)
+ if utxo.is_some()
+ && self.script_pubkey() == utxo.as_ref().unwrap().script_pubkey =>
+ {
+ Some(self.clone())
+ }
+ Descriptor::Bare(ms) | Descriptor::Sh(ms)
+ if psbt_input.redeem_script.is_some()
+ && &ms.encode() == psbt_input.redeem_script.as_ref().unwrap() =>
+ {
+ Some(self.clone())
+ }
+ Descriptor::Wsh(ms) | Descriptor::ShWsh(ms)
+ if psbt_input.witness_script.is_some()
+ && &ms.encode() == psbt_input.witness_script.as_ref().unwrap() =>
+ {
+ Some(self.clone())
+ }
+ _ => None,
}
}
- pub fn as_public_version(&self) -> Result<ExtendedDescriptor, Error> {
- let keys: RefCell<BTreeMap<String, Box<dyn Key>>> = RefCell::new(BTreeMap::new());
-
- let translatefpk = |string: &String| -> Result<_, Error> {
- let public = self.keys.get(string).unwrap().public(&self.ctx)?;
-
- let result = format!("{}", public);
- keys.borrow_mut().insert(string.clone(), public);
-
- Ok(result)
- };
- let translatefpkh = |string: &String| -> Result<_, Error> {
- let public = self.keys.get(string).unwrap().public(&self.ctx)?;
-
- let result = format!("{}", public);
- keys.borrow_mut().insert(string.clone(), public);
-
- Ok(result)
- };
-
- let internal = self.internal.translate_pk(translatefpk, translatefpkh)?;
-
- Ok(ExtendedDescriptor {
- internal,
- keys: keys.into_inner(),
- ctx: self.ctx.clone(),
- })
- }
-}
-
-impl ExtractPolicy for ExtendedDescriptor {
- fn extract_policy(&self) -> Result<Option<Policy>, Error> {
- self.internal.extract_policy(&self.keys)
- }
+ // fn address_type(&self) -> Option<AddressType> {
+ // match self {
+ // Descriptor::Pkh(_) => Some(AddressType::Pkh),
+ // Descriptor::Wpkh(_) => Some(AddressType::Wpkh),
+ // Descriptor::ShWpkh(_) => Some(AddressType::ShWpkh),
+ // Descriptor::Sh(_) => Some(AddressType::Sh),
+ // Descriptor::Wsh(_) => Some(AddressType::Wsh),
+ // Descriptor::ShWsh(_) => Some(AddressType::ShWsh),
+ // _ => None,
+ // }
+ // }
}
-impl TryFrom<&str> for ExtendedDescriptor {
- type Error = Error;
+#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord, Default)]
+struct DummyKey();
- fn try_from(value: &str) -> Result<Self, Self::Error> {
- let internal = StringDescriptor::from_str(value)?;
- ExtendedDescriptor::new(internal)
+impl fmt::Display for DummyKey {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "DummyKey")
}
}
-impl TryFrom<StringDescriptor> for ExtendedDescriptor {
- type Error = Error;
+impl std::str::FromStr for DummyKey {
+ type Err = ();
- fn try_from(other: StringDescriptor) -> Result<Self, Self::Error> {
- ExtendedDescriptor::new(other)
+ fn from_str(_: &str) -> Result<Self, Self::Err> {
+ Ok(DummyKey::default())
}
}
-impl FromStr for ExtendedDescriptor {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- Self::try_from(s)
- }
-}
+impl miniscript::MiniscriptKey for DummyKey {
+ type Hash = DummyKey;
-impl Into<String> for ExtendedDescriptor {
- fn into(self) -> String {
- format!("{}", self.internal)
+ fn to_pubkeyhash(&self) -> DummyKey {
+ DummyKey::default()
}
}
mod test {
use std::str::FromStr;
+ use bitcoin::consensus::encode::deserialize;
use bitcoin::hashes::hex::FromHex;
- use bitcoin::{Network, PublicKey};
+ use bitcoin::util::psbt;
- 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().into_iter().collect::<Vec<_>>().len(),
- 1
- );
- }
+ use super::*;
+ use crate::psbt::PSBTUtils;
#[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().into_iter().collect::<Vec<_>>().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().into_iter().collect::<Vec<_>>().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();
- }
+ fn test_derive_from_psbt_input_wpkh() {
+ let psbt: psbt::PartiallySignedTransaction = deserialize(&Vec::<u8>::from_hex("70736274ff010052010000000162307be8e431fbaff807cdf9cdc3fde44d740211bc8342c31ffd6ec11fe35bcc0100000000ffffffff01328601000000000016001493ce48570b55c42c2af816aeaba06cfee1224fae000000000001011fa08601000000000016001493ce48570b55c42c2af816aeaba06cfee1224fae010304010000000000").unwrap()).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")))
+ let descriptor = Descriptor::<DescriptorPublicKey>::from_str(
+ "wpkh(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)",
)
+ .unwrap();
+
+ let result = descriptor.derive_from_psbt_input(&psbt.inputs[0], psbt.get_utxo_for(0));
+ println!("{:?}", result);
}
}
use std::cmp::max;
use std::collections::{BTreeMap, HashSet, VecDeque};
+use std::sync::Arc;
use serde::ser::SerializeMap;
use serde::{Serialize, Serializer};
use bitcoin::hashes::*;
-use bitcoin::secp256k1::Secp256k1;
use bitcoin::util::bip32::Fingerprint;
use bitcoin::PublicKey;
-use miniscript::{Descriptor, Miniscript, ScriptContext, Terminal};
+use miniscript::descriptor::DescriptorPublicKey;
+use miniscript::{Descriptor, Miniscript, MiniscriptKey, ScriptContext, Terminal};
#[allow(unused_imports)]
use log::{debug, error, info, trace};
+use crate::descriptor::ExtractPolicy;
+use crate::wallet::signer::{SignerId, SignersContainer};
+
use super::checksum::get_checksum;
use super::error::Error;
-use crate::descriptor::{Key, MiniscriptExtractPolicy};
-use crate::psbt::PSBTSatisfier;
+use super::XKeyUtils;
#[derive(Debug, Clone, Default, Serialize)]
pub struct PKOrF {
}
impl PKOrF {
- fn from_key(k: &Box<dyn Key>) -> Self {
- let secp = Secp256k1::gen_new();
-
- let pubkey = k.as_public_key(&secp, None).unwrap();
- if let Some(fing) = k.fingerprint(&secp) {
- PKOrF {
- fingerprint: Some(fing),
+ fn from_key(k: &DescriptorPublicKey) -> Self {
+ match k {
+ DescriptorPublicKey::PubKey(pubkey) => PKOrF {
+ pubkey: Some(*pubkey),
..Default::default()
- }
- } else {
- PKOrF {
- pubkey: Some(pubkey),
+ },
+ DescriptorPublicKey::XPub(xpub) => PKOrF {
+ fingerprint: Some(xpub.root_fingerprint()),
..Default::default()
- }
+ },
+ }
+ }
+
+ fn from_key_hash(k: hash160::Hash) -> Self {
+ PKOrF {
+ pubkey_hash: Some(k),
+ ..Default::default()
}
}
}
}
fn make_multisig(
- keys: Vec<Option<&Box<dyn Key>>>,
+ keys: &Vec<DescriptorPublicKey>,
+ signers: Arc<SignersContainer<DescriptorPublicKey>>,
threshold: usize,
) -> Result<Option<Policy>, PolicyError> {
if threshold == 0 {
return Ok(None);
}
- let parsed_keys = keys.iter().map(|k| PKOrF::from_key(k.unwrap())).collect();
+ let parsed_keys = keys.iter().map(|k| PKOrF::from_key(k)).collect();
let mut contribution = Satisfaction::Partial {
n: keys.len(),
conditions: Default::default(),
};
for (index, key) in keys.iter().enumerate() {
- let val = if key.is_some() && key.unwrap().has_secret() {
- Satisfaction::Complete {
- condition: Default::default(),
- }
- } else {
- Satisfaction::None
- };
- contribution.add(&val, index)?;
+ if let Some(_) = signers.find(signer_id(key)) {
+ contribution.add(
+ &Satisfaction::Complete {
+ condition: Default::default(),
+ },
+ index,
+ )?;
+ }
}
contribution.finalize()?;
Ok(Some(policy))
}
- pub fn satisfy<Ctx: ScriptContext>(
- &mut self,
- _satisfier: &PSBTSatisfier,
- _desc_node: &Terminal<PublicKey, Ctx>,
- ) {
- //self.satisfaction = self.item.satisfy(satisfier, desc_node);
- //self.contribution += &self.satisfaction;
- }
-
pub fn requires_path(&self) -> bool {
self.get_requirements(&BTreeMap::new()).is_err()
}
}
}
-fn signature_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> {
- 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
- };
+fn signer_id(key: &DescriptorPublicKey) -> SignerId<DescriptorPublicKey> {
+ match key {
+ DescriptorPublicKey::PubKey(pubkey) => pubkey.to_pubkeyhash().into(),
+ DescriptorPublicKey::XPub(xpub) => xpub.root_fingerprint().into(),
+ }
+}
- policy
- })
+fn signature(
+ key: &DescriptorPublicKey,
+ signers: Arc<SignersContainer<DescriptorPublicKey>>,
+) -> Policy {
+ let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key(key)).into();
+
+ policy.contribution = if signers.find(signer_id(key)).is_some() {
+ 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();
+fn signature_key(
+ key_hash: &<DescriptorPublicKey as MiniscriptKey>::Hash,
+ signers: Arc<SignersContainer<DescriptorPublicKey>>,
+) -> Policy {
+ let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key_hash(*key_hash)).into();
- key.map(|k| {
- let pubkey = k.as_public_key(&secp, None).unwrap();
- let mut policy: Policy = if let Some(fing) = k.fingerprint(&secp) {
- SatisfiableItem::SignatureKey(PKOrF {
- fingerprint: Some(fing),
- ..Default::default()
- })
- } else {
- SatisfiableItem::SignatureKey(PKOrF {
- pubkey_hash: Some(hash160::Hash::hash(&pubkey.to_bytes())),
- ..Default::default()
- })
+ if let Some(_) = signers.find(SignerId::PkHash(*key_hash)) {
+ policy.contribution = Satisfaction::Complete {
+ condition: Default::default(),
}
- .into();
- policy.contribution = if k.has_secret() {
- Satisfaction::Complete {
- condition: Default::default(),
- }
- } else {
- Satisfaction::None
- };
+ }
- policy
- })
+ policy
}
-impl<Ctx: ScriptContext> MiniscriptExtractPolicy for Miniscript<String, Ctx> {
+impl<Ctx: ScriptContext> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx> {
fn extract_policy(
&self,
- lookup_map: &BTreeMap<String, Box<dyn Key>>,
+ signers: Arc<SignersContainer<DescriptorPublicKey>>,
) -> Result<Option<Policy>, Error> {
Ok(match &self.node {
// Leaves
Terminal::True | Terminal::False => None,
- Terminal::PkK(pubkey) => signature_from_string(lookup_map.get(pubkey)),
- Terminal::PkH(pubkey_hash) => signature_key_from_string(lookup_map.get(pubkey_hash)),
+ Terminal::PkK(pubkey) => Some(signature(pubkey, Arc::clone(&signers))),
+ Terminal::PkH(pubkey_hash) => Some(signature_key(pubkey_hash, Arc::clone(&signers))),
Terminal::After(value) => {
let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into();
policy.contribution = Satisfaction::Complete {
Terminal::Hash160(hash) => {
Some(SatisfiableItem::HASH160Preimage { hash: *hash }.into())
}
- Terminal::Multi(k, pks) => {
- Policy::make_multisig(pks.iter().map(|s| lookup_map.get(s)).collect(), *k)?
- }
+ Terminal::Multi(k, pks) => Policy::make_multisig(pks, Arc::clone(&signers), *k)?,
// Identities
Terminal::Alt(inner)
| Terminal::Swap(inner)
| Terminal::DupIf(inner)
| Terminal::Verify(inner)
| Terminal::NonZero(inner)
- | Terminal::ZeroNotEqual(inner) => inner.extract_policy(lookup_map)?,
+ | Terminal::ZeroNotEqual(inner) => inner.extract_policy(Arc::clone(&signers))?,
// Complex policies
- Terminal::AndV(a, b) | Terminal::AndB(a, b) => {
- Policy::make_and(a.extract_policy(lookup_map)?, b.extract_policy(lookup_map)?)?
- }
+ Terminal::AndV(a, b) | Terminal::AndB(a, b) => Policy::make_and(
+ a.extract_policy(Arc::clone(&signers))?,
+ b.extract_policy(Arc::clone(&signers))?,
+ )?,
Terminal::AndOr(x, y, z) => Policy::make_or(
- Policy::make_and(x.extract_policy(lookup_map)?, y.extract_policy(lookup_map)?)?,
- z.extract_policy(lookup_map)?,
+ Policy::make_and(
+ x.extract_policy(Arc::clone(&signers))?,
+ y.extract_policy(Arc::clone(&signers))?,
+ )?,
+ z.extract_policy(Arc::clone(&signers))?,
)?,
Terminal::OrB(a, b)
| Terminal::OrD(a, b)
| Terminal::OrC(a, b)
- | Terminal::OrI(a, b) => {
- Policy::make_or(a.extract_policy(lookup_map)?, b.extract_policy(lookup_map)?)?
- }
+ | Terminal::OrI(a, b) => Policy::make_or(
+ a.extract_policy(Arc::clone(&signers))?,
+ b.extract_policy(Arc::clone(&signers))?,
+ )?,
Terminal::Thresh(k, nodes) => {
let mut threshold = *k;
let mapped: Vec<_> = nodes
.iter()
- .map(|n| n.extract_policy(lookup_map))
+ .map(|n| n.extract_policy(Arc::clone(&signers)))
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.filter_map(|x| x)
}
}
-impl MiniscriptExtractPolicy for Descriptor<String> {
+impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
fn extract_policy(
&self,
- lookup_map: &BTreeMap<String, Box<dyn Key>>,
+ signers: Arc<SignersContainer<DescriptorPublicKey>>,
) -> Result<Option<Policy>, Error> {
match self {
Descriptor::Pk(pubkey)
| Descriptor::Pkh(pubkey)
| Descriptor::Wpkh(pubkey)
- | Descriptor::ShWpkh(pubkey) => Ok(signature_from_string(lookup_map.get(pubkey))),
- Descriptor::Bare(inner) | Descriptor::Sh(inner) => {
- Ok(inner.extract_policy(lookup_map)?)
- }
- Descriptor::Wsh(inner) | Descriptor::ShWsh(inner) => {
- Ok(inner.extract_policy(lookup_map)?)
- }
+ | Descriptor::ShWpkh(pubkey) => Ok(Some(signature(pubkey, signers))),
+ Descriptor::Bare(inner) | Descriptor::Sh(inner) => Ok(inner.extract_policy(signers)?),
+ Descriptor::Wsh(inner) | Descriptor::ShWsh(inner) => Ok(inner.extract_policy(signers)?),
}
}
}
-use bitcoin::{Address, OutPoint, Script, Txid};
+use bitcoin::{Address, OutPoint};
#[derive(Debug)]
pub enum Error {
SpendingPolicyRequired,
InvalidPolicyPathError(crate::descriptor::policy::PolicyError),
- // Signing errors (expected, received)
- InputTxidMismatch((Txid, OutPoint)),
- InputRedeemScriptMismatch((Script, Script)), // scriptPubKey, redeemScript
- InputWitnessScriptMismatch((Script, Script)), // scriptPubKey, redeemScript
- InputUnknownSegwitScript(Script),
- InputMissingWitnessScript(usize),
- MissingUTXO,
+ Signer(crate::wallet::signer::SignerError),
// Blockchain interface errors
Uncapable(crate::blockchain::Capability),
Descriptor(crate::descriptor::error::Error),
Encode(bitcoin::consensus::encode::Error),
+ Miniscript(miniscript::Error),
BIP32(bitcoin::util::bip32::Error),
Secp256k1(bitcoin::secp256k1::Error),
JSON(serde_json::Error),
crate::descriptor::policy::PolicyError,
InvalidPolicyPathError
);
+impl_error!(crate::wallet::signer::SignerError, Signer);
impl_error!(bitcoin::consensus::encode::Error, Encode);
+impl_error!(miniscript::Error, Miniscript);
impl_error!(bitcoin::util::bip32::Error, BIP32);
impl_error!(bitcoin::secp256k1::Error, Secp256k1);
impl_error!(serde_json::Error, JSON);
pub mod database;
pub mod descriptor;
pub mod psbt;
-pub mod signer;
pub mod types;
pub mod wallet;
-use std::collections::BTreeMap;
+use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
+use bitcoin::TxOut;
-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,
- assume_height_reached: bool,
- create_height: Option<u32>,
- current_height: Option<u32>,
-}
-
-impl<'a> PSBTSatisfier<'a> {
- pub fn new(
- input: &'a psbt::Input,
- assume_height_reached: bool,
- create_height: Option<u32>,
- current_height: Option<u32>,
- ) -> Self {
- PSBTSatisfier {
- input,
- assume_height_reached,
- create_height,
- current_height,
- }
- }
+pub trait PSBTUtils {
+ fn get_utxo_for(&self, input_index: usize) -> Option<TxOut>;
}
-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);
+impl PSBTUtils for PSBT {
+ fn get_utxo_for(&self, input_index: usize) -> Option<TxOut> {
+ let tx = &self.global.unsigned_tx;
- for (pk, _) in &self.input.partial_sigs {
- if &pk.to_pubkeyhash() == hash {
- return Some(*pk);
- }
+ if input_index >= tx.input.len() {
+ return None;
}
- 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,
- };
+ 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
}
- }
-
- None
- }
-
- fn check_older(&self, height: u32) -> bool {
- // TODO: also check if `nSequence` right
- debug!("check_older: {}", height);
-
- if let Some(current_height) = self.current_height {
- // TODO: test >= / >
- current_height as u64 >= self.create_height.unwrap_or(0) as u64 + height as u64
} else {
- self.assume_height_reached
- }
- }
-
- fn check_after(&self, height: u32) -> bool {
- // TODO: also check if `nLockTime` is right
- debug!("check_after: {}", height);
-
- if let Some(current_height) = self.current_height {
- current_height > height
- } else {
- self.assume_height_reached
- }
- }
-}
-
-#[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);
+ None
}
-
- 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
- }
- }
-}
+++ /dev/null
-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)
- }
-}
label: &str,
include_blockheight: bool,
) -> Result<Self, &'static str> {
- let descriptor = wallet.descriptor.as_ref().to_string();
+ let descriptor = wallet
+ .descriptor
+ .to_string_with_secret(&wallet.signers.as_key_map());
Self::is_compatible_with_core(&descriptor)?;
let blockheight = match wallet.database.borrow().iter_txs(false) {
!= wallet
.change_descriptor
.as_ref()
- .map(|d| d.as_ref().to_string())
+ .map(|d| d.to_string_with_secret(&wallet.change_signers.as_key_map()))
{
return Err("Incompatible change descriptor");
}
#[test]
fn test_export_multi() {
let descriptor = "wsh(multi(2,\
- [73756c7f/48h/0h/0h/2h]tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*,\
- [f9f62194/48h/0h/0h/2h]tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/0/*,\
- [c98b1535/48h/0h/0h/2h]tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/0/*\
+ [73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*,\
+ [f9f62194/48'/0'/0'/2']tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/0/*,\
+ [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/0/*\
))";
let change_descriptor = "wsh(multi(2,\
- [73756c7f/48h/0h/0h/2h]tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/1/*,\
- [f9f62194/48h/0h/0h/2h]tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/1/*,\
- [c98b1535/48h/0h/0h/2h]tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
+ [73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/1/*,\
+ [f9f62194/48'/0'/0'/2']tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/1/*,\
+ [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
))";
let wallet: OfflineWallet<_> = Wallet::new_offline(
use std::collections::HashMap;
use std::collections::{BTreeMap, HashSet};
use std::ops::{Deref, DerefMut};
-use std::str::FromStr;
+use std::sync::Arc;
-use bitcoin::blockdata::opcodes;
-use bitcoin::blockdata::script::Builder;
use bitcoin::consensus::encode::serialize;
+use bitcoin::util::bip32::ChildNumber;
use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
-use bitcoin::{
- Address, Network, OutPoint, PublicKey, Script, SigHashType, Transaction, TxOut, Txid,
-};
+use bitcoin::{Address, Network, OutPoint, Script, SigHashType, Transaction, TxOut, Txid};
-use miniscript::BitcoinSig;
+use miniscript::descriptor::DescriptorPublicKey;
#[allow(unused_imports)]
use log::{debug, error, info, trace};
pub mod coin_selection;
pub mod export;
mod rbf;
+pub mod signer;
pub mod time;
pub mod tx_builder;
pub mod utils;
+use signer::{Signer, SignersContainer};
use tx_builder::TxBuilder;
-use utils::{FeeRate, IsDust};
+use utils::{After, FeeRate, IsDust, Older};
use crate::blockchain::{Blockchain, OfflineBlockchain, OnlineBlockchain, Progress};
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
-use crate::descriptor::{get_checksum, DescriptorMeta, ExtendedDescriptor, ExtractPolicy, Policy};
+use crate::descriptor::{
+ get_checksum, DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, Policy,
+};
use crate::error::Error;
-use crate::psbt::{utils::PSBTUtils, PSBTSatisfier, PSBTSigner};
-use crate::signer::Signer;
+use crate::psbt::PSBTUtils;
+// use crate::psbt::{utils::PSBTUtils, PSBTSatisfier, PSBTSigner};
use crate::types::*;
const CACHE_ADDR_BATCH_SIZE: u32 = 100;
pub struct Wallet<B: Blockchain, D: BatchDatabase> {
descriptor: ExtendedDescriptor,
change_descriptor: Option<ExtendedDescriptor>,
+
+ signers: Arc<SignersContainer<DescriptorPublicKey>>,
+ change_signers: Arc<SignersContainer<DescriptorPublicKey>>,
+
network: Network,
current_height: Option<u32>,
ScriptType::External,
get_checksum(descriptor)?.as_bytes(),
)?;
- let descriptor = ExtendedDescriptor::from_str(descriptor)?;
- let change_descriptor = match change_descriptor {
+ let (descriptor, keymap) = ExtendedDescriptor::parse_secret(descriptor)?;
+ let signers = Arc::new(SignersContainer::from(keymap));
+ let (change_descriptor, change_signers) = match change_descriptor {
Some(desc) => {
database.check_descriptor_checksum(
ScriptType::Internal,
get_checksum(desc)?.as_bytes(),
)?;
- let parsed = ExtendedDescriptor::from_str(desc)?;
- if !parsed.same_structure(descriptor.as_ref()) {
- return Err(Error::DifferentDescriptorStructure);
- }
+ let (change_descriptor, change_keymap) = ExtendedDescriptor::parse_secret(desc)?;
+ let change_signers = Arc::new(SignersContainer::from(change_keymap));
+ // if !parsed.same_structure(descriptor.as_ref()) {
+ // return Err(Error::DifferentDescriptorStructure);
+ // }
- Some(parsed)
+ (Some(change_descriptor), change_signers)
}
- None => None,
+ None => (None, Arc::new(SignersContainer::new())),
};
Ok(Wallet {
descriptor,
change_descriptor,
+ signers,
+ change_signers,
+
network,
current_height: None,
let index = self.fetch_and_increment_index(ScriptType::External)?;
self.descriptor
- .derive(index)?
+ .derive(&[ChildNumber::from_normal_idx(index).unwrap()])
.address(self.network)
.ok_or(Error::ScriptDoesntHaveAddressForm)
}
}
// TODO: fetch both internal and external policies
- let policy = self.descriptor.extract_policy()?.unwrap();
+ let policy = self
+ .descriptor
+ .extract_policy(Arc::clone(&self.signers))?
+ .unwrap();
if policy.requires_path() && builder.policy_path.is_none() {
return Err(Error::SpendingPolicyRequired);
}
// TODO: use the right weight instead of the maximum, and only fall-back to it if the
// script is unknown in the database
let input_witness_weight = std::cmp::max(
- self.get_descriptor_for(ScriptType::Internal)
+ self.get_descriptor_for_script_type(ScriptType::Internal)
.0
.max_satisfaction_weight(),
- self.get_descriptor_for(ScriptType::External)
+ self.get_descriptor_for_script_type(ScriptType::External)
.0
.max_satisfaction_weight(),
);
// `get_deget_descriptor_for` to find what's the ScriptType for `Internal`
// addresses really is, because if there's no change_descriptor it's actually equal
// to "External"
- let (_, change_type) = self.get_descriptor_for(ScriptType::Internal);
+ let (_, change_type) = self.get_descriptor_for_script_type(ScriptType::Internal);
match self
.database
.borrow()
// TODO: use the right weight instead of the maximum, and only fall-back to it if the
// script is unknown in the database
let input_witness_weight = std::cmp::max(
- self.get_descriptor_for(ScriptType::Internal)
+ self.get_descriptor_for_script_type(ScriptType::Internal)
.0
.max_satisfaction_weight(),
- self.get_descriptor_for(ScriptType::External)
+ self.get_descriptor_for_script_type(ScriptType::External)
.0
.max_satisfaction_weight(),
);
// this helps us doing our job later
self.add_input_hd_keypaths(&mut psbt)?;
- let tx = &psbt.global.unsigned_tx;
-
- let mut signer = PSBTSigner::from_descriptor(&psbt.global.unsigned_tx, &self.descriptor)?;
- if let Some(desc) = &self.change_descriptor {
- let change_signer = PSBTSigner::from_descriptor(&psbt.global.unsigned_tx, desc)?;
- signer.extend(change_signer)?;
- }
-
- // sign everything we can. TODO: ideally we should only sign with the keys in the policy
- // path selected, if present
- for (i, input) in psbt.inputs.iter_mut().enumerate() {
- let sighash = input.sighash_type.unwrap_or(SigHashType::All);
- let prevout = tx.input[i].previous_output;
-
- let mut partial_sigs = BTreeMap::new();
- {
- let mut push_sig = |pubkey: &PublicKey, opt_sig: Option<BitcoinSig>| {
- if let Some((signature, sighash)) = opt_sig {
- let mut concat_sig = Vec::new();
- concat_sig.extend_from_slice(&signature.serialize_der());
- concat_sig.extend_from_slice(&[sighash as u8]);
- //input.partial_sigs.insert(*pubkey, concat_sig);
- partial_sigs.insert(*pubkey, concat_sig);
- }
- };
-
- if let Some(non_wit_utxo) = &input.non_witness_utxo {
- if non_wit_utxo.txid() != prevout.txid {
- return Err(Error::InputTxidMismatch((non_wit_utxo.txid(), prevout)));
- }
-
- let prev_script = &non_wit_utxo.output
- [psbt.global.unsigned_tx.input[i].previous_output.vout as usize]
- .script_pubkey;
-
- // return (signature, sighash) from here
- let sign_script = if let Some(redeem_script) = &input.redeem_script {
- if &redeem_script.to_p2sh() != prev_script {
- return Err(Error::InputRedeemScriptMismatch((
- prev_script.clone(),
- redeem_script.clone(),
- )));
- }
-
- redeem_script
- } else {
- prev_script
- };
-
- for (pubkey, (fing, path)) in &input.hd_keypaths {
- push_sig(
- pubkey,
- signer.sig_legacy_from_fingerprint(
- i,
- sighash,
- fing,
- path,
- sign_script,
- )?,
- );
- }
- // TODO: this sucks, we sign with every key
- for pubkey in signer.all_public_keys() {
- push_sig(
- pubkey,
- signer.sig_legacy_from_pubkey(i, sighash, pubkey, sign_script)?,
- );
- }
- } else if let Some(witness_utxo) = &input.witness_utxo {
- let value = witness_utxo.value;
-
- let script = match &input.redeem_script {
- Some(script) if script.to_p2sh() != witness_utxo.script_pubkey => {
- return Err(Error::InputRedeemScriptMismatch((
- witness_utxo.script_pubkey.clone(),
- script.clone(),
- )))
- }
- Some(script) => script,
- None => &witness_utxo.script_pubkey,
- };
-
- let sign_script = if script.is_v0_p2wpkh() {
- self.to_p2pkh(&script.as_bytes()[2..])
- } else if script.is_v0_p2wsh() {
- match &input.witness_script {
- None => Err(Error::InputMissingWitnessScript(i)),
- Some(witness_script) if script != &witness_script.to_v0_p2wsh() => {
- Err(Error::InputRedeemScriptMismatch((
- script.clone(),
- witness_script.clone(),
- )))
- }
- Some(witness_script) => Ok(witness_script),
- }?
- .clone()
- } else {
- return Err(Error::InputUnknownSegwitScript(script.clone()));
- };
-
- for (pubkey, (fing, path)) in &input.hd_keypaths {
- push_sig(
- pubkey,
- signer.sig_segwit_from_fingerprint(
- i,
- sighash,
- fing,
- path,
- &sign_script,
- value,
- )?,
- );
- }
- // TODO: this sucks, we sign with every key
- for pubkey in signer.all_public_keys() {
- push_sig(
- pubkey,
- signer.sig_segwit_from_pubkey(
- i,
- sighash,
- pubkey,
- &sign_script,
- value,
- )?,
- );
- }
- } else {
- return Err(Error::MissingUTXO);
- }
+ for index in 0..psbt.inputs.len() {
+ self.signers.sign(&mut psbt, index)?;
+ if self.change_descriptor.is_some() {
+ self.change_signers.sign(&mut psbt, index)?;
}
-
- // push all the signatures into the psbt
- input.partial_sigs.append(&mut partial_sigs);
}
// attempt to finalize
pub fn policies(&self, script_type: ScriptType) -> Result<Option<Policy>, Error> {
match (script_type, self.change_descriptor.as_ref()) {
- (ScriptType::External, _) => Ok(self.descriptor.extract_policy()?),
+ (ScriptType::External, _) => {
+ Ok(self.descriptor.extract_policy(Arc::clone(&self.signers))?)
+ }
(ScriptType::Internal, None) => Ok(None),
- (ScriptType::Internal, Some(desc)) => Ok(desc.extract_policy()?),
+ (ScriptType::Internal, Some(desc)) => {
+ Ok(desc.extract_policy(Arc::clone(&self.change_signers))?)
+ }
}
}
script_type: ScriptType,
) -> Result<Option<ExtendedDescriptor>, Error> {
match (script_type, self.change_descriptor.as_ref()) {
- (ScriptType::External, _) => Ok(Some(self.descriptor.as_public_version()?)),
+ (ScriptType::External, _) => Ok(Some(self.descriptor.clone())),
(ScriptType::Internal, None) => Ok(None),
- (ScriptType::Internal, Some(desc)) => Ok(Some(desc.as_public_version()?)),
+ (ScriptType::Internal, Some(desc)) => Ok(Some(desc.clone())),
}
}
) -> Result<bool, Error> {
let mut tx = psbt.global.unsigned_tx.clone();
- for (n, input) in tx.input.iter_mut().enumerate() {
- // safe to run only on the descriptor because we assume the change descriptor also has
- // the same structure
- let desc = self.descriptor.derive_from_psbt_input(psbt, n);
- debug!("{:?}", psbt.inputs[n].hd_keypaths);
- debug!("reconstructed descriptor is {:?}", desc);
-
- let desc = match desc {
- Err(_) => return Ok(false),
- Ok(desc) => desc,
- };
-
+ for (n, (input, psbt_input)) in tx.input.iter_mut().zip(psbt.inputs.iter()).enumerate() {
// if the height is None in the database it means it's still unconfirmed, so consider
// that as a very high value
let create_height = self
n, input.previous_output, create_height, current_height
);
- let satisfier =
- PSBTSatisfier::new(&psbt.inputs[n], false, create_height, current_height);
+ // - Try to derive the descriptor by looking at the txout. If it's in our database, we
+ // know exactly which `script_type` to use, and which derivation index it is
+ // - If that fails, try to derive it by looking at the psbt input: the complete logic
+ // is in `src/descriptor/mod.rs`, but it will basically look at `hd_keypaths`,
+ // `redeem_script` and `witness_script` to determine the right derivation
+ // - If that also fails, it will try it on the internal descriptor, if present
+ let desc = if let Some(desc) = psbt
+ .get_utxo_for(n)
+ .map(|txout| self.get_descriptor_for_txout(&txout))
+ .transpose()?
+ .flatten()
+ {
+ desc
+ } else if let Some(desc) = self
+ .descriptor
+ .derive_from_psbt_input(psbt_input, psbt.get_utxo_for(n))
+ {
+ desc
+ } else if let Some(desc) = self
+ .change_descriptor
+ .as_ref()
+ .and_then(|desc| desc.derive_from_psbt_input(psbt_input, psbt.get_utxo_for(n)))
+ {
+ desc
+ } else {
+ debug!("Couldn't find the right derived descriptor for input {}", n);
+ return Ok(false);
+ };
- match desc.satisfy(input, satisfier) {
+ match desc.satisfy(
+ input,
+ (
+ psbt_input.clone(),
+ After::new(current_height, false),
+ Older::new(current_height, create_height, false),
+ ),
+ ) {
Ok(_) => continue,
Err(e) => {
debug!("satisfy error {:?} for input {}", e, n);
// Internals
- fn get_descriptor_for(&self, script_type: ScriptType) -> (&ExtendedDescriptor, ScriptType) {
+ fn get_descriptor_for_script_type(
+ &self,
+ script_type: ScriptType,
+ ) -> (&ExtendedDescriptor, ScriptType) {
let desc = match script_type {
ScriptType::Internal if self.change_descriptor.is_some() => (
self.change_descriptor.as_ref().unwrap(),
desc
}
- fn to_p2pkh(&self, pubkey_hash: &[u8]) -> Script {
- Builder::new()
- .push_opcode(opcodes::all::OP_DUP)
- .push_opcode(opcodes::all::OP_HASH160)
- .push_slice(pubkey_hash)
- .push_opcode(opcodes::all::OP_EQUALVERIFY)
- .push_opcode(opcodes::all::OP_CHECKSIG)
- .into_script()
+ fn get_descriptor_for_txout(&self, txout: &TxOut) -> Result<Option<ExtendedDescriptor>, Error> {
+ Ok(self
+ .database
+ .borrow()
+ .get_path_from_script_pubkey(&txout.script_pubkey)?
+ .map(|(script_type, child)| (self.get_descriptor_for_script_type(script_type).0, child))
+ .map(|(desc, child)| desc.derive(&[ChildNumber::from_normal_idx(child).unwrap()])))
}
fn get_change_address(&self) -> Result<Script, Error> {
- let (desc, script_type) = self.get_descriptor_for(ScriptType::Internal);
+ let (desc, script_type) = self.get_descriptor_for_script_type(ScriptType::Internal);
let index = self.fetch_and_increment_index(script_type)?;
- Ok(desc.derive(index)?.script_pubkey())
+ Ok(desc
+ .derive(&[ChildNumber::from_normal_idx(index).unwrap()])
+ .script_pubkey())
}
fn fetch_and_increment_index(&self, script_type: ScriptType) -> Result<u32, Error> {
- let (descriptor, script_type) = self.get_descriptor_for(script_type);
+ let (descriptor, script_type) = self.get_descriptor_for_script_type(script_type);
let index = match descriptor.is_fixed() {
true => 0,
false => self
from: u32,
mut count: u32,
) -> Result<(), Error> {
- let (descriptor, script_type) = self.get_descriptor_for(script_type);
+ let (descriptor, script_type) = self.get_descriptor_for_script_type(script_type);
if descriptor.is_fixed() {
if from > 0 {
return Ok(());
let start_time = time::Instant::new();
for i in from..(from + count) {
address_batch.set_script_pubkey(
- &descriptor.derive(i)?.script_pubkey(),
+ &descriptor
+ .derive(&[ChildNumber::from_normal_idx(i).unwrap()])
+ .script_pubkey(),
script_type,
i,
)?;
None => continue,
};
- let (desc, _) = self.get_descriptor_for(script_type);
+ let (desc, _) = self.get_descriptor_for_script_type(script_type);
psbt_input.hd_keypaths = desc.get_hd_keypaths(child)?;
- let derived_descriptor = desc.derive(child)?;
+ let derived_descriptor = desc.derive(&[ChildNumber::from_normal_idx(child).unwrap()]);
psbt_input.redeem_script = derived_descriptor.psbt_redeem_script();
psbt_input.witness_script = derived_descriptor.psbt_witness_script();
.borrow()
.get_path_from_script_pubkey(&tx_output.script_pubkey)?
{
- let (desc, _) = self.get_descriptor_for(script_type);
+ let (desc, _) = self.get_descriptor_for_script_type(script_type);
psbt_output.hd_keypaths = desc.get_hd_keypaths(child)?;
}
}
debug!("Found descriptor {:?}/{}", script_type, child);
// merge hd_keypaths
- let (desc, _) = self.get_descriptor_for(script_type);
+ let (desc, _) = self.get_descriptor_for_script_type(script_type);
let mut hd_keypaths = desc.get_hd_keypaths(child)?;
psbt_input.hd_keypaths.append(&mut hd_keypaths);
}
#[cfg(test)]
mod test {
- use bitcoin::Network;
+ use std::str::FromStr;
- use miniscript::Descriptor;
+ use bitcoin::Network;
use crate::database::memory::MemoryDatabase;
use crate::database::Database;
- use crate::descriptor::ExtendedDescriptor;
use crate::types::ScriptType;
use super::*;
descriptor: &str,
) -> (
OfflineWallet<MemoryDatabase>,
- (ExtendedDescriptor, Option<ExtendedDescriptor>),
+ (String, Option<String>),
bitcoin::Txid,
) {
let descriptors = testutils!(@descriptors (descriptor));
let wallet: OfflineWallet<_> = Wallet::new_offline(
- &descriptors.0.to_string(),
+ &descriptors.0,
None,
Network::Regtest,
MemoryDatabase::new(),
--- /dev/null
+use std::any::Any;
+use std::collections::HashMap;
+use std::fmt;
+
+use bitcoin::blockdata::opcodes;
+use bitcoin::blockdata::script::Builder as ScriptBuilder;
+use bitcoin::hashes::{hash160, Hash};
+use bitcoin::secp256k1::{Message, Secp256k1};
+use bitcoin::util::bip32::{ExtendedPrivKey, Fingerprint};
+use bitcoin::util::{bip143, psbt};
+use bitcoin::{PrivateKey, SigHash, SigHashType};
+
+use miniscript::descriptor::{DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, KeyMap};
+use miniscript::{Legacy, MiniscriptKey, Segwitv0};
+
+use crate::descriptor::XKeyUtils;
+
+/// Identifier of a signer in the `SignersContainers`. Used as a key to find the right signer among
+/// many of them
+#[derive(Debug, PartialEq, Eq, Hash)]
+pub enum SignerId<Pk: MiniscriptKey> {
+ PkHash(<Pk as MiniscriptKey>::Hash),
+ Fingerprint(Fingerprint),
+}
+
+impl From<hash160::Hash> for SignerId<DescriptorPublicKey> {
+ fn from(hash: hash160::Hash) -> SignerId<DescriptorPublicKey> {
+ SignerId::PkHash(hash)
+ }
+}
+
+impl From<Fingerprint> for SignerId<DescriptorPublicKey> {
+ fn from(fing: Fingerprint) -> SignerId<DescriptorPublicKey> {
+ SignerId::Fingerprint(fing)
+ }
+}
+
+/// Signing error
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum SignerError {
+ /// The private key is missing for the required public key
+ MissingKey,
+ /// The user canceled the operation
+ UserCanceled,
+ /// The sighash is missing in the PSBT input
+ MissingSighash,
+ /// Input index is out of range
+ InputIndexOutOfRange,
+ /// The `non_witness_utxo` field of the transaction is required to sign this input
+ MissingNonWitnessUtxo,
+ /// The `non_witness_utxo` specified is invalid
+ InvalidNonWitnessUtxo,
+ /// The `witness_utxo` field of the transaction is required to sign this input
+ MissingWitnessUtxo,
+ /// The `witness_script` field of the transaction is requied to sign this input
+ MissingWitnessScript,
+ /// The fingerprint and derivation path are missing from the psbt input
+ MissingHDKeypath,
+}
+
+/// Trait for signers
+pub trait Signer: fmt::Debug {
+ fn sign(
+ &self,
+ psbt: &mut psbt::PartiallySignedTransaction,
+ input_index: usize,
+ ) -> Result<(), SignerError>;
+
+ fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
+ None
+ }
+}
+
+impl Signer for DescriptorXKey<ExtendedPrivKey> {
+ fn sign(
+ &self,
+ psbt: &mut psbt::PartiallySignedTransaction,
+ input_index: usize,
+ ) -> Result<(), SignerError> {
+ if input_index >= psbt.inputs.len() {
+ return Err(SignerError::InputIndexOutOfRange);
+ }
+
+ let deriv_path = match psbt.inputs[input_index]
+ .hd_keypaths
+ .iter()
+ .filter_map(|(_, &(fingerprint, ref path))| self.matches(fingerprint.clone(), &path))
+ .next()
+ {
+ Some(deriv_path) => deriv_path,
+ None => return Ok(()), // TODO: should report an error maybe?
+ };
+
+ let ctx = Secp256k1::signing_only();
+
+ let derived_key = self.xkey.derive_priv(&ctx, &deriv_path).unwrap();
+ derived_key.private_key.sign(psbt, input_index)
+ }
+
+ fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
+ Some(DescriptorSecretKey::XPrv(self.clone()))
+ }
+}
+
+impl Signer for PrivateKey {
+ fn sign(
+ &self,
+ psbt: &mut psbt::PartiallySignedTransaction,
+ input_index: usize,
+ ) -> Result<(), SignerError> {
+ if input_index >= psbt.inputs.len() {
+ return Err(SignerError::InputIndexOutOfRange);
+ }
+
+ let ctx = Secp256k1::signing_only();
+
+ let pubkey = self.public_key(&ctx);
+ if psbt.inputs[input_index].partial_sigs.contains_key(&pubkey) {
+ return Ok(());
+ }
+
+ // FIXME: use the presence of `witness_utxo` as an indication that we should make a bip143
+ // sig. Does this make sense? Should we add an extra argument to explicitly swith between
+ // these? The original idea was to declare sign() as sign<Ctx: ScriptContex>() and use Ctx,
+ // but that violates the rules for trait-objects, so we can't do it.
+ let (hash, sighash) = match psbt.inputs[input_index].witness_utxo {
+ Some(_) => Segwitv0::sighash(psbt, input_index)?,
+ None => Legacy::sighash(psbt, input_index)?,
+ };
+
+ let signature = ctx.sign(
+ &Message::from_slice(&hash.into_inner()[..]).unwrap(),
+ &self.key,
+ );
+
+ let mut final_signature = Vec::with_capacity(75);
+ final_signature.extend_from_slice(&signature.serialize_der());
+ final_signature.push(sighash.as_u32() as u8);
+
+ psbt.inputs[input_index]
+ .partial_sigs
+ .insert(pubkey, final_signature);
+
+ Ok(())
+ }
+
+ fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
+ Some(DescriptorSecretKey::PrivKey(self.clone()))
+ }
+}
+
+/// Container for multiple signers
+#[derive(Debug, Default)]
+pub struct SignersContainer<Pk: MiniscriptKey>(HashMap<SignerId<Pk>, Box<dyn Signer>>);
+
+impl SignersContainer<DescriptorPublicKey> {
+ pub fn as_key_map(&self) -> KeyMap {
+ self.0
+ .values()
+ .filter_map(|signer| signer.descriptor_secret_key())
+ .filter_map(|secret| secret.as_public().ok().map(|public| (public, secret)))
+ .collect()
+ }
+}
+
+impl<Pk: MiniscriptKey + Any> Signer for SignersContainer<Pk> {
+ fn sign(
+ &self,
+ psbt: &mut psbt::PartiallySignedTransaction,
+ input_index: usize,
+ ) -> Result<(), SignerError> {
+ for signer in self.0.values() {
+ signer.sign(psbt, input_index)?;
+ }
+
+ Ok(())
+ }
+}
+
+impl From<KeyMap> for SignersContainer<DescriptorPublicKey> {
+ fn from(keymap: KeyMap) -> SignersContainer<DescriptorPublicKey> {
+ let mut container = SignersContainer::new();
+
+ for (_, secret) in keymap {
+ match secret {
+ DescriptorSecretKey::PrivKey(private_key) => container.add_external(
+ SignerId::from(
+ private_key
+ .public_key(&Secp256k1::signing_only())
+ .to_pubkeyhash(),
+ ),
+ Box::new(private_key),
+ ),
+ DescriptorSecretKey::XPrv(xprv) => {
+ container.add_external(SignerId::from(xprv.root_fingerprint()), Box::new(xprv))
+ }
+ };
+ }
+
+ container
+ }
+}
+
+impl<Pk: MiniscriptKey> SignersContainer<Pk> {
+ /// Default constructor
+ pub fn new() -> Self {
+ SignersContainer(HashMap::new())
+ }
+
+ /// Adds an external signer to the container for the specified id. Optionally returns the
+ /// signer that was previosuly in the container, if any
+ pub fn add_external(
+ &mut self,
+ id: SignerId<Pk>,
+ signer: Box<dyn Signer>,
+ ) -> Option<Box<dyn Signer>> {
+ self.0.insert(id, signer)
+ }
+
+ /// Removes a signer from the container and returns it
+ pub fn remove(&mut self, id: SignerId<Pk>) -> Option<Box<dyn Signer>> {
+ self.0.remove(&id)
+ }
+
+ /// Returns the list of identifiers of all the signers in the container
+ pub fn ids(&self) -> Vec<&SignerId<Pk>> {
+ self.0.keys().collect()
+ }
+
+ /// Finds the signer with a given id in the container
+ pub fn find(&self, id: SignerId<Pk>) -> Option<&Box<dyn Signer>> {
+ self.0.get(&id)
+ }
+}
+
+pub trait ComputeSighash {
+ fn sighash(
+ psbt: &psbt::PartiallySignedTransaction,
+ input_index: usize,
+ ) -> Result<(SigHash, SigHashType), SignerError>;
+}
+
+impl ComputeSighash for Legacy {
+ fn sighash(
+ psbt: &psbt::PartiallySignedTransaction,
+ input_index: usize,
+ ) -> Result<(SigHash, SigHashType), SignerError> {
+ if input_index >= psbt.inputs.len() {
+ return Err(SignerError::InputIndexOutOfRange);
+ }
+
+ let psbt_input = &psbt.inputs[input_index];
+ let tx_input = &psbt.global.unsigned_tx.input[input_index];
+
+ let sighash = psbt_input.sighash_type.ok_or(SignerError::MissingSighash)?;
+ let script = match &psbt_input.redeem_script {
+ &Some(ref redeem_script) => redeem_script.clone(),
+ &None => {
+ let non_witness_utxo = psbt_input
+ .non_witness_utxo
+ .as_ref()
+ .ok_or(SignerError::MissingNonWitnessUtxo)?;
+ let prev_out = non_witness_utxo
+ .output
+ .get(tx_input.previous_output.vout as usize)
+ .ok_or(SignerError::InvalidNonWitnessUtxo)?;
+
+ prev_out.script_pubkey.clone()
+ }
+ };
+
+ Ok((
+ psbt.global
+ .unsigned_tx
+ .signature_hash(input_index, &script, sighash.as_u32()),
+ sighash,
+ ))
+ }
+}
+
+impl ComputeSighash for Segwitv0 {
+ fn sighash(
+ psbt: &psbt::PartiallySignedTransaction,
+ input_index: usize,
+ ) -> Result<(SigHash, SigHashType), SignerError> {
+ if input_index >= psbt.inputs.len() {
+ return Err(SignerError::InputIndexOutOfRange);
+ }
+
+ let psbt_input = &psbt.inputs[input_index];
+
+ let sighash = psbt_input.sighash_type.ok_or(SignerError::MissingSighash)?;
+
+ let witness_utxo = psbt_input
+ .witness_utxo
+ .as_ref()
+ .ok_or(SignerError::MissingNonWitnessUtxo)?;
+ let value = witness_utxo.value;
+
+ let script = match &psbt_input.witness_script {
+ &Some(ref witness_script) => witness_script.clone(),
+ &None => {
+ if witness_utxo.script_pubkey.is_v0_p2wpkh() {
+ ScriptBuilder::new()
+ .push_opcode(opcodes::all::OP_DUP)
+ .push_opcode(opcodes::all::OP_HASH160)
+ .push_slice(&witness_utxo.script_pubkey[2..])
+ .push_opcode(opcodes::all::OP_EQUALVERIFY)
+ .push_opcode(opcodes::all::OP_CHECKSIG)
+ .into_script()
+ } else {
+ return Err(SignerError::MissingWitnessScript);
+ }
+ }
+ };
+
+ Ok((
+ bip143::SigHashCache::new(&psbt.global.unsigned_tx).signature_hash(
+ input_index,
+ &script,
+ value,
+ sighash,
+ ),
+ sighash,
+ ))
+ }
+}
+use miniscript::{MiniscriptKey, Satisfier};
+
// De-facto standard "dust limit" (even though it should change based on the output type)
const DUST_LIMIT_SATOSHI: u64 = 546;
}
}
+pub struct After {
+ pub current_height: Option<u32>,
+ pub assume_height_reached: bool,
+}
+
+impl After {
+ pub(crate) fn new(current_height: Option<u32>, assume_height_reached: bool) -> After {
+ After {
+ current_height,
+ assume_height_reached,
+ }
+ }
+}
+
+impl<Pk: MiniscriptKey> Satisfier<Pk> for After {
+ fn check_after(&self, n: u32) -> bool {
+ if let Some(current_height) = self.current_height {
+ current_height >= n
+ } else {
+ self.assume_height_reached
+ }
+ }
+}
+
+pub struct Older {
+ pub current_height: Option<u32>,
+ pub create_height: Option<u32>,
+ pub assume_height_reached: bool,
+}
+
+impl Older {
+ pub(crate) fn new(
+ current_height: Option<u32>,
+ create_height: Option<u32>,
+ assume_height_reached: bool,
+ ) -> Older {
+ Older {
+ current_height,
+ create_height,
+ assume_height_reached,
+ }
+ }
+}
+
+impl<Pk: MiniscriptKey> Satisfier<Pk> for Older {
+ fn check_older(&self, n: u32) -> bool {
+ if let Some(current_height) = self.current_height {
+ // TODO: test >= / >
+ current_height as u64 >= self.create_height.unwrap_or(0) as u64 + n as u64
+ } else {
+ self.assume_height_reached
+ }
+ }
+}
+
pub struct ChunksIterator<I: Iterator> {
iter: I,
size: usize,
#parsed_sig_ident()
}
- fn get_wallet_from_descriptors(descriptors: &(ExtendedDescriptor, Option<ExtendedDescriptor>)) -> Wallet<#return_type, MemoryDatabase> {
- Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref().map(|d| d.to_string()).as_deref(), Network::Regtest, MemoryDatabase::new(), get_blockchain()).unwrap()
+ fn get_wallet_from_descriptors(descriptors: &(String, Option<String>)) -> Wallet<#return_type, MemoryDatabase> {
+ Wallet::new(&descriptors.0.to_string(), descriptors.1.as_deref(), Network::Regtest, MemoryDatabase::new(), get_blockchain()).unwrap()
}
- fn init_single_sig() -> (Wallet<#return_type, MemoryDatabase>, (ExtendedDescriptor, Option<ExtendedDescriptor>), TestClient) {
+ fn init_single_sig() -> (Wallet<#return_type, MemoryDatabase>, (String, Option<String>), TestClient) {
let descriptors = testutils! {
@descriptors ( "wpkh(Alice)" ) ( "wpkh(Alice)" ) ( @keys ( "Alice" => (@generate_xprv "/44'/0'/0'/0/*", "/44'/0'/0'/1/*") ) )
};
let tx = testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
};
+ println!("{:?}", tx);
let txid = test_client.receive(tx);
wallet.sync(noop_progress(), None).unwrap();
#[serial]
fn test_sync_after_send() {
let (wallet, descriptors, mut test_client) = init_single_sig();
+ println!("{}", descriptors.0);
let node_addr = test_client.get_node_address(None);
test_client.receive(testutils! {
let (psbt, details) = wallet.create_tx(TxBuilder::from_addressees(vec![(node_addr, 25_000)])).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction");
- wallet.broadcast(psbt.extract_tx()).unwrap();
+ let tx = psbt.extract_tx();
+ println!("{}", bitcoin::consensus::encode::serialize_hex(&tx));
+ wallet.broadcast(tx).unwrap();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), details.received);
#[macro_export]
macro_rules! testutils {
( @external $descriptors:expr, $child:expr ) => ({
- $descriptors.0.derive($child).expect("Derivation error").address(bitcoin::Network::Regtest).expect("No address form")
+ use miniscript::descriptor::{Descriptor, DescriptorPublicKey};
+
+ let parsed = Descriptor::<DescriptorPublicKey>::parse_secret(&$descriptors.0).expect("Failed to parse descriptor in `testutils!(@external)`").0;
+ parsed.derive(&[bitcoin::util::bip32::ChildNumber::from_normal_idx($child).unwrap()]).address(bitcoin::Network::Regtest).expect("No address form")
});
( @internal $descriptors:expr, $child:expr ) => ({
- $descriptors.1.expect("Missing internal descriptor").derive($child).expect("Derivation error").address(bitcoin::Network::Regtest).expect("No address form")
+ use miniscript::descriptor::{Descriptor, DescriptorPublicKey};
+
+ let parsed = Descriptor::<DescriptorPublicKey>::parse_secret(&$descriptors.1.expect("Missing internal descriptor")).expect("Failed to parse descriptor in `testutils!(@internal)`").0;
+ parsed.derive(&[bitcoin::util::bip32::ChildNumber::from_normal_idx($child).unwrap()]).address(bitcoin::Network::Regtest).expect("No address form")
});
( @e $descriptors:expr, $child:expr ) => ({ testutils!(@external $descriptors, $child) });
( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) });
use std::collections::HashMap;
use std::convert::TryInto;
+ use miniscript::descriptor::{Descriptor, DescriptorPublicKey};
+
let mut keys: HashMap<&'static str, (String, Option<String>, Option<String>)> = HashMap::new();
$(
keys = testutils!{ @keys $( $keys )* };
}
}).unwrap();
- let external: ExtendedDescriptor = external.try_into().unwrap();
+ let external = external.to_string();
- let mut internal = None::<ExtendedDescriptor>;
+ let mut internal = None::<String>;
$(
let string_internal: Descriptor<String> = FromStr::from_str($internal_descriptor).unwrap();
}
}).unwrap();
- internal = Some(string_internal.try_into().unwrap());
+ internal = Some(string_internal.to_string());
)*
(external, internal)
use bitcoin::blockdata::script::Builder;
use bitcoin::blockdata::transaction::{OutPoint, TxIn, TxOut};
use bitcoin::hash_types::{BlockHash, TxMerkleNode};
- use bitcoin::util::hash::BitcoinHash;
let block_template: serde_json::Value = self
.call("getblocktemplate", &[json!({"rules": ["segwit"]})])
self.wait_for_block(height as usize);
- block.header.bitcoin_hash().to_hex()
+ block.header.block_hash().to_hex()
}
pub fn generate(&mut self, num_blocks: u64) {