Upgrade to rust-bitcoin 0.28 and miniscript 7.0

This commit is contained in:
Alekos Filini
2022-04-14 17:20:46 +02:00
parent 7aa2746c51
commit a16c18255c
14 changed files with 400 additions and 320 deletions

View File

@@ -52,10 +52,10 @@ use std::hash::{Hash, Hasher};
use std::ops::Deref;
use bitcoin::hashes::hash160;
use bitcoin::PublicKey;
use bitcoin::{PublicKey, XOnlyPublicKey};
use miniscript::{descriptor::Wildcard, Descriptor, DescriptorPublicKey};
use miniscript::{MiniscriptKey, ToPublicKey, TranslatePk};
use miniscript::descriptor::{DescriptorSinglePub, SinglePubKey, Wildcard};
use miniscript::{Descriptor, DescriptorPublicKey, MiniscriptKey, ToPublicKey, TranslatePk};
use crate::wallet::utils::SecpCtx;
@@ -128,21 +128,44 @@ impl<'s> MiniscriptKey for DerivedDescriptorKey<'s> {
fn is_uncompressed(&self) -> bool {
self.0.is_uncompressed()
}
fn serialized_len(&self) -> usize {
self.0.serialized_len()
}
}
impl<'s> ToPublicKey for DerivedDescriptorKey<'s> {
fn to_public_key(&self) -> PublicKey {
match &self.0 {
DescriptorPublicKey::SinglePub(ref spub) => spub.key.to_public_key(),
DescriptorPublicKey::XPub(ref xpub) => {
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: SinglePubKey::XOnly(_),
..
}) => panic!("Found x-only public key in non-tr descriptor"),
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: SinglePubKey::FullKey(ref pk),
..
}) => *pk,
DescriptorPublicKey::XPub(ref xpub) => PublicKey::new(
xpub.xkey
.derive_pub(self.1, &xpub.derivation_path)
.expect("Shouldn't fail, only normal derivations")
.public_key
}
.public_key,
),
}
}
fn to_x_only_pubkey(&self) -> XOnlyPublicKey {
match &self.0 {
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: SinglePubKey::XOnly(ref pk),
..
}) => *pk,
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: SinglePubKey::FullKey(ref pk),
..
}) => XOnlyPublicKey::from(pk.inner),
DescriptorPublicKey::XPub(ref xpub) => XOnlyPublicKey::from(
xpub.xkey
.derive_pub(self.1, &xpub.derivation_path)
.expect("Shouldn't fail, only normal derivations")
.public_key,
),
}
}

View File

