bdk/src/descriptor/policy.rs

527 lines
16 KiB
Rust
Raw Normal View History

2020-02-15 21:27:51 +01:00
use std::collections::{BTreeMap, HashSet};
2020-02-07 23:22:28 +01:00
use serde::Serialize;
use bitcoin::hashes::*;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::util::bip32::Fingerprint;
2020-02-15 21:27:51 +01:00
use bitcoin::util::psbt;
2020-02-07 23:22:28 +01:00
use bitcoin::PublicKey;
use miniscript::{Descriptor, Miniscript, Terminal};
use descriptor::{Key, MiniscriptExtractPolicy};
#[derive(Debug, Serialize)]
pub struct PKOrF {
#[serde(skip_serializing_if = "Option::is_none")]
pubkey: Option<PublicKey>,
#[serde(skip_serializing_if = "Option::is_none")]
fingerprint: Option<Fingerprint>,
}
impl PKOrF {
fn from_key(k: &Box<dyn Key>) -> Self {
let secp = Secp256k1::gen_new();
2020-02-15 21:27:51 +01:00
let pubkey = k.as_public_key(&secp, None).unwrap();
2020-02-07 23:22:28 +01:00
if let Some(fing) = k.fingerprint(&secp) {
PKOrF {
fingerprint: Some(fing),
pubkey: None,
}
} else {
PKOrF {
fingerprint: None,
2020-02-15 21:27:51 +01:00
pubkey: Some(pubkey),
2020-02-07 23:22:28 +01:00
}
}
}
}
#[derive(Debug, Serialize)]
#[serde(tag = "type", rename_all = "UPPERCASE")]
pub enum SatisfiableItem {
// Leaves
Signature(PKOrF),
SignatureKey {
#[serde(skip_serializing_if = "Option::is_none")]
fingerprint: Option<Fingerprint>,
#[serde(skip_serializing_if = "Option::is_none")]
pubkey_hash: Option<hash160::Hash>,
},
SHA256Preimage {
hash: sha256::Hash,
},
HASH256Preimage {
hash: sha256d::Hash,
},
RIPEMD160Preimage {
hash: ripemd160::Hash,
},
HASH160Preimage {
hash: hash160::Hash,
},
AbsoluteTimelock {
2020-02-15 21:27:51 +01:00
value: u32,
2020-02-07 23:22:28 +01:00
},
RelativeTimelock {
2020-02-15 21:27:51 +01:00
value: u32,
2020-02-07 23:22:28 +01:00
},
// Complex item
Thresh {
items: Vec<Policy>,
threshold: usize,
},
Multisig {
keys: Vec<PKOrF>,
threshold: usize,
},
}
impl SatisfiableItem {
pub fn is_leaf(&self) -> bool {
match self {
SatisfiableItem::Thresh {
items: _,
threshold: _,
} => false,
_ => true,
}
}
2020-02-15 21:27:51 +01:00
fn satisfy(&self, _input: &psbt::Input) -> Satisfaction {
Satisfaction::None
}
2020-02-07 23:22:28 +01:00
}
2020-02-15 21:27:51 +01:00
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(tag = "type", rename_all = "UPPERCASE")]
pub enum Satisfaction {
Complete {
#[serde(skip_serializing_if = "PathRequirements::is_null")]
condition: PathRequirements,
},
Partial {
m: usize,
n: usize,
completed: HashSet<usize>,
},
None,
}
impl Satisfaction {
fn from_items_threshold(items: HashSet<usize>, threshold: usize) -> Satisfaction {
Satisfaction::Partial {
m: items.len(),
n: threshold,
completed: items,
}
}
}
impl<'a> std::ops::Add<&'a Satisfaction> for Satisfaction {
type Output = Satisfaction;
fn add(self, other: &'a Satisfaction) -> Satisfaction {
&self + other
}
}
impl<'a, 'b> std::ops::Add<&'b Satisfaction> for &'a Satisfaction {
type Output = Satisfaction;
fn add(self, other: &'b Satisfaction) -> Satisfaction {
match (self, other) {
// complete-complete
(
Satisfaction::Complete { condition: mut a },
Satisfaction::Complete { condition: b },
) => {
a.merge(&b).unwrap();
Satisfaction::Complete { condition: a }
}
// complete-<any>
(Satisfaction::Complete { condition }, _) => Satisfaction::Complete {
condition: *condition,
},
(_, Satisfaction::Complete { condition }) => Satisfaction::Complete {
condition: *condition,
},
// none-<any>
(Satisfaction::None, any) => any.clone(),
(any, Satisfaction::None) => any.clone(),
// partial-partial
(
Satisfaction::Partial {
m: _,
n: a_n,
completed: a_items,
},
Satisfaction::Partial {
m: _,
n: _,
completed: b_items,
},
) => {
let union: HashSet<_> = a_items.union(&b_items).cloned().collect();
Satisfaction::Partial {
m: union.len(),
n: *a_n,
completed: union,
}
}
}
}
2020-02-07 23:22:28 +01:00
}
#[derive(Debug, Serialize)]
pub struct Policy {
#[serde(flatten)]
item: SatisfiableItem,
2020-02-15 21:27:51 +01:00
satisfaction: Satisfaction,
contribution: Satisfaction,
2020-02-07 23:22:28 +01:00
}
2020-02-15 21:27:51 +01:00
#[derive(Debug, Default, Eq, PartialEq, Clone, Copy, Serialize)]
2020-02-07 23:22:28 +01:00
pub struct PathRequirements {
2020-02-15 21:27:51 +01:00
#[serde(skip_serializing_if = "Option::is_none")]
2020-02-07 23:22:28 +01:00
pub csv: Option<u32>,
2020-02-15 21:27:51 +01:00
#[serde(skip_serializing_if = "Option::is_none")]
2020-02-07 23:22:28 +01:00
pub timelock: Option<u32>,
}
impl PathRequirements {
pub fn merge(&mut self, other: &Self) -> Result<(), PolicyError> {
if other.is_null() {
return Ok(());
}
match (self.csv, other.csv) {
(Some(old), Some(new)) if old != new => Err(PolicyError::DifferentCSV(old, new)),
_ => {
self.csv = self.csv.or(other.csv);
Ok(())
}
}?;
match (self.timelock, other.timelock) {
2020-02-15 21:27:51 +01:00
// TODO: we could actually set the timelock to the highest of the two, but we would
// have to first check that they are both in the same "unit" (blocks vs time)
2020-02-07 23:22:28 +01:00
(Some(old), Some(new)) if old != new => Err(PolicyError::DifferentTimelock(old, new)),
_ => {
self.timelock = self.timelock.or(other.timelock);
Ok(())
}
}?;
Ok(())
}
pub fn is_null(&self) -> bool {
self.csv.is_none() && self.timelock.is_none()
}
}
#[derive(Debug)]
pub enum PolicyError {
NotEnoughItemsSelected(usize),
TooManyItemsSelected(usize),
IndexOutOfRange(usize, usize),
DifferentCSV(u32, u32),
DifferentTimelock(u32, u32),
}
impl Policy {
pub fn new(item: SatisfiableItem) -> Self {
Policy {
item,
2020-02-15 21:27:51 +01:00
satisfaction: Satisfaction::None,
contribution: Satisfaction::None,
2020-02-07 23:22:28 +01:00
}
}
pub fn make_and(a: Option<Policy>, b: Option<Policy>) -> Option<Policy> {
match (a, b) {
(None, None) => None,
(Some(x), None) | (None, Some(x)) => Some(x),
2020-02-15 21:27:51 +01:00
(Some(a), Some(b)) => Self::make_thresh(vec![a, b], 2),
2020-02-07 23:22:28 +01:00
}
}
pub fn make_or(a: Option<Policy>, b: Option<Policy>) -> Option<Policy> {
match (a, b) {
(None, None) => None,
(Some(x), None) | (None, Some(x)) => Some(x),
2020-02-15 21:27:51 +01:00
(Some(a), Some(b)) => Self::make_thresh(vec![a, b], 1),
2020-02-07 23:22:28 +01:00
}
}
2020-02-15 21:27:51 +01:00
pub fn make_thresh(items: Vec<Policy>, threshold: usize) -> Option<Policy> {
2020-02-07 23:22:28 +01:00
if threshold == 0 {
return None;
}
2020-02-15 21:27:51 +01:00
let contribution = items.iter().fold(
Satisfaction::Partial {
m: 0,
n: threshold,
completed: HashSet::new(),
},
|acc, x| acc + &x.contribution,
);
let mut policy: Policy = SatisfiableItem::Thresh { items, threshold }.into();
policy.contribution = contribution;
Some(policy)
2020-02-07 23:22:28 +01:00
}
2020-02-15 21:27:51 +01:00
fn make_multisig(keys: Vec<Option<&Box<dyn Key>>>, threshold: usize) -> Option<Policy> {
let parsed_keys = keys.iter().map(|k| PKOrF::from_key(k.unwrap())).collect();
let mut policy: Policy = SatisfiableItem::Multisig {
keys: parsed_keys,
threshold,
}
.into();
let our_keys = keys
.iter()
.enumerate()
.filter(|(_, x)| x.is_some() && x.unwrap().has_secret())
.map(|(k, _)| k)
2020-02-07 23:22:28 +01:00
.collect();
2020-02-15 21:27:51 +01:00
policy.contribution = Satisfaction::from_items_threshold(our_keys, threshold);
Some(policy)
}
pub fn satisfy(&mut self, input: &psbt::Input) {
self.satisfaction = self.item.satisfy(input);
2020-02-07 23:22:28 +01:00
}
pub fn requires_path(&self) -> bool {
self.get_requirements(&vec![]).is_err()
}
pub fn get_requirements(
&self,
path: &Vec<Vec<usize>>,
) -> Result<PathRequirements, PolicyError> {
self.recursive_get_requirements(path, 0)
}
fn recursive_get_requirements(
&self,
path: &Vec<Vec<usize>>,
index: usize,
) -> Result<PathRequirements, PolicyError> {
// if items.len() == threshold, selected can be omitted and we take all of them by default
let default = match &self.item {
SatisfiableItem::Thresh { items, threshold } if items.len() == *threshold => {
(0..*threshold).into_iter().collect()
}
_ => vec![],
};
let selected = match path.get(index) {
_ if !default.is_empty() => &default,
Some(arr) => arr,
_ => &default,
};
match &self.item {
SatisfiableItem::Thresh { items, threshold } => {
let mapped_req = items
.iter()
.map(|i| i.recursive_get_requirements(path, index + 1))
.collect::<Result<Vec<_>, _>>()?;
// if all the requirements are null we don't care about `selected` because there
// are no requirements
if mapped_req.iter().all(PathRequirements::is_null) {
return Ok(PathRequirements::default());
}
// if we have something, make sure we have enough items. note that the user can set
// an empty value for this step in case of n-of-n, because `selected` is set to all
// the elements above
if selected.len() < *threshold {
return Err(PolicyError::NotEnoughItemsSelected(index));
}
// check the selected items, see if there are conflicting requirements
let mut requirements = PathRequirements::default();
for item_index in selected {
requirements.merge(
mapped_req
.get(*item_index)
.ok_or(PolicyError::IndexOutOfRange(*item_index, index))?,
)?;
}
Ok(requirements)
}
_ if !selected.is_empty() => Err(PolicyError::TooManyItemsSelected(index)),
2020-02-15 21:27:51 +01:00
SatisfiableItem::AbsoluteTimelock { value } => Ok(PathRequirements {
2020-02-07 23:22:28 +01:00
csv: None,
2020-02-15 21:27:51 +01:00
timelock: Some(*value),
2020-02-07 23:22:28 +01:00
}),
2020-02-15 21:27:51 +01:00
SatisfiableItem::RelativeTimelock { value } => Ok(PathRequirements {
csv: Some(*value),
2020-02-07 23:22:28 +01:00
timelock: None,
}),
_ => Ok(PathRequirements::default()),
}
}
}
impl From<SatisfiableItem> for Policy {
fn from(other: SatisfiableItem) -> Self {
Self::new(other)
}
}
fn signature_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> {
2020-02-15 21:27:51 +01:00
key.map(|k| {
let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key(k)).into();
policy.contribution = if k.has_secret() {
Satisfaction::Complete {
condition: Default::default(),
}
} else {
Satisfaction::None
};
policy
})
2020-02-07 23:22:28 +01:00
}
fn signature_key_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> {
let secp = Secp256k1::gen_new();
key.map(|k| {
2020-02-15 21:27:51 +01:00
let pubkey = k.as_public_key(&secp, None).unwrap();
let mut policy: Policy = if let Some(fing) = k.fingerprint(&secp) {
2020-02-07 23:22:28 +01:00
SatisfiableItem::SignatureKey {
fingerprint: Some(fing),
pubkey_hash: None,
}
} else {
SatisfiableItem::SignatureKey {
fingerprint: None,
2020-02-15 21:27:51 +01:00
pubkey_hash: Some(hash160::Hash::hash(&pubkey.to_bytes())),
2020-02-07 23:22:28 +01:00
}
}
2020-02-15 21:27:51 +01:00
.into();
policy.contribution = if k.has_secret() {
Satisfaction::Complete {
condition: Default::default(),
}
} else {
Satisfaction::None
};
policy
2020-02-07 23:22:28 +01:00
})
}
impl MiniscriptExtractPolicy for Miniscript<String> {
fn extract_policy(&self, lookup_map: &BTreeMap<String, Box<dyn Key>>) -> Option<Policy> {
match &self.node {
// Leaves
Terminal::True | Terminal::False => None,
Terminal::Pk(pubkey) => signature_from_string(lookup_map.get(pubkey)),
Terminal::PkH(pubkey_hash) => signature_key_from_string(lookup_map.get(pubkey_hash)),
2020-02-15 21:27:51 +01:00
Terminal::After(value) => {
let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into();
policy.contribution = Satisfaction::Complete {
condition: PathRequirements {
csv: None,
timelock: Some(*value),
},
};
Some(policy)
2020-02-07 23:22:28 +01:00
}
2020-02-15 21:27:51 +01:00
Terminal::Older(value) => {
let mut policy: Policy = SatisfiableItem::RelativeTimelock { value: *value }.into();
policy.contribution = Satisfaction::Complete {
condition: PathRequirements {
csv: Some(*value),
timelock: None,
},
};
Some(policy)
2020-02-07 23:22:28 +01:00
}
Terminal::Sha256(hash) => Some(SatisfiableItem::SHA256Preimage { hash: *hash }.into()),
Terminal::Hash256(hash) => {
Some(SatisfiableItem::HASH256Preimage { hash: *hash }.into())
}
Terminal::Ripemd160(hash) => {
Some(SatisfiableItem::RIPEMD160Preimage { hash: *hash }.into())
}
Terminal::Hash160(hash) => {
Some(SatisfiableItem::HASH160Preimage { hash: *hash }.into())
}
2020-02-15 21:27:51 +01:00
Terminal::ThreshM(k, pks) => {
Policy::make_multisig(pks.iter().map(|s| lookup_map.get(s)).collect(), *k)
}
2020-02-07 23:22:28 +01:00
// Identities
Terminal::Alt(inner)
| Terminal::Swap(inner)
| Terminal::Check(inner)
| Terminal::DupIf(inner)
| Terminal::Verify(inner)
| Terminal::NonZero(inner)
| Terminal::ZeroNotEqual(inner) => inner.extract_policy(lookup_map),
// Complex policies
Terminal::AndV(a, b) | Terminal::AndB(a, b) => {
Policy::make_and(a.extract_policy(lookup_map), b.extract_policy(lookup_map))
}
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),
),
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::Thresh(k, nodes) => {
let mut threshold = *k;
let mapped: Vec<_> = nodes
.iter()
.filter_map(|n| n.extract_policy(lookup_map))
.collect();
if mapped.len() < nodes.len() {
threshold = match threshold.checked_sub(nodes.len() - mapped.len()) {
None => return None,
Some(x) => x,
};
}
Policy::make_thresh(mapped, threshold)
}
}
}
}
impl MiniscriptExtractPolicy for Descriptor<String> {
fn extract_policy(&self, lookup_map: &BTreeMap<String, Box<dyn Key>>) -> Option<Policy> {
match self {
Descriptor::Pk(pubkey)
| Descriptor::Pkh(pubkey)
| Descriptor::Wpkh(pubkey)
| Descriptor::ShWpkh(pubkey) => signature_from_string(lookup_map.get(pubkey)),
Descriptor::Bare(inner)
| Descriptor::Sh(inner)
| Descriptor::Wsh(inner)
| Descriptor::ShWsh(inner) => inner.extract_policy(lookup_map),
}
}
}