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

CIP-0038? | Arbitrary Script as Native Script spending conditions #309

Closed
wants to merge 6 commits into from
Closed
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions CIP-XXXX/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
CIP: XXXX
Title: Arbitrary Script as Native Script spending conditions
Authors: Sebastien Guillemot <seba@dcspark.io>
Comments-URI: TBD
Status: Draft
Type: Standards
Created: 2022-07-27
License: CC-BY-4.0
---

# Abstract

Native scripts are often easier to work with as any application that allows users to enter an ADA address to receive funds supports native scripts (the same is not true for Plutus scripts as the app would need to know how to structure the datum). However, limited composability between native scripts and Plutus scripts limits leveraging this fact.

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

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.
Copy link
Contributor

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?

Copy link
Contributor

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 even SimpleScriptV2 as a language and this would then be SimpleScriptV3).

Copy link

@catch-21 catch-21 Aug 1, 2022

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?

This is unfortunately the case. All spending scripts require datum (it is the 2-argument scripts that do not, such as for minting)

Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

is about the same size of change

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)

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you elaborate on this? By this do you mean that when specifying a reference input, the datum still needs to be provided in the witness field of the tx, even if the input you're pointing to is an online datum?

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).

Copy link
Contributor Author

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.

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.

Copy link
Contributor

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.

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

Encoding the datum in the address is an interesting idea! That makes addresses a bit longer, but then resolving the ada handle address could encode the target datum.

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.

There are also cases where people have seen an address in their wallet from interacting with a dApp, and thought they could send more funds to that address to continue to interact with the dApp, resulting in locked funds.

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.

Copy link
Contributor

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


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:
Copy link
Contributor

Choose a reason for hiding this comment

The 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:

proxyValidator ::
  -- | Proxied script hash
  ValidatorHash ->
  -- | Datum, to be ignored. We could also define '()' to be the canonical datum for proxy contracts.
  BuiltinData ->
  -- | Redeemer, also not needed (depending on complexity of proxy conditions).
  BuiltinData ->
  ScriptContext ->
  Bool
proxyValidator target _ _ context =
  -- Very basic example of just ensuring all value gets paid to the right target, to be extended.
  valueSpent txInfo == valueLockedBy txInfo target
 where  
  ScriptContext{scriptContextTxInfo = txInfo} = context

Copy link
Contributor Author

@SebastienGllmt SebastienGllmt Aug 1, 2022

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

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

if we enable datum-less execution of Plutus scripts (which I think we should)

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The 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 RequireScript.

Are you planning to create a CIP for that or shall I create one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

feel free to create one

Copy link
Contributor

Choose a reason for hiding this comment

The 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 80 (an array of size zero)?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm much more in favour of something like this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

These problems could be solved by adding a many new native script conditions such as enforcing that the transaction that spends it needs to have a certain NFT as part of its inputs or that the transaction contains a specific output. However, there is no guarantee this is flexible enough for all use-cases and these may bring unnecessary feature creep to the native script feature.

Instead, 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.
Copy link
Contributor

Choose a reason for hiding this comment

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

I am worried that this design opens up the door to the "double satisfaction problem". In other words, someone might place a RequireScript scripthash condition in their native script, expecting the plutus script to execute exactly once, with un-intended consequences if the script runs is used multiple times in the same transaction. Also, does it matter that the new condition has no way of specifying the datum for the script?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm true, it's possible for somebody to write a smart contract such that using it as a native script condition could lead to unintended exploits. You could try and tackle some of the common solutions to double satisfaction (predicate on the datum, NFT to indicate real version, etc.) inside native scripts but this also complicated the native script language. I feel it would be better to tackle and double satisfaction problem by having the native script require the presence of 2 Plutus scripts -- one is the real script you need and the second is just a script that checks for any double satisfaction issues

Copy link
Contributor

Choose a reason for hiding this comment

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

I definitely understand why folks would want this feature.

The beauty of the native scripts, as I see it, is their simplicity. Maybe we could have this CIP be dependent of having a good solution to the double satisfaction problem?

Choose a reason for hiding this comment

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

What exactly is the "double satisfaction problem"?

@JaredCorduan's question doesn't specify how the unintended consequences can arise (and of what nature?). Aren't Plutus scripts supposed to be pure functions? So what if a Plutus script runs twice or more?

Copy link
Contributor

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

@JaredCorduan Thanks for the link to an excellent explainer of the problem!

