policy: Build SatisfiableItem::*Signature based on the context

Also refactor our code to lookup signatures in PSBTs to use the context
This commit is contained in:
Alekos Filini 2022-05-24 11:24:48 +02:00
parent ff1abc63e0
commit 572c3ee70d
No known key found for this signature in database
GPG Key ID: 431401E4A4530061
2 changed files with 123 additions and 66 deletions

View File

@ -572,13 +572,12 @@ impl Policy {
Ok(Some(policy)) Ok(Some(policy))
} }
fn make_multisig( fn make_multisig<Ctx: ScriptContext + 'static>(
keys: &[DescriptorPublicKey], keys: &[DescriptorPublicKey],
signers: &SignersContainer, signers: &SignersContainer,
build_sat: BuildSatisfaction, build_sat: BuildSatisfaction,
threshold: usize, threshold: usize,
sorted: bool, sorted: bool,
is_ecdsa: bool,
secp: &SecpCtx, secp: &SecpCtx,
) -> Result<Option<Policy>, PolicyError> { ) -> Result<Option<Policy>, PolicyError> {
if threshold == 0 { if threshold == 0 {
@ -607,9 +606,7 @@ impl Policy {
} }
if let Some(psbt) = build_sat.psbt() { if let Some(psbt) = build_sat.psbt() {
if is_ecdsa && ecdsa_signature_in_psbt(psbt, key, secp) if Ctx::find_signature(psbt, key, secp) {
|| !is_ecdsa && schnorr_signature_in_psbt(psbt, key, secp)
{
satisfaction.add( satisfaction.add(
&Satisfaction::Complete { &Satisfaction::Complete {
condition: Default::default(), condition: Default::default(),
@ -737,13 +734,15 @@ fn signer_id(key: &DescriptorPublicKey, secp: &SecpCtx) -> SignerId {
} }
} }
fn signature( fn make_generic_signature<M: Fn() -> SatisfiableItem, F: Fn(&Psbt) -> bool>(
key: &DescriptorPublicKey, key: &DescriptorPublicKey,
signers: &SignersContainer, signers: &SignersContainer,
build_sat: BuildSatisfaction, build_sat: BuildSatisfaction,
secp: &SecpCtx, secp: &SecpCtx,
make_policy: M,
find_sig: F,
) -> Policy { ) -> Policy {
let mut policy: Policy = SatisfiableItem::EcdsaSignature(PkOrF::from_key(key, secp)).into(); let mut policy: Policy = make_policy().into();
policy.contribution = if signers.find(signer_id(key, secp)).is_some() { policy.contribution = if signers.find(signer_id(key, secp)).is_some() {
Satisfaction::Complete { Satisfaction::Complete {
@ -754,7 +753,7 @@ fn signature(
}; };
if let Some(psbt) = build_sat.psbt() { if let Some(psbt) = build_sat.psbt() {
policy.satisfaction = if ecdsa_signature_in_psbt(psbt, key, secp) { policy.satisfaction = if find_sig(psbt) {
Satisfaction::Complete { Satisfaction::Complete {
condition: Default::default(), condition: Default::default(),
} }
@ -794,39 +793,78 @@ fn generic_sig_in_psbt<
}) })
} }
fn ecdsa_signature_in_psbt(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool { trait SigExt: ScriptContext {
generic_sig_in_psbt( fn make_signature(
psbt, key: &DescriptorPublicKey,
key, signers: &SignersContainer,
secp, build_sat: BuildSatisfaction,
|pk| SinglePubKey::FullKey(PublicKey::new(*pk)), secp: &SecpCtx,
|input, pk| match pk { ) -> Policy;
SinglePubKey::FullKey(pk) => input.partial_sigs.contains_key(pk),
_ => false, fn find_signature(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool;
},
)
} }
fn schnorr_signature_in_psbt(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool { impl<T: ScriptContext + 'static> SigExt for T {
generic_sig_in_psbt( fn make_signature(
psbt, key: &DescriptorPublicKey,
key, signers: &SignersContainer,
secp, build_sat: BuildSatisfaction,
|pk| SinglePubKey::XOnly((*pk).into()), secp: &SecpCtx,
|input, pk| { ) -> Policy {
let pk = match pk { if T::as_enum().is_taproot() {
SinglePubKey::XOnly(pk) => pk, make_generic_signature(
_ => return false, key,
}; signers,
build_sat,
secp,
|| SatisfiableItem::SchnorrSignature(PkOrF::from_key(key, secp)),
|psbt| Self::find_signature(psbt, key, secp),
)
} else {
make_generic_signature(
key,
signers,
build_sat,
secp,
|| SatisfiableItem::EcdsaSignature(PkOrF::from_key(key, secp)),
|psbt| Self::find_signature(psbt, key, secp),
)
}
}
// This assumes the internal key is never used in the script leaves, which I think is fn find_signature(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool {
// reasonable if T::as_enum().is_taproot() {
match &input.tap_internal_key { generic_sig_in_psbt(
Some(ik) if ik == pk => input.tap_key_sig.is_some(), psbt,
_ => input.tap_script_sigs.keys().any(|(sk, _)| sk == pk), key,
} secp,
}, |pk| SinglePubKey::XOnly((*pk).into()),
) |input, pk| {
let pk = match pk {
SinglePubKey::XOnly(pk) => pk,
_ => return false,
};
if input.tap_internal_key == Some(*pk) && input.tap_key_sig.is_some() {
true
} else {
input.tap_script_sigs.keys().any(|(sk, _)| sk == pk)
}
},
)
} else {
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,
},
)
}
}
} }
impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx> { impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx> {
@ -839,8 +877,10 @@ impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublic
Ok(match &self.node { Ok(match &self.node {
// Leaves // Leaves
Terminal::True | Terminal::False => None, Terminal::True | Terminal::False => None,
Terminal::PkK(pubkey) => Some(signature(pubkey, signers, build_sat, secp)), Terminal::PkK(pubkey) => Some(Ctx::make_signature(pubkey, signers, build_sat, secp)),
Terminal::PkH(pubkey_hash) => Some(signature(pubkey_hash, signers, build_sat, secp)), Terminal::PkH(pubkey_hash) => {
Some(Ctx::make_signature(pubkey_hash, signers, build_sat, secp))
}
Terminal::After(value) => { Terminal::After(value) => {
let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into(); let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into();
policy.contribution = Satisfaction::Complete { policy.contribution = Satisfaction::Complete {
@ -901,15 +941,9 @@ impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublic
Terminal::Hash160(hash) => { Terminal::Hash160(hash) => {
Some(SatisfiableItem::Hash160Preimage { hash: *hash }.into()) Some(SatisfiableItem::Hash160Preimage { hash: *hash }.into())
} }
Terminal::Multi(k, pks) | Terminal::MultiA(k, pks) => Policy::make_multisig( Terminal::Multi(k, pks) | Terminal::MultiA(k, pks) => {
pks, Policy::make_multisig::<Ctx>(pks, signers, build_sat, *k, false, secp)?
signers, }
build_sat,
*k,
false,
!Ctx::as_enum().is_taproot(),
secp,
)?,
// Identities // Identities
Terminal::Alt(inner) Terminal::Alt(inner)
| Terminal::Swap(inner) | Terminal::Swap(inner)
@ -999,28 +1033,42 @@ impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
build_sat: BuildSatisfaction, build_sat: BuildSatisfaction,
secp: &SecpCtx, secp: &SecpCtx,
) -> Result<Option<Policy>, Error> { ) -> Result<Option<Policy>, Error> {
fn make_sortedmulti<Ctx: ScriptContext>( fn make_sortedmulti<Ctx: ScriptContext + 'static>(
keys: &SortedMultiVec<DescriptorPublicKey, Ctx>, keys: &SortedMultiVec<DescriptorPublicKey, Ctx>,
signers: &SignersContainer, signers: &SignersContainer,
build_sat: BuildSatisfaction, build_sat: BuildSatisfaction,
secp: &SecpCtx, secp: &SecpCtx,
) -> Result<Option<Policy>, Error> { ) -> Result<Option<Policy>, Error> {
Ok(Policy::make_multisig( Ok(Policy::make_multisig::<Ctx>(
keys.pks.as_ref(), keys.pks.as_ref(),
signers, signers,
build_sat, build_sat,
keys.k, keys.k,
true, true,
true,
secp, secp,
)?) )?)
} }
match self { match self {
Descriptor::Pkh(pk) => Ok(Some(signature(pk.as_inner(), signers, build_sat, secp))), Descriptor::Pkh(pk) => Ok(Some(miniscript::Legacy::make_signature(
Descriptor::Wpkh(pk) => Ok(Some(signature(pk.as_inner(), signers, build_sat, secp))), pk.as_inner(),
signers,
build_sat,
secp,
))),
Descriptor::Wpkh(pk) => Ok(Some(miniscript::Segwitv0::make_signature(
pk.as_inner(),
signers,
build_sat,
secp,
))),
Descriptor::Sh(sh) => match sh.as_inner() { Descriptor::Sh(sh) => match sh.as_inner() {
ShInner::Wpkh(pk) => Ok(Some(signature(pk.as_inner(), signers, build_sat, secp))), ShInner::Wpkh(pk) => Ok(Some(miniscript::Segwitv0::make_signature(
pk.as_inner(),
signers,
build_sat,
secp,
))),
ShInner::Ms(ms) => Ok(ms.extract_policy(signers, build_sat, secp)?), ShInner::Ms(ms) => Ok(ms.extract_policy(signers, build_sat, secp)?),
ShInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, build_sat, secp), ShInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, build_sat, secp),
ShInner::Wsh(wsh) => match wsh.as_inner() { ShInner::Wsh(wsh) => match wsh.as_inner() {
@ -1036,17 +1084,26 @@ impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
}, },
Descriptor::Bare(ms) => Ok(ms.as_inner().extract_policy(signers, build_sat, secp)?), Descriptor::Bare(ms) => Ok(ms.as_inner().extract_policy(signers, build_sat, secp)?),
Descriptor::Tr(tr) => { Descriptor::Tr(tr) => {
let mut items = vec![signature(tr.internal_key(), signers, build_sat, secp)]; // If there's no tap tree, treat this as a single sig, otherwise build a `Thresh`
items.append( // node with threshold = 1 and the key spend signature plus all the tree leaves
&mut tr let key_spend_sig =
.iter_scripts() miniscript::Tap::make_signature(tr.internal_key(), signers, build_sat, secp);
.filter_map(|(_, ms)| {
ms.extract_policy(signers, build_sat, secp).transpose()
})
.collect::<Result<Vec<_>, _>>()?,
);
Ok(Policy::make_thresh(items, 1)?) if tr.taptree().is_none() {
Ok(Some(key_spend_sig))
} else {
let mut items = vec![key_spend_sig];
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)?)
}
} }
} }
} }

View File

@ -4223,7 +4223,7 @@ pub(crate) mod test {
let (wallet, _, _) = get_funded_wallet(get_test_tr_repeated_key()); let (wallet, _, _) = get_funded_wallet(get_test_tr_repeated_key());
let addr = wallet.get_address(AddressIndex::New).unwrap(); let addr = wallet.get_address(AddressIndex::New).unwrap();
let path = vec![("u6ugnnck".to_string(), vec![0])] let path = vec![("rn4nre9c".to_string(), vec![0])]
.into_iter() .into_iter()
.collect(); .collect();