On a UTxO-style blockchain such as Cardano, transaction outputs are normally to the (hash) of a public key; in order to spend such an output, the spending transaction must be signed with the corresponding private key as evidence that the party creating the transaction has the right to spend the output.
The extended UTxO model introduces a second kind of output: an output to a smart contract: a piece of code
f. Along with the contract itself, the output also contains an additional argument
d, known as the datum. When a transaction spends such an output,
f(d) is executed; when
f indicates that all is fine, the transaction is approved; otherwise, it is rejected.
An immediate consequence of this model is that outputs are only verified when they are spent, not when they are created. This can lead to some tricky-to-verify initial conditions. We will explore this problem in this blog post, and suggest some ways in which we can solve it. Along the way, we will recap some often misunderstood Plutus concepts.
This work was done as part of the development of Be, a (smart) contract platform.
Stage restriction: datums vs parameters
Script outputs consist of three things: the value at the output, the hash of the script, and a datum or datum hash. We will ignore the value in the rest of this section.
We can think of a script hash and a datum as a pair of a function
f and an argument datum
d, which we might write as
f(d). Scripts often have arguments other than the datum too; we might write this as
fx1, .., xN(d). These additional arguments are commonly referred to as parameters.
The difference between parameters and the datum is one of stage: parameters are applied at the stage of script compilation, whereas the datum is applied at the stage of script execution. Let’s consider this from a few different angles.
(Data) values1 that must be computed off-chain should be parameters; values that must be computed on-chain should be in the datum.
For example, stateful scripts often have to compute their next state. Since this computation happens on-chain, this state must therefore live in the script’s datum.
From the on-chain code’s perspective,
fx’are different scripts with different hashes, whereas
f(d')look like the same script, applied to different datums.
Sometimes there are reasons to prefer that a particular value is a parameter rather than a datum: after all, datums are just values and cannot a priori be trusted (we will come back to this in detail later). In principle it’s not impossible to compute a parameter on-chain, but it’s difficult: we must have sufficient information on-chain to be able to compute, for a given
x, the hash of
fx(that is, the hash of the serialised source code of
fx). In practice, on Cardano this is currently impossible, because the appropriate hashing algorithm is not available on-chain.2
Conversely, sometimes we might prefer that a value is stored in the datum rather than is passed as a parameter. Off-chain code can easily recognize outputs to
f(d)in the current UTxO set without needing to be aware of
d; however, outputs to
fxcannot be recognized as such without
x, since the hash of
fxis different for every
Self-reference: direct and indirect recursion
Scripts often need to be aware of their own hash. For example, a script
f may need to check that any transaction spending an output to
f also contains an output back to
Clearly, the source of
f cannot literally contain the hash of that very same source code: the hash would be uncomputable. Scripts must therefore be told their own hash when they run. This has some important consequences.
For example, suppose we have a parameterized minting policy
πf, such that
πf only allows minting of tokens if those tokens are output to
f. Suppose furthermore that
f needs to check if particular inputs contain
πf tokens. In principle this should be fine: since
f is told its own hash, it should be able to compute the hash of
πf. However, we run into the stage restriction discussed above: the script’s own hash is a run-time value, whereas the parameter to
π must be provided at compile time. Again, it’s not in principle impossible to solve this problem3, but in practice it’s difficult, and as we saw, in fact currently impossible on Cardano for technical reasons.
We must therefore tell script
f what the hash of
πf is. This cannot be a parameter, because this would lead to uncomputable self-referential hashes again. Indeed, even our notation would break down here:
We can put (the hash of)
πf in the datum
(f, (πf, d), V) is unproblematic for hash computations, but now we have a different problem:
f has no way of verifying that the datum contains the right hash. We will come back to back this point below.
It may also be possible to break the mutual dependency between
πf in different ways. For example, perhaps
f has an associated
NFTo, so that we could parameterize both
NFTo instead. As we shall see, this is also not without problems.
In the EUTxO model, state is never actually updated; instead, old state is consumed and new state is created. Typically, this state resides in the datum of a script:
Tx is validated,
f can verify the evolution of
dnew, but it cannot verify
dold; instead, we want to reason inductively, and say that
f must at some point have verified that previous state as well:
Unfortunately, this is an induction without a base case, because the first output to
f is not verified by
The critical transaction here is
Tx1: ideally it should not only verify that
d1 is the correct next state after
d0, but also the base case:
d0 should be the correct initial state.
Unfortunately, the fact that
d0 should be the base case is deducible from
Tx0, but on Cardano that information is not reflected in
Tx1 is the only context available to
f when it validates
d0. This means that contracts do not know when the base case should apply, and hence cannot verify their own base case.
There are two ways to solve this problem.
Declare that it is the responsibility of the off-chain code to verify the base case. In rare cases it may be possible to verify this by inspecting the current datum, but in most cases it will mean looking through the chain history to find the original output (
Tx0in the diagram above).
We can solve the problem entirely on-chain through the clever use of NFTs. We will discuss this in detail below.
Often stateful scripts have an associated NFT, anchored at a random output
o, which is included in the value of each script output:
Such NFT is useful to be able to uniquely identify the current script output.4 However, the presence of such an NFT does not change the narrative above in any meaningful way. While it is true that if the NFT is minted in
f checks for the presence of the NFT as part of its checks), then
d0 must indeed be the base case, but this is once again not visible from
Conversely, the presence of this kind of NFT in an input does not mean that that input spends a script output to
f; this will only be true once the NFT is locked in the script (at which point
f will validate that it will remain locked in the script). The base case should be that the NFT is locked in the contract immediately when it’s minted, but verifying this again involves checking the chain history.
The NFT minting policy
NFTo simply checks that the enclosing transaction spends output
o. Since outputs can only be spent once, this guarantees that the resulting token is indeed a singleton. If we additionally want the guarantee that the NFT is locked in some script
f immediately upon minting, we can define an NFT minting policy which is parameterized by an output and a script it’s intended for:
NFTo, f would verify that the enclosing transaction spends
o, and that the resulting token is locked in a script output to
Unfortunately, this leads to precisely the kind of mutual dependency between
NFTo, f that we discussed above:
f needs to know the hash of
NFTo, f so that it can verify that the NFT remains locked in the script, and
NFTo, f needs to know the hash of
f so that it can verify the NFT is locked in the script when it is first minted.
We must therefore store the hash of the NFT in the datum associated with
f, but now
f has no way of verifying that datum. It can verify that the hash never changes, but it has no way of verifying that the hash is correct. It’s another example of the base case problem discussed above, but in a sense even more severe: even if the script could tell when the base case should apply, it would still not know what the base case should be.
Fortunately, if we use an NFT to identify script outputs, we can solve the base case problem once and for all, by additionally parameterizing the NFT with the expected initial state.
NFTo, f, d will allow minting if the enclosing transaction
- locks the resulting token in an output to script
- the associated datum5 is
(NFTo, f, d, d)
This means that if we interact with a script through
NFTo, f, d, the NFT guarantees the inductive base case. Put another way: we have encoded the base case right in the hash of the NFT.6
Stateful minting policies
A minting policy
π is a script that determines whether a particular token is allowed to be minted or burned. Minting policies are stateless: they do not have associated datums (indeed, there are no outputs to a minting policy), nor can we somehow encode state in the hash of the minting policy itself, because we have no way of “evolving” that hash.
Many minting policies however do need some state. When this happens, the minting policy needs to be paired with a stateful script
f. The minting policy
π could then check the transaction for an input that spends a script output
(f, d), and then use the associated datum
d as its state. This is unproblematic as long as the minting policy only needs to read the state, but does not need to modify it. In this case,
f is still responsible for checking the state in the continuation output, as normal.
If however the minting policy does need to change the state, we have a problem.
We could attempt to set things up such that the minting policy
πinforms the regular script
πwill take over duties for verifying the continuation output, by having the minting policy verify that the redeemer7 for
ftells it not to check the continuation output. This is however not sound: a malicious user could create a transaction that doesn’t mint but does use that special redeemer value, and would then be able to change the state of
fcould check the transaction to see if it it burns or mints any
πtokens, and delegate verification of the output datum to
πwhen this is the case. This is sound but results in the same kind of mutual dependency problem that we already encountered above:
πnow needs the hash of
f(to recognize outputs to
fneeds the hash of
π(to check whether or not any
πtokens are minted).
Most stateful scripts use some state token
NFTo. It is tempting to think that we could break the cycle by having the minting policy
πcheck for the input with
NFToinstead of the input spending a script output to
πno longer needs to know the hash of
f. Unfortunately, as we saw, we are only guaranteed that the presence of the NFT implies that the input spends an output to
fif we verify through other means that the NFT is locked in the script
fimmediately upon minting.8
We must therefore store the hash of the minting policy in the datum of
f. We can use the parameterized NFT we discussed above, and use
NFTo, f, (πf, d)to ensure that the initial value of the minting policy is correct9 (
fitself must ensure that the hash never changes).
Variable base case
When we discussed inductive reasoning, we saw that scripts cannot verify their own base case. We will now consider what happens when the base case for one script is another script:
In this case there is no fixed base case for
f; instead, the guarantee required by
f is that
g has verified
d; as always, this guarantee must be derivable from information present in
We can do this by defining a minting policy
Linkg, f, which allows minting in a transaction
Tx'spends an input to
- The only
Linkg, ftokens in the outputs of
Tx'are locked in outputs to
f then have the following responsibilities:
gmust verify the datums in all outputs to
fmust check for the presence of the
Linkg, ftoken in the input it is verifying. It must also check that the only outputs of the
Linkg, ftoken in transaction
Txare back to
These tokens are not NFTs, and do not need to be. We just need to be make sure the tokens never ``leak’’ (which the rules above do ensure).
Once more we have to solve the problem of mutual dependencies between
Linkg, f and between
Linkg, f. First, we can assume without loss of generality that
g is stateful; after all, if it wasn’t, then this setup provides no benefits over a setup with a fixed base case. We can therefore store the hash of
Linkg, f in the state (datum) associated with
g, and then rely on the
NFTo, g, (Linkg, ; f, d’) to ensure that the hash is recorded correctly. We can use
g to verify that the datum for
f also records the correct hash of
This also avoids any dependencies between
g can identify outputs to
f by looking for outputs that include a
Linkg, f token, and
f verifies that its inputs include that token.
Stateful Plutus scripts can verify that the evolution of state happens according to the rules defined in the script, but cannot verify the initial state: they lack the context that would tell them when the base case applies.
Moreover, the lack of on-chain script hash computation (or, put another way, the strict stage separation between script parameters and the script datum) means that we must often include the hash of one script in the datum of another; these hashes cannot be verified at all, even if the script could know when a state should be initial.
Often the verification of these initial conditions are relegated to off-chain code instead, but this is unsatisfactory and dangerous. The same on-chain code could be interacted with by multiple off-chain applications; a single forgotten check in one of those off-chain applications could result in security vulnerabilities.
Stateful scripts often have an associated NFT, used to identify the current script output in a UTxO set. As it turns out, such NFTs are subject to their own unverified initial conditions. However, we showed that we can resolve all of these problems by defining an
NFTo, f, d which verifies that the NFT is locked in the script
f immediately upon minting and that the initial datum is
d. In addition, we saw that if the base case for one script
f is another script
g, we can use
NFTo, g, d to guarantee the base case for
g, and a special token
Linkg, f to verify the base case for
Data values as opposed to currency values.↩︎
Script hashes use BLAKE-224; the available on-chain hashing algorithms are SHA2-256, SHA3-256, and BLAKE-256.↩︎
fwould need enough information to be able to compute
This can be useful off-chain, in order to figure out which output in the UTxO set corresponds to the current state of the contract we’re interacting with. It can also be useful on-chain, in case there could be multiple instances of
fwithin a single transaction, and the script needs to figure out which output belongs to which input.↩︎
This latter check is unproblematic becauses scripts are told their own hash when they run.↩︎
It is true that the hash is carrying a lot of weight here; we are depending on that fixed size hash to encode a lot of information. However, this is no different from representing script outputs in the first place; we are comfortable with hashes representing
fx1, .., xN, in which case the hash is also encoding a lot of information: the definition of
fas well as all parameters
As we saw earlier, a script output contains (the hash of) a function
fx1, .., xN, and a datum
d. When a script output is spent, the spending input additionally provides a redeemer value
r; the actual code that is run is then
fx1, .., xN(d, r). In Plutus V1 it in fact not possible for the minting policy to check the redeemer value for other scripts, as this information is not present in the
ScriptContext; this is resolved in V2.↩︎
We cannot use
NFTo, f, d: this is guaranteed to be locked in the script immediately but, unlike
NFTo, still depends on
πtokens are minted, we could rely on
πinstead to verify that the right hash is recorded. However, this check will not happen until the first
π-token is minted, and we wouldn’t know what happened before then.↩︎