I'd say, if the RequireScript scripts work just like normal Plutus spending validation scripts, then exactly the same solutions apply (as described in the link). For example, the script could make sure the outputs of the spending transaction refer back to the outputs being spent 1:1.

Copy link
Contributor

Choose a reason for hiding this comment

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

indeed, I just wanted to make sure that we would be consistent with however Plutus solves the problem, and I wanted to point out that the situation is at least slightly more complicated than folks might realize at first glance.

Copy link

@fallen-icarus fallen-icarus Jan 26, 2023

Choose a reason for hiding this comment

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

@JaredCorduan

someone might place a RequireScript scripthash condition in their native script, expecting the plutus script to execute exactly once, with un-intended consequences if the script runs is used multiple times in the same transaction.

The "double satisfaction problem" seems related to the redundant executions of spending scripts (see here). Right now, spending scripts are executed once for every UTxO spent from the script's address and their is no way around this. In my Maybe Datum PR, I try to address this issue by allowing tx-level spending scripts (i.e., spending scripts that are only executed once per tx). With this change, your concern can be addressed by only using RequiredScript scripthash with tx-level scripts if the "double satisfaction problem" is relevant.

Thanks to the eUTxO model, you can already design validators that check ALL the inputs from the script's address and then check that the tx outputs satisfy the conditions for the leaving balance. So to use your link's example, since B is trying to trick the validator by using one tx for both UTxOs, the validator can see that T1 and T2 are leaving the address in that tx. Therefore, it can easily verify if the proper amount (20 ADA in this case) goes to A's address. I created a DEX proof-of-concept that uses composable atomic swaps which do exactly this (here). The issue with these tx-level validator designs is that, even though they validate based off the tx context as a whole, they are still forced to be executed once per UTxO which makes them inefficient to use due to redundant executions. So while my DEX does not need to be worried about the "double satisfaction problem", it does suffer from the redundant executions. The Maybe Datum PR seeks to address this in plutusV3.

Edit: Fixed saying "20 ADA" is leaving instead of "T1 and T2" in link's example.

Copy link
Contributor

Choose a reason for hiding this comment

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

So to use your link's example, since B is trying to trick the validator by using one tx for both UTxOs, the validator can see that T1 and T2 are leaving the address in that tx. Therefore, it can easily verify if the proper amount (20 ADA in this case) goes to A's address

yep, absolutely. if you are aware of the problem, you can guard against it. the problem is that if you are not aware of the potential problem, it's an easy trap to fall into. I haven't grokked your Maybe Datum CIP yet, but thanks for the link.

Copy link
Contributor

Choose a reason for hiding this comment

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

Instead, 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.

I think I may have misunderstood this condition. In fact, I don't know what it means at all. Perhaps you could add as part of the specification an explanation of how the new script clause is actually validated (which you don't say anywhere!).

My original reading was that this meant that there must be some other input which is locked with the given script, but it could also be read as saying that the given script must be run as if it was the validator script for the current input and pass in that case. These are quite different.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

here must be some other input which is locked with the given script

This was the meaning I intended


# 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>
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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"
},
]
}
```

Choose a reason for hiding this comment

The 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
## Script Execution
The proposed spending condition of `"type": "script"` may refer to any simple or Plutus script via the `scriptHash` field. The actual script is presented by the spending transaction: either in the witness set or in a reference input.
Depending on the type of the referred script, additional processing is done.
- native (a.k.a. simple) scripts are recursively evaluated as described in https://github.com/input-output-hk/cardano-node/blob/master/doc/reference/simple-scripts.md, with the addition of the `RequireScript` constructor proposed here.
- Plutus scripts are executed as regular spending script as (informally) described in https://github.com/input-output-hk/cardano-node/blob/master/doc/reference/plutus/plutus-spending-script-example.md. This means, the output's datum is passed to the script, as well as the redeemer presented by the spending transaction, and script context (constructed according to the Plutus version) is passed as the third argument.

Copy link

@imikushin imikushin Dec 2, 2022

Choose a reason for hiding this comment

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

Copy link
Contributor Author

@SebastienGllmt SebastienGllmt Dec 6, 2022

Choose a reason for hiding this comment

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

additional processing is done

I'm not sure what you mean by this. No additional processing is done, since the script referenced in RequireScript must be present in some other input and that input is the one who is doing the processing

Choose a reason for hiding this comment

The 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 additional processing is done. and keep the rest?

# 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.