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

Conversation

SebastienGllmt
Copy link
Contributor

@SebastienGllmt SebastienGllmt commented Jul 31, 2022

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.

You can learn more about one of the motivations here: https://www.youtube.com/watch?v=5cI1DfrlO0E


see rendered Markdown

@SebastienGllmt SebastienGllmt changed the title [CIP-???] Arbitrary script [CIP-???] Arbitrary Script as Native Script spending conditions Jul 31, 2022
@SebastienGllmt SebastienGllmt changed the title [CIP-???] Arbitrary Script as Native Script spending conditions CIP-???? | Arbitrary Script as Native Script spending conditions Jul 31, 2022
Copy link
Contributor

@ch1bo ch1bo left a comment

Choose a reason for hiding this comment

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

Instead of extending the native (aka. simple) scripts to encode such rules, IMO it should be possible to solve your use case using plutus directly. If not today, then this CIP should be about extending / changing plutus to what you need?


# 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


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


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.

@KtorZ KtorZ changed the title CIP-???? | Arbitrary Script as Native Script spending conditions CIP-0038 | Arbitrary Script as Native Script spending conditions Aug 2, 2022
@KtorZ KtorZ changed the title CIP-0038 | Arbitrary Script as Native Script spending conditions CIP-0038? | Arbitrary Script as Native Script spending conditions Aug 2, 2022
@keyan-m
Copy link

keyan-m commented Aug 5, 2022

The idea of being able to redeem datum-less UTxOs at script addresses is very desirable. Here's another idea: have you considered "extending" the validation protocol, such that a value of unit is implied from absence of a datum hash? This solution, I believe, is a simple modification, does not necessitate a hard fork, and also doesn't require a proxy script.

The downside of this solution that I can think of, is that developers should then be "careful" with their data models to avoid conflicts, as the Data equivalent of () is not unique to unit. While this is akin to not having type-level guarantees, is it really a deal-breaker?

One possible solution, that can merely reduce the chances of said conflict, is to introduce another datatype similar to () with a slightly different Data representation. Perhaps something like Constr (-1) [] (using BuiltinData constructor for demonstration), which is something that is "less likely" to appear in most contracts.

@JaredCorduan
Copy link
Contributor

have you considered "extending" the validation protocol, such that a value of unit is implied from absence of a datum hash? This solution, I believe, is a simple modification, does not necessitate a hard fork, and also doesn't require a proxy script.

I believe this solution does require a hard fork. Imagine you have a group of nodes using the current latest version of the node, and another group of nodes using the modification you described. The first group will not allow datum-less Plutus outputs to be spent, but the later will allow it. Hence they disagree about what is and is not a valid block.

@keyan-m
Copy link

keyan-m commented Aug 5, 2022

I believe this solution does require a hard fork. Imagine you have a group of nodes using the current latest version of the node, and another group of nodes using the modification you described. The first group will not allow datum-less Plutus outputs to be spent, but the later will allow it. Hence they disagree about what is and is not a valid block.

I see, thank you. I believe I had succumbed to wishful thinking (sigh).

@michaelpj
Copy link
Contributor

Here's another idea: have you considered "extending" the validation protocol, such that a value of unit is implied from absence of a datum hash?

I'd be reluctant to do anything like this that represents the absence of a datum with a value that could also appear as a valid present datum. That is, I'd want to have the script get Maybe Datum rather than Datum where there is some special value of Datum that we just hope people don't provide explicitly. But that's also a big change and we'd need to decide how to represent that Maybe.

@keyan-m
Copy link

keyan-m commented Aug 8, 2022

I'd be reluctant to do anything like this that represents the absence of a datum with a value that could also appear as a valid present datum. That is, I'd want to have the script get Maybe Datum rather than Datum where there is some special value of Datum that we just hope people don't provide explicitly. But that's also a big change and we'd need to decide how to represent that Maybe.

