Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect miniscripts that have a satisfaction with a combination of time locks and height locks #121

Merged
merged 5 commits into from
Oct 1, 2020

Conversation

sanket1729
Copy link
Member

No description provided.

/// contains_timelock[1] = csv with times
/// contains_timelock[2] = cltv with heights
/// contains_timelock[3] = cltv with times
/// contains_timelock[4] = combination of any heightlocks and timelocks
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please create a structure for this instead of using a [bool; 5] :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can impl Default on it so you can construct it quickly

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear, I can't review this PR any further until this is fixed :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@apoelstra, This meaning the logic condition? I can change that quickly

// disjunction
assert!(ConcretePol::from_str("or(after(1000000000),after(100))").is_ok());
// conjunction
assert!(ConcretePol::from_str("and(after(1000000000),after(100))").is_err());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should this fail?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A conjunction of height lock and timelock?

Copy link
Member

@apoelstra apoelstra Aug 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, right, I forgot that timelocks are defined implicitly by having a value >500000000.

I really don't like this, we shuold modify Miniscript to use a different syntax for them.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any suggestions? For starters, I like the way shesek did it for minsc. https://min.sc/#time-durations

@sanket1729 sanket1729 force-pushed the heightlocks branch 4 times, most recently from ca46d42 to bb4991a Compare August 28, 2020 23:44
@@ -94,7 +95,13 @@ pub struct Older(pub u32);

impl<Pk: MiniscriptKey> Satisfier<Pk> for Older {
fn check_older(&self, n: u32) -> bool {
n <= self.0
if !((n < HEIGHT_TIME_THRESHOLD && self.0 < HEIGHT_TIME_THRESHOLD)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be simpler to check either

(n < HEIGHT_TIME_THRESHOLD) == (self.0 < HEIGHT_TIME_THRESHOLD)

or just

n < HEIGHT_TIME_THRESHOLD && self.0 >= HEIGHT_TIME_THRESHOLD

Copy link
Member Author

@sanket1729 sanket1729 Sep 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My logic for these is trying to implement "as it is" written in the BIP.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which BIP?

My suggestion is logically equivalent to what's written here, for what it's worth, it's just shorter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki . I can change this too, just was a bit pedantic in trying to copy the logic conditions directly.

@@ -104,7 +111,13 @@ pub struct After(pub u32);

impl<Pk: MiniscriptKey> Satisfier<Pk> for After {
fn check_after(&self, n: u32) -> bool {
n <= self.0
if !((n < HEIGHT_TIME_THRESHOLD && self.0 < HEIGHT_TIME_THRESHOLD)
|| (n >= HEIGHT_TIME_THRESHOLD && self.0 >= HEIGHT_TIME_THRESHOLD))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto here

@@ -115,6 +118,9 @@ impl fmt::Display for PolicyError {
"Policy entailment only supports {} terminals",
ENTAILMENT_MAX_TERMINALS
),
PolicyError::HeightTimeLockCombination => {
f.write_str("Cannot lift polcies that have a heightlock and timelock combination")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo policies

@sanket1729 sanket1729 force-pushed the heightlocks branch 3 times, most recently from 4f65119 to 1e55cad Compare September 25, 2020 19:37
// timelocks[1]: satisfaction contains csv timelock
// timelocks[2]: satisfaction contains cltv heightlock
// timelocks[3]: satisfaction contains cltv timelock
// timelocks[4]: satisfaction contains an invalid combination
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete this comment

// handy function for combining `and` timelocks
// This can be operator overloaded in future
pub(crate) fn comb_and_timelocks(a: Self, b: Self) -> Self {
Self::combine_thresh_timelocks(2, vec![a, b].into_iter())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you do once(a).chain(once(b)) here rather than Vec::into_iter. (You need to use std::iter::once.) Saves the allocation.

/// cltv with times
pub cltv_with_time: bool,
/// combination of any heightlocks and timelocks
pub contains_combination: bool,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually need this field? Or can we just check (csv_with_height && csv_with_time) || (cltv_with_height && cltv_with_time)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah - it is there to cover the disjunction case, where you may have a heighlock branch and a timelock branch, but they are independent so it is ok.

// 2) hieghtlocks and timelocks are distributed across two branches:
// and we check if any of the existing branches has correponding lock
// that cannot be combined
// If k == 1, then this is no combination possible.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this whole comment block confusing. I think you should replace it with

// Propagate all fields of `TimelockInfo` from each of the node's children to the node
// itself (by taking the logical-or of all of them). In case `k == 1` (this is a disjunction)
// this is all we need to do: the node may behave like any of its children, for purposes
// of timelock accounting.
//
// If `k > 1` we have the additional consideration that if any two children have conflicting
// timelock requirements, this represents an inaccessible spending branch. If there are
// two such children we must set the `contains_combination` field.

|| (timelock_info.csv_with_time && sub_timelock.csv_with_height)
|| (timelock_info.cltv_with_time && sub_timelock.cltv_with_height)
|| (timelock_info.cltv_with_height && sub_timelock.cltv_with_time);
}
Copy link
Member

@apoelstra apoelstra Oct 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can move this block out of the loop and simplify it:

if k >= 2 {
    timelock_info.contains_combination |= timelock_info.csv_with_time && timelock_info.csv_with_height;
    timelock_info.contains_combination |= timelock_info.cltv_with_time && timelock_info.cltv_with_height;
}

(or however you want to format it)

Copy link
Member

@apoelstra apoelstra Oct 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once it is out of the loop you can replace the loop with fold, as

let mut ret = sub_timelocks.fold(TimelockInfo::default(), |acc, x| TimelockInfo {
    csv_with_time: acc.csv_with_time | x.csv_with_time,
    csv_with_height: acc.csv_with_height | x.csv_with_height,
    cltv_with_time: acc.cltv_with_time | x.cltv_with_time;
    cltv_with_height: acc.cltv_with_height | x.cltv_with_height;
    contains_combination: acc.contains_combination | x.contains_combination;
});
if k > 1 {
    ret.contains_combination |= ret.csv_with_time && ret.csv_with_height;
    ret.contains_combination |= ret.cltv_with_time && ret.cltv_with_height;
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect. As it will set contains_combination for the following policy too.
thresh(2,pk(),pk(),or(time,height))

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unforetunately, I think it is necessary to check k>2 inside of loop.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think your existing code has the same issue.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, no, I see -- the distinction is that only the one child has any timelocks at all. Interesting.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so I leave it up to you whether you want to bother using fold instead of a loop. But can you add a comment above the k >= 2 line saying

// If more than one branch may be taken, and some other branch has a requirement
// that conflicts with this one, set `contains_combination`

or something

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing to fold

Copy link
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack a662105

@apoelstra apoelstra merged commit f36e4f9 into rust-bitcoin:master Oct 1, 2020
@sanket1729 sanket1729 mentioned this pull request Oct 1, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants