Skip to content

Commit a0d28b3

Browse files
committed
feat(policy): change semantic::Policy Display to mathematical notation
Use ∧/∨ operators and #{...} ≥ k syntax so the output cannot be confused with compilable concrete policy. Remove serde roundtrip since Display is intentionally no longer parseable.
1 parent 04f1c58 commit a0d28b3

4 files changed

Lines changed: 33 additions & 18 deletions

File tree

examples/htlc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ fn main() {
3737
// Lift the descriptor into an abstract policy.
3838
assert_eq!(
3939
format!("{}", htlc_descriptor.lift().unwrap()),
40-
"or(and(pk(022222222222222222222222222222222222222222222222222222222222222222),sha256(1111111111111111111111111111111111111111111111111111111111111111)),and(pk(020202020202020202020202020202020202020202020202020202020202020202),older(4444)))"
40+
"((pk(022222222222222222222222222222222222222222222222222222222222222222)sha256(1111111111111111111111111111111111111111111111111111111111111111))(pk(020202020202020202020202020202020202020202020202020202020202020202)older(4444)))"
4141
);
4242

4343
// Get the scriptPubkey for this Wsh descriptor.

src/descriptor/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2175,15 +2175,15 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))";
21752175
// Taproot structure is erased but key order preserved..
21762176
let desc = Descriptor::<String>::from_str("tr(ROOT,{pk(A1),{pk(B1),pk(B2)}})").unwrap();
21772177
let lift = desc.lift().unwrap();
2178-
assert_eq!(lift.to_string(), "or(pk(ROOT),or(pk(A1),pk(B1),pk(B2)))",);
2178+
assert_eq!(lift.to_string(), "(pk(ROOT)(pk(A1)pk(B1)pk(B2)))");
21792179
let desc = Descriptor::<String>::from_str("tr(ROOT,{{pk(A1),pk(B1)},pk(B2)})").unwrap();
21802180
let lift = desc.lift().unwrap();
2181-
assert_eq!(lift.to_string(), "or(pk(ROOT),or(pk(A1),pk(B1),pk(B2)))",);
2181+
assert_eq!(lift.to_string(), "(pk(ROOT)(pk(A1)pk(B1)pk(B2)))");
21822182

21832183
// And normalization happens
21842184
let desc = Descriptor::<String>::from_str("tr(ROOT,{0,{0,0}})").unwrap();
21852185
let lift = desc.lift().unwrap();
2186-
assert_eq!(lift.to_string(), "or(pk(ROOT),UNSATISFIABLE)",);
2186+
assert_eq!(lift.to_string(), "(pk(ROOT)UNSATISFIABLE)");
21872187
}
21882188

21892189
#[test]

src/policy/mod.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -251,12 +251,6 @@ mod tests {
251251
assert_eq!(s.to_lowercase(), output.to_lowercase());
252252
}
253253

254-
fn semantic_policy_rtt(s: &str) {
255-
let sem = SemanticPol::from_str(s).unwrap();
256-
let output = sem.normalized().to_string();
257-
assert_eq!(s.to_lowercase(), output.to_lowercase());
258-
}
259-
260254
#[test]
261255
fn test_timelock_validity() {
262256
// only height
@@ -279,17 +273,25 @@ mod tests {
279273
concrete_policy_rtt("or(99@pk(X),1@pk(Y))");
280274
concrete_policy_rtt("and(pk(X),or(99@pk(Y),1@older(12960)))");
281275

282-
semantic_policy_rtt("pk()");
283-
semantic_policy_rtt("or(pk(X),pk(Y))");
284-
semantic_policy_rtt("and(pk(X),pk(Y))");
285-
286276
//fuzzer crashes
287277
assert!(ConcretePol::from_str("thresh()").is_err());
288278
assert!(SemanticPol::from_str("thresh(0)").is_err());
289279
assert!(SemanticPol::from_str("thresh()").is_err());
290280
concrete_policy_rtt("ripemd160()");
291281
}
292282

283+
#[test]
284+
fn semantic_display_uses_mathematical_notation() {
285+
let pol = SemanticPol::from_str("and(pk(A),pk(B))").unwrap();
286+
assert_eq!(pol.normalized().to_string(), "(pk(A) ∧ pk(B))");
287+
288+
let pol = SemanticPol::from_str("or(pk(A),pk(B))").unwrap();
289+
assert_eq!(pol.normalized().to_string(), "(pk(A) ∨ pk(B))");
290+
291+
let pol = SemanticPol::from_str("thresh(2,pk(A),pk(B),pk(C))").unwrap();
292+
assert_eq!(pol.normalized().to_string(), "#{pk(A), pk(B), pk(C)} ≥ 2");
293+
}
294+
293295
#[test]
294296
fn compile_invalid() {
295297
// Since the root Error does not support Eq type, we have to

src/policy/semantic.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -261,12 +261,26 @@ impl<Pk: MiniscriptKey> fmt::Display for Policy<Pk> {
261261
Policy::Ripemd160(ref h) => write!(f, "ripemd160({})", h),
262262
Policy::Hash160(ref h) => write!(f, "hash160({})", h),
263263
Policy::Thresh(ref thresh) => {
264+
let mut iter = thresh.iter();
265+
let first = iter.next().expect("thresholds are never empty");
264266
if thresh.k() == thresh.n() {
265-
thresh.display("and", false).fmt(f)
267+
write!(f, "({}", first)?;
268+
for sub in iter {
269+
write!(f, " ∧ {}", sub)?;
270+
}
271+
f.write_str(")")
266272
} else if thresh.k() == 1 {
267-
thresh.display("or", false).fmt(f)
273+
write!(f, "({}", first)?;
274+
for sub in iter {
275+
write!(f, " ∨ {}", sub)?;
276+
}
277+
f.write_str(")")
268278
} else {
269-
thresh.display("thresh", true).fmt(f)
279+
write!(f, "#{{{}", first)?;
280+
for sub in iter {
281+
write!(f, ", {}", sub)?;
282+
}
283+
write!(f, "}} ≥ {}", thresh.k())
270284
}
271285
}
272286
}
@@ -281,7 +295,6 @@ impl<Pk: FromStrKey> str::FromStr for Policy<Pk> {
281295
}
282296
}
283297

284-
serde_string_impl_pk!(Policy, "a miniscript semantic policy");
285298

286299
impl<Pk: FromStrKey> expression::FromTree for Policy<Pk> {
287300
fn from_tree(root: expression::TreeIterItem) -> Result<Policy<Pk>, Error> {

0 commit comments

Comments
 (0)