@@ -1054,7 +1054,9 @@ mod test {
}
#[test]
#[should_panic(expected = "Miniscript(ContextError(CompressedOnly))")]
#[should_panic(
expected = "Miniscript(ContextError(CompressedOnly(\"04b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a87378ec38ff91d43e8c2092ebda601780485263da089465619e0358a5c1be7ac91f4\")))"
)]
fn test_dsl_miniscript_checks() {
let mut uncompressed_pk =
PrivateKey::from_wif("L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6").unwrap();

View File

@@ -17,9 +17,10 @@
use std::collections::{BTreeMap, HashMap, HashSet};
use std::ops::Deref;
use bitcoin::secp256k1;
use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource};
use bitcoin::util::psbt;
use bitcoin::{Network, PublicKey, Script, TxOut};
use bitcoin::{Network, Script, TxOut};
use miniscript::descriptor::{DescriptorType, InnerXKey};
pub use miniscript::{
@@ -58,7 +59,7 @@ pub type DerivedDescriptor<'s> = Descriptor<DerivedDescriptorKey<'s>>;
///
/// [`psbt::Input`]: bitcoin::util::psbt::Input
/// [`psbt::Output`]: bitcoin::util::psbt::Output
pub type HdKeyPaths = BTreeMap<PublicKey, KeySource>;
pub type HdKeyPaths = BTreeMap<secp256k1::PublicKey, KeySource>;
/// Trait for types which can be converted into an [`ExtendedDescriptor`] and a [`KeyMap`] usable by a wallet in a specific [`Network`]
pub trait IntoWalletDescriptor {
@@ -306,6 +307,7 @@ pub(crate) trait DerivedDescriptorMeta {
pub(crate) trait DescriptorMeta {
fn is_witness(&self) -> bool;
fn is_taproot(&self) -> bool;
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError>;
fn derive_from_hd_keypaths<'s>(
&self,
@@ -328,21 +330,21 @@ pub(crate) trait DescriptorScripts {
impl<'s> DescriptorScripts for DerivedDescriptor<'s> {
fn psbt_redeem_script(&self) -> Option<Script> {
match self.desc_type() {
DescriptorType::ShWpkh => Some(self.explicit_script()),
DescriptorType::ShWsh => Some(self.explicit_script().to_v0_p2wsh()),
DescriptorType::Sh => Some(self.explicit_script()),
DescriptorType::Bare => Some(self.explicit_script()),
DescriptorType::ShSortedMulti => Some(self.explicit_script()),
DescriptorType::ShWpkh => Some(self.explicit_script().unwrap()),
DescriptorType::ShWsh => Some(self.explicit_script().unwrap().to_v0_p2wsh()),
DescriptorType::Sh => Some(self.explicit_script().unwrap()),
DescriptorType::Bare => Some(self.explicit_script().unwrap()),
DescriptorType::ShSortedMulti => Some(self.explicit_script().unwrap()),
_ => None,
}
}
fn psbt_witness_script(&self) -> Option<Script> {
match self.desc_type() {
DescriptorType::Wsh => Some(self.explicit_script()),
DescriptorType::ShWsh => Some(self.explicit_script()),
DescriptorType::Wsh => Some(self.explicit_script().unwrap()),
DescriptorType::ShWsh => Some(self.explicit_script().unwrap()),
DescriptorType::WshSortedMulti | DescriptorType::ShWshSortedMulti => {
Some(self.explicit_script())
Some(self.explicit_script().unwrap())
}
_ => None,
}
@@ -362,6 +364,10 @@ impl DescriptorMeta for ExtendedDescriptor {
)
}
fn is_taproot(&self) -> bool {
self.desc_type() == DescriptorType::Tr
}
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError> {
let mut answer = Vec::new();
@@ -448,7 +454,10 @@ impl DescriptorMeta for ExtendedDescriptor {
let descriptor = self.as_derived_fixed(secp);
match descriptor.desc_type() {
// TODO: add pk() here
DescriptorType::Pkh | DescriptorType::Wpkh | DescriptorType::ShWpkh
DescriptorType::Pkh
| DescriptorType::Wpkh
| DescriptorType::ShWpkh
| DescriptorType::Tr
if utxo.is_some()
&& descriptor.script_pubkey() == utxo.as_ref().unwrap().script_pubkey =>
{
@@ -456,7 +465,7 @@ impl DescriptorMeta for ExtendedDescriptor {
}
DescriptorType::Bare | DescriptorType::Sh | DescriptorType::ShSortedMulti
if psbt_input.redeem_script.is_some()
&& &descriptor.explicit_script()
&& &descriptor.explicit_script().unwrap()
== psbt_input.redeem_script.as_ref().unwrap() =>
{
Some(descriptor)
@@ -466,7 +475,7 @@ impl DescriptorMeta for ExtendedDescriptor {
| DescriptorType::ShWshSortedMulti
| DescriptorType::WshSortedMulti
if psbt_input.witness_script.is_some()
&& &descriptor.explicit_script()
&& &descriptor.explicit_script().unwrap()
== psbt_input.witness_script.as_ref().unwrap() =>
{
Some(descriptor)

View File

@@ -43,23 +43,27 @@ use serde::ser::SerializeMap;
use serde::{Serialize, Serializer};
use bitcoin::hashes::*;
use bitcoin::secp256k1;
use bitcoin::util::bip32::Fingerprint;
use bitcoin::PublicKey;
use bitcoin::{PublicKey, XOnlyPublicKey};
use miniscript::descriptor::{DescriptorPublicKey, ShInner, SortedMultiVec, WshInner};
use miniscript::descriptor::{
DescriptorPublicKey, DescriptorSinglePub, ShInner, SinglePubKey, SortedMultiVec, WshInner,
};
use miniscript::{Descriptor, Miniscript, MiniscriptKey, Satisfier, ScriptContext, Terminal};
#[allow(unused_imports)]
use log::{debug, error, info, trace};
use crate::descriptor::ExtractPolicy;
use crate::keys::ExtScriptContext;
use crate::wallet::signer::{SignerId, SignersContainer};
use crate::wallet::utils::{self, After, Older, SecpCtx};
use super::checksum::get_checksum;
use super::error::Error;
use super::XKeyUtils;
use bitcoin::util::psbt::PartiallySignedTransaction as Psbt;
use bitcoin::util::psbt::{Input as PsbtInput, PartiallySignedTransaction as Psbt};
use miniscript::psbt::PsbtInputSatisfier;
/// Raw public key or extended key fingerprint
@@ -68,6 +72,8 @@ pub struct PkOrF {
#[serde(skip_serializing_if = "Option::is_none")]
pubkey: Option<PublicKey>,
#[serde(skip_serializing_if = "Option::is_none")]
x_only_pubkey: Option<XOnlyPublicKey>,
#[serde(skip_serializing_if = "Option::is_none")]
pubkey_hash: Option<hash160::Hash>,
#[serde(skip_serializing_if = "Option::is_none")]
fingerprint: Option<Fingerprint>,
@@ -76,8 +82,18 @@ pub struct PkOrF {
impl PkOrF {
fn from_key(k: &DescriptorPublicKey, secp: &SecpCtx) -> Self {
match k {
DescriptorPublicKey::SinglePub(pubkey) => PkOrF {
pubkey: Some(pubkey.key),
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: SinglePubKey::FullKey(pk),
..
}) => PkOrF {
pubkey: Some(*pk),
..Default::default()
},
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: SinglePubKey::XOnly(pk),
..
}) => PkOrF {
x_only_pubkey: Some(*pk),
..Default::default()
},
DescriptorPublicKey::XPub(xpub) => PkOrF {
@@ -93,10 +109,10 @@ impl PkOrF {
#[serde(tag = "type", rename_all = "UPPERCASE")]
pub enum SatisfiableItem {
// Leaves
/// Signature for a raw public key
Signature(PkOrF),
/// Signature for an extended key fingerprint
SignatureKey(PkOrF),
/// ECDSA Signature for a raw public key
EcdsaSignature(PkOrF),
/// Schnorr Signature for a raw public key
SchnorrSignature(PkOrF),
/// SHA256 preimage hash
Sha256Preimage {
/// The digest value
@@ -571,6 +587,7 @@ impl Policy {
build_sat: BuildSatisfaction,
threshold: usize,
sorted: bool,
is_ecdsa: bool,
secp: &SecpCtx,
) -> Result<Option<Policy>, PolicyError> {
if threshold == 0 {
@@ -599,7 +616,9 @@ impl Policy {
}
if let Some(psbt) = build_sat.psbt() {
if signature_in_psbt(psbt, key, secp) {
if is_ecdsa && ecdsa_signature_in_psbt(psbt, key, secp)
|| !is_ecdsa && schnorr_signature_in_psbt(psbt, key, secp)
{
satisfaction.add(
&Satisfaction::Complete {
condition: Default::default(),
@@ -715,7 +734,14 @@ impl From<SatisfiableItem> for Policy {
fn signer_id(key: &DescriptorPublicKey, secp: &SecpCtx) -> SignerId {
match key {
DescriptorPublicKey::SinglePub(pubkey) => pubkey.key.to_pubkeyhash().into(),
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: SinglePubKey::FullKey(pk),
..
}) => pk.to_pubkeyhash().into(),
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: SinglePubKey::XOnly(pk),
..
}) => pk.to_pubkeyhash().into(),
DescriptorPublicKey::XPub(xpub) => xpub.root_fingerprint(secp).into(),
}
}
@@ -726,7 +752,7 @@ fn signature(
build_sat: BuildSatisfaction,
secp: &SecpCtx,
) -> Policy {
let mut policy: Policy = SatisfiableItem::Signature(PkOrF::from_key(key, secp)).into();
let mut policy: Policy = SatisfiableItem::EcdsaSignature(PkOrF::from_key(key, secp)).into();
policy.contribution = if signers.find(signer_id(key, secp)).is_some() {
Satisfaction::Complete {
@@ -737,7 +763,7 @@ fn signature(
};
if let Some(psbt) = build_sat.psbt() {
policy.satisfaction = if signature_in_psbt(psbt, key, secp) {
policy.satisfaction = if ecdsa_signature_in_psbt(psbt, key, secp) {
Satisfaction::Complete {
condition: Default::default(),
}
@@ -749,10 +775,19 @@ fn signature(
policy
}
fn signature_in_psbt(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool {
fn generic_sig_in_psbt<
C: Fn(&PsbtInput, &SinglePubKey) -> bool,
M: Fn(&secp256k1::PublicKey) -> SinglePubKey,
>(
psbt: &Psbt,
key: &DescriptorPublicKey,
secp: &SecpCtx,
map: M,
check: C,
) -> bool {
//TODO check signature validity
psbt.inputs.iter().all(|input| match key {
DescriptorPublicKey::SinglePub(key) => input.partial_sigs.contains_key(&key.key),
DescriptorPublicKey::SinglePub(DescriptorSinglePub { key, .. }) => check(input, key),
DescriptorPublicKey::XPub(xpub) => {
let pubkey = input
.bip32_derivation
@@ -761,14 +796,49 @@ fn signature_in_psbt(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) ->
.map(|(p, _)| p);
//TODO check actual derivation matches
match pubkey {
Some(pubkey) => input.partial_sigs.contains_key(pubkey),
Some(pubkey) => check(input, &map(pubkey)),
None => false,
}
}
})
}
impl<Ctx: ScriptContext> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx> {
fn ecdsa_signature_in_psbt(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool {
generic_sig_in_psbt(
psbt,
key,
secp,
|pk| SinglePubKey::FullKey(PublicKey::new(*pk)),
|input, pk| match pk {
SinglePubKey::FullKey(pk) => input.partial_sigs.contains_key(pk),
_ => false,
},
)
}
fn schnorr_signature_in_psbt(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool {
generic_sig_in_psbt(
psbt,
key,
secp,
|pk| SinglePubKey::XOnly((*pk).into()),
|input, pk| {
let pk = match pk {
SinglePubKey::XOnly(pk) => pk,
_ => return false,
};
// This assumes the internal key is never used in the script leaves, which I think is
// reasonable
match &input.tap_internal_key {
Some(ik) if ik == pk => input.tap_key_sig.is_some(),
_ => input.tap_script_sigs.keys().any(|(sk, _)| sk == pk),
}
},
)
}
impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx> {
fn extract_policy(
&self,
signers: &SignersContainer,
@@ -840,9 +910,15 @@ impl<Ctx: ScriptContext> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx>
Terminal::Hash160(hash) => {
Some(SatisfiableItem::Hash160Preimage { hash: *hash }.into())
}
Terminal::Multi(k, pks) => {
Policy::make_multisig(pks, signers, build_sat, *k, false, secp)?
}
Terminal::Multi(k, pks) | Terminal::MultiA(k, pks) => Policy::make_multisig(
pks,
signers,
build_sat,
*k,
false,
!Ctx::as_enum().is_taproot(),
secp,
)?,
// Identities
Terminal::Alt(inner)
| Terminal::Swap(inner)
@@ -944,6 +1020,7 @@ impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
build_sat,
keys.k,
true,
true,
secp,
)?)
}
@@ -967,6 +1044,19 @@ impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
WshInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, build_sat, secp),
},
Descriptor::Bare(ms) => Ok(ms.as_inner().extract_policy(signers, build_sat, secp)?),
Descriptor::Tr(tr) => {
let mut items = vec![signature(tr.internal_key(), signers, build_sat, secp)];
items.append(
&mut tr
.iter_scripts()
.filter_map(|(_, ms)| {
ms.extract_policy(signers, build_sat, secp).transpose()
})
.collect::<Result<Vec<_>, _>>()?,
);
Ok(Policy::make_thresh(items, 1)?)
}
}
}
}
@@ -978,7 +1068,7 @@ mod test {
use super::*;
use crate::descriptor::derived::AsDerived;
use crate::descriptor::policy::SatisfiableItem::{Multisig, Signature, Thresh};
use crate::descriptor::policy::SatisfiableItem::{EcdsaSignature, Multisig, Thresh};
use crate::keys::{DescriptorKey, IntoDescriptorKey};
use crate::wallet::signer::SignersContainer;
use bitcoin::secp256k1::Secp256k1;
@@ -1000,7 +1090,7 @@ mod test {
) -> (DescriptorKey<Ctx>, DescriptorKey<Ctx>, Fingerprint) {
let path = bip32::DerivationPath::from_str(path).unwrap();
let tprv = bip32::ExtendedPrivKey::from_str(tprv).unwrap();
let tpub = bip32::ExtendedPubKey::from_private(secp, &tprv);
let tpub = bip32::ExtendedPubKey::from_priv(secp, &tprv);
let fingerprint = tprv.fingerprint(secp);
let prvkey = (tprv, path.clone()).into_descriptor_key().unwrap();
let pubkey = (tpub, path).into_descriptor_key().unwrap();
@@ -1026,7 +1116,7 @@ mod test {
.unwrap();
assert!(
matches!(&policy.item, Signature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
matches!(&policy.item, EcdsaSignature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
);
assert!(matches!(&policy.contribution, Satisfaction::None));
@@ -1041,7 +1131,7 @@ mod test {
.unwrap();
assert!(
matches!(&policy.item, Signature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
matches!(&policy.item, EcdsaSignature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
);
assert!(
matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
@@ -1194,7 +1284,7 @@ mod test {
.unwrap();
assert!(
matches!(&policy.item, Signature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
matches!(&policy.item, EcdsaSignature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
);
assert!(matches!(&policy.contribution, Satisfaction::None));
@@ -1210,7 +1300,7 @@ mod test {
.unwrap();
assert!(
matches!(&policy.item, Signature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
matches!(&policy.item, EcdsaSignature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
);
assert!(
matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)