I agree. But just for the sake of argument, shouldn't the contract already handle random unauthorized UTxOs? Would there really be an issue with this conflict?

Also, I think Datum to Maybe Datum might be a bit misleading. BuiltinData to Maybe BuiltinData is the requirement, which I don't think can be easily implemented with the current infrastructure.

@michaelpj
Copy link
Contributor

But just for the sake of argument, shouldn't the contract already handle random unauthorized UTxOs? Would there really be an issue with this conflict?

I don't understand what you're suggesting, could you elaborate?

@keyan-m
Copy link

keyan-m commented Aug 9, 2022

I don't understand what you're suggesting, could you elaborate?

I might need further consolidation regarding my grasp of the eUTxO model, but this is my understanding: since anyone can send any UTxO with arbitrary datum to a script address, a Plutus script should already expect random UTxOs.

Assuming a validator implicitly injects a pre-defined value in absence of a datum, two things may happen: either the script does not expect the structure, in which case the transaction is likely to fail (depending on the implementation), or it may already expect it. So in the latter case, from the script's perspective, a datum-less UTxO will be equivalent to a UTxO carrying one of the expected "states."

If that statement is correct, can we assume that this pre-defined value (implied by lack of datum) is already handled by existing smart contracts? Would that cause any issues, or lead to security flaws?

This is just for the sake of argument of course, since this solution is going to lead to a hard fork anyways (thanks to @JaredCorduan), such a hacky approach is unnecessary. @SebastienGllmt seems to be on the right track.

@michaelpj
Copy link
Contributor

michaelpj commented Aug 9, 2022

Anyone can create outputs with any script hash and any datum, correct. They can also omit the datum. But if they do that, it will fail before even running the script.

I don't think that scripts will handle "wrong" datums. My expectation is that this will almost certainly just cause most scripts to fail immediately. There is no problem here: the person creating such a weird UTXO has just made a complicated un-spendable output for themselves.

@dcoutts
Copy link
Contributor

dcoutts commented Sep 14, 2022

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

@SebastienGllmt I really don't get this argument. Even if you know you're sending to a script address with a simple script, that doesn't make it "safe". Unless you can see and analyse the script, then you/your-wallet do not know what it does, or under what conditions someone might get the funds. It's not "safe" to send to a script address without knowing the script. You have literally no idea what it does.

Now it's certainly the case that an advantage of the simple script language is that it is so simple that it can be analysed and so in principle wallets could explain to users what the effect is. But once you know the script at the script address, then you know the language, and there was no advantage to encoding the language into the script address itself.

@SebastienGllmt
Copy link
Contributor Author

@SebastienGllmt I really don't get this argument.

responded here to avoid duplicating the discussion

@asutherlandus
Copy link

Thanks @SebastienGllmt I think this is a really important discussion to be having. I'm still trying to wrap my head around the scope of the problem we are trying to solve. There are a number of solutions being compared here, but without a good handle on the problem space its hard to compare trade-offs.

I think a document with some use cases would be really helpful. Here is a contribution of my favorite use case.

As a user of a lite wallet I would like to interact with a DApp without having to trust any centralized services. I am willing to trust the wallet code, but do not want to sign any transactions my wallet has not created. I want to be sure that the address to which I am sending funds is safe to use. Additionally, if the DApp fails to accept my funds for some reason I would like to be able to get them returned to me.

It seems like the combination of reference scripts and proxy contracts are a great solution to this use case. By publishing scripts to the blockchain as reference scripts we get a permanent way to refer to and talk about scripts. Whether by social reputation or formal audit I can trust that a published script is the one I want to use. In the case of a DAO or crowdfunding scenario there is the danger that the script condition will never be met and proxy contracts give me a way to get my funds back.

Your suggestion to use native scripts extended in this way seems to solve this use case nicely.

I think there may be an alternative solution using the existing Plutus machinery. We publish a generic proxy contract as a reference script that takes 2 addresses in the Datum one for the forwarding address and one for the return address.

