Use miniscript::DescriptorPublicKey

This allows us to remove all our custom "ExtendedDescriptor" implementation since that is
now built directly in miniscript.
This commit is contained in:
Alekos Filini 2020-08-12 12:51:50 +02:00
parent ddc2bded99
commit 5777431135
No known key found for this signature in database
GPG Key ID: 5E8AFC3034FDFA4F
19 changed files with 935 additions and 1763 deletions

View File

@ -2,7 +2,7 @@
name = "magical-bitcoin-wallet" name = "magical-bitcoin-wallet"
version = "0.1.0" version = "0.1.0"
edition = "2018" 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] [dependencies]
magical-macros = { path = "./macros" } magical-macros = { path = "./macros" }
@ -25,6 +25,10 @@ rocksdb = { version = "0.14", optional = true }
socks = { version = "0.3", optional = true } socks = { version = "0.3", optional = true }
lazy_static = { version = "1.4", 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 # Platform-specific dependencies
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "0.2", features = ["rt-core"] } tokio = { version = "0.2", features = ["rt-core"] }

View File

@ -1,8 +1,9 @@
extern crate magical_bitcoin_wallet; extern crate magical_bitcoin_wallet;
extern crate serde_json; 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::bitcoin::*;
use magical_bitcoin_wallet::descriptor::*; use magical_bitcoin_wallet::descriptor::*;
@ -14,13 +15,14 @@ fn main() {
and_v(vc:pk_h(cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy),older(1000))\ 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); 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()); 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); println!("{:?}", derived_desc);
let addr = derived_desc.address(Network::Testnet).unwrap(); let addr = derived_desc.address(Network::Testnet).unwrap();

View File

@ -7,7 +7,7 @@ use std::sync::{Arc, Mutex};
use log::{debug, error, info, trace}; use log::{debug, error, info, trace};
use bitcoin::network::message_blockdata::Inventory; use bitcoin::network::message_blockdata::Inventory;
use bitcoin::{BitcoinHash, OutPoint, Transaction, Txid}; use bitcoin::{OutPoint, Transaction, Txid};
use rocksdb::{Options, SliceTransform, DB}; use rocksdb::{Options, SliceTransform, DB};
@ -274,7 +274,7 @@ impl OnlineBlockchain for CompactFiltersBlockchain {
let block_height = headers.get_height_for(block_hash)?.unwrap_or(0); let block_height = headers.get_height_for(block_hash)?.unwrap_or(0);
let saved_correct_block = match headers.get_full_block(block_height)? { 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, _ => false,
}; };

View File

@ -16,7 +16,6 @@ use bitcoin::hash_types::FilterHash;
use bitcoin::hashes::hex::FromHex; use bitcoin::hashes::hex::FromHex;
use bitcoin::hashes::{sha256d, Hash}; use bitcoin::hashes::{sha256d, Hash};
use bitcoin::util::bip158::BlockFilter; use bitcoin::util::bip158::BlockFilter;
use bitcoin::util::hash::BitcoinHash;
use bitcoin::util::uint::Uint256; use bitcoin::util::uint::Uint256;
use bitcoin::Block; use bitcoin::Block;
use bitcoin::BlockHash; use bitcoin::BlockHash;
@ -257,7 +256,7 @@ impl ChainStore<Full> {
); );
batch.put_cf( batch.put_cf(
cf_handle, cf_handle,
StoreEntry::BlockHeaderIndex(Some(genesis.bitcoin_hash())).get_key(), StoreEntry::BlockHeaderIndex(Some(genesis.block_hash())).get_key(),
&0usize.to_be_bytes(), &0usize.to_be_bytes(),
); );
store.write(batch)?; store.write(batch)?;
@ -290,7 +289,7 @@ impl ChainStore<Full> {
.get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(index)).get_key())? .get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(index)).get_key())?
.unwrap(), .unwrap(),
)?; )?;
answer.push((header.bitcoin_hash(), index)); answer.push((header.block_hash(), index));
if let Some(new_index) = index.checked_sub(step) { if let Some(new_index) = index.checked_sub(step) {
index = new_index; index = new_index;
@ -322,7 +321,7 @@ impl ChainStore<Full> {
let mut batch = WriteBatch::default(); let mut batch = WriteBatch::default();
batch.put_cf( batch.put_cf(
new_cf_handle, new_cf_handle,
StoreEntry::BlockHeaderIndex(Some(header.bitcoin_hash())).get_key(), StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
&from.to_be_bytes(), &from.to_be_bytes(),
); );
batch.put_cf( batch.put_cf(
@ -406,7 +405,7 @@ impl ChainStore<Full> {
batch.delete_cf( batch.delete_cf(
cf_handle, cf_handle,
StoreEntry::BlockHeaderIndex(Some(header.bitcoin_hash())).get_key(), StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
); );
} }
@ -461,7 +460,7 @@ impl ChainStore<Full> {
.map(|data| { .map(|data| {
let (header, _): (BlockHeader, Uint256) = let (header, _): (BlockHeader, Uint256) =
deserialize(&data).map_err(|_| CompactFiltersError::DataCorruption)?; deserialize(&data).map_err(|_| CompactFiltersError::DataCorruption)?;
Ok::<_, CompactFiltersError>(header.bitcoin_hash()) Ok::<_, CompactFiltersError>(header.block_hash())
}) })
.transpose()?) .transpose()?)
} }
@ -574,7 +573,7 @@ impl<T: StoreType> ChainStore<T> {
.map(|(_, v)| -> Result<_, CompactFiltersError> { .map(|(_, v)| -> Result<_, CompactFiltersError> {
let (header, _): (BlockHeader, Uint256) = SerializeDb::deserialize(&v)?; let (header, _): (BlockHeader, Uint256) = SerializeDb::deserialize(&v)?;
Ok(header.bitcoin_hash()) Ok(header.block_hash())
}) })
.transpose()?) .transpose()?)
} }
@ -593,7 +592,7 @@ impl<T: StoreType> ChainStore<T> {
.get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(from)).get_key())? .get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(from)).get_key())?
.map(|result| { .map(|result| {
let (header, work): (BlockHeader, Uint256) = SerializeDb::deserialize(&result)?; let (header, work): (BlockHeader, Uint256) = SerializeDb::deserialize(&result)?;
Ok::<_, CompactFiltersError>((header.bitcoin_hash(), work)) Ok::<_, CompactFiltersError>((header.block_hash(), work))
}) })
.transpose()? .transpose()?
.ok_or(CompactFiltersError::DataCorruption)?; .ok_or(CompactFiltersError::DataCorruption)?;
@ -603,13 +602,13 @@ impl<T: StoreType> ChainStore<T> {
return Err(CompactFiltersError::InvalidHeaders); return Err(CompactFiltersError::InvalidHeaders);
} }
last_hash = header.bitcoin_hash(); last_hash = header.block_hash();
accumulated_work = accumulated_work + header.work(); accumulated_work = accumulated_work + header.work();
let height = from + index + 1; let height = from + index + 1;
batch.put_cf( batch.put_cf(
cf_handle, cf_handle,
StoreEntry::BlockHeaderIndex(Some(header.bitcoin_hash())).get_key(), StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
&(height).to_be_bytes(), &(height).to_be_bytes(),
); );
batch.put_cf( batch.put_cf(
@ -647,8 +646,8 @@ pub struct FilterHeader {
filter_hash: FilterHash, filter_hash: FilterHash,
} }
impl BitcoinHash<FilterHeaderHash> for FilterHeader { impl FilterHeader {
fn bitcoin_hash(&self) -> FilterHeaderHash { fn header_hash(&self) -> FilterHeaderHash {
let mut hash_data = self.filter_hash.into_inner().to_vec(); let mut hash_data = self.filter_hash.into_inner().to_vec();
hash_data.extend_from_slice(&self.prev_header_hash); hash_data.extend_from_slice(&self.prev_header_hash);
sha256d::Hash::hash(&hash_data).into() sha256d::Hash::hash(&hash_data).into()
@ -794,7 +793,7 @@ impl CFStore {
prev_header_hash: last_hash, prev_header_hash: last_hash,
filter_hash, filter_hash,
}; };
last_hash = filter_header.bitcoin_hash(); last_hash = filter_header.header_hash();
filter_header filter_header
}) })

