Skip to content
Open
Changes from all 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
156 changes: 156 additions & 0 deletions CIP-????/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
---
CIP: ?
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
CIP: ?
CIP: "?"

We've found out by trial & error that quoting this character prevents the YAML error on GitHub preview mapping keys are not allowed in this context at line 1 column 6 but haven't gotten this into the documentation & templates yet.

Title: Better script purposes
Category: Plutus
Status: Proposed
Authors:
- Michele Nuzzi <[email protected]>
Implementors: []
Discussions:
- https://github.com/cardano-foundation/CIPs/pull/1072
Created: 2025-08-06
License: CC-BY-4.0
---

## Abstract
<!-- A short (\~200 word) description of the proposed solution and the technical issue being addressed. -->
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
<!-- A short (\~200 word) description of the proposed solution and the technical issue being addressed. -->
<!-- A short (\~200 word) description of the proposed solution and the technical issue being addressed. -->

Please remove template comment scaffolding here & all the way down.

This CIP proposes better script purposes to optimize for common operations for each purpose

## Motivation: why is this CIP necessary?
<!-- A clear explanation that introduces the reason for a proposal, its use cases and stakeholders. If the CIP changes an established design then it must outline design issues that motivate a rework. For complex proposals, authors must write a Cardano Problem Statement (CPS) as defined in CIP-9999 and link to it as the `Motivation`. -->

Common operations such as deriving the hash of the script being executed, or finding the relevant data for validation could be optimized if the trusted data provided to the script was more complete.
Copy link
Contributor

Choose a reason for hiding this comment

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

For completeness it's good to briefly explain why obtaining the hash of the script being executed is a common and useful operation.


The same result could be achieved by including this data in the redeemer, however these alternative solutions ofter imply greater complexity in the offchain code, and do not come without overhead on-chian, since the redeemer cannot be trusted.

This CIP would solve those problems, with great improvements on the development experience as well as the resulting onchain scripts complexity and efficiency.


## Specification
<!-- The technical specification should describe the proposed improvement in sufficient technical detail. In particular, it should provide enough information that an implementation can be performed solely on the basis of the design in the CIP. This is necessary to facilitate multiple, interoperable implementations. This must include how the CIP should be versioned, if not covered under an optional Versioning main heading. If a proposal defines structure of on-chain data it must include a CDDL schema in its specification.-->