In both cases I think the real problem is that wallets need to know about the "calling conventions" of these proxy scripts. The wallet needs to know how to create the sending transaction as well as the return one. I think we may really need a broader CIP for wallet <-> DApp interaction conventions.

Please forgive me if this is already in a CIP or possibly in the wallet specification document, I can't keep all of these things in my head.

@JaredCorduan
Copy link
Contributor

Using a native script as a proxy contract renders the user input (aka the datum) pretty useless. I don't think we should give up so soon on finding a way to build rich interactions between wallets and Dapps. And I think that @asutherlandus is on to something with the new features we've just enabled with the Vasil HF!

Consider something like a new convention for "Dapp addresses". It could contain a normal cardano address together with a transaction input. This transaction input would point to a transaction output on chain that contains 1) a reference script and 2) an inline datum. The reference script would correspond to the script in the address (and could be checked by the wallet by checking the hash). The inline datum would provide a schema so that the wallet could get user input. Note that this also solve the problems discussed in #310.

@SebastienGllmt
Copy link
Contributor Author

I don't think we should give up so soon on finding a way to build rich interactions between wallets and Dapps

I don't think anybody is suggesting we give up. We should definitely come up with other CIPs that enable features like this (I like your suggestion!). However, any other approach is purely complementary to the feature in this CIP. Nothing stops us from implement this CIP which is a fairly easy-to-understand & easy-to-implement feature while also working on other complementary options like the one you described which are more involved

@michaelpj
Copy link
Contributor

This proposal makes it easier to pay to a script address by making it so you don't need to pay to the script address, you instead pay to the native script address. However:

  1. It still doesn't handle cases where you need a datum. If you want to send money to a DAO and you want it to be e.g. properly credited as your contribution, presumably you need to provide a datum that identifies you. Putting it another way, a payment to a script output with no datum can only be a pure donation. That seems to meant that this can't cover many (most?) of the use cases.
    • Ultimately, this is a symptom of the fact that this CIP does not solve the underlying problem that it's hard to provide script-address+datum in many contexts.
  2. It does complicate the native script language in a way that's not obviously desirable or safe, as Jared comments.

I think the alternative that I'm most keen on right now is the one that I sketched in an earlier comment: #309 (comment)

Briefly:

  • We define an extended address format that allows for additional information to be included in an address string, analogous to how query parameters work in URLs.
  • We define a way to provide a datum in the extended address format, as bytes or whatever.
  • We teach systems that currently work with Cardano addresses to work with extended addresses.

The reason I like this is that:

  1. It requires no changes to Cardano itself. The extended address format is purely an off-chain thing, on-chain addresses remain the same.
  2. It does solve the problem that it's hard to provide script-address+datum in many contexts, by giving a fairly natural way to pass it all around as one "piece of information".

i.e. it's both easier to implement and more general.

@michaelpj
Copy link
Contributor