View File

@ -1,372 +0,0 @@
use std::fmt::{self, Display};
use std::str::FromStr;
use bitcoin::hashes::hex::{FromHex, ToHex};
use bitcoin::secp256k1;
use bitcoin::util::base58;
use bitcoin::util::bip32::{
ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, Fingerprint,
};
use bitcoin::PublicKey;
#[allow(unused_imports)]
use log::{debug, error, info, trace};
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum DerivationIndex {
Fixed,
Normal,
Hardened,
}
impl DerivationIndex {
fn as_path(&self, index: u32) -> DerivationPath {
match self {
DerivationIndex::Fixed => vec![],
DerivationIndex::Normal => vec![ChildNumber::Normal { index }],
DerivationIndex::Hardened => vec![ChildNumber::Hardened { index }],
}
.into()
}
}
impl Display for DerivationIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let chars = match *self {
Self::Fixed => "",
Self::Normal => "/*",
Self::Hardened => "/*'",
};
write!(f, "{}", chars)
}
}
#[derive(Clone, Debug)]
pub struct DescriptorExtendedKey {
pub master_fingerprint: Option<Fingerprint>,
pub master_derivation: Option<DerivationPath>,
pub pubkey: ExtendedPubKey,
pub secret: Option<ExtendedPrivKey>,
pub path: DerivationPath,
pub final_index: DerivationIndex,
}
impl DescriptorExtendedKey {
pub fn full_path(&self, index: u32) -> DerivationPath {
let mut final_path: Vec<ChildNumber> = 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"));
}
}

View File

@ -1,181 +0,0 @@
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
}
}

View File

