-
Notifications
You must be signed in to change notification settings - Fork 329
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
CIP-0038? | Arbitrary Script as Native Script spending conditions #309
Changes from all commits
2a7790b
fcbdbd7
33b47ac
5f52150
96a3e85
3202ace
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,151 @@ | ||||||||||||||||||||
--- | ||||||||||||||||||||
CIP: 38 | ||||||||||||||||||||
Title: Arbitrary Script as Native Script spending conditions | ||||||||||||||||||||
Authors: Sebastien Guillemot <[email protected]> | ||||||||||||||||||||
Comments-URI: TBD | ||||||||||||||||||||
Status: Draft | ||||||||||||||||||||
Type: Standards | ||||||||||||||||||||
Created: 2022-07-27 | ||||||||||||||||||||
License: CC-BY-4.0 | ||||||||||||||||||||
--- | ||||||||||||||||||||
|
||||||||||||||||||||
# Abstract | ||||||||||||||||||||
|
||||||||||||||||||||
Native scripts behave differently than Plutus scripts, notably by being easier & cheaper to work with for both users and developers. However, limited composability between native scripts and Plutus scripts limits leveraging these facts. | ||||||||||||||||||||
|
||||||||||||||||||||
This CIP introduces a way to use native scripts as a starting point for more complex interactions which helps unlock use cases such as simple proxy contracts | ||||||||||||||||||||
|
||||||||||||||||||||
# Motivation #1: Proxy contracts | ||||||||||||||||||||
|
||||||||||||||||||||
Suppose that you are part of a DAO whose funds are managed by a Plutus contract. Your DAO, in order to receive payments, would like receive ADA or tokens to its script address directly. However, this is non-trivial because applications cannot sent to arbitrary Plutus scripts as they do not know how to structure the datum or what other kind of restrictions may exist for this contract. | ||||||||||||||||||||
|
||||||||||||||||||||
To solve this, one way would be to instead have a proxy contract that receives funds and forwards them to your DAO with the proper structure. Native scripts at the moment can play this role by creating a native script multisig where some set of DAO members have their public keys specified in the spending condition of the multisig. However, this approach has the following problems: | ||||||||||||||||||||
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 believe such a proxy contract could be written in Plutus today. For example with plutus-tx syntax:
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. Using a Plutus script to solve this problem won't work unless a PlutusV3 is made that accept no-datum inputs as mentioned in the thread above. I also go more into detail about this in a video (I forgot to add it to the original PR description) Additionally, native scripts also have the benefit that they don't need collateral so they behave slightly differently that script kinds that require a phase-2 validation. Even if we enable datum-less execution of Plutus scripts (which I think we should), this change to native scripts may still be useful to some -- especially if we end up adding other languages in the future that also don't require phase-2 validation 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 also think we should make this possible! Even if we do disagree on the amount of work needed to extend simple vs. plutus scripts, it still seems to me that this would be the minimum viable feature the motivation of this CIP requires. Would datum-less execution of Plutus scripts allow the use cases you have in mind? 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. If you have datum-less support for Plutus you still would need 2 hops to achieve this kind of proxy contract because you would need a datum-less templated Plutus script where the template hard-codes the datum content into the contract which then is used to forward the datum to the final contract That is to say, I think datum-less contracts also enable the same proxy infrastructure 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. Yes, the basic structure for the "proxy contract" use case is the same. But the plutus script would also allow to encode other things than just an embedded datum, i.e. whatever you intend to be putting in that side-car plutus script referred by Are you planning to create a CIP for that or shall I create one? 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. feel free to create one 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. Could we solve this problem with a convention for an "empty datum"? Perhaps the cbor 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'm much more in favour of something like this. 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. As another argument in favor of this adding this native script solution as an alternative to any Plutus improvements, this native script version combined with #310 makes it easier for wallets to know it's safe for a user to send to an address. If they are sending to a Plutus script, there is no guarantee the Plutus script is handling the empty datum script so a warning would still have to be shown to the user |
||||||||||||||||||||
|
||||||||||||||||||||
- There is no way to guarantee that these members will actual forward the funds to the DAO instead of pocketing the funds | ||||||||||||||||||||
- DAO membership may not easily be representable by a small set of fixed public keys | ||||||||||||||||||||
|
||||||||||||||||||||
To avoid unnecessary feature creep to the native script feature, the most generic solution is to allow a new condition where a native scripts can only be spent if a specific Plutus script is also part of the transaction input. This allows the multisig to simply handle receiving the funds and having all the complex logic (DAO membership checks, output checks, etc.) to be added to the Plutus script. | ||||||||||||||||||||
|
||||||||||||||||||||
# Motivation #2: Duplicate script condition cost reduction | ||||||||||||||||||||
|
||||||||||||||||||||
If you have a system that handles many Plutus UTXO entries locked by the same condition, spending many of them at the same may be expensive due to the cumulative Plutus execution cost of every utxo. This CIP would allow having all utxos locked under the same native script that all can only be spent according to some master Plutus singleton that encodes the spending condition. Depending on the contract, this can easily bring cost of a transaction down from 100 Plutus contract executions to just 1 Plutus contract and 99 native script (cheap) executions. | ||||||||||||||||||||
|
||||||||||||||||||||
# Motivation #3: Evolving scripts | ||||||||||||||||||||
|
||||||||||||||||||||
Some systems may want the behavior of their script to change at a specific slot number. A good example of this is an NFT mint that may want one behavior to start (ex: allow minting new tokens), but switch to another behavior later (ex: allow minting or burning only in special situations) | ||||||||||||||||||||
|
||||||||||||||||||||
This CIP enables this behavior by using the `before` and `after` functionality of native scripts to toggle the behavior of the contract | ||||||||||||||||||||
|
||||||||||||||||||||
```json | ||||||||||||||||||||
{ | ||||||||||||||||||||
"type": "any", | ||||||||||||||||||||
"scripts": [{ | ||||||||||||||||||||
// standard multisig "before" clause" | ||||||||||||||||||||
"type": "all", | ||||||||||||||||||||
"scripts": [{ | ||||||||||||||||||||
"type": "before", | ||||||||||||||||||||
"slot": 40272443 | ||||||||||||||||||||
}, | ||||||||||||||||||||
{ | ||||||||||||||||||||
"type": "sig", | ||||||||||||||||||||
"keyHash": "ed6f3e2144d70e839d8701f23ebcca229bcfde8e1d6b7838bda11ac8" | ||||||||||||||||||||
} | ||||||||||||||||||||
] | ||||||||||||||||||||
}, { | ||||||||||||||||||||
// after some time has passed, switch to now using a multisig to govern the mint/burn behavior | ||||||||||||||||||||
"type": "all", | ||||||||||||||||||||
"scripts": [{ | ||||||||||||||||||||
"type": "after", | ||||||||||||||||||||
"slot": 40272443 | ||||||||||||||||||||
}, | ||||||||||||||||||||
{ | ||||||||||||||||||||
"type": "script", | ||||||||||||||||||||
"scriptHash": "plutus_script_hash_here" | ||||||||||||||||||||
} | ||||||||||||||||||||
] | ||||||||||||||||||||
}] | ||||||||||||||||||||
} | ||||||||||||||||||||
``` | ||||||||||||||||||||
|
||||||||||||||||||||
Native scripts are nice for minting assets, but have no way to add conditions for burning assets post-mint. | ||||||||||||||||||||
If you have a system that handles many Plutus UTXO entries locked by the same condition, spending many of them at the same may be expensive due to the cumulative Plutus execution cost of every utxo. This CIP would allow having all utxos locked under the same native script that all can only be spent according to some master Plutus singleton that encodes the spending condition. Depending on the contract, this can easily bring cost of a transaction down from 100 Plutus contract executions to just 1 Plutus contract and 99 native script (cheap) executions. | ||||||||||||||||||||
|
||||||||||||||||||||
# Motivation #4: Better smart contract wallets | ||||||||||||||||||||
|
||||||||||||||||||||
Some projects want to create smart-contract powered wallets. However, in a lot of cases, these don't actually need all the power of Plutus in the average use case. That is to say, a native script can be used to encode the happy path as well as a fallback Plutus path when needed | ||||||||||||||||||||
|
||||||||||||||||||||
```json | ||||||||||||||||||||
{ | ||||||||||||||||||||
"type": "any", | ||||||||||||||||||||
"scripts": [{ | ||||||||||||||||||||
// in the general case, the user such signs like normal | ||||||||||||||||||||
"type": "sig", | ||||||||||||||||||||
"keyHash": "ed6f3e2144d70e839d8701f23ebcca229bcfde8e1d6b7838bda11ac8" | ||||||||||||||||||||
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. One of the problems is that this only gives you limited account abstraction. What if instead of ed25519, you want some other cryptographic scheme for the multisig? Currently, the only way to access secp256k1 is through Plutus, and if we ever try and move to post-quantum crypto (#441), these would end up all being Plutus To properly support these, we would probably need a separate CIP to extend native script |
||||||||||||||||||||
}, | ||||||||||||||||||||
{ | ||||||||||||||||||||
// but they may use a more complicated Plutus path when needed | ||||||||||||||||||||
"type": "script", | ||||||||||||||||||||
"scriptHash": "plutus_script_hash_here" | ||||||||||||||||||||
} | ||||||||||||||||||||
] | ||||||||||||||||||||
} | ||||||||||||||||||||
``` | ||||||||||||||||||||
|
||||||||||||||||||||
# Specification | ||||||||||||||||||||
|
||||||||||||||||||||
The current definition of native scripts uses the following BNF notation | ||||||||||||||||||||
|
||||||||||||||||||||
```BNF | ||||||||||||||||||||
<native_script> ::= | ||||||||||||||||||||
<RequireSignature> <vkeyhash> | ||||||||||||||||||||
| <RequireTimeBefore> <slotno> | ||||||||||||||||||||
| <RequireTimeAfter> <slotno> | ||||||||||||||||||||
|
||||||||||||||||||||
| <RequireAllOf> <native_script>* | ||||||||||||||||||||
| <RequireAnyOf> <native_script>* | ||||||||||||||||||||
| <RequireMOf> <num> <native_script>* | ||||||||||||||||||||
``` | ||||||||||||||||||||
|
||||||||||||||||||||
Importantly, note that native scripts can only require other native scripts and not plutus scripts or any other future script type introduced. | ||||||||||||||||||||
|
||||||||||||||||||||
Therefore, this proposal suggests the definition be changed to | ||||||||||||||||||||
|
||||||||||||||||||||
```BNF | ||||||||||||||||||||
<native_script> ::= | ||||||||||||||||||||
<RequireSignature> <vkeyhash> | ||||||||||||||||||||
| <RequireScript> <scripthash> | ||||||||||||||||||||
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. Summary of a conversation with Las on this: Although require script is sufficient to do everything one would need, it's kind of tedious because there is no way to specify a datum or a tag (script, mint) in the native script, so it means you need to inline checks in the Plutus script. We could extend this CIP to enable adding other restrictions to the script (ex: passed with a given datum or tag) if that's acceptable to people If we do this, we also have to consider how to handle inline datums and reference scripts as well |
||||||||||||||||||||
| <RequireTimeBefore> <slotno> | ||||||||||||||||||||
| <RequireTimeAfter> <slotno> | ||||||||||||||||||||
|
||||||||||||||||||||
| <RequireAllOf> <native_script>* | ||||||||||||||||||||
| <RequireAnyOf> <native_script>* | ||||||||||||||||||||
| <RequireMOf> <num> <native_script>* | ||||||||||||||||||||
``` | ||||||||||||||||||||
|
||||||||||||||||||||
Which we propose uses the `type: script` when used in JSON notation such as in the following example: | ||||||||||||||||||||
|
||||||||||||||||||||
```json | ||||||||||||||||||||
{ | ||||||||||||||||||||
"type": "all", | ||||||||||||||||||||
"scripts": | ||||||||||||||||||||
[ | ||||||||||||||||||||
{ | ||||||||||||||||||||
"type": "script", | ||||||||||||||||||||
"scriptHash": "b275b08c999097247f7c17e77007c7010cd19f20cc086ad99d398538" | ||||||||||||||||||||
}, | ||||||||||||||||||||
] | ||||||||||||||||||||
} | ||||||||||||||||||||
``` | ||||||||||||||||||||
|
||||||||||||||||||||
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. It wouldn't hurt to add a section on how exactly the referred scripts are going to be interpreted. Here's what I would add.
Suggested change
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. 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'm not sure what you mean by this. No additional processing is done, since the script referenced in 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. Perhaps, it is bad wording. Would it be clearer if we remove the phrase |
||||||||||||||||||||
Here is an illustration of transactions with their input set annotated by which ones fail and which ones succeed | ||||||||||||||||||||
|
||||||||||||||||||||
![image](https://user-images.githubusercontent.com/2608559/227744589-b8610d44-ff75-4559-b022-de75c2cb542b.png) | ||||||||||||||||||||
|
||||||||||||||||||||
# Backwards compatibility | ||||||||||||||||||||
|
||||||||||||||||||||
Currently there are two versions of native scripts: | ||||||||||||||||||||
|
||||||||||||||||||||
- V1 starting in Shelley that had `RequireAllOf`, `RequireAnyOf`, `RequireMOf`, `RequireSignature` | ||||||||||||||||||||
- V2 starting in Allegra that added `RequireTimeBefore`, `RequireTimeAfter` | ||||||||||||||||||||
|
||||||||||||||||||||
Note that all that was required to add functionality in Allegra was to create a new native script language internally (`SimpleScriptV1` vs `SimpleScriptV2`) inside the Cardano codebase and did not require a new hash namespace or a new address for existing scripts. The same should be true for this proposal. |
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.
Are you sure that a UTxO with
DatumNone
paid to a plutus script is unspendable? I have not tried this yet, but it should be possible if the script does not look for a datum? If not, then this might be the solution you are looking for instead?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 this not be possible today, this would likely need to go into a
PlutusV3
, but changing the native scripts syntax is about the same size of change (IIRC there is evenSimpleScriptV2
as a language and this would then beSimpleScriptV3
).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 is unfortunately the case. All spending scripts require datum (it is the 2-argument scripts that do not, such as for minting)
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.
@james-iohk is indeed correct. note that you cannot tell if a script hash inside of a transaction output is a native script or a plutus script without having the script.
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 disagree with this because any change to the Plutus context breaks same-transaction composability (i.e. you wouldn't be able to spend a PlutusV2 tx in the same tx as a PlutusV3 tx)
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 answer is yes, but let me try to spell it out. An inline datum in a transaction output can only be used for the script (hash) inside the given output. You cannot just use a reference input whose corresponding output has an inline datum, and have that datum be totted around to all the places it is needed (like how reference scripts work).
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'm also not sure what you mean by this. The PlutusContext contains all the reference inputs of the transaction. These outputs all contain the datum which may be available inlined.
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, it is true that inline datums attached to reference inputs end up in the script context, but there is a requirement that each datum hash must have its preimage in the transaction witnesses. What I am saying as that reference inputs (which point to inline datums) cannot substitute for this requirement.
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 am thinking of something like how URLs work. We are quite familiar with the idea of passing additional information in URLs via e.g. query parameters. That gives you the "one link to click" UX but with the ability to include extra payload to be sent. You could imagine an address format like
address?datum=<cbor hex>
. Maybe this is bad UX for other reasons, I just wanted to float it as an idea.This seems pretty bad. I would think that on the wallet side you want to be pretty careful with letting people send money to script addresses, but I'm not sure what the ideal UX is.
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.
Exactly; some wallets are starting to warn about that, but the main problem is that the wallets can't tell the difference between a native script and a Plutus script without seeing the script bytes somewhere on-chain. It's totally safe to send funds to a multi-sig address without a datum. They both start 0x70. Hence, I think, #310