I discovered https://cips.cardano.org/cips/cip13/, so a simpler proposal would be:

  • Take another look at CIP-13 (it could clearly do with a bit of work, e.g. it's not multi-asset aware)
  • Add a specification for a datum query parameter for payment links
  • Use CIP-13 payment links in places where we currently use addresses and need more flexibility

@rphair
Copy link
Collaborator

rphair commented Oct 24, 2022

@michaelpj: Take another look at CIP-13

In the meantime since this proposal's last revision, another author & I came up with some good reasons for replacing CIP-0013 with a more generalised URI scheme... and then some slightly better reasons for not doing it: https://forum.cardano.org/t/cip-generalized-cardano-urls/57464

TL;DR since there were so few uses for URIs (only payment & stake pool links), and (as today) still so little support in wallets, the URL scheme never evolved any further... so much I didn't recall until your last comment that it only covered ada payments as supported by Yoroi in the early days.

Maybe a more diverse syntax for Payment URIs would weigh back in favour of a generalised URI scheme after all... but maybe not if it's just adding a single datum parameter and/or a key-value pair list of coin IDs and amounts (FYI I wrote the other half of the proposal, "stake pool links" which defines a similar key-value pair list for stake delegation proportions).

cc @SebastienGllmt @v-almonacid


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.

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

]
}
```

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?

@KtorZ KtorZ added Category: Ledger Proposals belonging to the 'Ledger' category. and removed Candidate CIP labels Mar 18, 2023
@SebastienGllmt
Copy link
Contributor Author

SebastienGllmt commented Mar 21, 2023

For Milkomeda, we're also interested in using this exact functionality for handling of some UTXO entries

Users, to use Milkomeda, may want to send their funds to a native scripts. We can make these native scripts only spendable if a specific Plutus input also exists. This Plutus script does nothing but check if the transaction contains a specific NFT in its input set. This means our transactions only need to process 1 Plutus script, and all other UTXO entries are just simple native script spends

This allows all Milkomeda funds to be managed by simple native scripts, and allows us to upgrade the control mechanism of the permission NFT without ever having to "upgrade" any Milkomeda UTXO entry (which isn't easily doable since Milkomeda has a lot of UTXO entries and upgrading them all costs a non-trivial amount of ADA in fees) unless it's to upgrade Plutus versions

@imikushin
Copy link

I like this. Much better with added motivations.

```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

