-
Notifications
You must be signed in to change notification settings - Fork 2
Add state commitment functions for persistent storage via Taproot #46
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
base: master
Are you sure you want to change the base?
Changes from all commits
de01644
46f9593
7987384
a3cda02
9bc94e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| fn own_script_hash_with_state(state_data: u256) -> u256 { | ||
| // This is the bulk of our "compute state commitment" logic. | ||
| let tap_leaf: u256 = jet::tapleaf_hash(); | ||
| let state_ctx1: Ctx8 = jet::tapdata_init(); | ||
| let state_ctx2: Ctx8 = jet::sha_256_ctx_8_add_32(state_ctx1, state_data); | ||
| let state_leaf: u256 = jet::sha_256_ctx_8_finalize(state_ctx2); | ||
| let tap_node: u256 = jet::build_tapbranch(tap_leaf, state_leaf); | ||
|
|
||
| // Compute a taptweak using this. | ||
| let bip0341_key: u256 = 0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0; | ||
| let tweaked_key: u256 = jet::build_taptweak(bip0341_key, tap_node); | ||
|
|
||
| // Turn the taptweak into a script hash. | ||
| let hash_ctx1: Ctx8 = jet::sha_256_ctx_8_init(); | ||
| let hash_ctx2: Ctx8 = jet::sha_256_ctx_8_add_2(hash_ctx1, 0x5120); // Segwit v1, length 32 | ||
| let hash_ctx3: Ctx8 = jet::sha_256_ctx_8_add_32(hash_ctx2, tweaked_key); | ||
| jet::sha_256_ctx_8_finalize(hash_ctx3) | ||
| } | ||
|
|
||
| pub fn load(state_data: u256) { | ||
| // Assert that the input state is correct, i.e. "load". | ||
| // | ||
| // Enforce that the state commitment hash in the Taptree alongside | ||
| // the current input is equal to state_data. (This must be a result | ||
| // of the transaction builder's having constructed the prior | ||
| // transaction so that this is true.) | ||
| // Panics otherwise. | ||
| assert!(jet::eq_256( | ||
| own_script_hash_with_state(state_data), | ||
| unwrap(jet::input_script_hash(jet::current_index())) | ||
| )); | ||
| } | ||
|
|
||
| pub fn store(new_state: u256, index: u32) { | ||
| // Assert that the output state is correct, i.e. "store". | ||
| // | ||
| // The index parameter specifies the output index where the | ||
| // new copy of this covenant is located. Depending on the | ||
| // covenant convention, that could be jet::current_index() | ||
| // (same index as input), some other hard-coded index | ||
| // demanded by convention, or could even be flexible and | ||
| // determined by a witness parameter. | ||
| // | ||
| // Enforce that the state commitment hash in the Taptree alongside | ||
| // the specified output is equal to new_state. (This must be a | ||
| // result of the transaction builder constructing the transaction | ||
| // so that this is true.) | ||
| // Panics otherwise. | ||
| assert!(jet::eq_256( | ||
| own_script_hash_with_state(new_state), | ||
| unwrap(jet::output_script_hash(index)) | ||
| )); | ||
| } | ||
|
Comment on lines
+20
to
+53
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These functions are also very useful, but we can only provide a universal interface for them if the user passes the pre-calculated
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I certainly see the problem that these functions are not as general as they could be and I think the instinct to generalize them more is a very good one. I was talking to an AI last week about this, and it pointed out the related issue that, once we have a way to recognize a specific other contract, we may want to have covenants that have an "exit through other covenant" (e.g. you can withdraw from the existing covenant into a specified new covenant with a derived state). In a sense @apoelstra is currently actively working on the "way to recognize a specific other contract" for these purposes, and the mechanisms for multiple covenants to deliberately cooperate on cospending and recognizing each other. I also think it's a good catch that the internal key hard-coding is questionable (as another example of the generality issue). I'm a little torn about this: I really regret not having a straightforward and officially documented way for people to do covenant enforcement right now. I can see that this way is possibly making too many assumptions and is not general enough, but I would love to figure out how to get covenant-enforcement mechanisms into the standard library early on so that people can make use of them, since covenants are such an important use case for Simplicity! The greatest level of generality for these functions would presumably also not assume that the destination contract has the same identity as the current contract (but allow that to be specified). The possible disadvantages of this generality are (1) @apoelstra hasn't finished specifying some of the tools that we might need for that, and (2) understanding how to use it might be more intimidating for developers, as instead of an interface like
we might get something more like
This is great in a way. because it has more flexibility and more use cases, but also potentially far more challenging for people to understand. Perhaps we should implement that more general mechanism, and then also wrap it with a
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that there are a lot of things to consider in this matter, which will need to be resolved in the future or are already being resolved. Therefore, std will continue to change many more times, depending on the language's capabilities and the needs of developers |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| use crate::lib::storage::{load, store}; | ||
| use crate::helper::if_test_this_function; | ||
|
|
||
| fn main() { | ||
| let fn_idx: u8 = witness::FUNCTION_INDEX; | ||
|
|
||
| let state_data: u256 = witness::STATE_DATA; | ||
| let new_state: u256 = witness::NEW_STATE; | ||
| let index: u32 = witness::INDEX; | ||
|
|
||
| match if_test_this_function(0, fn_idx) { true => { load(state_data); }, false => (), }; | ||
| match if_test_this_function(1, fn_idx) { true => { store(new_state, index); }, false => (), }; | ||
| match if_test_this_function(2, fn_idx) { true => { load(state_data); store(new_state, index); }, 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 we need to break this function down into two types of functions:
internal_keyand atap_nodeand calculates the resultingscript_hashThe thing is, the Taproot Storage structure can vary across different contracts, so
stdneeds to provide universal functions