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:
parent
ddc2bded99
commit
5777431135
@ -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"] }
|
||||||
|
@ -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();
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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
|
||||||
})
|
})
|
||||||
|
@ -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"));
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)?)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
src/error.rs
13
src/error.rs
@ -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);
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
277
src/psbt/mod.rs
277
src/psbt/mod.rs
@ -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)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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(
|
||||||
|
@ -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
327
src/wallet/signer.rs
Normal 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,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
@ -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);
|
||||||
|
@ -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) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user