@ -1,45 +1,275 @@
use std::cell::RefCell; use std::collections::{BTreeMap, HashMap};
use std::collections::BTreeMap;
use std::convert::{Into, TryFrom};
use std::fmt; use std::fmt;
use std::str::FromStr; use std::sync::Arc;
use bitcoin::hashes::{hash160, Hash}; use bitcoin::hashes::hash160;
use bitcoin::secp256k1::{All, Secp256k1}; use bitcoin::secp256k1::Secp256k1;
use bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey, Fingerprint}; use bitcoin::util::bip32::{ChildNumber, DerivationPath, Fingerprint};
use bitcoin::util::psbt::PartiallySignedTransaction as PSBT; use bitcoin::util::psbt;
use bitcoin::{PrivateKey, PublicKey, Script}; use bitcoin::{PublicKey, Script, TxOut};
use miniscript::descriptor::{DescriptorPublicKey, DescriptorXKey, InnerXKey};
pub use miniscript::{ 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 checksum;
pub mod error; pub mod error;
pub mod extended_key;
mod keys;
pub mod policy; pub mod policy;
// use crate::wallet::utils::AddressType;
use crate::wallet::signer::SignersContainer;
pub use self::checksum::get_checksum; pub use self::checksum::get_checksum;
use self::error::Error; use self::error::Error;
pub use self::extended_key::{DerivationIndex, DescriptorExtendedKey};
pub use self::policy::Policy; 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( fn extract_policy(
&self, &self,
lookup_map: &BTreeMap<String, Box<dyn Key>>, signers: Arc<SignersContainer<DescriptorPublicKey>>,
) -> Result<Option<Policy>, Error>; ) -> Result<Option<Policy>, Error>;
} }
pub trait ExtractPolicy { pub trait XKeyUtils {
fn extract_policy(&self) -> Result<Option<Policy>, Error>; fn full_path(&self, append: &[ChildNumber]) -> DerivationPath;
fn root_fingerprint(&self) -> Fingerprint;
}
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(),
};
if self.is_wildcard {
full_path
.into_iter()
.chain(append.into_iter())
.cloned()
.collect()
} else {
full_path
}
}
fn root_fingerprint(&self) -> Fingerprint {
match &self.source {
&Some((fingerprint, _)) => fingerprint.clone(),
&None => self.xkey.xkey_fingerprint(),
}
}
}
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 trait DescriptorScripts {
fn psbt_redeem_script(&self) -> Option<Script>;
fn psbt_witness_script(&self) -> Option<Script>;
}
impl<T> DescriptorScripts for Descriptor<T>
where
T: miniscript::MiniscriptKey + miniscript::ToPublicKey,
{
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,
}
}
fn psbt_witness_script(&self) -> Option<Script> {
match self {
Descriptor::Wsh(ref script) => Some(script.encode()),
Descriptor::ShWsh(ref script) => Some(script.encode()),
_ => None,
}
}
}
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,
}
}
fn get_hd_keypaths(&self, index: u32) -> Result<HDKeyPaths, Error> {
let mut answer = BTreeMap::new();
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()) };
self.translate_pk(translatefpk, translatefpkh)?;
Ok(answer)
}
fn is_fixed(&self) -> bool {
let mut found_wildcard = false;
let translatefpk = |key: &DescriptorPublicKey| -> Result<_, Error> {
match key {
DescriptorPublicKey::PubKey(_) => {}
DescriptorPublicKey::XPub(xpub) => {
if xpub.is_wildcard {
found_wildcard = true;
}
}
}
Ok(DummyKey::default())
};
let translatefpkh = |_: &hash160::Hash| -> Result<_, Error> { Ok(DummyKey::default()) };
self.translate_pk(translatefpk, translatefpkh).unwrap();
!found_wildcard
}
fn derive_from_hd_keypaths(&self, hd_keypaths: &HDKeyPaths) -> Option<Self> {
let index: HashMap<_, _> = hd_keypaths.values().cloned().collect();
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());
}
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()
});
}
Ok(DummyKey::default())
};
let translatefpkh = |_: &hash160::Hash| -> Result<_, Error> { Ok(DummyKey::default()) };
self.translate_pk(translatefpk, translatefpkh).unwrap();
derive_path.map(|path| self.derive(path.as_ref()))
}
fn derive_from_psbt_input(
&self,
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;
}
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,
}
}
// 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,
// }
// }
} }
#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord, Default)] #[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord, Default)]
@ -67,480 +297,27 @@ impl miniscript::MiniscriptKey for DummyKey {
} }
} }
pub type DerivedDescriptor = Descriptor<PublicKey>;
pub type StringDescriptor = Descriptor<String>;
pub trait DescriptorMeta {
fn is_witness(&self) -> bool;
fn psbt_redeem_script(&self) -> Option<Script>;
fn psbt_witness_script(&self) -> Option<Script>;
}
impl<T> DescriptorMeta for Descriptor<T>
where
T: miniscript::MiniscriptKey + miniscript::ToPublicKey,
{
fn is_witness(&self) -> bool {
match self {
Descriptor::Bare(_) | Descriptor::Pk(_) | Descriptor::Pkh(_) | Descriptor::Sh(_) => {
false
}
Descriptor::Wpkh(_)
| Descriptor::ShWpkh(_)
| Descriptor::Wsh(_)
| Descriptor::ShWsh(_) => true,
}
}
fn psbt_redeem_script(&self) -> Option<Script> {
match self {
Descriptor::ShWpkh(_) => Some(self.witness_script()),
Descriptor::ShWsh(ref script) => Some(script.encode().to_v0_p2wsh()),
Descriptor::Sh(ref script) => Some(script.encode()),
_ => None,
}
}
fn psbt_witness_script(&self) -> Option<Script> {
match self {
Descriptor::Wsh(ref script) => Some(script.encode()),
Descriptor::ShWsh(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)));
}
return Err(Error::KeyParsingError(string.to_string()));
}
fn new(sd: StringDescriptor) -> Result<Self, Error> {
let ctx = Secp256k1::gen_new();
let keys: RefCell<BTreeMap<String, Box<dyn Key>>> = RefCell::new(BTreeMap::new());
let translatefpk = |string: &String| -> Result<_, Error> {
let (key, parsed) = Self::parse_string(string)?;
keys.borrow_mut().insert(key, parsed);
Ok(DummyKey::default())
};
let translatefpkh = |string: &String| -> Result<_, Error> {
let (key, parsed) = Self::parse_string(string)?;
keys.borrow_mut().insert(key, parsed);
Ok(DummyKey::default())
};
sd.translate_pk(translatefpk, translatefpkh)?;
Ok(ExtendedDescriptor {
internal: sd,
keys: keys.into_inner(),
ctx,
})
}
pub fn derive_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),
};
// if !self.same_structure(&derived_desc) {
// Err(Error::CantDeriveWithMiniscript)
// } else {
Ok(derived_desc)
// }
}
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),
};
// if !self.same_structure(&derived_desc) {
// Err(Error::CantDeriveWithMiniscript)
// } else {
Ok(derived_desc)
// }
}
pub fn derive_from_psbt_input(
&self,
psbt: &PSBT,
input_index: usize,
) -> Result<DerivedDescriptor, Error> {
let get_pk_from_partial_sigs = || {
// here we need the public key.. since it's a single sig, there are only two
// options: we can either find it in the `partial_sigs`, or we can't. if we
// can't, it means that we can't even satisfy the input, so we can exit knowing
// that we did our best to try to find it.
psbt.inputs[input_index]
.partial_sigs
.keys()
.nth(0)
.ok_or(Error::MissingPublicKey)
};
if let Some(wit_script) = &psbt.inputs[input_index].witness_script {
self.derive_with_miniscript_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)?)
}
} 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)?)
}
} 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))
};
let translatefpkh =
|xpub: &String| Ok(hash160::Hash::hash(&translatefpk(xpub)?.to_bytes()));
Ok(self.internal.translate_pk(translatefpk, translatefpkh)?)
}
pub fn get_xprv(&self) -> impl IntoIterator<Item = ExtendedPrivKey> + '_ {
self.keys
.iter()
.filter(|(_, v)| v.xprv().is_some())
.map(|(_, v)| v.xprv().unwrap())
}
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(
&self,
index: u32,
) -> Result<BTreeMap<PublicKey, (Fingerprint, DerivationPath)>, Error> {
let mut answer = BTreeMap::new();
for (_, key) in &self.keys {
if let Some(fingerprint) = key.fingerprint(&self.ctx) {
let derivation_path = key.full_path(index).unwrap();
let pubkey = key.as_public_key(&self.ctx, Some(index))?;
answer.insert(pubkey, (fingerprint, derivation_path));
}
}
Ok(answer)
}
pub fn max_satisfaction_weight(&self) -> usize {
let fake_pk = PublicKey::from_slice(&[
2, 140, 40, 169, 123, 248, 41, 139, 192, 210, 61, 140, 116, 148, 82, 163, 46, 105, 75,
101, 227, 10, 148, 114, 163, 149, 74, 179, 15, 229, 50, 76, 170,
])
.unwrap();
let translated: Descriptor<PublicKey> = self
.internal
.translate_pk(
|_| -> Result<_, ()> { Ok(fake_pk.clone()) },
|_| -> Result<_, ()> { Ok(Default::default()) },
)
.unwrap();
translated.max_satisfaction_weight()
}
pub fn is_fixed(&self) -> bool {
self.keys.iter().all(|(_, key)| key.is_fixed())
}
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,
}
}
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)
}
}
impl TryFrom<&str> for ExtendedDescriptor {
type Error = Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let internal = StringDescriptor::from_str(value)?;
ExtendedDescriptor::new(internal)
}
}
impl TryFrom<StringDescriptor> for ExtendedDescriptor {
type Error = Error;
fn try_from(other: StringDescriptor) -> Result<Self, Self::Error> {
ExtendedDescriptor::new(other)
}
}
impl FromStr for ExtendedDescriptor {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(s)
}
}
impl Into<String> for ExtendedDescriptor {
fn into(self) -> String {
format!("{}", self.internal)
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::str::FromStr; use std::str::FromStr;
use bitcoin::consensus::encode::deserialize;
use bitcoin::hashes::hex::FromHex; use bitcoin::hashes::hex::FromHex;
use bitcoin::{Network, PublicKey}; use bitcoin::util::psbt;
use crate::descriptor::*; use super::*;
use crate::psbt::PSBTUtils;
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] #[test]
fn test_descriptor_parse_wif() { fn test_derive_from_psbt_input_wpkh() {
let string = "pkh(cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy)"; let psbt: psbt::PartiallySignedTransaction = deserialize(&Vec::<u8>::from_hex("70736274ff010052010000000162307be8e431fbaff807cdf9cdc3fde44d740211bc8342c31ffd6ec11fe35bcc0100000000ffffffff01328601000000000016001493ce48570b55c42c2af816aeaba06cfee1224fae000000000001011fa08601000000000016001493ce48570b55c42c2af816aeaba06cfee1224fae010304010000000000").unwrap()).unwrap();
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
);
}
#[test] let descriptor = Descriptor::<DescriptorPublicKey>::from_str(
fn test_descriptor_parse_pubkey() { "wpkh(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)",
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();
}
#[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")))
) )
.unwrap();
let result = descriptor.derive_from_psbt_input(&psbt.inputs[0], psbt.get_utxo_for(0));
println!("{:?}", result);
} }
} }

View File

@ -1,23 +1,26 @@
use std::cmp::max; use std::cmp::max;
use std::collections::{BTreeMap, HashSet, VecDeque}; use std::collections::{BTreeMap, HashSet, VecDeque};
use std::sync::Arc;
use serde::ser::SerializeMap; use serde::ser::SerializeMap;
use serde::{Serialize, Serializer}; use serde::{Serialize, Serializer};
use bitcoin::hashes::*; use bitcoin::hashes::*;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::util::bip32::Fingerprint; use bitcoin::util::bip32::Fingerprint;
use bitcoin::PublicKey; use bitcoin::PublicKey;
use miniscript::{Descriptor, Miniscript, ScriptContext, Terminal}; use miniscript::descriptor::DescriptorPublicKey;
use miniscript::{Descriptor, Miniscript, MiniscriptKey, ScriptContext, Terminal};
#[allow(unused_imports)] #[allow(unused_imports)]
use log::{debug, error, info, trace}; use log::{debug, error, info, trace};
use crate::descriptor::ExtractPolicy;
use crate::wallet::signer::{SignerId, SignersContainer};
use super::checksum::get_checksum; use super::checksum::get_checksum;
use super::error::Error; use super::error::Error;
use crate::descriptor::{Key, MiniscriptExtractPolicy}; use super::XKeyUtils;
use crate::psbt::PSBTSatisfier;
#[derive(Debug, Clone, Default, Serialize)] #[derive(Debug, Clone, Default, Serialize)]
pub struct PKOrF { pub struct PKOrF {
@ -30,21 +33,24 @@ pub struct PKOrF {
} }
impl PKOrF { impl PKOrF {
fn from_key(k: &Box<dyn Key>) -> Self { fn from_key(k: &DescriptorPublicKey) -> Self {
let secp = Secp256k1::gen_new(); match k {
DescriptorPublicKey::PubKey(pubkey) => PKOrF {
pubkey: Some(*pubkey),
..Default::default()
},
DescriptorPublicKey::XPub(xpub) => PKOrF {
fingerprint: Some(xpub.root_fingerprint()),
..Default::default()
},
}
}
let pubkey = k.as_public_key(&secp, None).unwrap(); fn from_key_hash(k: hash160::Hash) -> Self {
if let Some(fing) = k.fingerprint(&secp) {
PKOrF { PKOrF {
fingerprint: Some(fing), pubkey_hash: Some(k),
..Default::default() ..Default::default()
} }
} else {
PKOrF {
pubkey: Some(pubkey),
..Default::default()
}
}
} }
} }
@ -445,14 +451,15 @@ impl Policy {
} }
fn make_multisig( fn make_multisig(
keys: Vec<Option<&Box<dyn Key>>>, keys: &Vec<DescriptorPublicKey>,
signers: Arc<SignersContainer<DescriptorPublicKey>>,
threshold: usize, threshold: usize,
) -> Result<Option<Policy>, PolicyError> { ) -> Result<Option<Policy>, PolicyError> {
if threshold == 0 { if threshold == 0 {
return Ok(None); 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 { let mut contribution = Satisfaction::Partial {
n: keys.len(), n: keys.len(),
@ -461,14 +468,14 @@ impl Policy {
conditions: Default::default(), conditions: Default::default(),
}; };
for (index, key) in keys.iter().enumerate() { for (index, key) in keys.iter().enumerate() {
let val = if key.is_some() && key.unwrap().has_secret() { if let Some(_) = signers.find(signer_id(key)) {
Satisfaction::Complete { contribution.add(
&Satisfaction::Complete {
condition: Default::default(), condition: Default::default(),
},
index,
)?;
} }
} else {
Satisfaction::None
};
contribution.add(&val, index)?;
} }
contribution.finalize()?; contribution.finalize()?;
@ -482,15 +489,6 @@ impl Policy {
Ok(Some(policy)) 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 { pub fn requires_path(&self) -> bool {
self.get_requirements(&BTreeMap::new()).is_err() self.get_requirements(&BTreeMap::new()).is_err()
} }
@ -566,10 +564,20 @@ impl From<SatisfiableItem> for Policy {
} }
} }
fn signature_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> { fn signer_id(key: &DescriptorPublicKey) -> SignerId<DescriptorPublicKey> {
key.map(|k| { match key {
let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key(k)).into(); DescriptorPublicKey::PubKey(pubkey) => pubkey.to_pubkeyhash().into(),
policy.contribution = if k.has_secret() { DescriptorPublicKey::XPub(xpub) => xpub.root_fingerprint().into(),
}
}
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 { Satisfaction::Complete {
condition: Default::default(), condition: Default::default(),
} }
@ -578,48 +586,33 @@ fn signature_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> {
}; };
policy policy
})
} }
fn signature_key_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> { fn signature_key(
let secp = Secp256k1::gen_new(); 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| { if let Some(_) = signers.find(SignerId::PkHash(*key_hash)) {
let pubkey = k.as_public_key(&secp, None).unwrap(); policy.contribution = Satisfaction::Complete {
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()
})
}
.into();
policy.contribution = if k.has_secret() {
Satisfaction::Complete {
condition: Default::default(), 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( fn extract_policy(
&self, &self,
lookup_map: &BTreeMap<String, Box<dyn Key>>, signers: Arc<SignersContainer<DescriptorPublicKey>>,
) -> Result<Option<Policy>, Error> { ) -> Result<Option<Policy>, Error> {
Ok(match &self.node { Ok(match &self.node {
// Leaves // Leaves
Terminal::True | Terminal::False => None, Terminal::True | Terminal::False => None,
Terminal::PkK(pubkey) => signature_from_string(lookup_map.get(pubkey)), Terminal::PkK(pubkey) => Some(signature(pubkey, Arc::clone(&signers))),
Terminal::PkH(pubkey_hash) => signature_key_from_string(lookup_map.get(pubkey_hash)), Terminal::PkH(pubkey_hash) => Some(signature_key(pubkey_hash, Arc::clone(&signers))),
Terminal::After(value) => { Terminal::After(value) => {
let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into(); let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into();
policy.contribution = Satisfaction::Complete { policy.contribution = Satisfaction::Complete {
@ -652,9 +645,7 @@ impl<Ctx: ScriptContext> MiniscriptExtractPolicy for Miniscript<String, Ctx> {
Terminal::Hash160(hash) => { Terminal::Hash160(hash) => {
Some(SatisfiableItem::HASH160Preimage { hash: *hash }.into()) Some(SatisfiableItem::HASH160Preimage { hash: *hash }.into())
} }
Terminal::Multi(k, pks) => { Terminal::Multi(k, pks) => Policy::make_multisig(pks, Arc::clone(&signers), *k)?,
Policy::make_multisig(pks.iter().map(|s| lookup_map.get(s)).collect(), *k)?
}
// Identities // Identities
Terminal::Alt(inner) Terminal::Alt(inner)
| Terminal::Swap(inner) | Terminal::Swap(inner)
@ -662,26 +653,31 @@ impl<Ctx: ScriptContext> MiniscriptExtractPolicy for Miniscript<String, Ctx> {
| Terminal::DupIf(inner) | Terminal::DupIf(inner)
| Terminal::Verify(inner) | Terminal::Verify(inner)
| Terminal::NonZero(inner) | Terminal::NonZero(inner)
| Terminal::ZeroNotEqual(inner) => inner.extract_policy(lookup_map)?, | Terminal::ZeroNotEqual(inner) => inner.extract_policy(Arc::clone(&signers))?,
// Complex policies // Complex policies
Terminal::AndV(a, b) | Terminal::AndB(a, b) => { Terminal::AndV(a, b) | Terminal::AndB(a, b) => Policy::make_and(
Policy::make_and(a.extract_policy(lookup_map)?, b.extract_policy(lookup_map)?)? a.extract_policy(Arc::clone(&signers))?,
} b.extract_policy(Arc::clone(&signers))?,
)?,
Terminal::AndOr(x, y, z) => Policy::make_or( Terminal::AndOr(x, y, z) => Policy::make_or(
Policy::make_and(x.extract_policy(lookup_map)?, y.extract_policy(lookup_map)?)?, Policy::make_and(
z.extract_policy(lookup_map)?, x.extract_policy(Arc::clone(&signers))?,
y.extract_policy(Arc::clone(&signers))?,
)?,
z.extract_policy(Arc::clone(&signers))?,
)?, )?,
Terminal::OrB(a, b) Terminal::OrB(a, b)
| Terminal::OrD(a, b) | Terminal::OrD(a, b)
| Terminal::OrC(a, b) | Terminal::OrC(a, b)
| Terminal::OrI(a, b) => { | Terminal::OrI(a, b) => Policy::make_or(
Policy::make_or(a.extract_policy(lookup_map)?, b.extract_policy(lookup_map)?)? a.extract_policy(Arc::clone(&signers))?,
} b.extract_policy(Arc::clone(&signers))?,
)?,
Terminal::Thresh(k, nodes) => { Terminal::Thresh(k, nodes) => {
let mut threshold = *k; let mut threshold = *k;
let mapped: Vec<_> = nodes let mapped: Vec<_> = nodes
.iter() .iter()
.map(|n| n.extract_policy(lookup_map)) .map(|n| n.extract_policy(Arc::clone(&signers)))
.collect::<Result<Vec<_>, _>>()? .collect::<Result<Vec<_>, _>>()?
.into_iter() .into_iter()
.filter_map(|x| x) .filter_map(|x| x)
@ -700,22 +696,18 @@ impl<Ctx: ScriptContext> MiniscriptExtractPolicy for Miniscript<String, Ctx> {
} }
} }
impl MiniscriptExtractPolicy for Descriptor<String> { impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
fn extract_policy( fn extract_policy(
&self, &self,
lookup_map: &BTreeMap<String, Box<dyn Key>>, signers: Arc<SignersContainer<DescriptorPublicKey>>,
) -> Result<Option<Policy>, Error> { ) -> Result<Option<Policy>, Error> {
match self { match self {
Descriptor::Pk(pubkey) Descriptor::Pk(pubkey)
| Descriptor::Pkh(pubkey) | Descriptor::Pkh(pubkey)
| Descriptor::Wpkh(pubkey) | Descriptor::Wpkh(pubkey)
| Descriptor::ShWpkh(pubkey) => Ok(signature_from_string(lookup_map.get(pubkey))), | Descriptor::ShWpkh(pubkey) => Ok(Some(signature(pubkey, signers))),
Descriptor::Bare(inner) | Descriptor::Sh(inner) => { Descriptor::Bare(inner) | Descriptor::Sh(inner) => Ok(inner.extract_policy(signers)?),
Ok(inner.extract_policy(lookup_map)?) Descriptor::Wsh(inner) | Descriptor::ShWsh(inner) => Ok(inner.extract_policy(signers)?),
}
Descriptor::Wsh(inner) | Descriptor::ShWsh(inner) => {
Ok(inner.extract_policy(lookup_map)?)
}
} }
} }
} }

View File

@ -1,4 +1,4 @@
use bitcoin::{Address, OutPoint, Script, Txid}; use bitcoin::{Address, OutPoint};
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
@ -25,13 +25,7 @@ pub enum Error {
SpendingPolicyRequired, SpendingPolicyRequired,
InvalidPolicyPathError(crate::descriptor::policy::PolicyError), InvalidPolicyPathError(crate::descriptor::policy::PolicyError),
// Signing errors (expected, received) Signer(crate::wallet::signer::SignerError),
InputTxidMismatch((Txid, OutPoint)),
InputRedeemScriptMismatch((Script, Script)), // scriptPubKey, redeemScript
InputWitnessScriptMismatch((Script, Script)), // scriptPubKey, redeemScript
InputUnknownSegwitScript(Script),
InputMissingWitnessScript(usize),
MissingUTXO,
// Blockchain interface errors // Blockchain interface errors
Uncapable(crate::blockchain::Capability), Uncapable(crate::blockchain::Capability),
@ -44,6 +38,7 @@ pub enum Error {
Descriptor(crate::descriptor::error::Error), Descriptor(crate::descriptor::error::Error),
Encode(bitcoin::consensus::encode::Error), Encode(bitcoin::consensus::encode::Error),
Miniscript(miniscript::Error),
BIP32(bitcoin::util::bip32::Error), BIP32(bitcoin::util::bip32::Error),
Secp256k1(bitcoin::secp256k1::Error), Secp256k1(bitcoin::secp256k1::Error),
JSON(serde_json::Error), JSON(serde_json::Error),
@ -75,8 +70,10 @@ impl_error!(
crate::descriptor::policy::PolicyError, crate::descriptor::policy::PolicyError,
InvalidPolicyPathError InvalidPolicyPathError
); );
impl_error!(crate::wallet::signer::SignerError, Signer);
impl_error!(bitcoin::consensus::encode::Error, Encode); impl_error!(bitcoin::consensus::encode::Error, Encode);
impl_error!(miniscript::Error, Miniscript);
impl_error!(bitcoin::util::bip32::Error, BIP32); impl_error!(bitcoin::util::bip32::Error, BIP32);
impl_error!(bitcoin::secp256k1::Error, Secp256k1); impl_error!(bitcoin::secp256k1::Error, Secp256k1);
impl_error!(serde_json::Error, JSON); impl_error!(serde_json::Error, JSON);

View File

@ -47,7 +47,6 @@ pub mod blockchain;
pub mod database; pub mod database;
pub mod descriptor; pub mod descriptor;
pub mod psbt; pub mod psbt;
pub mod signer;
pub mod types; pub mod types;
pub mod wallet; pub mod wallet;

View File

@ -1,271 +1,28 @@
use std::collections::BTreeMap; use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
use bitcoin::TxOut;
use bitcoin::hashes::{hash160, Hash}; pub trait PSBTUtils {
use bitcoin::util::bip143::SighashComponents; fn get_utxo_for(&self, input_index: usize) -> Option<TxOut>;
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> { impl PSBTUtils for PSBT {
pub fn new( fn get_utxo_for(&self, input_index: usize) -> Option<TxOut> {
input: &'a psbt::Input, let tx = &self.global.unsigned_tx;
assume_height_reached: bool,
create_height: Option<u32>,
current_height: Option<u32>,
) -> Self {
PSBTSatisfier {
input,
assume_height_reached,
create_height,
current_height,
}
}
}
impl<'a> PSBTSatisfier<'a> { if input_index >= tx.input.len() {
fn parse_sig(rawsig: &Vec<u8>) -> Option<BitcoinSig> { return None;
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 if let Some(input) = self.inputs.get(input_index) {
impl<'a> Satisfier<bitcoin::PublicKey> for PSBTSatisfier<'a> { if let Some(wit_utxo) = &input.witness_utxo {
// from https://docs.rs/miniscript/0.12.0/src/miniscript/psbt/mod.rs.html#96 Some(wit_utxo.clone())
fn lookup_sig(&self, pk: &bitcoin::PublicKey) -> Option<BitcoinSig> { } else if let Some(in_tx) = &input.non_witness_utxo {
debug!("lookup_sig: {}", pk); Some(in_tx.output[tx.input[input_index].previous_output.vout as usize].clone())
} else {
if let Some(rawsig) = self.input.partial_sigs.get(pk) { None
Self::parse_sig(&rawsig) }
} else { } else {
None None
} }
} }
fn lookup_pkh_pk(&self, hash: &hash160::Hash) -> Option<bitcoin::PublicKey> {
debug!("lookup_pkh_pk: {}", hash);
for (pk, _) in &self.input.partial_sigs {
if &pk.to_pubkeyhash() == hash {
return Some(*pk);
}
}
None
}
fn lookup_pkh_sig(&self, hash: &hash160::Hash) -> Option<(bitcoin::PublicKey, BitcoinSig)> {
debug!("lookup_pkh_sig: {}", hash);
for (pk, sig) in &self.input.partial_sigs {
if &pk.to_pubkeyhash() == hash {
return match Self::parse_sig(&sig) {
Some(bitcoinsig) => Some((*pk, bitcoinsig)),
None => None,
};
}
}
None
}
fn check_older(&self, height: u32) -> bool {
// TODO: also check if `nSequence` right
debug!("check_older: {}", height);
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);
}
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)))
})
}
} }

View File

@ -1,28 +0,0 @@
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
}
}
}

View File

@ -1,87 +0,0 @@
use bitcoin::util::bip32::{DerivationPath, Fingerprint};
use bitcoin::{PublicKey, Script, SigHashType};
use miniscript::miniscript::satisfy::BitcoinSig;
use crate::error::Error;
pub trait Signer {
fn sig_legacy_from_fingerprint(
&self,
index: usize,
sighash: SigHashType,
fingerprint: &Fingerprint,
path: &DerivationPath,
script: &Script,
) -> Result<Option<BitcoinSig>, Error>;
fn sig_legacy_from_pubkey(
&self,
index: usize,
sighash: SigHashType,
public_key: &PublicKey,
script: &Script,
) -> Result<Option<BitcoinSig>, Error>;
fn sig_segwit_from_fingerprint(
&self,
index: usize,
sighash: SigHashType,
fingerprint: &Fingerprint,
path: &DerivationPath,
script: &Script,
value: u64,
) -> Result<Option<BitcoinSig>, Error>;
fn sig_segwit_from_pubkey(
&self,
index: usize,
sighash: SigHashType,
public_key: &PublicKey,
script: &Script,
value: u64,
) -> Result<Option<BitcoinSig>, Error>;
}
#[allow(dead_code)]
impl dyn Signer {
fn sig_legacy_from_fingerprint(
&self,
_index: usize,
_sighash: SigHashType,
_fingerprint: &Fingerprint,
_path: &DerivationPath,
_script: &Script,
) -> Result<Option<BitcoinSig>, Error> {
Ok(None)
}
fn sig_legacy_from_pubkey(
&self,
_index: usize,
_sighash: SigHashType,
_public_key: &PublicKey,
_script: &Script,
) -> Result<Option<BitcoinSig>, Error> {
Ok(None)
}
fn sig_segwit_from_fingerprint(
&self,
_index: usize,
_sighash: SigHashType,
_fingerprint: &Fingerprint,
_path: &DerivationPath,
_script: &Script,
_value: u64,
) -> Result<Option<BitcoinSig>, Error> {
Ok(None)
}
fn sig_segwit_from_pubkey(
&self,
_index: usize,
_sighash: SigHashType,
_public_key: &PublicKey,
_script: &Script,
_value: u64,
) -> Result<Option<BitcoinSig>, Error> {
Ok(None)
}
}

View File

@ -35,7 +35,9 @@ impl WalletExport {
label: &str, label: &str,
include_blockheight: bool, include_blockheight: bool,
) -> Result<Self, &'static str> { ) -> 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)?; Self::is_compatible_with_core(&descriptor)?;
let blockheight = match wallet.database.borrow().iter_txs(false) { let blockheight = match wallet.database.borrow().iter_txs(false) {
@ -62,7 +64,7 @@ impl WalletExport {
!= wallet != wallet
.change_descriptor .change_descriptor
.as_ref() .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"); return Err("Incompatible change descriptor");
} }
@ -193,14 +195,14 @@ mod test {
#[test] #[test]
fn test_export_multi() { fn test_export_multi() {
let descriptor = "wsh(multi(2,\ let descriptor = "wsh(multi(2,\
[73756c7f/48h/0h/0h/2h]tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*,\ [73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*,\
[f9f62194/48h/0h/0h/2h]tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/0/*,\ [f9f62194/48'/0'/0'/2']tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/0/*,\
[c98b1535/48h/0h/0h/2h]tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/0/*\ [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/0/*\
))"; ))";
let change_descriptor = "wsh(multi(2,\ let change_descriptor = "wsh(multi(2,\
[73756c7f/48h/0h/0h/2h]tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/1/*,\ [73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/1/*,\
[f9f62194/48h/0h/0h/2h]tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/1/*,\ [f9f62194/48'/0'/0'/2']tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/1/*,\
[c98b1535/48h/0h/0h/2h]tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\ [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
))"; ))";
let wallet: OfflineWallet<_> = Wallet::new_offline( let wallet: OfflineWallet<_> = Wallet::new_offline(

View File

@ -2,17 +2,14 @@ use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::{BTreeMap, HashSet}; use std::collections::{BTreeMap, HashSet};
use std::ops::{Deref, DerefMut}; 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::consensus::encode::serialize;
use bitcoin::util::bip32::ChildNumber;
use bitcoin::util::psbt::PartiallySignedTransaction as PSBT; use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
use bitcoin::{ use bitcoin::{Address, Network, OutPoint, Script, SigHashType, Transaction, TxOut, Txid};
Address, Network, OutPoint, PublicKey, Script, SigHashType, Transaction, TxOut, Txid,
};
use miniscript::BitcoinSig; use miniscript::descriptor::DescriptorPublicKey;
#[allow(unused_imports)] #[allow(unused_imports)]
use log::{debug, error, info, trace}; use log::{debug, error, info, trace};
@ -20,19 +17,23 @@ use log::{debug, error, info, trace};
pub mod coin_selection; pub mod coin_selection;
pub mod export; pub mod export;
mod rbf; mod rbf;
pub mod signer;
pub mod time; pub mod time;
pub mod tx_builder; pub mod tx_builder;
pub mod utils; pub mod utils;
use signer::{Signer, SignersContainer};
use tx_builder::TxBuilder; use tx_builder::TxBuilder;
use utils::{FeeRate, IsDust}; use utils::{After, FeeRate, IsDust, Older};
use crate::blockchain::{Blockchain, OfflineBlockchain, OnlineBlockchain, Progress}; use crate::blockchain::{Blockchain, OfflineBlockchain, OnlineBlockchain, Progress};
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; 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::error::Error;
use crate::psbt::{utils::PSBTUtils, PSBTSatisfier, PSBTSigner}; use crate::psbt::PSBTUtils;
use crate::signer::Signer; // use crate::psbt::{utils::PSBTUtils, PSBTSatisfier, PSBTSigner};
use crate::types::*; use crate::types::*;
const CACHE_ADDR_BATCH_SIZE: u32 = 100; const CACHE_ADDR_BATCH_SIZE: u32 = 100;
@ -42,6 +43,10 @@ pub type OfflineWallet<D> = Wallet<OfflineBlockchain, D>;
pub struct Wallet<B: Blockchain, D: BatchDatabase> { pub struct Wallet<B: Blockchain, D: BatchDatabase> {
descriptor: ExtendedDescriptor, descriptor: ExtendedDescriptor,
change_descriptor: Option<ExtendedDescriptor>, change_descriptor: Option<ExtendedDescriptor>,
signers: Arc<SignersContainer<DescriptorPublicKey>>,
change_signers: Arc<SignersContainer<DescriptorPublicKey>>,
network: Network, network: Network,
current_height: Option<u32>, current_height: Option<u32>,
@ -66,27 +71,32 @@ where
ScriptType::External, ScriptType::External,
get_checksum(descriptor)?.as_bytes(), get_checksum(descriptor)?.as_bytes(),
)?; )?;
let descriptor = ExtendedDescriptor::from_str(descriptor)?; let (descriptor, keymap) = ExtendedDescriptor::parse_secret(descriptor)?;
let change_descriptor = match change_descriptor { let signers = Arc::new(SignersContainer::from(keymap));
let (change_descriptor, change_signers) = match change_descriptor {
Some(desc) => { Some(desc) => {
database.check_descriptor_checksum( database.check_descriptor_checksum(
ScriptType::Internal, ScriptType::Internal,
get_checksum(desc)?.as_bytes(), get_checksum(desc)?.as_bytes(),
)?; )?;
let parsed = ExtendedDescriptor::from_str(desc)?; let (change_descriptor, change_keymap) = ExtendedDescriptor::parse_secret(desc)?;
if !parsed.same_structure(descriptor.as_ref()) { let change_signers = Arc::new(SignersContainer::from(change_keymap));
return Err(Error::DifferentDescriptorStructure); // 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 { Ok(Wallet {
descriptor, descriptor,
change_descriptor, change_descriptor,
signers,
change_signers,
network, network,
current_height: None, current_height: None,
@ -100,7 +110,7 @@ where
let index = self.fetch_and_increment_index(ScriptType::External)?; let index = self.fetch_and_increment_index(ScriptType::External)?;
self.descriptor self.descriptor
.derive(index)? .derive(&[ChildNumber::from_normal_idx(index).unwrap()])
.address(self.network) .address(self.network)
.ok_or(Error::ScriptDoesntHaveAddressForm) .ok_or(Error::ScriptDoesntHaveAddressForm)
} }
@ -133,7 +143,10 @@ where
} }
// TODO: fetch both internal and external policies // 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() { if policy.requires_path() && builder.policy_path.is_none() {
return Err(Error::SpendingPolicyRequired); return Err(Error::SpendingPolicyRequired);
} }
@ -220,10 +233,10 @@ where
// TODO: use the right weight instead of the maximum, and only fall-back to it if the // TODO: use the right weight instead of the maximum, and only fall-back to it if the
// script is unknown in the database // script is unknown in the database
let input_witness_weight = std::cmp::max( let input_witness_weight = std::cmp::max(
self.get_descriptor_for(ScriptType::Internal) self.get_descriptor_for_script_type(ScriptType::Internal)
.0 .0
.max_satisfaction_weight(), .max_satisfaction_weight(),
self.get_descriptor_for(ScriptType::External) self.get_descriptor_for_script_type(ScriptType::External)
.0 .0
.max_satisfaction_weight(), .max_satisfaction_weight(),
); );
@ -369,7 +382,7 @@ where
// `get_deget_descriptor_for` to find what's the ScriptType for `Internal` // `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 // addresses really is, because if there's no change_descriptor it's actually equal
// to "External" // to "External"
let (_, change_type) = self.get_descriptor_for(ScriptType::Internal); let (_, change_type) = self.get_descriptor_for_script_type(ScriptType::Internal);
match self match self
.database .database
.borrow() .borrow()
@ -435,10 +448,10 @@ where
// TODO: use the right weight instead of the maximum, and only fall-back to it if the // TODO: use the right weight instead of the maximum, and only fall-back to it if the
// script is unknown in the database // script is unknown in the database
let input_witness_weight = std::cmp::max( let input_witness_weight = std::cmp::max(
self.get_descriptor_for(ScriptType::Internal) self.get_descriptor_for_script_type(ScriptType::Internal)
.0 .0
.max_satisfaction_weight(), .max_satisfaction_weight(),
self.get_descriptor_for(ScriptType::External) self.get_descriptor_for_script_type(ScriptType::External)
.0 .0
.max_satisfaction_weight(), .max_satisfaction_weight(),
); );
@ -545,139 +558,11 @@ where
// this helps us doing our job later // this helps us doing our job later
self.add_input_hd_keypaths(&mut psbt)?; self.add_input_hd_keypaths(&mut psbt)?;
let tx = &psbt.global.unsigned_tx; for index in 0..psbt.inputs.len() {
self.signers.sign(&mut psbt, index)?;
let mut signer = PSBTSigner::from_descriptor(&psbt.global.unsigned_tx, &self.descriptor)?; if self.change_descriptor.is_some() {
if let Some(desc) = &self.change_descriptor { self.change_signers.sign(&mut psbt, index)?;
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);
}
}
// push all the signatures into the psbt
input.partial_sigs.append(&mut partial_sigs);
} }
// attempt to finalize // attempt to finalize
@ -688,9 +573,13 @@ where
pub fn policies(&self, script_type: ScriptType) -> Result<Option<Policy>, Error> { pub fn policies(&self, script_type: ScriptType) -> Result<Option<Policy>, Error> {
match (script_type, self.change_descriptor.as_ref()) { 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, None) => Ok(None),
(ScriptType::Internal, Some(desc)) => Ok(desc.extract_policy()?), (ScriptType::Internal, Some(desc)) => {
Ok(desc.extract_policy(Arc::clone(&self.change_signers))?)
}
} }
} }
@ -699,9 +588,9 @@ where
script_type: ScriptType, script_type: ScriptType,
) -> Result<Option<ExtendedDescriptor>, Error> { ) -> Result<Option<ExtendedDescriptor>, Error> {
match (script_type, self.change_descriptor.as_ref()) { 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, None) => Ok(None),
(ScriptType::Internal, Some(desc)) => Ok(Some(desc.as_public_version()?)), (ScriptType::Internal, Some(desc)) => Ok(Some(desc.clone())),
} }
} }
@ -712,18 +601,7 @@ where
) -> Result<bool, Error> { ) -> Result<bool, Error> {
let mut tx = psbt.global.unsigned_tx.clone(); let mut tx = psbt.global.unsigned_tx.clone();
for (n, input) in tx.input.iter_mut().enumerate() { for (n, (input, psbt_input)) in tx.input.iter_mut().zip(psbt.inputs.iter()).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,
};
// if the height is None in the database it means it's still unconfirmed, so consider // if the height is None in the database it means it's still unconfirmed, so consider
// that as a very high value // that as a very high value
let create_height = self let create_height = self
@ -738,10 +616,43 @@ where
n, input.previous_output, create_height, current_height n, input.previous_output, create_height, current_height
); );
let satisfier = // - Try to derive the descriptor by looking at the txout. If it's in our database, we
PSBTSatisfier::new(&psbt.inputs[n], false, create_height, current_height); // 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, Ok(_) => continue,
Err(e) => { Err(e) => {
debug!("satisfy error {:?} for input {}", e, n); debug!("satisfy error {:?} for input {}", e, n);
@ -761,7 +672,10 @@ where
// Internals // 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 { let desc = match script_type {
ScriptType::Internal if self.change_descriptor.is_some() => ( ScriptType::Internal if self.change_descriptor.is_some() => (
self.change_descriptor.as_ref().unwrap(), self.change_descriptor.as_ref().unwrap(),
@ -773,25 +687,26 @@ where
desc desc
} }
fn to_p2pkh(&self, pubkey_hash: &[u8]) -> Script { fn get_descriptor_for_txout(&self, txout: &TxOut) -> Result<Option<ExtendedDescriptor>, Error> {
Builder::new() Ok(self
.push_opcode(opcodes::all::OP_DUP) .database
.push_opcode(opcodes::all::OP_HASH160) .borrow()
.push_slice(pubkey_hash) .get_path_from_script_pubkey(&txout.script_pubkey)?
.push_opcode(opcodes::all::OP_EQUALVERIFY) .map(|(script_type, child)| (self.get_descriptor_for_script_type(script_type).0, child))
.push_opcode(opcodes::all::OP_CHECKSIG) .map(|(desc, child)| desc.derive(&[ChildNumber::from_normal_idx(child).unwrap()])))
.into_script()
} }
fn get_change_address(&self) -> Result<Script, Error> { 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)?; 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> { 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() { let index = match descriptor.is_fixed() {
true => 0, true => 0,
false => self false => self
@ -818,7 +733,7 @@ where
from: u32, from: u32,
mut count: u32, mut count: u32,
) -> Result<(), Error> { ) -> 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 descriptor.is_fixed() {
if from > 0 { if from > 0 {
return Ok(()); return Ok(());
@ -832,7 +747,9 @@ where
let start_time = time::Instant::new(); let start_time = time::Instant::new();
for i in from..(from + count) { for i in from..(from + count) {
address_batch.set_script_pubkey( address_batch.set_script_pubkey(
&descriptor.derive(i)?.script_pubkey(), &descriptor
.derive(&[ChildNumber::from_normal_idx(i).unwrap()])
.script_pubkey(),
script_type, script_type,
i, i,
)?; )?;
@ -924,9 +841,9 @@ where
None => continue, 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)?; 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.redeem_script = derived_descriptor.psbt_redeem_script();
psbt_input.witness_script = derived_descriptor.psbt_witness_script(); psbt_input.witness_script = derived_descriptor.psbt_witness_script();
@ -957,7 +874,7 @@ where
.borrow() .borrow()
.get_path_from_script_pubkey(&tx_output.script_pubkey)? .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)?; psbt_output.hd_keypaths = desc.get_hd_keypaths(child)?;
} }
} }
@ -982,7 +899,7 @@ where
debug!("Found descriptor {:?}/{}", script_type, child); debug!("Found descriptor {:?}/{}", script_type, child);
// merge hd_keypaths // 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)?; let mut hd_keypaths = desc.get_hd_keypaths(child)?;
psbt_input.hd_keypaths.append(&mut hd_keypaths); psbt_input.hd_keypaths.append(&mut hd_keypaths);
} }
@ -1086,13 +1003,12 @@ where
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use bitcoin::Network; use std::str::FromStr;
use miniscript::Descriptor; use bitcoin::Network;
use crate::database::memory::MemoryDatabase; use crate::database::memory::MemoryDatabase;
use crate::database::Database; use crate::database::Database;
use crate::descriptor::ExtendedDescriptor;
use crate::types::ScriptType; use crate::types::ScriptType;
use super::*; use super::*;
@ -1205,12 +1121,12 @@ mod test {
descriptor: &str, descriptor: &str,
) -> ( ) -> (
OfflineWallet<MemoryDatabase>, OfflineWallet<MemoryDatabase>,
(ExtendedDescriptor, Option<ExtendedDescriptor>), (String, Option<String>),
bitcoin::Txid, bitcoin::Txid,
) { ) {
let descriptors = testutils!(@descriptors (descriptor)); let descriptors = testutils!(@descriptors (descriptor));
let wallet: OfflineWallet<_> = Wallet::new_offline( let wallet: OfflineWallet<_> = Wallet::new_offline(
&descriptors.0.to_string(), &descriptors.0,
None, None,
Network::Regtest, Network::Regtest,
MemoryDatabase::new(), MemoryDatabase::new(),

327
src/wallet/signer.rs Normal file
View File

@ -0,0 +1,327 @@
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,
))
}
}

View File

@ -1,3 +1,5 @@
use miniscript::{MiniscriptKey, Satisfier};
// De-facto standard "dust limit" (even though it should change based on the output type) // De-facto standard "dust limit" (even though it should change based on the output type)
const DUST_LIMIT_SATOSHI: u64 = 546; const DUST_LIMIT_SATOSHI: u64 = 546;
@ -42,6 +44,61 @@ impl std::default::Default for FeeRate {
} }
} }
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> { pub struct ChunksIterator<I: Iterator> {
iter: I, iter: I,
size: usize, size: usize,

View File

@ -67,11 +67,11 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
#parsed_sig_ident() #parsed_sig_ident()
} }
fn get_wallet_from_descriptors(descriptors: &(ExtendedDescriptor, Option<ExtendedDescriptor>)) -> Wallet<#return_type, MemoryDatabase> { fn get_wallet_from_descriptors(descriptors: &(String, Option<String>)) -> 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() 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! { let descriptors = testutils! {
@descriptors ( "wpkh(Alice)" ) ( "wpkh(Alice)" ) ( @keys ( "Alice" => (@generate_xprv "/44'/0'/0'/0/*", "/44'/0'/0'/1/*") ) ) @descriptors ( "wpkh(Alice)" ) ( "wpkh(Alice)" ) ( @keys ( "Alice" => (@generate_xprv "/44'/0'/0'/0/*", "/44'/0'/0'/1/*") ) )
}; };
@ -90,6 +90,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
let tx = testutils! { let tx = testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) @tx ( (@external descriptors, 0) => 50_000 )
}; };
println!("{:?}", tx);
let txid = test_client.receive(tx); let txid = test_client.receive(tx);
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
@ -272,6 +273,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
#[serial] #[serial]
fn test_sync_after_send() { fn test_sync_after_send() {
let (wallet, descriptors, mut test_client) = init_single_sig(); let (wallet, descriptors, mut test_client) = init_single_sig();
println!("{}", descriptors.0);
let node_addr = test_client.get_node_address(None); let node_addr = test_client.get_node_address(None);
test_client.receive(testutils! { test_client.receive(testutils! {
@ -284,7 +286,9 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
let (psbt, details) = wallet.create_tx(TxBuilder::from_addressees(vec![(node_addr, 25_000)])).unwrap(); let (psbt, details) = wallet.create_tx(TxBuilder::from_addressees(vec![(node_addr, 25_000)])).unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
assert!(finalized, "Cannot finalize transaction"); 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(); wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), details.received); assert_eq!(wallet.get_balance().unwrap(), details.received);

View File

@ -94,10 +94,16 @@ impl TestIncomingTx {
#[macro_export] #[macro_export]
macro_rules! testutils { macro_rules! testutils {
( @external $descriptors:expr, $child:expr ) => ({ ( @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 ) => ({ ( @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) }); ( @e $descriptors:expr, $child:expr ) => ({ testutils!(@external $descriptors, $child) });
( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) }); ( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) });
@ -169,6 +175,8 @@ macro_rules! testutils {
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryInto; use std::convert::TryInto;
use miniscript::descriptor::{Descriptor, DescriptorPublicKey};
let mut keys: HashMap<&'static str, (String, Option<String>, Option<String>)> = HashMap::new(); let mut keys: HashMap<&'static str, (String, Option<String>, Option<String>)> = HashMap::new();
$( $(
keys = testutils!{ @keys $( $keys )* }; keys = testutils!{ @keys $( $keys )* };
@ -189,9 +197,9 @@ macro_rules! testutils {
} }
}).unwrap(); }).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(); let string_internal: Descriptor<String> = FromStr::from_str($internal_descriptor).unwrap();
@ -209,7 +217,7 @@ macro_rules! testutils {
} }
}).unwrap(); }).unwrap();
internal = Some(string_internal.try_into().unwrap()); internal = Some(string_internal.to_string());
)* )*
(external, internal) (external, internal)
@ -349,7 +357,6 @@ impl TestClient {
use bitcoin::blockdata::script::Builder; use bitcoin::blockdata::script::Builder;
use bitcoin::blockdata::transaction::{OutPoint, TxIn, TxOut}; use bitcoin::blockdata::transaction::{OutPoint, TxIn, TxOut};
use bitcoin::hash_types::{BlockHash, TxMerkleNode}; use bitcoin::hash_types::{BlockHash, TxMerkleNode};
use bitcoin::util::hash::BitcoinHash;
let block_template: serde_json::Value = self let block_template: serde_json::Value = self
.call("getblocktemplate", &[json!({"rules": ["segwit"]})]) .call("getblocktemplate", &[json!({"rules": ["segwit"]})])
@ -432,7 +439,7 @@ impl TestClient {
self.wait_for_block(height as usize); 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) { pub fn generate(&mut self, num_blocks: u64) {