"scripts": [{
// in the general case, the user such signs like normal
"type": "sig",
"keyHash": "ed6f3e2144d70e839d8701f23ebcca229bcfde8e1d6b7838bda11ac8"
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 keyHash to support different crypto schemes instead of only exposing them in Plutus

@rphair
Copy link
Collaborator

rphair commented Apr 21, 2023

also re: "Account Abstraction" I wanted to highlight this video as useful for community review & for us to keep an eye on the relevance of this proposal 🧐 https://www.youtube.com/watch?v=d5ei9vkU15o

@leo42
Copy link
Contributor

leo42 commented Apr 25, 2023

I would love to see this implemented, It would allow for a host of "Exotic Wallets".

@fallen-icarus
Copy link

fallen-icarus commented Apr 30, 2023

I have a potential use case for this in mind but I have some considerations about this CIP. A note before getting to the considerations: in order to use beacon tokens properly with distributed dApps, all users must use the same spending script. This single spending script must capture the full functionality of the dApp. I am mentioning this requirement because this is influencing my perspective.

For context, imagine if you have a script with redeemers A, B, and C. Each redeemer will result in some complicated action taking place. Currently (for my use cases), since all three functions must fit into a single 16 kb script, compromises need to be made about what can go into the script for each function. Using the functionality described in this CIP, it would be possible to break up the aforementioned script into three separate scripts, one dedicated to each function. While before, 16 kb may not have been enough to fit all the functionality for all three functions, by breaking them up into separate scripts, 16 kb would be plenty for a each individual function. The native script itself would then tie the three scripts together by checking that one of the "sub-scripts" was executed in the transaction. This approach would allow all dApps to be modular in design and allow for more efficient executions since "unused logic" is never part of executions.

Consideration 1 - At least n but no more than m of k

In the scenario described above, each "sub-script" is dedicated to a given redeemer. It most cases, it would not make sense to allow multiple of the sub-scripts to execute in a given transaction. In the use case I have in mind, this can result in unexpected behavior. Therefore, it would be ideal if the sub-scripts could be forced to execute one at a time. Currently, this must go into the logic of each plutus script to check that they are not being "composed" improperly. Since this CIP is looking to change native scripts already, perhaps it can also add a feature to do this. The multisig is "true as long as n >= threshold". Here I am suggesting making an option for the inverse: "true as long as n <= threshold" but with the requirement that at least one of the scripts is still executed. To make this general in case certain applications do allow for multiple scripts to be executed in a given transaction, I think there should be an option for RequireAtLeastNButNoMoreThanMOfK (feel free to choose a different name).

Consideration 2 - How to execute the arbitrary script

Currently, executing a plutus script requires one of the following:

  1. Spending a UTxO from that script's address.
  2. Minting/burning a token governed by that script.
  3. Withdrawing rewards from that script's reward address.

In the above example, none of the above are relevant. Using any of the three has drawbacks:

  1. All users must share UTxOs whose only existence is to allow the spending script to execute. This creates a concurrency bottleneck.
  2. The extra tokens minted would just be noise since they wouldn't be needed for the actual dApp logic.
  3. Using a plutus staking script requires the address to be registered and delegated which requires a deposit for each script.

Technically, the staking script option is "good enough" for the example I described above since withdrawing 0 ADA is always possible. However, it is not ideal. The ideal scenario is to have a fourth option for an auxiliary script that can possibly take a redeemer (some use cases might require the redeemer). While there is an option for an auxiliary script with cardano-cli, it does not have an option for a redeemer and I do not know if native scripts can see it (AFAICT plutus scripts cannot).


To be clear, the above considerations are not necessary. However, IMO they would dramatically improve the usability of native scripts in contexts like those I described above. Thoughts?

@zhekson1
Copy link

zhekson1 commented Apr 30, 2023

@fallen-icarus perhaps instead of RequireAtLeastNButNoMoreThanMOfK, it would be enough to just have RequireNoMoreThanMOf, which can then used in combination with the existing RequireMOf to create ranges for validity. This would be simpler and more modular than RequireAtLeastNButNoMoreThanMOfK.

For example, to specify that exactly two of four potential plutus scripts are used, the native script can look something like:

{
  "type": "all",
  "scripts":
  [
    {
      "type": "atLeast",
      "required": 2,
      "scripts":
      [
        {
          "type": "script",
          "scriptHash": "<SCRIPT_HASH_1>"
        },
        {
          "type": "script",
          "scriptHash": "<SCRIPT_HASH_2>"
        },
        {
          "type": "script",
          "scriptHash": "<SCRIPT_HASH_3>"
        },
        {
          "type": "script",
          "scriptHash": "<SCRIPT_HASH_4>"
        }
      ]
    },
    {
      "type": "noMore",
      "required": 2,
      "scripts":
      [
        {
          "type": "script",
          "scriptHash": "<SCRIPT_HASH_1>"
        },
        {
          "type": "script",
          "scriptHash": "<SCRIPT_HASH_2>"
        },
        {
          "type": "script",
          "scriptHash": "<SCRIPT_HASH_3>"
        },
        {
          "type": "script",
          "scriptHash": "<SCRIPT_HASH_4>"
        }
      ]
    }
  ]
}

Where "type": "noMore" corresponds to RequireNoMoreThanMOf.

@zhekson1
Copy link

zhekson1 commented May 1, 2023

Actually, for even greater simplicity and modularity, maybe we can just add a "None" type (boolean inversion) to native scripts?

So, BNF description of native scripts would look like:

<native_script> ::=
             <RequireSignature>  <vkeyhash>
           | <RequireScript>     <scripthash>
           | <RequireTimeBefore> <slotno>
           | <RequireTimeAfter>  <slotno>

           | <RequireAllOf>      <native_script>*
           | <RequireAnyOf>      <native_script>*
           | <RequireNoneOf>      <native_script>*
           | <RequireMOf>        <num> <native_script>*

Prior to CIP38, there'd be no use for it, but now it may be extremely useful. It would allow for fine-grained control over what redeemers/contracts can or cannot be used in combination with each other.

@johnshearing
Copy link

johnshearing commented May 19, 2023

My one concern is that smart contracts must always be able to tell the difference between a wallet and another smart contract. This is to prevent voting fraud. I am sure Sebastien has already considered this or perhaps this is a non-issue for reasons I do not understand.

In any case, we should be able to accomplish timed recovery for lost seed phrases by using smart contracts as wallets as opposed to blurring the distinction between the two. That said, there seem to be many good reasons for account abstraction.

@SebastienGllmt
Copy link
Contributor Author

@johnshearing this proposal doesn't affect this since it doesn't get rid of end-user wallets in any way.

If you're talking about account abstraction more in general (as in a world where we move everything to contracts only and not what this CIP specifically is about), you could consider user accounts as native script with a single key in it or some other way to try and different scripts for users compared to entities, but keep in mind differentiating the two is never fully possible (since you could even have a non-contract wallet that is actually generated using an offchain MPC or something similar)

@johnshearing
Copy link

johnshearing commented Jun 6, 2023

Thanks @SebastienGllmt,
I am not thinking about getting rid of wallets but rather wondering, what are the unintended consequences if we can't tell the difference between the wallets and smart contracts? Specifically I was wondering what happens if you can't tell if voter delegation comes from a wallet or a smart contract? Would it be possible to build an automated delegation market using smart contracts where the reward for voter delegation goes up near the end of an election and where only a few more delegations are required to pass a proposal?

I think I hear you saying that already it is possible obscure the distinction between wallet and smart contract because you can imagine a way to generate a wallet using multi-party computation. So even if it does matter, it's a moot point with regard to CIP-0038

Now that I think about it, DReps will likely be able to determine which wallets are delegating and when so perhaps no smart contracts would even be needed to setup a voting delegation market.

Thanks for helping me work through this.

@vlasin
Copy link

vlasin commented Aug 20, 2023

Hello everyone,

I would like to express my support for this proposal.

I have two projects (https://encoins.io and https://wrapper.zkfold.io) that both utilize the paradigm where all Tx validation happens in one script, but the Tx may be spending many UTxOs locked by a (currently Plutus) script that implements the "Require Script" logic.

Spending 100-150 script UTxOs that just "Require Script" costs a significant percentage of the total ExUnits budget, especially in terms of memory. While I do not claim that we currently have the most efficient implementation (there is definitely some room for improvement), I do believe the numbers below represent the situation well enough. Here is an example of a transaction that uses the "Require Script" validator:
https://preprod.cardanoscan.io/transaction/6f707eb1101db696fbc961d05205682de29d1e3ca9331286b0c1482795fb3d61?tab=contracts
If we spend 150 such UTxOs in a single transaction, we consume 57% of the total memory budget and 32% of the CPU budget. For the most efficient implementation of such a validator, the numbers could be a bit lower, but still, there is a potential for a very significant performance improvement.

I believe the efficiency considerations alone justify having the proposed upgrade. An alternative, "Tx-level scripts", was discussed here. However, the implementation suggested there is much more complicated, from the perspective of a DApp developer at least.

Please let us know if we can help somehow in finalizing this proposal. Thanks!

@leo42
Copy link
Contributor

leo42 commented Aug 20, 2023

I would like to reiterate my support for this proposal, I am in the final stages of deploying a new product for Broclan.io .

Having to use Plutus validators directly has a significant impact on efficiency and a very large impact on UX.

Implementing my solutions using this script will make everything much more easy so use and safe for the end user.

@rphair
Copy link
Collaborator

rphair commented Mar 19, 2024

@SebastienGllmt we have a new proposal

... which Plutus reps are enthusiastic about implementing (#749 (review)) so please let us know how you feel about the different approaches, and whether you think your proposal here could or should be deprecated.

@rphair
Copy link
Collaborator

rphair commented Apr 21, 2024

@SebastienGllmt given #309 (comment) it seems this approach has been deprecated but please reopen this if you would like to make a case otherwise.

@leo42 since you were the last person to confirm support for this proposal, please post here and consider reopening this if you think what was originally proposed here is a better solution than the currently favoured #749.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Category: Ledger Proposals belonging to the 'Ledger' category.
Projects
None yet
Development

Successfully merging this pull request may close these issues.