-
Notifications
You must be signed in to change notification settings - Fork 130
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
Taproot interpreter support #301
Conversation
8e5defe
to
685d374
Compare
} | ||
} | ||
|
||
// While parsing we need to remember how to the hash was parsed so that we can |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In 374ab9a:
so that we can
what?
src/interpreter/inner.rs
Outdated
@@ -211,7 +211,7 @@ pub(super) fn from_txdata<'txin>( | |||
// Key spend | |||
// let sig = | |||
// Tr inference to be done in future commit | |||
todo!(); | |||
panic!("TODO"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In 277c783:
lol does this change anything?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope. Just allows compiling on MSRV :P . I was testing whether that particular commit compiles on rust 1.29
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 4e6352a
I have a single fixup commit in https://github.com/apoelstra/rust-miniscript/tree/2022-03--301-fixup which you can take or leave; it just cleans up one comment that I didn't really understand.
4e6352a
to
a6b9eb4
Compare
Cherry-picked your commit |
src/interpreter/inner.rs
Outdated
/// The script being evaluated is an actual script | ||
Script(Miniscript<bitcoin::PublicKey, NoChecksEcdsa>, ScriptType), | ||
Script(Miniscript<super::BitcoinKey, NoChecksEcdsa>, ScriptType), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When would we want a TR/XOnly with an ECDSA here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a later commit, the NoChecksEcdsa
is replaced by NoChecks
spk: &bitcoin::Script, | ||
script_sig: &'txin bitcoin::Script, | ||
witness: &'txin Witness, | ||
) -> Result<(Inner, Stack<'txin>, bitcoin::Script), Error> { | ||
) -> Result<(Inner, Stack<'txin>, Option<bitcoin::Script>), Error> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should the script_sig also be optional here then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That should be provided as Script::new
. Every segwti tx has an empty script sig instead of the None script sig.
src/interpreter/inner.rs
Outdated
// Key spend | ||
// let sig = | ||
// Tr inference to be done in future commit | ||
todo!(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
n.b. might be better not to commit a todo! here for better bisecting sometime down the line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a comment about todo!
not working on MSRV? Or that it might panic? If it is the latter, I think it is safe to do here because this is a new feature being added and no code path should reach here.
This also helps in making commits smaller/more atomic and easy to review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure about that, I just meant it's nice if I want to test the git history against a taproot fragment that I can see where a bug in the interpreter gets added (e.g., like the bug I pointed out below about stack sizes would not be catchable in a bisect here).
src/interpreter/inner.rs
Outdated
let script = miniscript.encode(); | ||
let miniscript = pre_taproot_to_no_checks(&miniscript); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add a comment here?
src/interpreter/inner.rs
Outdated
let script = miniscript.encode(); | ||
let miniscript = pre_taproot_to_no_checks(&miniscript); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add a comment on this line? why
src/interpreter/inner.rs
Outdated
// Convert a miniscript from a specified context into an insane miniscript | ||
// We need to remember how the hash160 was translated because while doing a checksig | ||
// we need to know whether to parse the public key provided in witness as x-only or full | ||
pub(super) fn pre_taproot_to_no_checks<Ctx: ScriptContext>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this function / its name is kinda confusing; maybe you can clarify the comment to discuss what the goal is (parse the public key provided in witness as x-only or full) and then what we do & why it accomplishes that?
Maybe you can also add a crate local trait IsPreTaproot to restrict what Ctx it can be called with.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or a trait crate local ToNoChecks and you can impl it default, and change just Tap CTX's impl?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The interpreter API operates on a NoChecks context because making it context-specific would involve lots of duplicate code for each context. The interpreter is for the use case when you already have a transaction and want to figure out what constraints were satisfied. The primary use-case for this feature was miniscript friendly block explorers that would output what constraints were satisfied for this given transaction. Hence, the insane scripts.
As for this PR, I have added a ToNoChecksTrait
that makes the code look cleaner.
src/interpreter/inner.rs
Outdated
|
||
// Convert a miniscript from a specified context into an insane miniscript | ||
#[allow(dead_code)] | ||
pub(super) fn taproot_to_no_checks<Ctx: ScriptContext>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this conversion insane? why do we do this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will add a comment elaborating this.
src/interpreter/mod.rs
Outdated
// - This does not implement ToPublicKey to avoid context dependant encoding/decoding of 33/32 | ||
// byte keys. This allows us to keep a single NoChecks context instead of a context for | ||
// for NoChecksSchnorr/NoChecksEcdsa. | ||
// Long term TODO: There really should be any need for Miniscript<Pk: MiniscriptKey> struct |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit; typo missing "not be any need"
src/interpreter/mod.rs
Outdated
|
||
// While parsing we need to remember how to the hash was parsed so that we can | ||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
enum TaggedHash160 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does "tagged" mean in this context?
just that we track what kind of key?
May call it TypedHash because TaggedHash now means something else
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I had the same confusion!
&'iter self, | ||
verify_sig: F, | ||
) -> Iter<'txin, 'iter, F> { | ||
verify_sig: Box<dyn FnMut(&KeySigPair) -> bool + 'iter>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think you can make F: dyn FnMut... + 'iter without forcing it to be Boxed explicitly
looks pretty good to me! |
n.b. rust-bitcoin/rust-bitcoin@04787d4 will seem to break some of the code using Prevouts, not clear if that's going into a 0.28 or not. is this commit going into RC2? |
src/interpreter/inner.rs
Outdated
// Our sighash structure and signature verification | ||
// does not support annex, return error | ||
return Err(Error::TapAnnexUnsupported); | ||
} else if wit_stack.len() >= 2 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this line is incorrect -- wit_stack can be 1 element (or in the case of CTV, empty!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the confusion comes from the fact that the tap_script and ctrl_blk are popped off the wit_stack at this point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, the script spend must contain at least two witness elements that are being popped here. The additional witness elements can be 0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nope; I still think it's incorrect.
referencing: https://github.com/bitcoin/bitcoin/blob/64a4483dc6798a9a7d8327d320a17b3c4d7d4ee0/src/script/interpreter.cpp#L1919
- if stack empty, fail
- Check if there is 2 things and one of the things is an annex, remove it
- If the stack is length 1 do keypath
- If the stack is length 2 do script spend
Whereas your code says:
- if stack empty, fail
- If the length is 1 do keyspend
- If the length is >= 2, pop the stack for CTRL and pop for tap script (stack length now 0 to N elemented):
- If CTRL is actually an annex (0x50), fail.
- Now control is actually a control, and script is a script for sure
- check that there are >= 2 elements on the stack; if not fail
- Execute the script
Clearly, the last step should not be happending! For example, the following witness fails:
<sig> <pk checksig> <ctrl>
because the script only needs one argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(the right number of arguments is zero)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clearly, the last step should not be happending! For example, the following witness fails:
Are you missing adding an example?
If I am understanding this correctly, this will fail when the key spend has annex? To be clear, my only concern here is we should fail cleanly on the annex, and not the exact emulation of bitcoin consensus.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, the 0
is a bug, my integration tests are failing on it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a commit fixing this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lol -- example was there, but markdown was hiding it because it looked like html
I am not sure if it will be 0.28. It is a good to have for rust-miniscript, but I think we may have to wait 0.28.1 |
src/interpreter/inner.rs
Outdated
.last() | ||
.and_then(|x| x.as_push().ok()) | ||
.map(|x| x.len() > 0 && x[0] == TAPROOT_ANNEX_PREFIX) | ||
.unwrap_or(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think slice patterns with .. are not stable yet, but it'd be great if this could be done with pattern matches...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it rust 2021 :P ? We are still with 1.29 MSRV :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://adventures.michaelfbryan.com/posts/daily/slice-patterns/
1.26 for slice patterns, 1.42 for the "everything else .." matching within them.
so i think it's only really viable once we're at 1.42
src/interpreter/inner.rs
Outdated
// Annex is non-standard, bitcoin consensus rules ignore it. | ||
// Our sighash structure and signature verification | ||
// does not support annex, return error | ||
return Err(Error::TapAnnexUnsupported); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you should keep this, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, indeed. Initially, I was thinking that we can just pop and ignore it. But as the comment says here, since we don't support signature verification with Annex, it is correct to Error
src/interpreter/inner.rs
Outdated
.map(|x| x.len() > 0 && x[0] == TAPROOT_ANNEX_PREFIX) | ||
.unwrap_or(false); | ||
if has_annex { | ||
wit_stack.pop(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return error here.
src/interpreter/inner.rs
Outdated
@@ -216,47 +228,36 @@ pub(super) fn from_txdata<'txin>( | |||
None, // Tr script code None | |||
)) | |||
} else { | |||
// wit_stack.len() >=2 | |||
// Check for annex | |||
let ctrl_blk = wit_stack.pop().ok_or(Error::UnexpectedStackEnd)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: mild preference to have an explicity match over wit_stack.len() for 0, 1, _ and return Error::UnexpectedStackEnd for 0, and then make a new error Errror::ImplementationInvariantBug or something for the pops here (so that we're not using expect or unwrap) -- for readability it makes it more clear to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to match over len().
I don't see any expects or unwraps in this code? Where are we using those?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we're not, i was just saying that now the ok_or is guaranteed to succeed, so it might make sense to make it an assertion via unwrap or expect (since it should be unreachable) or to add a Error::ImplementationError instead of unwraps or UnexpectedStackEnd (because the user should know they didn't do submit something invalid if it ever hits).
537f123
to
cd2ae51
Compare
.map(|x| x.len() > 0 && x[0] == TAPROOT_ANNEX_PREFIX) | ||
.unwrap_or(false); | ||
let has_annex = has_annex && (wit_stack.len() >= 2); | ||
if has_annex { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: may save a future developer some heart ache if you pop the annex still / comment that it should be popped. not required.
Verification of TR looks much cleaner now (dare I say correct?), thanks for the fixups. |
@apoelstra , ready for your review again |
cd2ae51
to
4d112c9
Compare
There are a few wierd changes that are waiting on upstream changes, but we should be good for 0.28.0 release. These changes would be caught by compiler after they are fixed upstream
I realize that introducing NoChecksEcdsa/NoChecksSchnorr was the wrong way to go. - Remove lifetimes from return types - Change script from Script to Option<Script> - Makes all the awkward hacks non-public - This makes the tests for internal non-exposed types really ackward with .into() etc, but I don't think this matters much as it is not a public API
This is an unituitive API that mutates interpreter for minor efficiency gains
This was a mistake. In the new API, we never decode/encode from NoChecks. We don't need a tag to know whether we want to parse 32/33 byte keys. We parse script with the correct context and then create another instance NoChecks
Rename TaggedHash160 -> TypedHash160
4d112c9
to
be83ac7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK be83ac7
b3f4c18 Correct redeem script/script code caluclation (sanket1729) e9b53e9 Update psbt finalize_* APIs to consume self (sanket1729) 472f803 Update Psbt APIs with _mut and _input support (sanket1729) daa80b7 Update finalize API to return vector of errors (sanket1729) 68cccb3 Add support for finalize input (sanket1729) 5fa86b2 Move input sighash type match checks into sanity_check (sanket1729) 49fe1ca Introduce PsbtExt trait (sanket1729) 0c64a70 Add more auto-derives to conversion error (sanket1729) 30fa409 Remove local function script_is_v1_p2tr (sanket1729) Pull request description: Based on #301 ACKs for top commit: apoelstra: ACK b3f4c18 Tree-SHA512: eaee7b50357c8dad94f11baf6f7116fe982c17b284f3d10f64d85cf4cd91b1c696d46f50b2f8b223dd5abd6d1243e77fa1f57f19a4253c78df747f90b423e3d9
Add taproot interpreter support. This is the second to last PR for taproot miniscript support. The last PR will add some more high-level APIs for taproot psbt and integration tests.