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"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
authors = ["Riccardo Casatta <riccardo@casatta.it>", "Alekos Filini <alekos.filini@gmail.com>"]
|
||||
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
|
||||
|
||||
[dependencies]
|
||||
magical-macros = { path = "./macros" }
|
||||
@ -25,6 +25,10 @@ rocksdb = { version = "0.14", optional = true }
|
||||
socks = { version = "0.3", optional = true }
|
||||
lazy_static = { version = "1.4", optional = true }
|
||||
|
||||
[patch.crates-io]
|
||||
bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin/", rev = "478e091" }
|
||||
miniscript = { git = "https://github.com/MagicalBitcoin/rust-miniscript", branch = "descriptor-public-key" }
|
||||
|
||||
# Platform-specific dependencies
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
tokio = { version = "0.2", features = ["rt-core"] }
|
||||
|
@ -1,8 +1,9 @@
|
||||
extern crate magical_bitcoin_wallet;
|
||||
extern crate serde_json;
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use magical_bitcoin_wallet::bitcoin::util::bip32::ChildNumber;
|
||||
use magical_bitcoin_wallet::bitcoin::*;
|
||||
use magical_bitcoin_wallet::descriptor::*;
|
||||
|
||||
@ -14,13 +15,14 @@ fn main() {
|
||||
and_v(vc:pk_h(cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy),older(1000))\
|
||||
))";
|
||||
|
||||
let extended_desc = ExtendedDescriptor::from_str(desc).unwrap();
|
||||
let (extended_desc, key_map) = ExtendedDescriptor::parse_secret(desc).unwrap();
|
||||
println!("{:?}", extended_desc);
|
||||
|
||||
let policy = extended_desc.extract_policy().unwrap();
|
||||
let signers = Arc::new(key_map.into());
|
||||
let policy = extended_desc.extract_policy(signers).unwrap();
|
||||
println!("policy: {}", serde_json::to_string(&policy).unwrap());
|
||||
|
||||
let derived_desc = extended_desc.derive(42).unwrap();
|
||||
let derived_desc = extended_desc.derive(&[ChildNumber::from_normal_idx(42).unwrap()]);
|
||||
println!("{:?}", derived_desc);
|
||||
|
||||
let addr = derived_desc.address(Network::Testnet).unwrap();
|
||||
|
@ -7,7 +7,7 @@ use std::sync::{Arc, Mutex};
|
||||
use log::{debug, error, info, trace};
|
||||
|
||||
use bitcoin::network::message_blockdata::Inventory;
|
||||
use bitcoin::{BitcoinHash, OutPoint, Transaction, Txid};
|
||||
use bitcoin::{OutPoint, Transaction, Txid};
|
||||
|
||||
use rocksdb::{Options, SliceTransform, DB};
|
||||
|
||||
@ -274,7 +274,7 @@ impl OnlineBlockchain for CompactFiltersBlockchain {
|
||||
|
||||
let block_height = headers.get_height_for(block_hash)?.unwrap_or(0);
|
||||
let saved_correct_block = match headers.get_full_block(block_height)? {
|
||||
Some(block) if &block.bitcoin_hash() == block_hash => true,
|
||||
Some(block) if &block.block_hash() == block_hash => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
|
@ -16,7 +16,6 @@ use bitcoin::hash_types::FilterHash;
|
||||
use bitcoin::hashes::hex::FromHex;
|
||||
use bitcoin::hashes::{sha256d, Hash};
|
||||
use bitcoin::util::bip158::BlockFilter;
|
||||
use bitcoin::util::hash::BitcoinHash;
|
||||
use bitcoin::util::uint::Uint256;
|
||||
use bitcoin::Block;
|
||||
use bitcoin::BlockHash;
|
||||
@ -257,7 +256,7 @@ impl ChainStore<Full> {
|
||||
);
|
||||
batch.put_cf(
|
||||
cf_handle,
|
||||
StoreEntry::BlockHeaderIndex(Some(genesis.bitcoin_hash())).get_key(),
|
||||
StoreEntry::BlockHeaderIndex(Some(genesis.block_hash())).get_key(),
|
||||
&0usize.to_be_bytes(),
|
||||
);
|
||||
store.write(batch)?;
|
||||
@ -290,7 +289,7 @@ impl ChainStore<Full> {
|
||||
.get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(index)).get_key())?
|
||||
.unwrap(),
|
||||
)?;
|
||||
answer.push((header.bitcoin_hash(), index));
|
||||
answer.push((header.block_hash(), index));
|
||||
|
||||
if let Some(new_index) = index.checked_sub(step) {
|
||||
index = new_index;
|
||||
@ -322,7 +321,7 @@ impl ChainStore<Full> {
|
||||
let mut batch = WriteBatch::default();
|
||||
batch.put_cf(
|
||||
new_cf_handle,
|
||||
StoreEntry::BlockHeaderIndex(Some(header.bitcoin_hash())).get_key(),
|
||||
StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
|
||||
&from.to_be_bytes(),
|
||||
);
|
||||
batch.put_cf(
|
||||
@ -406,7 +405,7 @@ impl ChainStore<Full> {
|
||||
|
||||
batch.delete_cf(
|
||||
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| {
|
||||
let (header, _): (BlockHeader, Uint256) =
|
||||
deserialize(&data).map_err(|_| CompactFiltersError::DataCorruption)?;
|
||||
Ok::<_, CompactFiltersError>(header.bitcoin_hash())
|
||||
Ok::<_, CompactFiltersError>(header.block_hash())
|
||||
})
|
||||
.transpose()?)
|
||||
}
|
||||
@ -574,7 +573,7 @@ impl<T: StoreType> ChainStore<T> {
|
||||
.map(|(_, v)| -> Result<_, CompactFiltersError> {
|
||||
let (header, _): (BlockHeader, Uint256) = SerializeDb::deserialize(&v)?;
|
||||
|
||||
Ok(header.bitcoin_hash())
|
||||
Ok(header.block_hash())
|
||||
})
|
||||
.transpose()?)
|
||||
}
|
||||
@ -593,7 +592,7 @@ impl<T: StoreType> ChainStore<T> {
|
||||
.get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(from)).get_key())?
|
||||
.map(|result| {
|
||||
let (header, work): (BlockHeader, Uint256) = SerializeDb::deserialize(&result)?;
|
||||
Ok::<_, CompactFiltersError>((header.bitcoin_hash(), work))
|
||||
Ok::<_, CompactFiltersError>((header.block_hash(), work))
|
||||
})
|
||||
.transpose()?
|
||||
.ok_or(CompactFiltersError::DataCorruption)?;
|
||||
@ -603,13 +602,13 @@ impl<T: StoreType> ChainStore<T> {
|
||||
return Err(CompactFiltersError::InvalidHeaders);
|
||||
}
|
||||
|
||||
last_hash = header.bitcoin_hash();
|
||||
last_hash = header.block_hash();
|
||||
accumulated_work = accumulated_work + header.work();
|
||||
|
||||
let height = from + index + 1;
|
||||
batch.put_cf(
|
||||
cf_handle,
|
||||
StoreEntry::BlockHeaderIndex(Some(header.bitcoin_hash())).get_key(),
|
||||
StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
|
||||
&(height).to_be_bytes(),
|
||||
);
|
||||
batch.put_cf(
|
||||
@ -647,8 +646,8 @@ pub struct FilterHeader {
|
||||
filter_hash: FilterHash,
|
||||
}
|
||||
|
||||
impl BitcoinHash<FilterHeaderHash> for FilterHeader {
|
||||
fn bitcoin_hash(&self) -> FilterHeaderHash {
|
||||
impl FilterHeader {
|
||||
fn header_hash(&self) -> FilterHeaderHash {
|
||||
let mut hash_data = self.filter_hash.into_inner().to_vec();
|
||||
hash_data.extend_from_slice(&self.prev_header_hash);
|
||||
sha256d::Hash::hash(&hash_data).into()
|
||||
@ -794,7 +793,7 @@ impl CFStore {
|
||||
prev_header_hash: last_hash,
|
||||
filter_hash,
|
||||
};
|
||||
last_hash = filter_header.bitcoin_hash();
|
||||
last_hash = filter_header.header_hash();
|
||||
|
||||
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;
|
||||
use std::convert::{Into, TryFrom};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use bitcoin::hashes::{hash160, Hash};
|
||||
use bitcoin::secp256k1::{All, Secp256k1};
|
||||
use bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey, Fingerprint};
|
||||
use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
|
||||
use bitcoin::{PrivateKey, PublicKey, Script};
|
||||
use bitcoin::hashes::hash160;
|
||||
use bitcoin::secp256k1::Secp256k1;
|
||||
use bitcoin::util::bip32::{ChildNumber, DerivationPath, Fingerprint};
|
||||
use bitcoin::util::psbt;
|
||||
use bitcoin::{PublicKey, Script, TxOut};
|
||||
|
||||
use miniscript::descriptor::{DescriptorPublicKey, DescriptorXKey, InnerXKey};
|
||||
pub use miniscript::{
|
||||
Descriptor, Legacy, Miniscript, MiniscriptKey, ScriptContext, Segwitv0, Terminal,
|
||||
Descriptor, Legacy, Miniscript, MiniscriptKey, ScriptContext, Segwitv0, Terminal, ToPublicKey,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::psbt::utils::PSBTUtils;
|
||||
|
||||
pub mod checksum;
|
||||
pub mod error;
|
||||
pub mod extended_key;
|
||||
mod keys;
|
||||
pub mod policy;
|
||||
|
||||
// use crate::wallet::utils::AddressType;
|
||||
use crate::wallet::signer::SignersContainer;
|
||||
|
||||
pub use self::checksum::get_checksum;
|
||||
use self::error::Error;
|
||||
pub use self::extended_key::{DerivationIndex, DescriptorExtendedKey};
|
||||
pub use self::policy::Policy;
|
||||
|
||||
use self::keys::Key;
|
||||
pub type ExtendedDescriptor = Descriptor<DescriptorPublicKey>;
|
||||
type HDKeyPaths = BTreeMap<PublicKey, (Fingerprint, DerivationPath)>;
|
||||
|
||||
trait MiniscriptExtractPolicy {
|
||||
pub trait ExtractPolicy {
|
||||
fn extract_policy(
|
||||
&self,
|
||||
lookup_map: &BTreeMap<String, Box<dyn Key>>,
|
||||
signers: Arc<SignersContainer<DescriptorPublicKey>>,
|
||||
) -> Result<Option<Policy>, Error>;
|
||||
}
|
||||
|
||||
pub trait ExtractPolicy {
|
||||
fn extract_policy(&self) -> Result<Option<Policy>, Error>;
|
||||
pub trait XKeyUtils {
|
||||
fn full_path(&self, append: &[ChildNumber]) -> DerivationPath;
|
||||
fn root_fingerprint(&self) -> Fingerprint;
|
||||
}
|
||||
|
||||
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)]
|
||||
@ -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)]
|
||||
mod test {
|
||||
use std::str::FromStr;
|
||||
|
||||
use bitcoin::consensus::encode::deserialize;
|
||||
use bitcoin::hashes::hex::FromHex;
|
||||
use bitcoin::{Network, PublicKey};
|
||||
use bitcoin::util::psbt;
|
||||
|
||||
use crate::descriptor::*;
|
||||
|
||||
macro_rules! hex_fingerprint {
|
||||
($hex:expr) => {
|
||||
Fingerprint::from_hex($hex).unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! hex_pubkey {
|
||||
($hex:expr) => {
|
||||
PublicKey::from_str($hex).unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! deriv_path {
|
||||
($str:expr) => {
|
||||
DerivationPath::from_str($str).unwrap()
|
||||
};
|
||||
|
||||
() => {
|
||||
DerivationPath::from(vec![])
|
||||
};
|
||||
}
|
||||
use super::*;
|
||||
use crate::psbt::PSBTUtils;
|
||||
|
||||
#[test]
|
||||
fn test_descriptor_parse_wif() {
|
||||
let string = "pkh(cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy)";
|
||||
let desc = ExtendedDescriptor::from_str(string).unwrap();
|
||||
assert!(desc.is_fixed());
|
||||
assert_eq!(
|
||||
desc.derive(0)
|
||||
.unwrap()
|
||||
.address(Network::Testnet)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx"
|
||||
);
|
||||
assert_eq!(
|
||||
desc.derive(42)
|
||||
.unwrap()
|
||||
.address(Network::Testnet)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx"
|
||||
);
|
||||
assert_eq!(
|
||||
desc.get_secret_keys().into_iter().collect::<Vec<_>>().len(),
|
||||
1
|
||||
);
|
||||
}
|
||||
fn test_derive_from_psbt_input_wpkh() {
|
||||
let psbt: psbt::PartiallySignedTransaction = deserialize(&Vec::<u8>::from_hex("70736274ff010052010000000162307be8e431fbaff807cdf9cdc3fde44d740211bc8342c31ffd6ec11fe35bcc0100000000ffffffff01328601000000000016001493ce48570b55c42c2af816aeaba06cfee1224fae000000000001011fa08601000000000016001493ce48570b55c42c2af816aeaba06cfee1224fae010304010000000000").unwrap()).unwrap();
|
||||
|
||||
#[test]
|
||||
fn test_descriptor_parse_pubkey() {
|
||||
let string = "pkh(039b6347398505f5ec93826dc61c19f47c66c0283ee9be980e29ce325a0f4679ef)";
|
||||
let desc = ExtendedDescriptor::from_str(string).unwrap();
|
||||
assert!(desc.is_fixed());
|
||||
assert_eq!(
|
||||
desc.derive(0)
|
||||
.unwrap()
|
||||
.address(Network::Testnet)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx"
|
||||
);
|
||||
assert_eq!(
|
||||
desc.derive(42)
|
||||
.unwrap()
|
||||
.address(Network::Testnet)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"mqwpxxvfv3QbM8PU8uBx2jaNt9btQqvQNx"
|
||||
);
|
||||
assert_eq!(
|
||||
desc.get_secret_keys().into_iter().collect::<Vec<_>>().len(),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_descriptor_parse_xpub() {
|
||||
let string = "pkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/*)";
|
||||
let desc = ExtendedDescriptor::from_str(string).unwrap();
|
||||
assert!(!desc.is_fixed());
|
||||
assert_eq!(
|
||||
desc.derive(0)
|
||||
.unwrap()
|
||||
.address(Network::Testnet)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"mxbXpnVkwARGtYXk5yeGYf59bGWuPpdE4X"
|
||||
);
|
||||
assert_eq!(
|
||||
desc.derive(42)
|
||||
.unwrap()
|
||||
.address(Network::Testnet)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
"mhtuS1QaEV4HPcK4bWk4Wvpd64SUjiC5Zt"
|
||||
);
|
||||
assert_eq!(desc.get_xprv().into_iter().collect::<Vec<_>>().len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "KeyParsingError")]
|
||||
fn test_descriptor_parse_fail() {
|
||||
let string = "pkh(this_is_not_a_valid_key)";
|
||||
ExtendedDescriptor::from_str(string).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_descriptor_hd_keypaths() {
|
||||
let string = "pkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/*)";
|
||||
let desc = ExtendedDescriptor::from_str(string).unwrap();
|
||||
let keypaths = desc.get_hd_keypaths(0).unwrap();
|
||||
assert!(keypaths.contains_key(&hex_pubkey!(
|
||||
"025d5fc65ebb8d44a5274b53bac21ff8307fec2334a32df05553459f8b1f7fe1b6"
|
||||
)));
|
||||
assert_eq!(
|
||||
keypaths.get(&hex_pubkey!(
|
||||
"025d5fc65ebb8d44a5274b53bac21ff8307fec2334a32df05553459f8b1f7fe1b6"
|
||||
)),
|
||||
Some(&(hex_fingerprint!("31a507b8"), deriv_path!("m/0")))
|
||||
let descriptor = Descriptor::<DescriptorPublicKey>::from_str(
|
||||
"wpkh(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = descriptor.derive_from_psbt_input(&psbt.inputs[0], psbt.get_utxo_for(0));
|
||||
println!("{:?}", result);
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,26 @@
|
||||
use std::cmp::max;
|
||||
use std::collections::{BTreeMap, HashSet, VecDeque};
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde::ser::SerializeMap;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use bitcoin::hashes::*;
|
||||
use bitcoin::secp256k1::Secp256k1;
|
||||
use bitcoin::util::bip32::Fingerprint;
|
||||
use bitcoin::PublicKey;
|
||||
|
||||
use miniscript::{Descriptor, Miniscript, ScriptContext, Terminal};
|
||||
use miniscript::descriptor::DescriptorPublicKey;
|
||||
use miniscript::{Descriptor, Miniscript, MiniscriptKey, ScriptContext, Terminal};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use log::{debug, error, info, trace};
|
||||
|
||||
use crate::descriptor::ExtractPolicy;
|
||||
use crate::wallet::signer::{SignerId, SignersContainer};
|
||||
|
||||
use super::checksum::get_checksum;
|
||||
use super::error::Error;
|
||||
use crate::descriptor::{Key, MiniscriptExtractPolicy};
|
||||
use crate::psbt::PSBTSatisfier;
|
||||
use super::XKeyUtils;
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize)]
|
||||
pub struct PKOrF {
|
||||
@ -30,21 +33,24 @@ pub struct PKOrF {
|
||||
}
|
||||
|
||||
impl PKOrF {
|
||||
fn from_key(k: &Box<dyn Key>) -> Self {
|
||||
let secp = Secp256k1::gen_new();
|
||||
fn from_key(k: &DescriptorPublicKey) -> Self {
|
||||
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();
|
||||
if let Some(fing) = k.fingerprint(&secp) {
|
||||
fn from_key_hash(k: hash160::Hash) -> Self {
|
||||
PKOrF {
|
||||
fingerprint: Some(fing),
|
||||
pubkey_hash: Some(k),
|
||||
..Default::default()
|
||||
}
|
||||
} else {
|
||||
PKOrF {
|
||||
pubkey: Some(pubkey),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -445,14 +451,15 @@ impl Policy {
|
||||
}
|
||||
|
||||
fn make_multisig(
|
||||
keys: Vec<Option<&Box<dyn Key>>>,
|
||||
keys: &Vec<DescriptorPublicKey>,
|
||||
signers: Arc<SignersContainer<DescriptorPublicKey>>,
|
||||
threshold: usize,
|
||||
) -> Result<Option<Policy>, PolicyError> {
|
||||
if threshold == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let parsed_keys = keys.iter().map(|k| PKOrF::from_key(k.unwrap())).collect();
|
||||
let parsed_keys = keys.iter().map(|k| PKOrF::from_key(k)).collect();
|
||||
|
||||
let mut contribution = Satisfaction::Partial {
|
||||
n: keys.len(),
|
||||
@ -461,14 +468,14 @@ impl Policy {
|
||||
conditions: Default::default(),
|
||||
};
|
||||
for (index, key) in keys.iter().enumerate() {
|
||||
let val = if key.is_some() && key.unwrap().has_secret() {
|
||||
Satisfaction::Complete {
|
||||
if let Some(_) = signers.find(signer_id(key)) {
|
||||
contribution.add(
|
||||
&Satisfaction::Complete {
|
||||
condition: Default::default(),
|
||||
},
|
||||
index,
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
Satisfaction::None
|
||||
};
|
||||
contribution.add(&val, index)?;
|
||||
}
|
||||
contribution.finalize()?;
|
||||
|
||||
@ -482,15 +489,6 @@ impl 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 {
|
||||
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> {
|
||||
key.map(|k| {
|
||||
let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key(k)).into();
|
||||
policy.contribution = if k.has_secret() {
|
||||
fn signer_id(key: &DescriptorPublicKey) -> SignerId<DescriptorPublicKey> {
|
||||
match key {
|
||||
DescriptorPublicKey::PubKey(pubkey) => pubkey.to_pubkeyhash().into(),
|
||||
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 {
|
||||
condition: Default::default(),
|
||||
}
|
||||
@ -578,48 +586,33 @@ fn signature_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> {
|
||||
};
|
||||
|
||||
policy
|
||||
})
|
||||
}
|
||||
|
||||
fn signature_key_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> {
|
||||
let secp = Secp256k1::gen_new();
|
||||
fn signature_key(
|
||||
key_hash: &<DescriptorPublicKey as MiniscriptKey>::Hash,
|
||||
signers: Arc<SignersContainer<DescriptorPublicKey>>,
|
||||
) -> Policy {
|
||||
let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key_hash(*key_hash)).into();
|
||||
|
||||
key.map(|k| {
|
||||
let pubkey = k.as_public_key(&secp, None).unwrap();
|
||||
let mut policy: Policy = if let Some(fing) = k.fingerprint(&secp) {
|
||||
SatisfiableItem::SignatureKey(PKOrF {
|
||||
fingerprint: Some(fing),
|
||||
..Default::default()
|
||||
})
|
||||
} else {
|
||||
SatisfiableItem::SignatureKey(PKOrF {
|
||||
pubkey_hash: Some(hash160::Hash::hash(&pubkey.to_bytes())),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
.into();
|
||||
policy.contribution = if k.has_secret() {
|
||||
Satisfaction::Complete {
|
||||
if let Some(_) = signers.find(SignerId::PkHash(*key_hash)) {
|
||||
policy.contribution = Satisfaction::Complete {
|
||||
condition: Default::default(),
|
||||
}
|
||||
} else {
|
||||
Satisfaction::None
|
||||
};
|
||||
}
|
||||
|
||||
policy
|
||||
})
|
||||
}
|
||||
|
||||
impl<Ctx: ScriptContext> MiniscriptExtractPolicy for Miniscript<String, Ctx> {
|
||||
impl<Ctx: ScriptContext> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx> {
|
||||
fn extract_policy(
|
||||
&self,
|
||||
lookup_map: &BTreeMap<String, Box<dyn Key>>,
|
||||
signers: Arc<SignersContainer<DescriptorPublicKey>>,
|
||||
) -> Result<Option<Policy>, Error> {
|
||||
Ok(match &self.node {
|
||||
// Leaves
|
||||
Terminal::True | Terminal::False => None,
|
||||
Terminal::PkK(pubkey) => signature_from_string(lookup_map.get(pubkey)),
|
||||
Terminal::PkH(pubkey_hash) => signature_key_from_string(lookup_map.get(pubkey_hash)),
|
||||
Terminal::PkK(pubkey) => Some(signature(pubkey, Arc::clone(&signers))),
|
||||
Terminal::PkH(pubkey_hash) => Some(signature_key(pubkey_hash, Arc::clone(&signers))),
|
||||
Terminal::After(value) => {
|
||||
let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into();
|
||||
policy.contribution = Satisfaction::Complete {
|
||||
@ -652,9 +645,7 @@ impl<Ctx: ScriptContext> MiniscriptExtractPolicy for Miniscript<String, Ctx> {
|
||||
Terminal::Hash160(hash) => {
|
||||
Some(SatisfiableItem::HASH160Preimage { hash: *hash }.into())
|
||||
}
|
||||
Terminal::Multi(k, pks) => {
|
||||
Policy::make_multisig(pks.iter().map(|s| lookup_map.get(s)).collect(), *k)?
|
||||
}
|
||||
Terminal::Multi(k, pks) => Policy::make_multisig(pks, Arc::clone(&signers), *k)?,
|
||||
// Identities
|
||||
Terminal::Alt(inner)
|
||||
| Terminal::Swap(inner)
|
||||
@ -662,26 +653,31 @@ impl<Ctx: ScriptContext> MiniscriptExtractPolicy for Miniscript<String, Ctx> {
|
||||
| Terminal::DupIf(inner)
|
||||
| Terminal::Verify(inner)
|
||||
| Terminal::NonZero(inner)
|
||||
| Terminal::ZeroNotEqual(inner) => inner.extract_policy(lookup_map)?,
|
||||
| Terminal::ZeroNotEqual(inner) => inner.extract_policy(Arc::clone(&signers))?,
|
||||
// Complex policies
|
||||
Terminal::AndV(a, b) | Terminal::AndB(a, b) => {
|
||||
Policy::make_and(a.extract_policy(lookup_map)?, b.extract_policy(lookup_map)?)?
|
||||
}
|
||||
Terminal::AndV(a, b) | Terminal::AndB(a, b) => Policy::make_and(
|
||||
a.extract_policy(Arc::clone(&signers))?,
|
||||
b.extract_policy(Arc::clone(&signers))?,
|
||||
)?,
|
||||
Terminal::AndOr(x, y, z) => Policy::make_or(
|
||||
Policy::make_and(x.extract_policy(lookup_map)?, y.extract_policy(lookup_map)?)?,
|
||||
z.extract_policy(lookup_map)?,
|
||||
Policy::make_and(
|
||||
x.extract_policy(Arc::clone(&signers))?,
|
||||
y.extract_policy(Arc::clone(&signers))?,
|
||||
)?,
|
||||
z.extract_policy(Arc::clone(&signers))?,
|
||||
)?,
|
||||
Terminal::OrB(a, b)
|
||||
| Terminal::OrD(a, b)
|
||||
| Terminal::OrC(a, b)
|
||||
| Terminal::OrI(a, b) => {
|
||||
Policy::make_or(a.extract_policy(lookup_map)?, b.extract_policy(lookup_map)?)?
|
||||
}
|
||||
| Terminal::OrI(a, b) => Policy::make_or(
|
||||
a.extract_policy(Arc::clone(&signers))?,
|
||||
b.extract_policy(Arc::clone(&signers))?,
|
||||
)?,
|
||||
Terminal::Thresh(k, nodes) => {
|
||||
let mut threshold = *k;
|
||||
let mapped: Vec<_> = nodes
|
||||
.iter()
|
||||
.map(|n| n.extract_policy(lookup_map))
|
||||
.map(|n| n.extract_policy(Arc::clone(&signers)))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.into_iter()
|
||||
.filter_map(|x| x)
|
||||
@ -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(
|
||||
&self,
|
||||
lookup_map: &BTreeMap<String, Box<dyn Key>>,
|
||||
signers: Arc<SignersContainer<DescriptorPublicKey>>,
|
||||
) -> Result<Option<Policy>, Error> {
|
||||
match self {
|
||||
Descriptor::Pk(pubkey)
|
||||
| Descriptor::Pkh(pubkey)
|
||||
| Descriptor::Wpkh(pubkey)
|
||||
| Descriptor::ShWpkh(pubkey) => Ok(signature_from_string(lookup_map.get(pubkey))),
|
||||
Descriptor::Bare(inner) | Descriptor::Sh(inner) => {
|
||||
Ok(inner.extract_policy(lookup_map)?)
|
||||
}
|
||||
Descriptor::Wsh(inner) | Descriptor::ShWsh(inner) => {
|
||||
Ok(inner.extract_policy(lookup_map)?)
|
||||
}
|
||||
| Descriptor::ShWpkh(pubkey) => Ok(Some(signature(pubkey, signers))),
|
||||
Descriptor::Bare(inner) | Descriptor::Sh(inner) => Ok(inner.extract_policy(signers)?),
|
||||
Descriptor::Wsh(inner) | Descriptor::ShWsh(inner) => Ok(inner.extract_policy(signers)?),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
13
src/error.rs
13
src/error.rs
@ -1,4 +1,4 @@
|
||||
use bitcoin::{Address, OutPoint, Script, Txid};
|
||||
use bitcoin::{Address, OutPoint};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@ -25,13 +25,7 @@ pub enum Error {
|
||||
SpendingPolicyRequired,
|
||||
InvalidPolicyPathError(crate::descriptor::policy::PolicyError),
|
||||
|
||||
// Signing errors (expected, received)
|
||||
InputTxidMismatch((Txid, OutPoint)),
|
||||
InputRedeemScriptMismatch((Script, Script)), // scriptPubKey, redeemScript
|
||||
InputWitnessScriptMismatch((Script, Script)), // scriptPubKey, redeemScript
|
||||
InputUnknownSegwitScript(Script),
|
||||
InputMissingWitnessScript(usize),
|
||||
MissingUTXO,
|
||||
Signer(crate::wallet::signer::SignerError),
|
||||
|
||||
// Blockchain interface errors
|
||||
Uncapable(crate::blockchain::Capability),
|
||||
@ -44,6 +38,7 @@ pub enum Error {
|
||||
Descriptor(crate::descriptor::error::Error),
|
||||
|
||||
Encode(bitcoin::consensus::encode::Error),
|
||||
Miniscript(miniscript::Error),
|
||||
BIP32(bitcoin::util::bip32::Error),
|
||||
Secp256k1(bitcoin::secp256k1::Error),
|
||||
JSON(serde_json::Error),
|
||||
@ -75,8 +70,10 @@ impl_error!(
|
||||
crate::descriptor::policy::PolicyError,
|
||||
InvalidPolicyPathError
|
||||
);
|
||||
impl_error!(crate::wallet::signer::SignerError, Signer);
|
||||
|
||||
impl_error!(bitcoin::consensus::encode::Error, Encode);
|
||||
impl_error!(miniscript::Error, Miniscript);
|
||||
impl_error!(bitcoin::util::bip32::Error, BIP32);
|
||||
impl_error!(bitcoin::secp256k1::Error, Secp256k1);
|
||||
impl_error!(serde_json::Error, JSON);
|
||||
|
@ -47,7 +47,6 @@ pub mod blockchain;
|
||||
pub mod database;
|
||||
pub mod descriptor;
|
||||
pub mod psbt;
|
||||
pub mod signer;
|
||||
pub mod types;
|
||||
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};
|
||||
use bitcoin::util::bip143::SighashComponents;
|
||||
use bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey, Fingerprint};
|
||||
use bitcoin::util::psbt;
|
||||
use bitcoin::{PrivateKey, PublicKey, Script, SigHashType, Transaction};
|
||||
|
||||
use bitcoin::secp256k1::{self, All, Message, Secp256k1};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use log::{debug, error, info, trace};
|
||||
|
||||
use miniscript::{BitcoinSig, MiniscriptKey, Satisfier};
|
||||
|
||||
use crate::descriptor::ExtendedDescriptor;
|
||||
use crate::error::Error;
|
||||
use crate::signer::Signer;
|
||||
|
||||
pub mod utils;
|
||||
|
||||
pub struct PSBTSatisfier<'a> {
|
||||
input: &'a psbt::Input,
|
||||
assume_height_reached: bool,
|
||||
create_height: Option<u32>,
|
||||
current_height: Option<u32>,
|
||||
pub trait PSBTUtils {
|
||||
fn get_utxo_for(&self, input_index: usize) -> Option<TxOut>;
|
||||
}
|
||||
|
||||
impl<'a> PSBTSatisfier<'a> {
|
||||
pub fn new(
|
||||
input: &'a psbt::Input,
|
||||
assume_height_reached: bool,
|
||||
create_height: Option<u32>,
|
||||
current_height: Option<u32>,
|
||||
) -> Self {
|
||||
PSBTSatisfier {
|
||||
input,
|
||||
assume_height_reached,
|
||||
create_height,
|
||||
current_height,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PSBTUtils for PSBT {
|
||||
fn get_utxo_for(&self, input_index: usize) -> Option<TxOut> {
|
||||
let tx = &self.global.unsigned_tx;
|
||||
|
||||
impl<'a> PSBTSatisfier<'a> {
|
||||
fn parse_sig(rawsig: &Vec<u8>) -> Option<BitcoinSig> {
|
||||
let (flag, sig) = rawsig.split_last().unwrap();
|
||||
let flag = bitcoin::SigHashType::from_u32(*flag as u32);
|
||||
let sig = match secp256k1::Signature::from_der(sig) {
|
||||
Ok(sig) => sig,
|
||||
Err(..) => return None,
|
||||
};
|
||||
Some((sig, flag))
|
||||
if input_index >= tx.input.len() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: also support hash preimages through the "unknown" section of PSBT
|
||||
impl<'a> Satisfier<bitcoin::PublicKey> for PSBTSatisfier<'a> {
|
||||
// from https://docs.rs/miniscript/0.12.0/src/miniscript/psbt/mod.rs.html#96
|
||||
fn lookup_sig(&self, pk: &bitcoin::PublicKey) -> Option<BitcoinSig> {
|
||||
debug!("lookup_sig: {}", pk);
|
||||
|
||||
if let Some(rawsig) = self.input.partial_sigs.get(pk) {
|
||||
Self::parse_sig(&rawsig)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
include_blockheight: bool,
|
||||
) -> Result<Self, &'static str> {
|
||||
let descriptor = wallet.descriptor.as_ref().to_string();
|
||||
let descriptor = wallet
|
||||
.descriptor
|
||||
.to_string_with_secret(&wallet.signers.as_key_map());
|
||||
Self::is_compatible_with_core(&descriptor)?;
|
||||
|
||||
let blockheight = match wallet.database.borrow().iter_txs(false) {
|
||||
@ -62,7 +64,7 @@ impl WalletExport {
|
||||
!= wallet
|
||||
.change_descriptor
|
||||
.as_ref()
|
||||
.map(|d| d.as_ref().to_string())
|
||||
.map(|d| d.to_string_with_secret(&wallet.change_signers.as_key_map()))
|
||||
{
|
||||
return Err("Incompatible change descriptor");
|
||||
}
|
||||
@ -193,14 +195,14 @@ mod test {
|
||||
#[test]
|
||||
fn test_export_multi() {
|
||||
let descriptor = "wsh(multi(2,\
|
||||
[73756c7f/48h/0h/0h/2h]tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*,\
|
||||
[f9f62194/48h/0h/0h/2h]tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/0/*,\
|
||||
[c98b1535/48h/0h/0h/2h]tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/0/*\
|
||||
[73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*,\
|
||||
[f9f62194/48'/0'/0'/2']tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/0/*,\
|
||||
[c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/0/*\
|
||||
))";
|
||||
let change_descriptor = "wsh(multi(2,\
|
||||
[73756c7f/48h/0h/0h/2h]tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/1/*,\
|
||||
[f9f62194/48h/0h/0h/2h]tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/1/*,\
|
||||
[c98b1535/48h/0h/0h/2h]tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
|
||||
[73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/1/*,\
|
||||
[f9f62194/48'/0'/0'/2']tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/1/*,\
|
||||
[c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
|
||||
))";
|
||||
|
||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
|
@ -2,17 +2,14 @@ use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use bitcoin::blockdata::opcodes;
|
||||
use bitcoin::blockdata::script::Builder;
|
||||
use bitcoin::consensus::encode::serialize;
|
||||
use bitcoin::util::bip32::ChildNumber;
|
||||
use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
|
||||
use bitcoin::{
|
||||
Address, Network, OutPoint, PublicKey, Script, SigHashType, Transaction, TxOut, Txid,
|
||||
};
|
||||
use bitcoin::{Address, Network, OutPoint, Script, SigHashType, Transaction, TxOut, Txid};
|
||||
|
||||
use miniscript::BitcoinSig;
|
||||
use miniscript::descriptor::DescriptorPublicKey;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use log::{debug, error, info, trace};
|
||||
@ -20,19 +17,23 @@ use log::{debug, error, info, trace};
|
||||
pub mod coin_selection;
|
||||
pub mod export;
|
||||
mod rbf;
|
||||
pub mod signer;
|
||||
pub mod time;
|
||||
pub mod tx_builder;
|
||||
pub mod utils;
|
||||
|
||||
use signer::{Signer, SignersContainer};
|
||||
use tx_builder::TxBuilder;
|
||||
use utils::{FeeRate, IsDust};
|
||||
use utils::{After, FeeRate, IsDust, Older};
|
||||
|
||||
use crate::blockchain::{Blockchain, OfflineBlockchain, OnlineBlockchain, Progress};
|
||||
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
|
||||
use crate::descriptor::{get_checksum, DescriptorMeta, ExtendedDescriptor, ExtractPolicy, Policy};
|
||||
use crate::descriptor::{
|
||||
get_checksum, DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, Policy,
|
||||
};
|
||||
use crate::error::Error;
|
||||
use crate::psbt::{utils::PSBTUtils, PSBTSatisfier, PSBTSigner};
|
||||
use crate::signer::Signer;
|
||||
use crate::psbt::PSBTUtils;
|
||||
// use crate::psbt::{utils::PSBTUtils, PSBTSatisfier, PSBTSigner};
|
||||
use crate::types::*;
|
||||
|
||||
const CACHE_ADDR_BATCH_SIZE: u32 = 100;
|
||||
@ -42,6 +43,10 @@ pub type OfflineWallet<D> = Wallet<OfflineBlockchain, D>;
|
||||
pub struct Wallet<B: Blockchain, D: BatchDatabase> {
|
||||
descriptor: ExtendedDescriptor,
|
||||
change_descriptor: Option<ExtendedDescriptor>,
|
||||
|
||||
signers: Arc<SignersContainer<DescriptorPublicKey>>,
|
||||
change_signers: Arc<SignersContainer<DescriptorPublicKey>>,
|
||||
|
||||
network: Network,
|
||||
|
||||
current_height: Option<u32>,
|
||||
@ -66,27 +71,32 @@ where
|
||||
ScriptType::External,
|
||||
get_checksum(descriptor)?.as_bytes(),
|
||||
)?;
|
||||
let descriptor = ExtendedDescriptor::from_str(descriptor)?;
|
||||
let change_descriptor = match change_descriptor {
|
||||
let (descriptor, keymap) = ExtendedDescriptor::parse_secret(descriptor)?;
|
||||
let signers = Arc::new(SignersContainer::from(keymap));
|
||||
let (change_descriptor, change_signers) = match change_descriptor {
|
||||
Some(desc) => {
|
||||
database.check_descriptor_checksum(
|
||||
ScriptType::Internal,
|
||||
get_checksum(desc)?.as_bytes(),
|
||||
)?;
|
||||
|
||||
let parsed = ExtendedDescriptor::from_str(desc)?;
|
||||
if !parsed.same_structure(descriptor.as_ref()) {
|
||||
return Err(Error::DifferentDescriptorStructure);
|
||||
}
|
||||
let (change_descriptor, change_keymap) = ExtendedDescriptor::parse_secret(desc)?;
|
||||
let change_signers = Arc::new(SignersContainer::from(change_keymap));
|
||||
// if !parsed.same_structure(descriptor.as_ref()) {
|
||||
// return Err(Error::DifferentDescriptorStructure);
|
||||
// }
|
||||
|
||||
Some(parsed)
|
||||
(Some(change_descriptor), change_signers)
|
||||
}
|
||||
None => None,
|
||||
None => (None, Arc::new(SignersContainer::new())),
|
||||
};
|
||||
|
||||
Ok(Wallet {
|
||||
descriptor,
|
||||
change_descriptor,
|
||||
signers,
|
||||
change_signers,
|
||||
|
||||
network,
|
||||
|
||||
current_height: None,
|
||||
@ -100,7 +110,7 @@ where
|
||||
let index = self.fetch_and_increment_index(ScriptType::External)?;
|
||||
|
||||
self.descriptor
|
||||
.derive(index)?
|
||||
.derive(&[ChildNumber::from_normal_idx(index).unwrap()])
|
||||
.address(self.network)
|
||||
.ok_or(Error::ScriptDoesntHaveAddressForm)
|
||||
}
|
||||
@ -133,7 +143,10 @@ where
|
||||
}
|
||||
|
||||
// TODO: fetch both internal and external policies
|
||||
let policy = self.descriptor.extract_policy()?.unwrap();
|
||||
let policy = self
|
||||
.descriptor
|
||||
.extract_policy(Arc::clone(&self.signers))?
|
||||
.unwrap();
|
||||
if policy.requires_path() && builder.policy_path.is_none() {
|
||||
return Err(Error::SpendingPolicyRequired);
|
||||
}
|
||||
@ -220,10 +233,10 @@ where
|
||||
// TODO: use the right weight instead of the maximum, and only fall-back to it if the
|
||||
// script is unknown in the database
|
||||
let input_witness_weight = std::cmp::max(
|
||||
self.get_descriptor_for(ScriptType::Internal)
|
||||
self.get_descriptor_for_script_type(ScriptType::Internal)
|
||||
.0
|
||||
.max_satisfaction_weight(),
|
||||
self.get_descriptor_for(ScriptType::External)
|
||||
self.get_descriptor_for_script_type(ScriptType::External)
|
||||
.0
|
||||
.max_satisfaction_weight(),
|
||||
);
|
||||
@ -369,7 +382,7 @@ where
|
||||
// `get_deget_descriptor_for` to find what's the ScriptType for `Internal`
|
||||
// addresses really is, because if there's no change_descriptor it's actually equal
|
||||
// to "External"
|
||||
let (_, change_type) = self.get_descriptor_for(ScriptType::Internal);
|
||||
let (_, change_type) = self.get_descriptor_for_script_type(ScriptType::Internal);
|
||||
match self
|
||||
.database
|
||||
.borrow()
|
||||
@ -435,10 +448,10 @@ where
|
||||
// TODO: use the right weight instead of the maximum, and only fall-back to it if the
|
||||
// script is unknown in the database
|
||||
let input_witness_weight = std::cmp::max(
|
||||
self.get_descriptor_for(ScriptType::Internal)
|
||||
self.get_descriptor_for_script_type(ScriptType::Internal)
|
||||
.0
|
||||
.max_satisfaction_weight(),
|
||||
self.get_descriptor_for(ScriptType::External)
|
||||
self.get_descriptor_for_script_type(ScriptType::External)
|
||||
.0
|
||||
.max_satisfaction_weight(),
|
||||
);
|
||||
@ -545,139 +558,11 @@ where
|
||||
// this helps us doing our job later
|
||||
self.add_input_hd_keypaths(&mut psbt)?;
|
||||
|
||||
let tx = &psbt.global.unsigned_tx;
|
||||
|
||||
let mut signer = PSBTSigner::from_descriptor(&psbt.global.unsigned_tx, &self.descriptor)?;
|
||||
if let Some(desc) = &self.change_descriptor {
|
||||
let change_signer = PSBTSigner::from_descriptor(&psbt.global.unsigned_tx, desc)?;
|
||||
signer.extend(change_signer)?;
|
||||
for index in 0..psbt.inputs.len() {
|
||||
self.signers.sign(&mut psbt, index)?;
|
||||
if self.change_descriptor.is_some() {
|
||||
self.change_signers.sign(&mut psbt, index)?;
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -688,9 +573,13 @@ where
|
||||
|
||||
pub fn policies(&self, script_type: ScriptType) -> Result<Option<Policy>, Error> {
|
||||
match (script_type, self.change_descriptor.as_ref()) {
|
||||
(ScriptType::External, _) => Ok(self.descriptor.extract_policy()?),
|
||||
(ScriptType::External, _) => {
|
||||
Ok(self.descriptor.extract_policy(Arc::clone(&self.signers))?)
|
||||
}
|
||||
(ScriptType::Internal, None) => Ok(None),
|
||||
(ScriptType::Internal, Some(desc)) => Ok(desc.extract_policy()?),
|
||||
(ScriptType::Internal, Some(desc)) => {
|
||||
Ok(desc.extract_policy(Arc::clone(&self.change_signers))?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -699,9 +588,9 @@ where
|
||||
script_type: ScriptType,
|
||||
) -> Result<Option<ExtendedDescriptor>, Error> {
|
||||
match (script_type, self.change_descriptor.as_ref()) {
|
||||
(ScriptType::External, _) => Ok(Some(self.descriptor.as_public_version()?)),
|
||||
(ScriptType::External, _) => Ok(Some(self.descriptor.clone())),
|
||||
(ScriptType::Internal, None) => Ok(None),
|
||||
(ScriptType::Internal, Some(desc)) => Ok(Some(desc.as_public_version()?)),
|
||||
(ScriptType::Internal, Some(desc)) => Ok(Some(desc.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
@ -712,18 +601,7 @@ where
|
||||
) -> Result<bool, Error> {
|
||||
let mut tx = psbt.global.unsigned_tx.clone();
|
||||
|
||||
for (n, input) in tx.input.iter_mut().enumerate() {
|
||||
// safe to run only on the descriptor because we assume the change descriptor also has
|
||||
// the same structure
|
||||
let desc = self.descriptor.derive_from_psbt_input(psbt, n);
|
||||
debug!("{:?}", psbt.inputs[n].hd_keypaths);
|
||||
debug!("reconstructed descriptor is {:?}", desc);
|
||||
|
||||
let desc = match desc {
|
||||
Err(_) => return Ok(false),
|
||||
Ok(desc) => desc,
|
||||
};
|
||||
|
||||
for (n, (input, psbt_input)) in tx.input.iter_mut().zip(psbt.inputs.iter()).enumerate() {
|
||||
// if the height is None in the database it means it's still unconfirmed, so consider
|
||||
// that as a very high value
|
||||
let create_height = self
|
||||
@ -738,10 +616,43 @@ where
|
||||
n, input.previous_output, create_height, current_height
|
||||
);
|
||||
|
||||
let satisfier =
|
||||
PSBTSatisfier::new(&psbt.inputs[n], false, create_height, current_height);
|
||||
// - Try to derive the descriptor by looking at the txout. If it's in our database, we
|
||||
// know exactly which `script_type` to use, and which derivation index it is
|
||||
// - If that fails, try to derive it by looking at the psbt input: the complete logic
|
||||
// is in `src/descriptor/mod.rs`, but it will basically look at `hd_keypaths`,
|
||||
// `redeem_script` and `witness_script` to determine the right derivation
|
||||
// - If that also fails, it will try it on the internal descriptor, if present
|
||||
let desc = if let Some(desc) = psbt
|
||||
.get_utxo_for(n)
|
||||
.map(|txout| self.get_descriptor_for_txout(&txout))
|
||||
.transpose()?
|
||||
.flatten()
|
||||
{
|
||||
desc
|
||||
} else if let Some(desc) = self
|
||||
.descriptor
|
||||
.derive_from_psbt_input(psbt_input, psbt.get_utxo_for(n))
|
||||
{
|
||||
desc
|
||||
} else if let Some(desc) = self
|
||||
.change_descriptor
|
||||
.as_ref()
|
||||
.and_then(|desc| desc.derive_from_psbt_input(psbt_input, psbt.get_utxo_for(n)))
|
||||
{
|
||||
desc
|
||||
} else {
|
||||
debug!("Couldn't find the right derived descriptor for input {}", n);
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
match desc.satisfy(input, satisfier) {
|
||||
match desc.satisfy(
|
||||
input,
|
||||
(
|
||||
psbt_input.clone(),
|
||||
After::new(current_height, false),
|
||||
Older::new(current_height, create_height, false),
|
||||
),
|
||||
) {
|
||||
Ok(_) => continue,
|
||||
Err(e) => {
|
||||
debug!("satisfy error {:?} for input {}", e, n);
|
||||
@ -761,7 +672,10 @@ where
|
||||
|
||||
// Internals
|
||||
|
||||
fn get_descriptor_for(&self, script_type: ScriptType) -> (&ExtendedDescriptor, ScriptType) {
|
||||
fn get_descriptor_for_script_type(
|
||||
&self,
|
||||
script_type: ScriptType,
|
||||
) -> (&ExtendedDescriptor, ScriptType) {
|
||||
let desc = match script_type {
|
||||
ScriptType::Internal if self.change_descriptor.is_some() => (
|
||||
self.change_descriptor.as_ref().unwrap(),
|
||||
@ -773,25 +687,26 @@ where
|
||||
desc
|
||||
}
|
||||
|
||||
fn to_p2pkh(&self, pubkey_hash: &[u8]) -> Script {
|
||||
Builder::new()
|
||||
.push_opcode(opcodes::all::OP_DUP)
|
||||
.push_opcode(opcodes::all::OP_HASH160)
|
||||
.push_slice(pubkey_hash)
|
||||
.push_opcode(opcodes::all::OP_EQUALVERIFY)
|
||||
.push_opcode(opcodes::all::OP_CHECKSIG)
|
||||
.into_script()
|
||||
fn get_descriptor_for_txout(&self, txout: &TxOut) -> Result<Option<ExtendedDescriptor>, Error> {
|
||||
Ok(self
|
||||
.database
|
||||
.borrow()
|
||||
.get_path_from_script_pubkey(&txout.script_pubkey)?
|
||||
.map(|(script_type, child)| (self.get_descriptor_for_script_type(script_type).0, child))
|
||||
.map(|(desc, child)| desc.derive(&[ChildNumber::from_normal_idx(child).unwrap()])))
|
||||
}
|
||||
|
||||
fn get_change_address(&self) -> Result<Script, Error> {
|
||||
let (desc, script_type) = self.get_descriptor_for(ScriptType::Internal);
|
||||
let (desc, script_type) = self.get_descriptor_for_script_type(ScriptType::Internal);
|
||||
let index = self.fetch_and_increment_index(script_type)?;
|
||||
|
||||
Ok(desc.derive(index)?.script_pubkey())
|
||||
Ok(desc
|
||||
.derive(&[ChildNumber::from_normal_idx(index).unwrap()])
|
||||
.script_pubkey())
|
||||
}
|
||||
|
||||
fn fetch_and_increment_index(&self, script_type: ScriptType) -> Result<u32, Error> {
|
||||
let (descriptor, script_type) = self.get_descriptor_for(script_type);
|
||||
let (descriptor, script_type) = self.get_descriptor_for_script_type(script_type);
|
||||
let index = match descriptor.is_fixed() {
|
||||
true => 0,
|
||||
false => self
|
||||
@ -818,7 +733,7 @@ where
|
||||
from: u32,
|
||||
mut count: u32,
|
||||
) -> Result<(), Error> {
|
||||
let (descriptor, script_type) = self.get_descriptor_for(script_type);
|
||||
let (descriptor, script_type) = self.get_descriptor_for_script_type(script_type);
|
||||
if descriptor.is_fixed() {
|
||||
if from > 0 {
|
||||
return Ok(());
|
||||
@ -832,7 +747,9 @@ where
|
||||
let start_time = time::Instant::new();
|
||||
for i in from..(from + count) {
|
||||
address_batch.set_script_pubkey(
|
||||
&descriptor.derive(i)?.script_pubkey(),
|
||||
&descriptor
|
||||
.derive(&[ChildNumber::from_normal_idx(i).unwrap()])
|
||||
.script_pubkey(),
|
||||
script_type,
|
||||
i,
|
||||
)?;
|
||||
@ -924,9 +841,9 @@ where
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let (desc, _) = self.get_descriptor_for(script_type);
|
||||
let (desc, _) = self.get_descriptor_for_script_type(script_type);
|
||||
psbt_input.hd_keypaths = desc.get_hd_keypaths(child)?;
|
||||
let derived_descriptor = desc.derive(child)?;
|
||||
let derived_descriptor = desc.derive(&[ChildNumber::from_normal_idx(child).unwrap()]);
|
||||
|
||||
psbt_input.redeem_script = derived_descriptor.psbt_redeem_script();
|
||||
psbt_input.witness_script = derived_descriptor.psbt_witness_script();
|
||||
@ -957,7 +874,7 @@ where
|
||||
.borrow()
|
||||
.get_path_from_script_pubkey(&tx_output.script_pubkey)?
|
||||
{
|
||||
let (desc, _) = self.get_descriptor_for(script_type);
|
||||
let (desc, _) = self.get_descriptor_for_script_type(script_type);
|
||||
psbt_output.hd_keypaths = desc.get_hd_keypaths(child)?;
|
||||
}
|
||||
}
|
||||
@ -982,7 +899,7 @@ where
|
||||
debug!("Found descriptor {:?}/{}", script_type, child);
|
||||
|
||||
// merge hd_keypaths
|
||||
let (desc, _) = self.get_descriptor_for(script_type);
|
||||
let (desc, _) = self.get_descriptor_for_script_type(script_type);
|
||||
let mut hd_keypaths = desc.get_hd_keypaths(child)?;
|
||||
psbt_input.hd_keypaths.append(&mut hd_keypaths);
|
||||
}
|
||||
@ -1086,13 +1003,12 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use bitcoin::Network;
|
||||
use std::str::FromStr;
|
||||
|
||||
use miniscript::Descriptor;
|
||||
use bitcoin::Network;
|
||||
|
||||
use crate::database::memory::MemoryDatabase;
|
||||
use crate::database::Database;
|
||||
use crate::descriptor::ExtendedDescriptor;
|
||||
use crate::types::ScriptType;
|
||||
|
||||
use super::*;
|
||||
@ -1205,12 +1121,12 @@ mod test {
|
||||
descriptor: &str,
|
||||
) -> (
|
||||
OfflineWallet<MemoryDatabase>,
|
||||
(ExtendedDescriptor, Option<ExtendedDescriptor>),
|
||||
(String, Option<String>),
|
||||
bitcoin::Txid,
|
||||
) {
|
||||
let descriptors = testutils!(@descriptors (descriptor));
|
||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
&descriptors.0.to_string(),
|
||||
&descriptors.0,
|
||||
None,
|
||||
Network::Regtest,
|
||||
MemoryDatabase::new(),
|
||||
|
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)
|
||||
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> {
|
||||
iter: I,
|
||||
size: usize,
|
||||
|
@ -67,11 +67,11 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
|
||||
#parsed_sig_ident()
|
||||
}
|
||||
|
||||
fn get_wallet_from_descriptors(descriptors: &(ExtendedDescriptor, Option<ExtendedDescriptor>)) -> Wallet<#return_type, MemoryDatabase> {
|
||||
Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref().map(|d| d.to_string()).as_deref(), Network::Regtest, MemoryDatabase::new(), get_blockchain()).unwrap()
|
||||
fn get_wallet_from_descriptors(descriptors: &(String, Option<String>)) -> Wallet<#return_type, MemoryDatabase> {
|
||||
Wallet::new(&descriptors.0.to_string(), descriptors.1.as_deref(), Network::Regtest, MemoryDatabase::new(), get_blockchain()).unwrap()
|
||||
}
|
||||
|
||||
fn init_single_sig() -> (Wallet<#return_type, MemoryDatabase>, (ExtendedDescriptor, Option<ExtendedDescriptor>), TestClient) {
|
||||
fn init_single_sig() -> (Wallet<#return_type, MemoryDatabase>, (String, Option<String>), TestClient) {
|
||||
let descriptors = testutils! {
|
||||
@descriptors ( "wpkh(Alice)" ) ( "wpkh(Alice)" ) ( @keys ( "Alice" => (@generate_xprv "/44'/0'/0'/0/*", "/44'/0'/0'/1/*") ) )
|
||||
};
|
||||
@ -90,6 +90,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
|
||||
let tx = testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000 )
|
||||
};
|
||||
println!("{:?}", tx);
|
||||
let txid = test_client.receive(tx);
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
@ -272,6 +273,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
|
||||
#[serial]
|
||||
fn test_sync_after_send() {
|
||||
let (wallet, descriptors, mut test_client) = init_single_sig();
|
||||
println!("{}", descriptors.0);
|
||||
let node_addr = test_client.get_node_address(None);
|
||||
|
||||
test_client.receive(testutils! {
|
||||
@ -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, finalized) = wallet.sign(psbt, None).unwrap();
|
||||
assert!(finalized, "Cannot finalize transaction");
|
||||
wallet.broadcast(psbt.extract_tx()).unwrap();
|
||||
let tx = psbt.extract_tx();
|
||||
println!("{}", bitcoin::consensus::encode::serialize_hex(&tx));
|
||||
wallet.broadcast(tx).unwrap();
|
||||
|
||||
wallet.sync(noop_progress(), None).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), details.received);
|
||||
|
@ -94,10 +94,16 @@ impl TestIncomingTx {
|
||||
#[macro_export]
|
||||
macro_rules! testutils {
|
||||
( @external $descriptors:expr, $child:expr ) => ({
|
||||
$descriptors.0.derive($child).expect("Derivation error").address(bitcoin::Network::Regtest).expect("No address form")
|
||||
use miniscript::descriptor::{Descriptor, DescriptorPublicKey};
|
||||
|
||||
let parsed = Descriptor::<DescriptorPublicKey>::parse_secret(&$descriptors.0).expect("Failed to parse descriptor in `testutils!(@external)`").0;
|
||||
parsed.derive(&[bitcoin::util::bip32::ChildNumber::from_normal_idx($child).unwrap()]).address(bitcoin::Network::Regtest).expect("No address form")
|
||||
});
|
||||
( @internal $descriptors:expr, $child:expr ) => ({
|
||||
$descriptors.1.expect("Missing internal descriptor").derive($child).expect("Derivation error").address(bitcoin::Network::Regtest).expect("No address form")
|
||||
use miniscript::descriptor::{Descriptor, DescriptorPublicKey};
|
||||
|
||||
let parsed = Descriptor::<DescriptorPublicKey>::parse_secret(&$descriptors.1.expect("Missing internal descriptor")).expect("Failed to parse descriptor in `testutils!(@internal)`").0;
|
||||
parsed.derive(&[bitcoin::util::bip32::ChildNumber::from_normal_idx($child).unwrap()]).address(bitcoin::Network::Regtest).expect("No address form")
|
||||
});
|
||||
( @e $descriptors:expr, $child:expr ) => ({ testutils!(@external $descriptors, $child) });
|
||||
( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) });
|
||||
@ -169,6 +175,8 @@ macro_rules! testutils {
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
|
||||
use miniscript::descriptor::{Descriptor, DescriptorPublicKey};
|
||||
|
||||
let mut keys: HashMap<&'static str, (String, Option<String>, Option<String>)> = HashMap::new();
|
||||
$(
|
||||
keys = testutils!{ @keys $( $keys )* };
|
||||
@ -189,9 +197,9 @@ macro_rules! testutils {
|
||||
}
|
||||
|
||||
}).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();
|
||||
|
||||
@ -209,7 +217,7 @@ macro_rules! testutils {
|
||||
}
|
||||
|
||||
}).unwrap();
|
||||
internal = Some(string_internal.try_into().unwrap());
|
||||
internal = Some(string_internal.to_string());
|
||||
)*
|
||||
|
||||
(external, internal)
|
||||
@ -349,7 +357,6 @@ impl TestClient {
|
||||
use bitcoin::blockdata::script::Builder;
|
||||
use bitcoin::blockdata::transaction::{OutPoint, TxIn, TxOut};
|
||||
use bitcoin::hash_types::{BlockHash, TxMerkleNode};
|
||||
use bitcoin::util::hash::BitcoinHash;
|
||||
|
||||
let block_template: serde_json::Value = self
|
||||
.call("getblocktemplate", &[json!({"rules": ["segwit"]})])
|
||||
@ -432,7 +439,7 @@ impl TestClient {
|
||||
|
||||
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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user