You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
With the way CIP-33 is currently implemented, and all cardano spending scripts for that matter, the script must be executed for every utxo that comes from the corresponding script address even if validation is based on the entire transaction (as opposed to individual inputs). Here is an example transaction that illustrates this:
The second and third utxo inputs are from the same script address. The script I am using above actually passes or fails based on the entire transaction, not a single utxo. This design does not take full advantage of the eUTxO model where a script can see the entire transaction during validation.
The issue with the current implementation is that it is causing redundant computation checks which result in a quadratic scaling of fees. Being that my script checks all the inputs and outputs to the transaction and decides based on that, below is the fee estimation with the current CIP-33 implementation:
The transaction fee increases linearly for every utxo (inputs + outputs) in the transaction, then quadratically if more than one reference script needs to be executed. The reason for this is that the script traverses all the inputs and outputs of the transaction to determine successful validation; the validation is not based on an individual utxo. So since my above transaction is spending two utxos from the script address, the reference script must be executed twice even though the second execution is completely redundant.
The Proposed Change
It would be better in this context if scripts could (but don't have to) work more like public key signatures: only one signature is required no matter how many utxos come from that user. The cardano-node is already capable of detecting if there is a utxo without the necessary accompanying script.
I admit I am ignorant about certain lower-level implementations so all I can do is ask: why can there not be a transaction level script where the datum and redeemer are attached as usual? Here is how I am proposing my above transaction would look instead:
I removed the spending-tx-in-reference lines and added a tx-level-spending-tx-in-reference after change-address. If validation needs to be done on a per utxo basis, the original spending-tx-in-reference lines can still be used.
Minting scripts can already be used in a similar manner:
Here the minting script is only executed once despite the minting occurring in two different outputs.
How would this work?
Two cases need to be considered:
A transaction level script is used where a utxo level script should be used.
A utxo level script is used where a transaction level script should be used.
The current way a spending script is written (in Haskell) is like this:
--| validator function before being compiled to plutusmkValidator::Datum->Redeemer->ScriptContext->Bool
mkValidator d r ctx =...
Meanwhile minting scripts are written like this:
--| minting policy before being compiled to plutusmkMintPolicy::Redeemer->ScriptContext->Bool
mkMintPolicy r ctx =...
So taking inspiration from the fact that minting policies don't deal with a datum, I propose the following change to how spending scripts are written:
--| new validator function before being compiled to plutusmkValidator::MaybeDatum->Redeemer->ScriptContext->Bool
mkValidator Nothing r ctx ={- the case where the script can be used at a transaction level -}
mkValidator (Just d) r ctx ={- the case where the script can be used at the utxo level -}
When the script is used with the tx-level-reference-script option, Nothing is passed in for the datum. On the other hand, when spending-tx-in-reference is used like usual, the datum will be parsed and passed with the Just. This way the code explicitly handles both of the above cases.
Using this technique, it would also be theoretically possible to spend a utxo at a script address even if it doesn't have a datum attached. This assumes the proper accompanying logic.
What if a malicious entity forcibly passes in the wrong version of the datum (Nothing when it should be Just d or vice versa)?
The script logic can be written to account for this so I argue it is up to the developer to defend against this kind of attack. A simple error message when the wrong version is used could suffice for most use cases. For my use case, the code would be:
mkValidator::MaybeDatum->Redeemer->ScriptContext->Bool
mkValidator (Just _) _ _ = traceError "Not meant to be used at the utxo level"--^ I don't want the script used in this case
mkValidator Nothing r ctx ={- do what I want -}
Can multiple transaction level scripts be used in one transaction?
I don't see why not. The node is capable of detecting if all relevant scripts are present in the transaction. The transaction would only be valid if all necessary scripts succeed.
Does this require a hardfork?
I do not know enough to say.
Extra Comment
I wasn't sure if this should be its own CIP or if it was related enough to CIP-33 to be opened as an Issue. Being that it is extending the behavior of CIP-33, I chose the latter.
The text was updated successfully, but these errors were encountered:
It would be interesting also to get @michaelpj to comment on this if he has time. I know he definitely would respond if this were submitted as a CPS: but enquiring before then could at least avoid some duplication of effort if this had already been considered somehow. (Note I am more of a Cardano generalist so this is above my own level.)
@rphair Thanks for pointing out that option. I hadn't considered it but you are probably right that a CPS is more appropriate for this. I will look to create one next week.
The Problem
With the way CIP-33 is currently implemented, and all cardano spending scripts for that matter, the script must be executed for every utxo that comes from the corresponding script address even if validation is based on the entire transaction (as opposed to individual inputs). Here is an example transaction that illustrates this:
The second and third utxo inputs are from the same script address. The script I am using above actually passes or fails based on the entire transaction, not a single utxo. This design does not take full advantage of the eUTxO model where a script can see the entire transaction during validation.
The issue with the current implementation is that it is causing redundant computation checks which result in a quadratic scaling of fees. Being that my script checks all the inputs and outputs to the transaction and decides based on that, below is the fee estimation with the current CIP-33 implementation:
The transaction fee increases linearly for every utxo (inputs + outputs) in the transaction, then quadratically if more than one reference script needs to be executed. The reason for this is that the script traverses all the inputs and outputs of the transaction to determine successful validation; the validation is not based on an individual utxo. So since my above transaction is spending two utxos from the script address, the reference script must be executed twice even though the second execution is completely redundant.
The Proposed Change
It would be better in this context if scripts could (but don't have to) work more like public key signatures: only one signature is required no matter how many utxos come from that user. The cardano-node is already capable of detecting if there is a utxo without the necessary accompanying script.
I admit I am ignorant about certain lower-level implementations so all I can do is ask: why can there not be a transaction level script where the datum and redeemer are attached as usual? Here is how I am proposing my above transaction would look instead:
I removed the
spending-tx-in-reference
lines and added atx-level-spending-tx-in-reference
afterchange-address
. If validation needs to be done on a per utxo basis, the originalspending-tx-in-reference
lines can still be used.Minting scripts can already be used in a similar manner:
Here the minting script is only executed once despite the minting occurring in two different outputs.
How would this work?
Two cases need to be considered:
The current way a spending script is written (in Haskell) is like this:
Meanwhile minting scripts are written like this:
So taking inspiration from the fact that minting policies don't deal with a datum, I propose the following change to how spending scripts are written:
When the script is used with the
tx-level-reference-script
option,Nothing
is passed in for the datum. On the other hand, whenspending-tx-in-reference
is used like usual, the datum will be parsed and passed with theJust
. This way the code explicitly handles both of the above cases.Using this technique, it would also be theoretically possible to spend a utxo at a script address even if it doesn't have a datum attached. This assumes the proper accompanying logic.
What if a malicious entity forcibly passes in the wrong version of the datum (
Nothing
when it should beJust d
or vice versa)?The script logic can be written to account for this so I argue it is up to the developer to defend against this kind of attack. A simple error message when the wrong version is used could suffice for most use cases. For my use case, the code would be:
Can multiple transaction level scripts be used in one transaction?
I don't see why not. The node is capable of detecting if all relevant scripts are present in the transaction. The transaction would only be valid if all necessary scripts succeed.
Does this require a hardfork?
I do not know enough to say.
Extra Comment
I wasn't sure if this should be its own CIP or if it was related enough to CIP-33 to be opened as an Issue. Being that it is extending the behavior of CIP-33, I chose the latter.
The text was updated successfully, but these errors were encountered: