Lurk
All scripts are written in a language called Lurk. For a full technical documentation we'll point you some external resources:
Lurk is Lisp
Lurk is a dialect of Lisp and has a lot of commonalities with other Lisp dialects. Here we're going to cover everything you need to write illium scripts in Lurk.
Literals
Literals are self-evaluating expressions. They include:
num
: a signed integer. Ex) 1 2 3, etcu64
: 64 bit unsigned integerschar
: A charactercomm
: Represents a cryptographic commitmentnil
: Represents a nil value or falset
: Represents true
Finite Fields
The underlying cryptography of Lurk makes use of finite fields. As such the largest number that can be represented by a num
is
0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000
All arithmetic operations are conducted modulo this number (+1). If your script relies on arithmetic operation performed on large numbers you'll need to take this into account.
The range of possible values for num looks like:
|-------------------------||--------------------------|
0 most-positive┘└most-negative largest-unsigned
Expressions
Expressions represent a piece of code or computation and usually (though not always) evaluate to a literal.
Arithmetic Expressions
Addition
(+ 3 5)
Evaluates to: 8
Subtraction
(- 3 5)
Evaluates to: -2
Multiplication
(* 3 5)
Evaluates to: 15
Division
(/ 15 5)
Evaluates to: 3
Mod
Mod only works on u64
(% (u64 8) (u64 3))
Evaluates to: 2u64
or
(% 8u64 3u64)
Evaluates to: 2u64
Equals
(= 7 7)
Evaluates to: t
Less than
(< 8 7)
Evaluates to: nil
Greater than
(> 8 7)
Evaluates to: t
Less than or equals
(<= 8 7)
Evaluates to: nil
Greater than or equals
(>= 8 7)
Evaluates to: t
Creating Variables
To create and assign variables you need to do so inside a let expression. The let expression takes the form:
(let <list of bindings> <expression>)
Let's see what this looks like:
(let ((x 3) (y 5)) (+ x y))
Evaluates to: 8
Take note of a couple of things. First, the first argument is a list
of multiple variable assignments. Even if you
only intend to assign one variable, you still need to wrap the bindings in an extra parentheses.
(let ((x 3)) x)
Evaluates to: 3
Second, the last argument to the let expression is an expression which makes use of the variables. The variables defined inside a let expression are only in scope for that expression and cannot be used outside of it. So typically you will make use of the variables in the inner expression.
Let expressions can be nested and the variables in the outer expression are in scope for the inner expression:
(let ((x 3) (y 5))
(let ((z 10))
(* x z)))
Evaluates to: 30
When assigning new variables we can make use of variables defined earlier in the same let expression:
(let ((x 3)
(y (+ x 20)))
y)
Evaluates to: 23
Later variables can shadow earlier ones:
(let ((a 1)
(a 2))
a)
Evaluates to: 2
And inner variables can shadow outer ones:
(let ((a 1))
(let ((a 2)) a))
Evaluates to: 2
Functions
Functions in lurk take the form of:
(lambda <list of arguments> <body>)
Example:
(lambda (x) (+ x 1))
Evaluates to: function
To call a function you need to create a list
(more on lists later) where the first element in the list is a function
and the remaining elements are the function arguments.
((lambda (x) (+ x 1)) 5)
Evaluates to: 6
Typically, you'll want to assign the function to a variable:
(let ((my-func (lambda (x)
(+ x 1))))
(my-func 10))
Evaluates to: 11
Functions can have multiple arguments:
((lambda (x y) (+ x y)) 3 5)
Evaluates to: 8
We can define recursive functions to execute loops but to do so we need to define them inside a letrec
expression
instead of a regular let
expression:
(letrec ((sum-upto (lambda (n) (if (= n 0)
0
(+ n (sum-upto (- n 1)))))))
(sum-upto 5))
Evaluates to: 15
Functions can receive other functions as inputs or emit functions as outputs:
(letrec ((map (lambda (f list)
(if (eq list nil)
nil
(cons (f (car list))
(map f (cdr list))))))
(square (lambda (x) (* x x))))
(map square '(1 2 3 4 5)))
Evaluates to: (1 4 9 16 25)
Cons
Cons cells are containers that hold two variables. They are created as follows:
(cons 1 2)
Evaluates to: (1 . 2)
(Note the . above is just notation to show this is a cons cell)
Variables inside a cons cell can be accessed using the built-in car
and cdr
functions.
car
: returns the first element of the cons cell (The cons cell remains unchanged; This is not a "pop" operation).cdr
: returns the last element of a cons cell.
(let ((x (cons 1 2)))
(car x))
Evaluates to: 1
(let ((x (cons 1 2)))
(cdr x))
Evaluates to: 2
Cons cells can be nested:
(cons 1 (cons 2 (cons 3 4)))
Evaluates to: (1 2 3 . 4)
When you have a nested cons data structure, the cdr
function returns a list
of all the remaining nested elements.
(let ((x (cons 1 (cons 2 (cons 3 4)))))
(cdr x))
Evaluates to: (2 3 . 4)
Lists
We've already seen above that lists look like this:
(x y z)
However, we also mentioned above that lurk treats the first element in a list as a function
and tries to execute it.
In the above list, lurk would try to execute x
as a function. If it was not a function, the script would error.
So how can we create a list in which the first element is not treated as a function?
We create a nested cons structure, where the last element of the last cons cell is nil
.
For example:
(cons 1 (cons 2 (cons 3 nil)))
Evaluates to: (1 2 3)
Note there is a shorthand way of creating a list using the quote
operator.
(quote (1 2 3))
Evaluates to: (1 2 3)
Or, even shorter:
'(1 2 3)
Evaluates to: (1 2 3)
However, be careful to avoid a pitfall here. The quote
operation will not evaluate any expression contained
within the list.
Notice the following does not evaluate to (1 2 7)
.
'(1 2 (+ 3 4))
Evaluates to: (1 2 (+ 3 4))
(essentially the raw data)
If you want to make it evaluate the inner expression you can do:
(eval (car (cdr (cdr '(1 2 (+ 3 4))))))
Evaluates to: 7
Or just build out the list using the cons/nil format defined above.
Strings
(let ((x "hello"))
x)
Evaluates to "hello"
The car
of a string returns a char
:
(let ((x "hello"))
(car x))
Evaluates to 'h'
A char can be prepended to a string with strcons
:
(strcons 'h' "ello")
Evaluates to "hello"
Notice the single quotes for a char and double quotes for a string.
A list of chars
built with strcons
returns a string:
(strcons 'h' (strcons 'e' (strcons 'l' (strcons 'l' (strcons 'o' "")))))
Evaluates to "hello"
If, Then, Else
If expressions take the form:
(if <condition> <then_expression> <else_expression>)
Example:
(if (= 4 (+ 2 2))
t
(+ 9 7))
Evaluates to: t
Non-nil expressions are treated as True in the conditional
(let ((x 3))
(if x
t
nil
)
)
Evaluates to: t
Finally, where =
tests the equality between integers, the eq
function tests the equality between expressions:
(if (eq (cons 1 2) (cons 1 2))
t
(+ 9 7))
Evaluates to: t
Hashing
Lurk has a builtin hash function which can hash any expression. This function uses the poseidon hashing algorithm.
(commit 5)
Evaluates to (comm 0x06332145e67677b767270ae6f73dd9c104cb50d70452f65e78081113ccff1b8e)
Notice the output is type comm
not num
.
If you want to check the equality you'll need to cast the output to a num
or cast your num
to a type comm
(= (num (commit 5)) 0x06332145e67677b767270ae6f73dd9c104cb50d70452f65e78081113ccff1b8e)
Evaluates to t
You can hash any expression:
(commit (cons 1 (cons 2 (cons 3 nil))))
Evaluates to (comm 0x2b86ff9e0f5845c44096867b82bc9ceb92572b978e62905181d644da4402fba5)
Debugging
The emit
function prints the value of the evaluated expression to the terminal and
returns the value.
((lambda (x y) (+ (emit x) y)) 3 5)
Prints: 3
Evaluates to: 8
Illium Locking Functions
As we've already seen illium unlocking functions are lambda
expressions. The entire expression must evaluate
to a function
and the body of the function must evaluate to either True or False (t or nil). Remember that any
non-nil return value will be treated at True.
Returns True
(lambda (locking-params unlocking-params input-index private-params public-params)
t
)
Also is True
(lambda (locking-params unlocking-params input-index private-params public-params)
7
)
Also is True
(lambda (locking-params unlocking-params input-index private-params public-params)
(cons 1 2)
)
Returns False
(lambda (locking-params unlocking-params input-index private-params public-params)
nil
)