diff --git a/src/policy/semantic.rs b/src/policy/semantic.rs index 740c493a7..6f18300a0 100644 --- a/src/policy/semantic.rs +++ b/src/policy/semantic.rs @@ -499,6 +499,34 @@ impl<Pk: MiniscriptKey> Policy<Pk> { ret } + /// Helper function for recursion in `absolute timelocks` + fn real_absolute_timelocks(&self) -> Vec<u32> { + match *self { + Policy::Unsatisfiable + | Policy::Trivial + | Policy::KeyHash(..) + | Policy::Sha256(..) + | Policy::Hash256(..) + | Policy::Ripemd160(..) + | Policy::Hash160(..) => vec![], + Policy::Older(..) => vec![], + Policy::After(t) => vec![t], + Policy::Threshold(_, ref subs) => subs.iter().fold(vec![], |mut acc, x| { + acc.extend(x.real_absolute_timelocks()); + acc + }), + } + } + + /// Returns a list of all absolute timelocks, not including 0, + /// which appear in the policy + pub fn absolute_timelocks(&self) -> Vec<u32> { + let mut ret = self.real_absolute_timelocks(); + ret.sort(); + ret.dedup(); + ret + } + /// Filter a policy by eliminating relative timelock constraints /// that are not satisfied at the given age. pub fn at_age(mut self, time: u32) -> Policy<Pk> { @@ -518,6 +546,25 @@ impl<Pk: MiniscriptKey> Policy<Pk> { self.normalized() } + /// Filter a policy by eliminating absolute timelock constraints + /// that are not satisfied at the given age. + pub fn at_height(mut self, time: u32) -> Policy<Pk> { + self = match self { + Policy::After(t) => { + if t > time { + Policy::Unsatisfiable + } else { + Policy::After(t) + } + } + Policy::Threshold(k, subs) => { + Policy::Threshold(k, subs.into_iter().map(|sub| sub.at_height(time)).collect()) + } + x => x, + }; + self.normalized() + } + /// Count the number of public keys and keyhashes referenced in a policy. /// Duplicate keys will be double-counted. pub fn n_keys(&self) -> usize { @@ -604,6 +651,7 @@ mod tests { let policy = StringPolicy::from_str("pkh()").unwrap(); assert_eq!(policy, Policy::KeyHash("".to_owned())); assert_eq!(policy.relative_timelocks(), vec![]); + assert_eq!(policy.absolute_timelocks(), vec![]); assert_eq!(policy.clone().at_age(0), policy.clone()); assert_eq!(policy.clone().at_age(10000), policy.clone()); assert_eq!(policy.n_keys(), 1); @@ -611,6 +659,7 @@ mod tests { let policy = StringPolicy::from_str("older(1000)").unwrap(); assert_eq!(policy, Policy::Older(1000)); + assert_eq!(policy.absolute_timelocks(), vec![]); assert_eq!(policy.relative_timelocks(), vec![1000]); assert_eq!(policy.clone().at_age(0), Policy::Unsatisfiable); assert_eq!(policy.clone().at_age(999), Policy::Unsatisfiable); @@ -628,6 +677,7 @@ mod tests { ) ); assert_eq!(policy.relative_timelocks(), vec![1000]); + assert_eq!(policy.absolute_timelocks(), vec![]); assert_eq!(policy.clone().at_age(0), Policy::KeyHash("".to_owned())); assert_eq!(policy.clone().at_age(999), Policy::KeyHash("".to_owned())); assert_eq!(policy.clone().at_age(1000), policy.clone().normalized()); @@ -658,6 +708,17 @@ mod tests { policy.relative_timelocks(), vec![1000, 2000, 10000] //sorted and dedup'd ); + + let policy = StringPolicy::from_str("after(1000)").unwrap(); + assert_eq!(policy, Policy::After(1000)); + assert_eq!(policy.absolute_timelocks(), vec![1000]); + assert_eq!(policy.relative_timelocks(), vec![]); + assert_eq!(policy.clone().at_height(0), Policy::Unsatisfiable); + assert_eq!(policy.clone().at_height(999), Policy::Unsatisfiable); + assert_eq!(policy.clone().at_height(1000), policy.clone()); + assert_eq!(policy.clone().at_height(10000), policy.clone()); + assert_eq!(policy.n_keys(), 0); + assert_eq!(policy.minimum_n_keys(), 0); } #[test]