From 0c695893bd805dcec5232b3733766bcfe1b94092 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Wed, 5 Aug 2020 15:26:00 -0500 Subject: [PATCH] Semantic analysis now uses only thresh --- examples/htlc.rs | 3 +- src/policy/concrete.rs | 10 ++ src/policy/mod.rs | 25 +++-- src/policy/semantic.rs | 218 ++++++++++++++++++----------------------- 4 files changed, 121 insertions(+), 135 deletions(-) diff --git a/examples/htlc.rs b/examples/htlc.rs index 7b69e2692..1a9de1f71 100644 --- a/examples/htlc.rs +++ b/examples/htlc.rs @@ -40,8 +40,7 @@ fn main() { assert_eq!( format!("{}", htlc_descriptor.lift()), - "or(and(pkh(4377a5acd66dc5cb67148a24818d1e51fa183bd2),and(pkh(4377a5acd66dc5cb67148a24818d1e51fa183bd2),older(4444))),sha256(1111111111111111111111111111111111111111111111111111111111111111))" - ); + "or(and(pkh(4377a5acd66dc5cb67148a24818d1e51fa183bd2),pkh(4377a5acd66dc5cb67148a24818d1e51fa183bd2),older(4444)),sha256(1111111111111111111111111111111111111111111111111111111111111111))"); assert_eq!( format!("{:x}", htlc_descriptor.script_pubkey()), diff --git a/src/policy/concrete.rs b/src/policy/concrete.rs index 476f68036..c800523ff 100644 --- a/src/policy/concrete.rs +++ b/src/policy/concrete.rs @@ -72,6 +72,10 @@ pub enum PolicyError { ZeroTime, /// `after` fragment can only have ` n < 2^31` TimeTooFar, + /// Semantic Policy Error: `And` `Or` fragments must take args: k > 1 + InsufficientArgsforAnd, + /// Semantic Policy Error: `And` `Or` fragments must take args: k > 1 + InsufficientArgsforOr, } impl error::Error for PolicyError { @@ -97,6 +101,12 @@ impl fmt::Display for PolicyError { f.write_str("Relative/Absolute time must be less than 2^31; n < 2^31") } PolicyError::ZeroTime => f.write_str("Time must be greater than 0; n > 0"), + PolicyError::InsufficientArgsforAnd => { + f.write_str("Semantic Policy 'And' fragment must have atleast 2 args ") + } + PolicyError::InsufficientArgsforOr => { + f.write_str("Semantic Policy 'Or' fragment must have atleast 2 args ") + } } } } diff --git a/src/policy/mod.rs b/src/policy/mod.rs index 68b486e4d..c16aadfdd 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -74,17 +74,20 @@ impl Liftable for Terminal { | Terminal::NonZero(ref sub) | Terminal::ZeroNotEqual(ref sub) => sub.node.lift(), Terminal::AndV(ref left, ref right) | Terminal::AndB(ref left, ref right) => { - Semantic::And(vec![left.node.lift(), right.node.lift()]) + Semantic::Threshold(2, vec![left.node.lift(), right.node.lift()]) } - Terminal::AndOr(ref a, ref b, ref c) => Semantic::Or(vec![ - Semantic::And(vec![a.node.lift(), c.node.lift()]), - b.node.lift(), - ]), + Terminal::AndOr(ref a, ref b, ref c) => Semantic::Threshold( + 1, + vec![ + Semantic::Threshold(2, vec![a.node.lift(), c.node.lift()]), + b.node.lift(), + ], + ), Terminal::OrB(ref left, ref right) | Terminal::OrD(ref left, ref right) | Terminal::OrC(ref left, ref right) | Terminal::OrI(ref left, ref right) => { - Semantic::Or(vec![left.node.lift(), right.node.lift()]) + Semantic::Threshold(1, vec![left.node.lift(), right.node.lift()]) } Terminal::Thresh(k, ref subs) => { Semantic::Threshold(k, subs.into_iter().map(|s| s.node.lift()).collect()) @@ -129,9 +132,11 @@ impl Liftable for Concrete { Concrete::Hash256(h) => Semantic::Hash256(h), Concrete::Ripemd160(h) => Semantic::Ripemd160(h), Concrete::Hash160(h) => Semantic::Hash160(h), - Concrete::And(ref subs) => Semantic::And(subs.iter().map(Liftable::lift).collect()), + Concrete::And(ref subs) => { + Semantic::Threshold(subs.len(), subs.iter().map(Liftable::lift).collect()) + } Concrete::Or(ref subs) => { - Semantic::Or(subs.iter().map(|&(_, ref sub)| sub.lift()).collect()) + Semantic::Threshold(1, subs.iter().map(|&(_, ref sub)| sub.lift()).collect()) } Concrete::Threshold(k, ref subs) => { Semantic::Threshold(k, subs.iter().map(Liftable::lift).collect()) @@ -158,7 +163,7 @@ mod tests { fn semantic_policy_rtt(s: &str) { let sem = SemanticPol::from_str(s).unwrap(); - let output = sem.to_string(); + let output = sem.normalized().to_string(); assert_eq!(s.to_lowercase(), output.to_lowercase()); } @@ -171,9 +176,11 @@ mod tests { semantic_policy_rtt("pkh()"); semantic_policy_rtt("or(pkh(),pkh())"); + semantic_policy_rtt("and(pkh(),pkh())"); //fuzzer crashes assert!(ConcretePol::from_str("thresh()").is_err()); + assert!(SemanticPol::from_str("thresh(0)").is_err()); assert!(SemanticPol::from_str("thresh()").is_err()); concrete_policy_rtt("ripemd160(aaaaaaaaaaaaaaaaaaaaaa0Daaaaaaaaaabaaaaa)"); } diff --git a/src/policy/semantic.rs b/src/policy/semantic.rs index 1114810cd..a08549449 100644 --- a/src/policy/semantic.rs +++ b/src/policy/semantic.rs @@ -51,10 +51,6 @@ pub enum Policy { Ripemd160(ripemd160::Hash), /// A HASH160 whose preimage must be provided to satisfy the descriptor Hash160(hash160::Hash), - /// A list of sub-policies, all of which must be satisfied - And(Vec>), - /// A list of sub-policies, one of which must be satisfied - Or(Vec>), /// A set of descriptors, satisfactions must be provided for `k` of them Threshold(usize, Vec>), } @@ -84,16 +80,6 @@ impl Policy { .collect(); new_subs.map(|ok| Policy::Threshold(k, ok)) } - Policy::And(ref subs) => Ok(Policy::And( - subs.iter() - .map(|sub| sub.translate_pkh(&mut translatefpkh)) - .collect::>, E>>()?, - )), - Policy::Or(ref subs) => Ok(Policy::Or( - subs.iter() - .map(|sub| sub.translate_pkh(&mut translatefpkh)) - .collect::>, E>>()?, - )), } } } @@ -110,33 +96,23 @@ impl fmt::Debug for Policy { Policy::Hash256(h) => write!(f, "hash256({})", h), Policy::Ripemd160(h) => write!(f, "ripemd160({})", h), Policy::Hash160(h) => write!(f, "hash160({})", h), - Policy::And(ref subs) => { - f.write_str("and(")?; - if !subs.is_empty() { - write!(f, "{:?}", subs[0])?; - for sub in &subs[1..] { - write!(f, ",{:?}", sub)?; - } + Policy::Threshold(k, ref subs) => { + if k == subs.len() { + write!(f, "and(")?; + } else if k == 1 { + write!(f, "or(")?; + } else { + write!(f, "thresh({}", k)?; } - f.write_str(")") - } - Policy::Or(ref subs) => { - f.write_str("or(")?; - if !subs.is_empty() { - write!(f, "{:?}", subs[0])?; - for sub in &subs[1..] { - write!(f, ",{:?}", sub)?; + for (i, sub) in subs.into_iter().enumerate() { + if i == 0 { + write!(f, "{}", sub)?; + } else { + write!(f, ",{}", sub)?; } } f.write_str(")") } - Policy::Threshold(k, ref subs) => { - write!(f, "thresh({}", k)?; - for sub in subs { - write!(f, ",{:?}", sub)?; - } - f.write_str(")") - } } } } @@ -153,33 +129,23 @@ impl fmt::Display for Policy { Policy::Hash256(h) => write!(f, "hash256({})", h), Policy::Ripemd160(h) => write!(f, "ripemd160({})", h), Policy::Hash160(h) => write!(f, "hash160({})", h), - Policy::And(ref subs) => { - f.write_str("and(")?; - if !subs.is_empty() { - write!(f, "{}", subs[0])?; - for sub in &subs[1..] { - write!(f, ",{}", sub)?; - } + Policy::Threshold(k, ref subs) => { + if k == subs.len() { + write!(f, "and(")?; + } else if k == 1 { + write!(f, "or(")?; + } else { + write!(f, "thresh({},", k)?; } - f.write_str(")") - } - Policy::Or(ref subs) => { - f.write_str("or(")?; - if !subs.is_empty() { - write!(f, "{}", subs[0])?; - for sub in &subs[1..] { + for (i, sub) in subs.into_iter().enumerate() { + if i == 0 { + write!(f, "{}", sub)?; + } else { write!(f, ",{}", sub)?; } } f.write_str(")") } - Policy::Threshold(k, ref subs) => { - write!(f, "thresh({}", k)?; - for sub in subs { - write!(f, ",{}", sub)?; - } - f.write_str(")") - } } } } @@ -213,7 +179,7 @@ where <::Hash as str::FromStr>::Err: ToString, { fn from_tree(top: &expression::Tree) -> Result, Error> { - match (top.name, top.args.len() as u32) { + match (top.name, top.args.len()) { ("UNSATISFIABLE", 0) => Ok(Policy::Unsatisfiable), ("TRIVIAL", 0) => Ok(Policy::Trivial), ("pkh", 1) => expression::terminal(&top.args[0], |pk| { @@ -237,28 +203,29 @@ where ("hash160", 1) => expression::terminal(&top.args[0], |x| { hash160::Hash::from_hex(x).map(Policy::Hash160) }), - ("and", _) => { - if top.args.len() != 2 { - return Err(Error::PolicyError(PolicyError::NonBinaryArgAnd)); + ("and", nsubs) => { + if nsubs < 2 { + return Err(Error::PolicyError(PolicyError::InsufficientArgsforAnd)); } - let mut subs = Vec::with_capacity(top.args.len()); + let mut subs = Vec::with_capacity(nsubs); for arg in &top.args { subs.push(Policy::from_tree(arg)?); } - Ok(Policy::And(subs)) + Ok(Policy::Threshold(nsubs, subs)) } - ("or", _) => { - if top.args.len() != 2 { - return Err(Error::PolicyError(PolicyError::NonBinaryArgOr)); + ("or", nsubs) => { + if nsubs < 2 { + return Err(Error::PolicyError(PolicyError::InsufficientArgsforOr)); } - let mut subs = Vec::with_capacity(top.args.len()); + let mut subs = Vec::with_capacity(nsubs); for arg in &top.args { subs.push(Policy::from_tree(arg)?); } - Ok(Policy::Or(subs)) + Ok(Policy::Threshold(1, subs)) } ("thresh", nsubs) => { - if nsubs == 0 { + if nsubs == 0 || nsubs == 1 { + // thresh() and thresh(k) are err return Err(errstr("thresh without args")); } if !top.args[0].args.is_empty() { @@ -266,7 +233,14 @@ where } let thresh = expression::parse_num(top.args[0].name)?; - if thresh >= nsubs { + + // thresh(1) and thresh(n) are disallowed in semantic policies + if thresh <= 1 || thresh >= (nsubs as u32 - 1) { + return Err(errstr( + "Semantic Policy thresh cannot have k = 1 or k =n, use `and`/`or` instead", + )); + } + if thresh >= (nsubs as u32) { return Err(errstr(top.args[0].name)); } @@ -286,36 +260,54 @@ impl Policy { /// `Unsatisfiable`s. Does not reorder any branches; use `.sort`. pub fn normalized(self) -> Policy { match self { - Policy::And(subs) => { - let mut ret_subs = Vec::with_capacity(subs.len()); - for sub in subs { - match sub.normalized() { - Policy::Trivial => {} - Policy::Unsatisfiable => return Policy::Unsatisfiable, - Policy::And(and_subs) => ret_subs.extend(and_subs), - x => ret_subs.push(x), - } - } - match ret_subs.len() { - 0 => Policy::Trivial, - 1 => ret_subs.pop().unwrap(), - _ => Policy::And(ret_subs), - } - } - Policy::Or(subs) => { + Policy::Threshold(k, subs) => { let mut ret_subs = Vec::with_capacity(subs.len()); + + let subs: Vec<_> = subs.into_iter().map(|sub| sub.normalized()).collect(); + let trivial_count = subs.iter().filter(|&pol| *pol == Policy::Trivial).count(); + let unsatisfied_count = subs + .iter() + .filter(|&pol| *pol == Policy::Unsatisfiable) + .count(); + + let n = subs.len() - unsatisfied_count - trivial_count; // remove all true/false + let m = k.checked_sub(trivial_count).map_or(0, |x| x); // satisfy all trivial + // m == n denotes `and` and m == 1 denotes `or` + let is_and = m == n; + let is_or = m == 1; for sub in subs { match sub { - Policy::Trivial => return Policy::Trivial, - Policy::Unsatisfiable => {} - Policy::Or(or_subs) => ret_subs.extend(or_subs), + Policy::Trivial | Policy::Unsatisfiable => {} + Policy::Threshold(1, or_subs) => { + if is_or { + ret_subs.extend(or_subs); + } else { + ret_subs.push(Policy::Threshold(1, or_subs)); + } + } + Policy::Threshold(k, and_subs) => { + if k == and_subs.len() && is_and { + ret_subs.extend(and_subs) + } else { + ret_subs.push(Policy::Threshold(k, and_subs)); + } + } x => ret_subs.push(x), } } - match ret_subs.len() { - 0 => Policy::Trivial, - 1 => ret_subs.pop().unwrap(), - _ => Policy::Or(ret_subs), + // Now reason about m of n threshold + if m == 0 { + Policy::Trivial + } else if m > ret_subs.len() { + Policy::Unsatisfiable + } else if ret_subs.len() == 1 { + ret_subs.pop().unwrap() + } else if is_and { + Policy::Threshold(ret_subs.len(), ret_subs) + } else if is_or { + Policy::Threshold(1, ret_subs) + } else { + Policy::Threshold(m, ret_subs) } } x => x, @@ -350,13 +342,7 @@ impl Policy { | Policy::Hash160(..) => vec![], Policy::After(..) => vec![], Policy::Older(t) => vec![t], - Policy::And(ref subs) | Policy::Threshold(_, ref subs) => { - subs.iter().fold(vec![], |mut acc, x| { - acc.extend(x.real_relative_timelocks()); - acc - }) - } - Policy::Or(ref subs) => subs.iter().fold(vec![], |mut acc, x| { + Policy::Threshold(_, ref subs) => subs.iter().fold(vec![], |mut acc, x| { acc.extend(x.real_relative_timelocks()); acc }), @@ -383,10 +369,6 @@ impl Policy { Policy::Older(t) } } - Policy::And(subs) => { - Policy::And(subs.into_iter().map(|sub| sub.at_age(time)).collect()) - } - Policy::Or(subs) => Policy::Or(subs.into_iter().map(|sub| sub.at_age(time)).collect()), Policy::Threshold(k, subs) => { Policy::Threshold(k, subs.into_iter().map(|sub| sub.at_age(time)).collect()) } @@ -407,10 +389,7 @@ impl Policy { | Policy::Hash256(..) | Policy::Ripemd160(..) | Policy::Hash160(..) => 0, - Policy::And(ref subs) | Policy::Threshold(_, ref subs) => { - subs.iter().map(|sub| sub.n_keys()).sum::() - } - Policy::Or(ref subs) => subs.iter().map(|sub| sub.n_keys()).sum::(), + Policy::Threshold(_, ref subs) => subs.iter().map(|sub| sub.n_keys()).sum::(), } } @@ -426,8 +405,6 @@ impl Policy { | Policy::Hash256(..) | Policy::Ripemd160(..) | Policy::Hash160(..) => 0, - Policy::And(ref subs) => subs.iter().map(Policy::minimum_n_keys).sum(), - Policy::Or(ref subs) => subs.iter().map(Policy::minimum_n_keys).min().unwrap_or(0), Policy::Threshold(k, ref subs) => { let mut sublens: Vec = subs.iter().map(Policy::minimum_n_keys).collect(); sublens.sort(); @@ -444,16 +421,6 @@ impl Policy { /// implemented. pub fn sorted(self) -> Policy { match self { - Policy::And(subs) => { - let mut new_subs: Vec<_> = subs.into_iter().map(Policy::sorted).collect(); - new_subs.sort(); - Policy::And(new_subs) - } - Policy::Or(subs) => { - let mut new_subs: Vec<_> = subs.into_iter().map(Policy::sorted).collect(); - new_subs.sort(); - Policy::Or(new_subs) - } Policy::Threshold(k, subs) => { let mut new_subs: Vec<_> = subs.into_iter().map(Policy::sorted).collect(); new_subs.sort(); @@ -514,13 +481,16 @@ mod tests { let policy = StringPolicy::from_str("or(pkh(),older(1000))").unwrap(); assert_eq!( policy, - Policy::Or(vec![Policy::KeyHash("".to_owned()), Policy::Older(1000),]) + Policy::Threshold( + 1, + vec![Policy::KeyHash("".to_owned()), Policy::Older(1000),] + ) ); assert_eq!(policy.relative_timelocks(), vec![1000]); 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()); - assert_eq!(policy.clone().at_age(10000), policy.clone()); + assert_eq!(policy.clone().at_age(1000), policy.clone().normalized()); + assert_eq!(policy.clone().at_age(10000), policy.clone().normalized()); assert_eq!(policy.n_keys(), 1); assert_eq!(policy.minimum_n_keys(), 0);