Sigma Propositions
Sigma Propositions (
SigmaProp) are the core return type of every ErgoScript contract.SigmaPropvalues represent conditions related to the transaction that must be met to spend a specific box.- They are similar to booleans in that they ultimately reduce to either
trueorfalseduring verification. SigmaPropenables the use of Zero-Knowledge Proofs, a crucial aspect of modern cryptography and a defining privacy feature of Ergo.- All contracts in ErgoScript must return a
SigmaPropvalue at the very end.- This final
SigmaProprepresents the complete set of conditions required to spend the box protected by the contract. Therefore, all logic within an ErgoScript contract should contribute to the outcome of this finalSigmaProp.
- This final
SigmaProp values can be constructed in several ways, but two common methods are used frequently in ErgoScript contracts.SigmaProps From Booleans
You can create
SigmaProp values from standard boolean expressions using the sigmaProp function. This allows you to define arbitrary spending conditions based on context variables, register values, etc.{
val mathIsHard: Boolean = (1 + 1) != 2
sigmaProp(mathIsHard) // SigmaProp created from a boolean using the sigmaProp function
// What would this contract evaluate to?
}SigmaProps From Public Keys
Public Keys (represented as
GroupElement in ErgoScript, essentially the part of your address that makes it unique) can be directly converted into SigmaProp values using functions like proveDlog. When such a SigmaProp is used, the contract checks if the transaction was signed by the corresponding private key. You can think of this as literally signing the transaction with your digital signature to prove authorization.{
// You can use the PK function to
// hardcode an address's public key into your contract
val myPK: SigmaProp = PK("9etXmP7D3ZkWssDopWcWkCPpjn22RVuEyXoFSbVPWAvvzDbcDXE")
myPK
}SigmaProp Operations
Much like booleans, you can use logical operations (
&& for AND, || for OR) on SigmaProp values to build more complex spending logic for your contract.{
val enoughERG = INPUTS(0).value > 1000000
val myPK = PK("9etXmP7D3ZkWssDopWcWkCPpjn22RVuEyXoFSbVPWAvvzDbcDXE")
sigmaProp(enoughERG) || myPK // What does this contract do? Under what conditions could such a contract be spent?
}You can see in the contract above that using
|| creates two distinct spending paths (conditions under which the box can be spent).Now that you've seen the basics, let's look at a simple ErgoScript contract example: the pin-lock contract mentioned earlier.
Pin-lock Contract
{
sigmaProp( INPUTS(0).R4[Coll[Byte]].get == blake2b256(OUTPUTS(0).R4[Coll[Byte]].get) )
}Don't worry if you don't understand all the functions used here (
blake2b256, .get); these are global functions covered elsewhere. What's happening here is:We can spend
INPUTS(0) (the first input box of the transaction) if and only if there exists an output box (specifically OUTPUTS(0), the first output) whose register R4 contains the Blake2b256 hash of the byte collection found in register R4 of INPUTS(0).This contract implicitly assumes the box being spent is
INPUTS(0). For a clearer example where the box explicitly refers to itself within its own contract, consider the version below using the SELF context variable:Pin-lock Contract (with SELF)
{
sigmaProp( SELF.R4[Coll[Byte]].get == blake2b256(OUTPUTS(0).R4[Coll[Byte]].get) )
}Are these two pin-lock contracts equivalent? That is, under what spending conditions might one contract evaluate to true while the other evaluates to false? (Hint: Consider what
INPUTS(0) refers to versus what SELF refers to).