this CIP proposes to modify the definition of `ScriptInfo` and `ScriptPurpose` in [`plutus-ledger-api`](https://github.com/IntersectMBO/plutus/blob/618480658ec1750179c2832c6b619bc2d872b225/plutus-ledger-api/src/PlutusLedgerApi/V3/Contexts.hs#L428-L461) as follows
Copy link
Collaborator

@rphair rphair Aug 7, 2025

Choose a reason for hiding this comment

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

Suggested change
this CIP proposes to modify the definition of `ScriptInfo` and `ScriptPurpose` in [`plutus-ledger-api`](https://github.com/IntersectMBO/plutus/blob/618480658ec1750179c2832c6b619bc2d872b225/plutus-ledger-api/src/PlutusLedgerApi/V3/Contexts.hs#L428-L461) as follows
This CIP proposes to modify the definition of `ScriptInfo` and `ScriptPurpose` in [`plutus-ledger-api`](https://github.com/IntersectMBO/plutus/blob/618480658ec1750179c2832c6b619bc2d872b225/plutus-ledger-api/src/PlutusLedgerApi/V3/Contexts.hs#L428-L461) as follows:

(small grammar errors at beginning & end)


```hs
data ScriptInfo
= MintingScript
-- hash of the script being executed (same as polciy)
V2.CurrencySymbol
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not ScriptHash for consistency sake?
It is always possible to go from ScriptHash to CurrencySymbol

Suggested change
V2.CurrencySymbol
V2.ScriptHash

-- | 0-based index of the given `CurrencySymbol` in `txInfoMint`
Haskell.Integer
Comment on lines +39 to +40
Copy link
Contributor

Choose a reason for hiding this comment

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

This index can't be added, because the order in which multi-assets are listed in the serialized transaction will not necessarily match the order in which they will appear in plutus context. We would need a similar CIP to CIP-128 for minting before we can go this route.

Copy link
Contributor

Choose a reason for hiding this comment

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

the order in which multi-assets are listed in the serialized transaction will not necessarily match the order in which they will appear in plutus context

I'm not sure why this is a problem? This is just for indexing into txInfoMint - why does the order matter?

Copy link
Contributor

Choose a reason for hiding this comment

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

Because this index can be different from the index that was actually supplied in the redeemer in the serialized transaction. And that is dangerous in my opinion, since many people do not know that.

Copy link
Contributor

@colll78 colll78 Aug 14, 2025

Choose a reason for hiding this comment

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

I don't think this is an unacceptable concern. The smart contract itself will still only succeed if whatever token indexed in the context matches what the contract expects, this would only impact offchain tooling which is for some reason relying on these indices for analytic purposes or similar non-value transfer related functionality.

I agree it would be better if they matched, but if ledger has other priorities to work on, or if this change to the ledger would take significant amount of time then I don't see any issues with introducing this now and then when you free up change the ledger in a subsequent hf to assure they indices provided in the serialized tx match the ones in the context.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is an unacceptable concern

It took me a moment to unwrap the double negation 😄

Yes, as far as the script execution is concerned it should be fine, unless the index in the redeemer pointer is also passed in the argument to the script, which wouldn't guarantee to match the index in the script info and the script purpose.

@colll78 and @zliu41 I'll then trust your judgement on this one and won't worry about.
We'll treat this as a separate concern that is only relevant to the Ledger

| SpendingScript
-- hash of the script being executed
V2.ScriptHash
-- | 0-based index of the input being spent in `txInfoInputs`
Haskell.Integer
Comment on lines +44 to +45
Copy link
Contributor

Choose a reason for hiding this comment

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

This index cannot be added until CIP-0128 - Preserving Order of Transaction Inputs is implemented. I think it is important to point out this prerequisite in this CIP.

-- removed optional datum, since its presence (or not) is determined by the resolved input
-- (Haskell.Maybe V2.Datum)
-- resolved input being spent
V3.TxOut
V3.TxOutRef
| RewardingScript
-- V2.Credential -- removed, since implictily it must be a script credential
-- hash of the script being executed
V2.ScriptHash
-- | 0-based index of the withdrawal being verified in `txInfoWdrl`
Haskell.Integer
Comment on lines +55 to +56
Copy link
Contributor

Choose a reason for hiding this comment

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

Same problem as for minting

| CertifyingScript
-- hash of the script being executed
V2.ScriptHash
-- | 0-based index of the given `TxCert` in `txInfoTxCerts`
Haskell.Integer
TxCert
| VotingScript
-- hash of the script being executed
V2.ScriptHash
-- | 0-based index of the given vote in `txInfoVotes`
Haskell.Integer
Comment on lines +66 to +67
Copy link
Contributor

Choose a reason for hiding this comment

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

Same issue as with minting. Order of votes is Haskell specific and the only way we would be able to report this index is if we preserve the order in which votes where submitted on the wire.
Note, that I am not opposing this, I am just pointing out an issue that would need to be resolved if we want to supply indices for maps.

Voter
| ProposingScript
-- hash of the script being executed
V2.ScriptHash
-- | 0-based index of the given `ProposalProcedure` in `txInfoProposalProcedures`
Haskell.Integer
ProposalProcedure
deriving stock (Generic, Haskell.Show, Haskell.Eq)
deriving anyclass (HasBlueprintDefinition)
deriving (Pretty) via (PrettyShow ScriptInfo)
```

```hs
data ScriptPurpose
Copy link
Contributor

Choose a reason for hiding this comment

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

I have the same comments for indices in the script purpose as I made for ScriptInfo

= MintingScript
-- hash of the script being executed (same as polciy)
V2.CurrencySymbol
-- | 0-based index of the given `CurrencySymbol` in `txInfoMint`
Haskell.Integer
| SpendingScript
-- hash of the script being executed
V2.ScriptHash
-- | 0-based index of the input being spent in `txInfoInputs`
Haskell.Integer
V3.TxOutRef
Copy link
Contributor

Choose a reason for hiding this comment

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

We could also add the resolved TxOut here as well. I think it would be useful for a script to know other outputs that are being spent in this transaction

Suggested change
V3.TxOutRef
V3.TxOut
V3.TxOutRef

With this suggestion, however, we are loosing any distinction between ScriptInfo and ScriptPurpose, so it would probably make sense then to remove ScritpInfo in favor of ScritpPurpose, like it was before.

Copy link
Contributor

Choose a reason for hiding this comment

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

Adding TxOut would be great if we can make sure that in the ledger code that generates the script context we don't do any redundant work (right now, where the same data exists in the script context in multiple places, we do redundant work to get and toPlutusType it each time).

Otherwise, since the proposed SpendingScript includes the index of that input, and Plutus is introducing BuiltinArray which will allow O(1) access of elements by index, this isn't a huge concern, because getting this TxOut will have a trivial cost. (Right now getting the resolved input from the information provided in SpendingScript / SpendingPurpose is extremely costly, it requires O(n) decoding of BuiltinData (each tx input), O(n) head operations (getting first field of the decoded input, which is TxOutRef), O(n) equality comparisons for identifying the correct TxOutRef.

With BuiltinArray all of this can be done by invoking single O(1) builtin.

Copy link
Contributor

Choose a reason for hiding this comment

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

It might not be a huge concern, but even if you are indexing into an array with O(1), I presume you would need to decode every element in the array before you could index it. So, if all your script cares about is its own TxOut, then I suspect it would be less expensive to just decode that TxOut from script purpose, rather than decode the full array and lookup that TxOut, right?

Copy link
Contributor

@colll78 colll78 Aug 19, 2025

Choose a reason for hiding this comment

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

No you don't need to decode any elements to index into it. You just need to call BI.unsafeDataAsArray, this is O(1), to get the array, which would be of type: BuiltinArray BuiltinData, and then you do BI.indexArray idx txInputsArray which is also O(1), and then you have BuiltinData which is ownInput, which you can extract whatever fields you want from.

(this assumes we have BuiltinArrays and a hardfork has occurred which changed the representation of the script context to have the inputs / outputs as builtinarray instead of builtinlist, and afaik the next HF isn't expected for a while)

| RewardingScript
-- V2.Credential -- removed, since implictily it must be a script credential
-- hash of the script being executed
V2.ScriptHash
-- | 0-based index of the withdrawal being verified in `txInfoWdrl`
Haskell.Integer
| CertifyingScript
-- hash of the script being executed
V2.ScriptHash
-- | 0-based index of the given `TxCert` in `txInfoTxCerts`
Haskell.Integer
TxCert
| VotingScript
-- hash of the script being executed
V2.ScriptHash
-- | 0-based index of the given vote in `txInfoVotes`
Haskell.Integer
Voter
| ProposingScript
-- hash of the script being executed
V2.ScriptHash
-- | 0-based index of the given `ProposalProcedure` in `txInfoProposalProcedures`
Haskell.Integer
ProposalProcedure
deriving stock (Generic, Haskell.Show, Haskell.Eq)
deriving anyclass (HasBlueprintDefinition)
deriving (Pretty) via (PrettyShow ScriptInfo)
```

## Rationale: how does this CIP achieve its goals?
<!-- The rationale fleshes out the specification by describing what motivated the design and what led to particular design decisions. It should describe alternate designs considered and related work. The rationale should provide evidence of consensus within the community and discuss significant objections or concerns raised during the discussion.

It must also explain how the proposal affects the backward compatibility of existing solutions when applicable. If the proposal responds to a CPS, the 'Rationale' section should explain how it addresses the CPS, and answer any questions that the CPS poses for potential solutions.
-->

This modification would be reflected in the underlying data representation as always having the script hash of the given script as the first field of the purpose, and the index relevant to the purpose as second element.

This allows to optimize for the very common operation of deriving the hash of the script itself onchain, greatly reducing onchain complexity and cost of execution.
Copy link
Contributor

Choose a reason for hiding this comment

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

The hash should be factored out of ScriptInfo and ScriptPurpose. Then you don't need a special optimization to get the hash.


The change also propses to add the resovled input in the case of the very common spending purpose, where the majority of the relevant informations is present.
Copy link
Contributor

Choose a reason for hiding this comment

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

We've discussed this exact change in the latest Ledger Working Group in the context of protected addresses CIP. Seems there is large demand for it.


## Path to Active

Changes are reflected on the plutus-ledger-api

### Acceptance Criteria
<!-- Describes what are the acceptance criteria whereby a proposal becomes 'Active' -->

The change is included in an hardfork
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
The change is included in an hardfork
The change is included in a hardfork.

This could use some elaboration but will wait until the Ledger responses are all in & Plutus reps have reviewed with respect to when this might be included (cc @zliu41).

Copy link
Contributor

Choose a reason for hiding this comment

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

This requires a new era and new ledger language version, not just a HF


### Implementation Plan
<!-- A plan to meet those criteria or `N/A` if an implementation plan is not applicable. -->
N/A

<!-- OPTIONAL SECTIONS: see CIP-0001 > Document > Structure table -->

## Copyright
<!-- The CIP must be explicitly licensed under acceptable copyright terms. Uncomment the license you wish to use (delete the other one) and ensure it matches the License field in the header.

If AI/LLMs were used in the creation of the copyright text, the author may choose to include a disclaimer to describe their application within the proposal.
-->

This CIP is licensed under [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/legalcode)
<!-- This CIP is licensed under [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0). -->