Skip to main content

Introduction to Alphabill Predicates

The playground simulates the Alphabill token execution environment. It is intended to help familiarize the developer with writing predicate and unlock scripts for Alphabill, and debugging their execution. During the predicate playground walkthrough, you will learn how to use the language to write several different kinds of common predicates, and use them to transfer ownership between simulated users.

The playground itself is run entirely locally in your browser, including key generation. It provides a simulated runtime environment that will help you understand how transferring bills works on Alphabill, without the need to run any other software. This resource is useful for testing, debugging, and get familiarizing yourself with the language.

The playground is not, and is not yet intended to be, an exact simulation of the Alphabill execution environment.

What are Predicates

In Alphabill, every bill has an "owner" that can change when the bill is transferred. When the owner wants to transfer a bill, they do so by sending a payment order that contains two important elements:

  • Unlock script: creates inputs to the predicate, and proves that they have the right to transfer the bill.
  • New predicate: indicates what the new predicate should be, if the unlock script succeeds.

A predicate is a function. Its code is stored in the bill's state, and it defines the conditions under which the bill can be transferred. It's like a puzzle—by solving it, you prove your right to decide the next puzzle for the bill. The user does that by providing the code for the next predicate that they want the bill to have in their transaction.

The purpose of the unlock script is to prepare arguments for the current predicate to consume by pushing the necessary values onto a stack, which is passed to the predicate. In effect, the user provides a solution to the puzzle that the predicate asks. Predicate functions are variadic—they accept any number of arguments, and the arguments are provided in order, through the input stack.

Next, the predicate starts executing on the same stack, and at the end, the stack either contains one element equal to true or it doesn't. If it does, then the owner can be changed; if not, then the payment order fails and the owner remains the same.

Many crypto users think of using their private keys to sign a transaction when spending. This type of use case is easily supported by Alphabill scripts, but a lot more is also possible. The system of predicates in Alphabill allows for more expressive conditions to be placed on how, when, to whom bills that can be transferred. It enables a non-Turing complete language for expressing digital rights. For now, there are just a few basic operations available, but there are still a number of different useful predicates that can be constructed, as shown below.

How are Predicates Evaluated

For each payment order that is evaluated, there is a stack that contains typed elements. The stack can be manipulated by the user using the script. As in the figure below, you can visualize the predicate. The operations execute left to right, and the data items, like signature, and public key are pushed onto the stack in that order.

Token predicate example

The operations of the predicate are encoded in a compact byte code representation in transactions. This is similar to Bitcoin script. The process of evaluating the transaction's validity is:

  1. The process starts with an empty stack.

  2. The unlock script is executed. This prepares the stack, and its execution results in some number of values being pushed onto the stack, in some order, to be used as arguments to the predicate.

  3. The predicate script is executed, starting with the stack in the state it was left in at step 2.

  4. If there is any runtime error, the predicate is considered to have evaluated false.

  5. If and only if, after full execution of all the predicate operations, the final state of the stack is a single element containing the value true, the predicate is considered to have evaluated true, and the transaction succeeds, allowing the predicate to be changed. Otherwise, it is considered to be false and the transaction fails.

Language Syntax

The initial version of the language is an assembly-like syntax, but over time, developers will be able to use more familiar high-level languages which will compile to target the interpreter.

There are a number of syntactic elements that are currently supported. Remembered that this is currently a very low-level language, which requires the user to manage the state of the execution stack when programming. A few important use cases are supported as per the examples below, and more useful things will be supported in the future.

push <value>Pushes the following value onto the top of the stack. This value could include a declared type, for example, one could push a pre-calculated hash digest onto the stack like: push sha256 hex <value> where the value is the hex encoded digest.
dupDuplicates the top value of the stack, so that the top and next values are the same.
verifyChecks if the top value of the stack is true, consumes it if so, and fails execution if not.
hash <algorithm>Uses the indicated algorithm to hash the top element of the stack. This operation consumes the top element and replaces it with the calculated digest value. The top element must be a hashable type (public key, hash, signature).
equalCompare and replace the two topmost values with a boolean value. The top two values must be of the same type (int, bool, public key, or hash), otherwise execution fails. After comparison, the two values are removed from the stack, and true is pushed if they are equal, false otherwise.
public key <algorithm> hex <value>Declares the following value to be of type "public key" using the indicated algorithm. Always followed by an encoding keyword, such as "hex" to indicate how the key data is encoded, as in the example.
hex <value> or base64 <value>Declares the following value to be encoded using the indicated method, and pushes the decoded bytes onto the stack. Always follows a data declaration statement as above.
check_signatureCheck a signature. Consumes the top two elements of the stack. The top element must be a public key type, and the second element in the stack must be a signature type. If types are not as expected, execution fails. After checking if the signature verifies using the provided public key, the two values are removed from the stack, and true is pushed if the signature is valid, false is pushed otherwise.
if and end ifConsume and branch based on the topmost stack value, which must be boolean.
notNegate the topmost stack value, which must be boolean.
commentsComments are preceded by the ; character.

The playground supports a variable syntax in order to make working with certain types of values, such as keys, hashes, and signatures, easier. Signatures, public keys and their hashes can be referred to in the script using the $ symbol. For example, if there is a signature called "my_signature" that has been created, it can be referred to as $my_signature in the script— this helps avoid the need to copy and paste long sequences of hard to understand hex encoded bytes. The compiler will expand this variable into the correct syntax for use in the predicate or unlock script.

The variable syntax can also be used to refer to calculated values related to keys. For example, $bob refers to the key pair of the Bob in a hypothetical payment. The public key part of the key pair of the public key can be accessed as: $bob.publicKey, while the hash digest of the public key can be accessed as: $bob.sha256 or $bob.sha512. The private key is not directly accessible, and cannot be pushed onto the stack for safety reasons.