Chialisp is a powerful and secure LISP-like language for encumbering and releasing funds with smart-contract capabilities. This website is a consolidated place to learn about Chialisp, CLVM and the conditions language.
Here’s a sample:
(mod (password new_puzhash amount)
(defconstant CREATE_COIN 51)
(if (= (sha256 password) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824))
(list (list CREATE_COIN new_puzhash amount))
Download Chialisp 0.4 in tar.gz format
1 – CLVM Basics
CLVM is the compiled, minimal version of ChiaLisp that is used by the Chia network. The full set of operators is documented here
This guide will cover the basics of the language and act as an introduction to the structure of programs. You should be able to follow along by running a version of clvm_tools.
ChiaLisp is built out of cons boxes and atoms. A cons box is defined as a pair of ChiaLisp objects. The items in a cons box can either be an atom or another cons box.
Cons boxes are represented as a parentheses with two elements separated by a
.. For example:
(200 . "hello")
("hello" . ("world" . "!!!"))
Are legal cons boxes, but the following is not.
(200 . 300 . 400)
A cons box is strictly only a pair. However, we can chain cons boxes together to construct lists.
Lists are enclosed by parentheses and each entry in the list is single spaced with no period between values. Lists are much more commonly used than cons boxes as they are more versatile.
(200 300 "hello" "world")
You can also nest lists.
("hello" ("nested" "list") ("world"))
Remember a list is a representation of consecutive cons boxes terminated in a null atom
(). The following expressions are equal:
(200 . (300 . (400 . ())))
(200 300 400)
Atoms are either literal binary blobs or variables. A program is actually just a list in polish notation.
There is no distinguishing of atom types in ChiaLisp. This means that
(100 0x65 0x68656c6c6f) and
(0x64 101 'hello') are equivalent lists. Internally however the blobs can be interpreted in a number of different ways depending on the operator. We will cover this in further detail later.
There are no support for floating point numbers in ChiaLisp, only integers. Internally integers are interpreted as 256 bit signed integers.
The math operators are
$ brun '(- (q . 6) (q . 5))' '()'
$ brun '(* (q . 2) (q . 4) (q . 5))' '()'
$ brun '(+ (q . 10) (q . 20) (q . 30) (q . 40))' '()'
You may have noticed that the multiplication example above takes more than two parameters in the list. This is because many operators can take a variable number of parameters.
* are commutative so the order of parameters does not matter. For non-commutative operations,
(- (q 100) (q 30) (q 20) (q 5)) is equivalent to
(- (q 100) (+ (q 30) (q 20) (q 5))). Similarly,
(/ 120 5 4 2) is equivalent to
(/ 120 (* 5 4 2)).
There is also support for negative values.
$ brun '(- (q . 5) (q . 7))' '()'
$ brun '(+ (q . 3) (q . -8))' '()'
To use hexadecimal numbers, simply prefix them with
$ brun '(+ (q . 0x000a) (q . 0x000b))' '()'
The final mathematical operator is equal which acts similarly to == in other languages.
$ brun '(= (q . 5) (q . 6))' '()'
$ brun '(= (q . 5) (q . 5))' '()'
As you can see above this language interprets some data as boolean values.
In this language an empty list
() evaluate to
False. Any other value evaluates to
True, though internally
True is represented with
$ brun '(= (q . 100) (q . 90))'
$ brun '(= (q . 100) (q . 100))'
The exception to this rule is
0 is exactly the same as
$ brun '(= (q . 0) ())' '()'
$ brun '(+ (q 70) ())' '()'
i operator takes the form
(i A B C) and acts as an if-statement that evaluates to
A is True and
$ brun '(i (q . 0) (q . 70) (q . 80))' '()'
$ brun '(i (q . 1) (q . 70) (q . 80))' '()'
$ brun '(i (q . 12) (q . 70) (q . 80))' '()'
$ brun '(i (q . ()) (q . 70) (q . 80))' '()'
Note that both
C are evaluated eagerly, just like all subexpressions. To defer evaluation until after the condition,
C must be quoted (with
q), and then evaluated with
$ brun '(a (i (q . 0) (q . (x (q . 1337) )) (q . 1)) ())'
Now seems like a good time to clarify further about lists and programs.
Lists and Programs
A list is any space-separated, ordered group of one or more elements inside brackets. For example:
(70 80 90 100),
(0xf00dbabe 48 "hello"), and
(90) are all valid lists.
Lists can even contain other lists, such as
("list" "list" ("sublist" "sublist" ("sub-sublist")) "list").
Programs are a subset of lists which can be evaluated using CLVM.
In order for a list to be a valid program:
- 1. The first item in the list must be a valid operator
- 2. Every item after the first must be a valid program
This is why literal values and non-program lists must be quoted using
q . .
Programs can contain non-program lists, but they also must be quoted, for example:
$ brun '(q . (80 90 100))' '()'
(80 90 100)
And now that we know we can have programs inside programs we can create programs such as:
$ brun '(i (= (q . 50) (q . 50)) (+ (q . 40) (q . 30)) (q . 20))' '()'
Programs in ChiaLisp tend to get built in this fashion. Smaller programs are assembled together to create a larger program. It is recommended that you create your programs in an editor with brackets matching!
f returns the first element in a passed list.
$ brun '(f (q . (80 90 100)))' '()'
r returns every element in a list except for the first.
$ brun '(r (q . (80 90 100)))' '()'
c prepends an element to a list
$ brun '(c (q . 70) (q . (80 90 100)))' '()'
(70 80 90 100)
And we can use combinations of these to access or replace any element we want from a list:
$ brun '(c (q . 100) (r (q . (60 110 120))))' '()'
(100 110 120)
$ brun '(f (r (r (q . (100 110 120 130 140)))))' '()'
Solutions and Environment Variables
Up until now our programs have not had any input or variables, however ChiaLisp does have support for a kind of variable which is passed in through a solution.
It’s important to remember that the context for ChiaLisp is for use in locking up coins with a puzzle program. This means that we need to be able to pass some information to the puzzle.
A solution is a list of values passed to the puzzle. The solution can be referenced with
$ brun '1' '("this" "is the" "solution")'
("this" "is the" "solution")
$ brun '(f 1)' '(80 90 100 110)'
$ brun '(r 1)' '(80 90 100 110)'
(90 100 110)
And remember lists can be nested too.
$ brun '(f (f (r 1)))' '((70 80) (90 100) (110 120))'
$ run '(f (f (r 1)))' '((70 80) ((91 92 93 94 95) 100) (110 120))'
(91 92 93 94 95)
These environment variables can be used in combination with all other operators.
$ run '(+ (f 1) (q 5))' '(10)'
$ run '(* (f 1) (f 1))' '(10)'
This program checks that the second variable is equal to the square of the first variable.
$ run '(= (f (r 1)) (* (f 1) (f 1)))' '(5 25)'
$ run '(= (f (r 1)) (* (f 1) (f 1)))' '(5 30)'
Accessing Environmental Variables Through Integers
In the above examples we were using
run, calling the higher level language, instead of
brun for the lower level language. This is because for the sake of minimalism in the lower level CLVM language, we address the solution with evaluated integers.
1 accesses the root of the tree and returns the entire solution list.
$ brun '1' '("example" "data" "for" "test")'
("example" "data" "for" "test")
After that, you can imagine a binary tree of
r, where each node is numbered.
$ brun '2' '("example" "data" "for" "test")'
$ brun '3' '("example" "data" "for" "test")'
("data" "for" "test")
And this is designed to work when there are lists inside lists too.
$ brun '4' '(("deeper" "example") "data" "for" "test")'
$ brun '5' '(("deeper" "example") "data" "for" "test")'
$ brun '6' '(("deeper" "example") "data" "for" "test")'
And so on.
End of Part 1
This marks the end of this section of the guide. In this section we have covered many of the basics of using ChiaLisp. It is recommended you play with using the information presented here for a bit before moving on.
2 – Coins, Spends and Wallets
This section of the guide will cover evaluating a program inside a program, how ChiaLisp relates to transactions and coins on the Chia network, and cover some techniques to create smart transactions using ChiaLisp.
A coin’s ID is constructed from 3 pieces of information.
- The ID of its parent
- The hash of its puzzle (AKA the puzzlehash)
- The amount that it is worth
To construct a coin ID simply take the hash of these 3 pieces of information concatenated in order.
coinID == sha256(parent_ID + puzzlehash + amount)
This means that a coin’s puzzle and amount are intrinsic parts of it. You cannot change a coin’s puzzle or amount, you can only spend a coin.
The body of a coin is also made up of these 3 pieces of information, but instead of being hashed, they are stored in full. Here is the actual code that defines a coin:
When you spend a coin you destroy it. Unless the behaviour of a puzzle designates what to do with the coin’s value when it is spent, the value of the coin is also destroyed in the spend.
To spend a coin you need 3 pieces of information (and an optional 4th).
- The coin’s ID
- The full source of the coin’s puzzle
- A solution to the coin’s puzzle
- (OPTIONAL) A collection of signatures grouped together, called an aggregated signature
Remember the puzzle and solution is the same as we covered in part 1, except the puzzle has already been stored inside the coin and anybody can submit a solution.
The network / ledger-sim has no concept of coin ownership, anybody can attempt to spend any coin on the network. It’s up to the puzzles to prevent coins from being stolen or spent in unintended ways.
If anybody can submit a solution for a coin, you maybe wondering how somebody can “own” a coin. By the end of the next section of the guide, hopefully it should be clear.
Puzzles and Solutions in Practice
So far in part 1 we have covered ChiaLisp programs that will evaluate to some result. Remember the first part represents a puzzle which is committed to locking up a coin, and the second part is a solution anybody can submit:
$ brun '(+ 2 5)' '(40 50)'
$ brun '(c (q 800) 1)' '("some data" 0xdeadbeef)'
(800 "some data" 0xdeadbeef)
These are fun exercises in isolation, but this format can be used to communicate instructions to the blockchain network of how a coin should behave when it is spent. This can be done by having the result of an evaluation be a list of OpCodes.
The OpCodes are split into two categories: “this spend is only valid if X” and “if this spend is valid then X”.
Here is the complete list of OpCodes along with their format and behaviour.
- AGG_SIG_UNSAFE –  – (49 0xpubkey 0xmessage): This spend is only valid if the attached aggregated signature contains a signature from the given public key of the given message. This is labeled unsafe because if you sign a message once, any other coins you have that require that signature may potentially also be unlocked. It’s probably better just to use AGG_SIG_ME because of the natural entropy introduced by the coin ID.
- AGG_SIG_ME –  – (50 0xpubkey 0xmessage): This spend is only valid if the attached aggregated signature contains a signature from the specified public key of that message concatenated with the coin’s ID.
- CREATE_COIN –  – (51 0xpuzzlehash amount): If this spend is valid, then create a new coin with the given puzzlehash and amount.
- ASSERT_FEE –  – (52 amount): This spend is only valid if there is unused value in this transaction equal to amount, which is explicitly to be used as the fee.
- CREATE_COIN_ANNOUNCEMENT –  – (60 message): If this spend is valid, this creates an ephemeral announcement with an ID dependent on the coin that creates it. Other coins can then assert an announcement exists for inter-coin communication inside a block.
- ASSERT_COIN_ANNOUNCEMENT –  – (61 0xannouncementID): This spend is only valid if there was an announcement in this block matching the announcementID. The announcementID is the hash of the message that was announced concatenated with the coin ID of the coin that announced it
announcementID == sha256(coinID + message).
- CREATE_PUZZLE_ANNOUNCEMENT –  – (62 message): If this spend is valid, this creates an ephemeral announcement with an ID dependent on the puzzle that creates it. Other coins can then assert an announcement exists for inter-coin communication inside a block.
- ASSERT_PUZZLE_ANNOUNCEMENT –  – (63 0xannouncementID): This spend is only valid if there was an announcement in this block matching the announcementID. The announcementID is the message that was announced concatenated with the puzzle hash of the coin that announced it
announcementID == sha256(puzzle_hash + message).
- ASSERT_MY_COIN_ID –  – (70 0xcoinID): This spend is only valid if the presented coin ID is exactly the same as the ID of the coin that contains this puzzle.
- ASSERT_MY_PARENT_ID –  – (71 0xparentID): This spend is only valid if the presented parent coin info is exactly the same as the parent coin info of the coin that contains this puzzle.
- ASSERT_MY_PUZZLE_HASH –  – (72 0xpuzzlehash): This spend is only valid if the presented puzzle hash is exactly the same as the puzzle hash of the coin that contains this puzzle.
- ASSERT_MY_AMOUNT –  – (73 0xamount): This spend is only valid if the presented amount is exactly the same as the amount of the coin that contains this puzzle.
- ASSERT_SECONDS_RELATIVE –  – (80 seconds): This spend is only valid if the given time has passed since this coin was created.
- ASSERT_SECONDS_ABSOLUTE –  – (81 time): This spend is only valid if the timestamp on this block is greater than the specified timestamp.
- ASSERT_HEIGHT_RELATIVE –  – (82 block_age): This spend is only valid if the specified number of blocks have passed since this coin was created.
- ASSERT_HEIGHT_ABSOLUTE –  – (83 block_height): This spend is only valid if the given block_height has been reached.
Conditions are returned as a list of lists in the form:
((51 0xabcd1234 200) (50 0x1234abcd) (53 0xdeadbeef))
Remember: this is what a puzzle should evaluate to when presented with a solution so that a full-node/ledger-sim can understand it.
Let’s create a few examples puzzles and solutions to demonstrate how this is used in practice.
Example 1: Password Locked Coin
Let’s create a coin that can be spent by anybody as long as they know the password.
To implement this we would have the hash of the password committed into the puzzle and, if presented with the correct password, the puzzle will return instructions to create a new coin with a puzzlehash given in the solution. For the following example the password is “hello” which has the hash value 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824. The implementation for the above coin would be thus:
(i (= (sha256 2) (q 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (c (q . 51) (c 5 (c (q . 100) ()))) (q "wrong password"))
This program takes the hash, with
(sha256 ), of the first element in the solution, with
2, and compares that value with the already committed. If the password is correct it will return
(c (q . 51) (c 5 (c (q . 100) (q ()))) which evaluates to
(51 0xmynewpuzzlehash 100). Remember,
51 is the OpCode to create a new coin using the puzzlehash presented in the solution, and
5 is equivalent to
(f (r 1)).
If the password is incorrect it will return the string “wrong password”.
The format for a solution to this is expected to be formatted as
(password newpuzzlehash). Remember, anybody can attempt to spend this coin as long as they know the coin’s ID and the full puzzle code.
Let’s test it out using clvm_tools.
$ brun '(i (= (sha256 2) (q 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (c (c (q 51) (c 5 (c (q 100) (q ())))) (q ())) (q "wrong password"))' '("let_me_in" 0xdeadbeef)'
$ brun '(i (= (sha256 2) (q 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (c (q 51) (c 5 (c (q 100) (q ())))) (q "wrong password"))' '("incorrect" 0xdeadbeef)'
$ brun '(i (= (sha256 2) (q 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (c (q 51) (c 5 (c (q 100) (q ())))) (q "wrong password"))' '("hello" 0xdeadbeef)'
((51 0xdeadbeef 100))
There is one final change we need to make before this is a complete smart transaction.
If you want to invalidate a spend then you need to raise an exception using
x. Otherwise you just have a valid spend that isn’t returning any OpCodes, and that would destroy our coin and not create a new one! So we need to change the fail condition to be
(x (q . "wrong password")) which means the transaction fails and the coin is not spent.
If we’re doing this then we should also change the
(i A B C) pattern to
(a (i A (q . B) (q . C)) 1). The reason for this is explained in part 3. For now don’t worry about why.
Here is our completed password protected coin:
(a (i (= (sha256 2) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q . (c (c (q . 51) (c 5 (c (q . 100) ()))) ())) (q . (x (q . "wrong password")))) 1)
Let’s test it out using clvm_tools:
$ brun '(a (i (= (sha256 2) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q . (c (c (q . 51) (c 5 (c (q . 100) ()))) ())) (q . (x (q . "wrong password")))) 1)' '("let_me_in" 0xdeadbeef)'
FAIL: clvm raise ("wrong password")
$ brun '(a (i (= (sha256 2) (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824)) (q . (c (c (q . 51) (c 5 (c (q . 100) ()))) ())) (q . (x (q . "wrong password")))) 1)' '("hello" 0xdeadbeef)'
((51 0xdeadbeef 100))
Generating OpCodes from the Puzzle vs. from the Solution
Let’s take a moment to consider the balance of power between the send and the spender. Another way of phrasing this is “how much control over the output should the solution have?”
Suppose we lock a coin up using the following puzzle:
(q . ((51 0x365bdd80582fcc2e4868076ab9f24b482a1f83f6d88fd795c362c43544380e7a 100)))
Regardless of what solution is passed this puzzle will always return instructions to create a new coin with the puzzlehash 0x365bdd80582fcc2e4868076ab9f24b482a1f83f6d88fd795c362c43544380e7a and the amount 100.
$ brun '(q . ((51 0x365bdd80582fcc2e4868076ab9f24b482a1f83f6d88fd795c362c43544380e7a 100)))' '(80 90 "hello")'
((51 0x365bdd80582fcc2e4868076ab9f24b482a1f83f6d88fd795c362c43544380e7a 100))
$ brun '(q . ((51 0x365bdd80582fcc2e4868076ab9f24b482a1f83f6d88fd795c362c43544380e7a 100)))' '("it doesn't matter what we put here")'
((51 0x365bdd80582fcc2e4868076ab9f24b482a1f83f6d88fd795c362c43544380e7a 100))
In this example the result of spending the coin is entirely determined from the puzzle. Even though anybody could initiate the spend of the coin, the person that locked the coin up has all the power in the way that the coin is spent as the solution doesn’t matter at all.
Conversely lets consider a coin locked up with the following puzzle:
This example may look a little weird, because most ChiaLisp programs are lists, and this is just an atom, but it is still a valid program. This puzzle simply returns the entire solution to the blockchain. You can think about this in terms of power and control. The person that locked the coin up has given all the power to the person who provides the solution.
$ brun '1' '((51 0xf00dbabe 50) (51 0xfadeddab 50))'
((51 0xf00dbabe 50) (51 0xfadeddab 50))
$ brun '1' '((51 0xf00dbabe 75) (51 0xfadeddab 15) (51 0x1234abcd 10))'
((51 0xf00dbabe 75) (51 0xfadeddab 15) (51 0x1234abcd 10))
In this situation, not only can anybody can spend the coin, they can spend it however they like! This balance of power determines a lot of how puzzles are designed in ChiaLisp.
For example, let’s create a puzzle that lets the spender choose the output, but with one stipulation.
(c (q . (51 0xcafef00d 200)) 1)
This will let the spender return any conditions and OpCodes they want via the solution but will always add the condition to create a coin with the puzzlehash 0xcafef00d and value 200.
$ brun '(c (q . (51 0xcafef00d 200)) 1)' '((51 0xf00dbabe 75) (51 0xfadeddab 15) (51 0x1234abcd 10))'
((51 0xcafef00d 200) (51 0xf00dbabe 75) (51 0xfadeddab 15) (51 0x1234abcd 10))
This section is intended to demonstrate the point that OpCodes can come from both the recipient’s solution and from the sender’s puzzle, and how that represents trust and the balance of power.
In the next exercise we will put everything we know together and create the “standard” transaction in Chia that underpins how wallets are able to send money to each other.
Example: Signature Locked Coin
To ‘send a coin to somebody’ you simply create a puzzle that requires the recipients signature, but then allows them to return any other OpCodes that they like. This means that the coin cannot be spent by anybody else, but the outputs are entirely decided by the recipient.
We can construct the following smart transaction where AGGSIG is 50 and the recipient’s pubkey is
(c (c (q . 50) (c (q . 0xfadedcab) (c (sha256 2) (q . ())))) 3)
This puzzle forces the resultant evaluation to contain
(50 0xpubkey *hash_of_first_solution_arg*) but then adds on all of the conditions presented in the solution.
Let’s test it out in clvm_tools – for this example the recipient’s pubkey will be represented as 0xdeadbeef. The recipient wants to spend the coin to create a new coin which is locked up with the puzzle 0xfadeddab.
$ brun '(c (c (q . 50) (c (q . 0xfadedcab) (c (sha256 2) (q . ())))) 3)' '("hello" (51 0xcafef00d 200))'
((50 0xfadedcab 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824) (51 0xcafef00d 200))
Let’s pull back and add some context here.
A wallet is some software that has several features that make it easy for a user to interact with coins.
- A wallet keeps track of public and private keys
- A wallet can generate puzzles and solutions
- A wallet can sign things with its keys
- A wallet can identify and remember what coins that the user ‘owns’
- A wallet can spend coins
You may be wondering how a wallet is able to identify what coins that the user ‘owns’ if any person can attempt to spend a coin. This is because all wallets already know and agree on what the standard format for sending a coin to somebody is. They know what their own pubkeys are, so when a new coin is created a wallet can check if the puzzle inside that coin is a ‘standard send puzzle’ to one of their pubkeys. If it is, then that coin can be considered to be owned by that ‘wallet’ as nobody else can spend it.
If the wallet that ‘owns’ the coin then wanted to send that coin on again to somebody else, they would generate a ‘standard send puzzle’ but with the new recipient’s pubkey. They could then spend the coin that they own, destroying it, and creating a new coin that is locked up with the new recipients pubkey in the process. The new recipient can then identify that it ‘owns’ the coin and can send it on as they wish later.
Change making is simple. If a wallet spends less than the total value of a coin, they can create another coin with the remaining portion of value, and lock it up with the standard puzzle for themselves again. You can split a coin up into as many new coins with fractions of the original value as you’d like.
You cannot create two coins of the same value, with the same puzzlehash, from the same parent as this will lead to an ID collision and the spend will be rejected.
Coin Aggregation and Spend Bundles
You can aggregate a bunch of smaller coins together into one large coin. To do this, you can create a SpendBundle which groups together one or more spends so that they cannot be split. The SpendBundle also contains an Aggregated Signature object which is how the AGGSIG condition can check if a value has been signed.
You can also further tighten the link between them by using ASSERT_COIN_CONSUMED. Suppose you have a 20 coin and an 80 coin. In the 20 coin you can make it return
(CREATE_COIN 0xnewpuzhash 100) in the spend. Then in the 80 coin you can make it return
(ASSERT_COIN_CONSUMED 0xcoinID). The coupling inside the SpendBundle and the 80 value asserting its relationship to the 20 means that the value from the 80 coin is channeled into the creation of the new value 100 coin.
We can construct an even more powerful version of the signature locked coin to use as our standard transaction.
(c (c (q . 50) (c (q . 0xfadedcab) (c (sha256 2) (q . ())))) (a 5 11))
The first part is mostly the same, the puzzle always returns an AGGSIG check for the pubkey
0xfadedcab. However it only checks for the first element of the solution. This is because instead of the solution for this puzzle being a list of OpConditions to be printed out, the solution is a program/solution pair. This means that the recipient can run their own program as part of the solution generation, or sign a puzzle and let somebody else provide the solution.
The new program and solution inside the solution are evaluated and the result of that is added to the OpCode output. We will cover in more detail how this works in the next part of this guide.
A basic solution for this standard transaction might look like:
("hello" (q . ((51 0xmynewpuzzlehash 50) (51 0xanothernewpuzzlehash 50))) (q . ()))
Running that in the clvm_tools looks like this:
$ brun '(c (c (q . 50) (c (q . 0xfadedcab) (c (sha256 2) (q . ())))) (a 5 11))' '("hello" (q . ((51 0xdeadbeef 50) (51 0xf00dbabe 50))) (q . ()))'
((50 0xfadeddab 0x1f82d4d4c6a32459143cf8f8d27ca04be337a59f07238f1f2c31aaf0cd51d153) (51 0xdeadbeef 50) (51 0xf00dbabe 50))
Coin ownership refers to the concept of creating a coin with a puzzle that means it can only be spent when signed by the private key of the coin’s “owner”. The goal of wallet software is to generate, interpret and manage these kinds of coins and puzzles.
The next part of this guide will go further in depth in ChiaLisp, and cover how to write more complex puzzles. If any of the material in this part of the guide has got you confused, try returning to it after the next part.
3 – Deeper into CLVM
This section of the guide will cover how ChiaLisp relates to transactions and coins on the Chia network.
Lazy Evaluation in ChiaLisp
As we saw in part 1, programs are often structured around
(i A B C) to control flow. ChiaLisp evaluates programs as trees, where the leaves are evaluated first. This can cause unexpected problems if you are not aware of it. Consider the following program which uses
x which immediately halts and throws an error if it is evaluated.
$ brun '(i (q . 1) (q . 100) (x (q . "still being evaluated")))'
FAIL: clvm raise (0x7374696c6c206265696e67206576616c7561746564)
This is because ChiaLisp evaluates both of the leaves even though it will only follow the path of one.
To get around this we can use the following design pattern to replace (i A B C).
(a (i (A) (q B) (q C)) (a))
Applying this to our above example looks like this:
$ brun '(a (i (q . 1) (q . (q . 100)) (q . (x (q . "still being evaluated")))) 1)'
It is worth keeping this in mind whenever you write an
(i A B C).
If you’re wondering how this works (and how the standard transaction from part 2 worked), then allow me to introduce Evaluate.
Introduction to Evaluate
In Part 1 we mentioned that a program is usually a list where the first element is an operator, and every subsequent element is a valid program. We can also run programs with new arguments inside a program.
This looks like this:
(a *(puzzle)* (*solution)*)
Let’s put this into practice.
Here is a program that evaluates the program
(+ 2 (q 5))) and uses the list
(70 80 90) or
(80 90 100) as the solution.
$ brun '(a (q . (+ 2 (q . 5))) (q . (70 80 90)))' '(20 30 40)'
$ brun '(a (q . (+ 2 (q . 5))) (q . (80 90 100)))' '(20 30 40)'
Notice how the original solution
(20 30 40) does not matter for the new evaluation environment. In this example we use
q . to quote both the new puzzle and the new solution to prevent them from being prematurely evaluated.
A neat trick that we can pull is that we can define the new solution in terms of the outer solution. In this next example we will add the first element of the old solution to our new solution.
$ brun '(a (q . (+ 2 (q . 5))) (c 2 (q . (70 80 90))))' '(20 30 40)'
However it’s not just the new solution that we can affect using this, we can also pass programs as parameters.
Programs as Parameters
The core CLVM does not have an operator for creating user defined functions. It does, however, allow programs to be passed as parameters, which can be used for similar results.
Here is a puzzle that executes the program contained in
2 (the first solution argument) with the solution
$ brun '(a 2 (q . (12)))' '((* 2 (q . 2)))'
Taking this further we can make the puzzle run a new evaluation that only uses parameters from its old solution:
$ brun '(a 2 1)' '((* 5 (q . 2)) 10)'
We can use this technique to implement recursive programs.
4 – The High Level Language, Compiler, and Functions
This guide assumes that you have already read the previous parts. It is highly recommended that you do so as the higher level language is built directly on top of the lower level language.
The first difference you need to be aware of for the higher level language is that you should call
run instead of
brun. This lets the runtime know that it should be including higher level features.
The first higher level feature you should be aware of is that it is no longer necessary to quote atoms!
$ brun '(+ 200 200)'
FAIL: first of non-cons ()
$ run '(+ 200 200)'
Run also gives us access to a number of convenient high level operators, which we will cover now.
list takes any number of parameters and returns them put inside a list. This saves us from having to manually create nested
(c (A) (c (B) (q ()))) calls, which can get messy quickly.
$ run '(list 100 "test" 0xdeadbeef)'
(100 "test" 0xdeadbeef)
if automatically puts our
i statement into the lazy evaluation form so we do not need to worry about the unused code path being evaluated.
$ run '(if 1 (q . "success") (x))' '(100)'
$ run '(if 0 (q . "success") (x))' '(100)'
FAIL: clvm raise ()
qq allows us to quote something with selected portions being evaluated inside by using
unquote. The advantages of this may not be immediately obvious but are extremely useful in practice as it allows us to substitute out sections of predetermined code.
Suppose we are writing a program that returns another coin’s puzzle. We know that a puzzle takes the form:
(c (c (q . 50) (c (q . 0xpubkey) (c (sha256 2) (q . ())))) (a 5 11)) However we will want to change 0xpubkey to a value passed to us through our solution.
@ allows us to access the arguments in the higher level language
$ run '(qq (c (c (q 50) (c (q (unquote (f @))) (c (sha256 2) (q ())))) (a 5 11)))' '(0xdeadbeef)'
(c (c (q 50) (c (q 0xdeadbeef) (c (sha256 2) (q ())))) (a 5 11))
Compiling to CLVM with Mod
It is important to remember that in practice smart contracts will run using the lower level language, so none of the above operators will work on the network. What we can do however is compile them down to the lower level language. This is where
mod comes in.
mod is an operator that lets the runtime know that it needs to be compiling the code rather than actually running it.
(mod A B) takes two or more parameters. The first is used to name parameters that are passed in, and the last is the higher level script which is to be compiled.
Below we name our arguments
arg_two and then access
arg_one inside our main program
$ run '(mod (arg_one arg_two) (list arg_one))'
(c 2 (q ()))
As you can see it returns our program in compiled lower level form.
$ brun '(c 2 (q ()))' '(100 200 300)'
You may be wondering what other parameters
mod takes, between variable names and source code.
Functions, Macros and Constants
In the higher level language we can define functions, macros, and constants before our program by using
We can define as many of these as we like before the main source code. Usually a program will be structured like this:
(mod (arg_one arg_two)
(defconstant const_name value)
(defun function_name (parameter_one parameter_two) (*function_code*))
(defun another_function (param_one param_two param_three) (*function_code*))
(defmacro macro_name (param_one param_two) (*macro_code*))
A few things to note:
- Functions can reference themselves in their code but macros cannot as they are inserted at compile time, similar to inline functions.
- Both functions and macros can reference other functions, macros and constants.
- Macros that refer to their parameters must be quasiquoted with the parameters unquoted
- Be careful of infinite loops in macros that reference other macros.
- Comments can be written with semicolons
Now lets look at some example programs using functions.
; function definitions
(defun factorial (input)
(if (= input 1) 1 (* (factorial (- input 1)) input))
We can save these files to .clvm files which can be run from the command line. Saving the above example as
factorial.clvm allows us to do the following.
$ run factorial.clvm
(a (q 2 2 (c 2 (c 5 ()))) (c (q 2 (i (= 5 (q . 1)) (q 1 . 1) (q 18 (a 2 (c 2 (c (- 5 (q . 1)) ()))) 5)) 1) 1))
$ brun '(a (q 2 2 (c 2 (c 5 ()))) (c (q 2 (i (= 5 (q . 1)) (q 1 . 1) (q 18 (a 2 (c 2 (c (- 5 (q . 1)) ()))) 5)) 1) 1))' '(5)'
Squaring a List
Now lets do an example which uses macros as well. When writing a macro it must be quasiquoted with the parameters being unquoted.
We can also take this time to show another feature of the compiler. You can name each parameter in a list or you can name the list itself. This works at any place where you name parameters, and allows you to handle lists where you aren’t sure of the size.
Here we define a macro to square a parameter and then a function to square a list.
(defmacro square (input)
(qq (* (unquote input) (unquote input)))
(defun sqre_list (my_list)
(c (square (f my_list)) (sqre_list (r my_list)))
Compiling and running this code results in this:
$ run square_list.clvm
(a (q 2 2 (c 2 (c 3 ()))) (c (q 2 (i 5 (q 4 (* 9 9) (a 2 (c 2 (c 13 ())))) (q . 5)) 1) 1))
$ brun '(a (q 2 2 (c 2 (c 3 ()))) (c (q 2 (i 5 (q 4 (* 9 9) (a 2 (c 2 (c 13 ())))) (q . 5)) 1) 1))' '(10 9 8 7)'
(100 81 64 49)
You should now have the context and knowledge needed to write your own chialisp programs. Remember from part 2 that these programs run on the blockchain and instruct the blockchain what to do with the coin’s value
The Great Chia Glossary
This guide will act as a glossary for many of the concepts utilized in Chia. If you are familiar with how Bitcoin transactions work, a lot of this will be familiar.
- Coin (TXO/transaction output) – A coin stores value. All coins are generated as the output of a transaction or a coinbase reward or fee target. A coin is spent exactly once, allowing its value to go into other coins, and is then permanently destroyed. Each unspent coin is locked with a ChiaLisp program which is that coin’s puzzle, and whoever has the information to solve that puzzle is the person who can spend that coin. The most basic puzzle has a public key and accepts a solution which contains a list of conditions signed by the corresponding private key, so only the owner of the private key can unlock the coin and spend it.
- Unspent Coin (UTXO/unspent transaction output) – A coin which has been created but not yet spent and hence is storing value. Unspents (UTXO set/unspent transaction output set) – This is the set of all unspent coins on the network. It is used to check if a transaction is valid, acting as a lookup for the puzzles. It maps a coin ID to a birthdate in blockheight. A transaction must contain a reveal of the information used to calculate the ID in order for it to be possible to validate because the unspents doesn’t contain that information, only hashes which can be used to validate it.
- Coin ID/CoinName (TXO ID/transaction output ID) – The ID of a coin in Chia is generated by hashing the primary input ID, puzzle hash, and amount concatenated in that order. This is very different from Bitcoin which uses much more data to form the TXO ID, restricting what smart contracts are capable of.
- Primary Input/Parent – When a coin is created the coin that was used as input in the transaction is designated as the primary input. This is used to create the coin ID. If more that one coin is used up as an input in a transaction then one of the coins is designated the primary input, and the others simply reinforce the transaction.
- Spend/CoinSolution – A spend is a reveal of a coin’s ID, along with the full puzzle code, and a solution to be ran with the puzzle. The result of a spend is determined by the returned Op Constraints after running the puzzle with the solution.
- Spend Bundle – A spend bundle is a collection of spends grouped together with an aggregated signature to be sent to the network.
- ChiaLisp – ChiaLisp is the Turing-complete functional language which the puzzles for spending coin are programmed in.
- Puzzle (Scriptpubkey) – A ChiaLisp program which specifies the behaviour of a coin when it is spent. A puzzle can either reject a solution or output a set of constraints.
- Solution (Scriptsig) – This is some ChiaLisp which is passed to the puzzle for evaluation when a transaction is submitted.
- CLVM – The CLVM is the ChiaLisp Virtual Machine which is the sandboxed environment that puzzles and solutions are run in. The CLVM only runs the compiled minimal version of ChiaLisp, though a compiler can convert the higher level ChiaLisp to the compiled minimal version.
- Aggregated Signature/AggSig – Aggregated Signatures allow us to condense multiple signatures into a single aggregated signature, such that if we know a public key and value we can verify if it exists inside of the single aggregate. This uses BLS non-interactive aggregation.
- Prepend Signature – Prepend signatures are used so that we can retain metadata about the structure of an aggregated signature. TODO: Expand
- Op Constraints/Conditions – Constraints are returned by the puzzle when it’s passed the solution. If all of the returned conditions are met then a transaction is valid.
- Wallet – Software written to interact with transactions. Chia uses Hierarchical Deterministic Wallets (HD Wallets). This means that they can generate many different public keys that are all valid and verifiable as unique to that wallet. A wallet contains a coin if it possesses the information necessary to unlock that coin and create a transaction which spends it.
- Puzzle Generator – A wallet will use a Puzzle Generator to define how it wants to receive transactions. Most wallets will want to generate the standard transaction, however by storing a ChiaLisp program that generate a puzzle, all a Sending Wallet needs to do is ask the Recipient Wallet what its Program Generator is and then run that to create the puzzle to lock the coin up with.
- Puzzle Generator ID – This is the hash of a wallet’s puzzle generator. A wallet can do a hash-lookup and see if it already knows the source code for that puzzle generator. If not, it will request the full source code and store that information in its lookup table.
- Smart Contract – A smart contract is a specialised ChiaLisp puzzle which locks a coin up and enables complex blockchain interactions.
- Coloured Coins – Coloured Coins are a special kind of chia coin which are created by users. A coloured coin is a uniquely marked subset of chia which can’t be forged and can be linked to other assets.
- Authorized Payees – Authorized Payees is a smart contract that means that Wallet A can give Wallet B some money, but Wallet B is only allowed to spend that money in ways that Wallet A has explicitly authorised.
- Decentralised ID – A decentralised ID is a smart contract that enables a wallet to act as an ID which can create messages to other IDs.