Writing Smart Signatures on Algorand Using TEAL

Do repost and rate:

This is the fifth course about developing smart contracts and signatures on the Algorand blockchain. This document is meant for developers aspiring to tap into the growing Algorand ecosystem.

Previous tutorials have already covered the differences between PyTeal and TEAL. Most developers prefer to use PyTeal to write smart contracts and smart signatures. However, once you compile them for deployment at your Algorand sandbox node, you’ll end up with a TEAL program. For that reason, the purpose of this tutorial is to cover the basics of TEAL so you can understand the structure and opcodes used in TEAL smart signatures (and smart contracts).

Excited? ??

Requirements

  • A running Algorand sandbox or Algorand node. More information can be found here
  • At least one funded address in the sandbox
  • `goal` (part of the Algorand sandbox by default)

Quick links

  • Smart signature programming with TEAL 
  • How to verify your TEAL passphrase code

Smart signature programming with TEAL

As explained in the previous tutorials, a smart signature contains TEAL logic that determines if a spending transaction gets approved or not. The below example shows a short smart signature that performs the following actions:

1. Verify if the fee is below 10,000 microAlgos

2. Load an argument and verify if it matches the expected passphrase (check the length and check the passphrase itself)

To get started, let’s create a `passphrase.teal` file within the `/sandbox/artifacts` folder in your Algorand sandbox. You’ll use this file to write our smart signature TEAL program.

```shtouch ./artifacts/passphrase.teal```

Now, add the following code to the `passphrase.teal` file.

```#pragma version 5// Valid passphrase input: "weather comfort erupt verb pet range endorse exhibit tree brush crane man"txn Feeint 10000<=// Check length of passphrasearg 0lenint 73==&&// The sha256 value of the passphrasearg 0sha256byte base64 30AT2gOReDBdJmLBO/DgvjC6hIXgACecTpFDcP1bJHU===&&```

Let’s try to understand what happens at each line of the program. Let’s assume we send a transaction to this program with a fee set to `9999` and the correct passphrase.

Step 1: Load the paid transaction fee

Code: `txn Fee` (docs – More info about the fee property and all other properties in the transaction reference)

The `txn` opcode expects a `field` as input. For instance, you want to load the fee paid (`9999`) for the transaction submitted to our smart signature. Therefore, we mention the `Fee` field. The `txn` opcode will then push this data to the top of the stack. We can use this opcode to access all kinds of properties from our transaction, such as the `Sender`, `Receiver`, `Amount`, or the transaction type using `Type`. You can find all possibilities in the documentation.

As you can see, index `0` in our stack now contains the value 9999.

```txtIndex     Stack0         9999  ```

Step 2: Push the maximum transaction fee

Code: `int 10000` (docs)

Push the uint64 value 10000 to the stack (index 1). It’s the maximum allowed transaction fee for this transaction. In the next step, you’ll compare both values.

```txtIndex     Stack0         99991         10000```

Step 3: Compare received and expected transaction fees

Code: `<=` (docs)

Compares the last two values if they are lower than or equal to each other. In this case, 9999 is lower than 10000. Therefore, the operation pushes the success value `1` to the stack.

If you take a look at the documentation, it mentions that it pops two uint64 values (`Pops: … stack, {uint64 A}, {uint64 B}`) and pushes a single uint64 value.

```txtIndex     Stack0         1```

Step 4: Load the submitted passphrase argument

Code: `arg 0` (docs)

Loads the argument at position 0 passed with the transaction. In our example, we’ve submitted a single argument that represents the base64 version of the expected passphrase (“weather comfort erupt verb pet range endorse exhibit tree brush crane man”). Each further argument we pass with the transaction can be accessed using `arg 1`, `arg 2`, and so on. As you can see, the stack shows the string version of the argument.

```txtStack0 11 "weather comfort erupt ..."```

Step 5: Calculate the length of the passphrase argument

Code: `len` (docs)

The `len` opcode will pop a []byte (byte string) and calculate its length. This length is added back to the stack (73).

```txtIndex     Stack0         11         73```

Step 6: Load expected passphrase length

Code: `int 73` (docs)

Push the uint64 value 73 to the stack, the expected length of the passphrase.

```txtIndex     Stack0         11         732         73```

Step 7: Compare received and expected passphrase length

Code: `==` (docs)

Verifies if the last two values on the stack are equal. The `==` opcode can compare both byte[] and uint64 values. Because both values are equal, it pushes a success value (1) to the stack.

```txtIndex     Stack0         11         1```

Step 8: Chain conditions using “&&” opcode

Code: `&&` (docs)

It consumes two uint64 values. If both values are not zero, then it returns 1 (true). If both are zero or one of the two values is equal to zero, then it returns 0 (false). **This is a handy operator for chaining conditions.** Because we have two `1` values, the expression will evaluate to `1` (true). It’s also important because the program has to end with a single success value to succeed which means a single `1` should be left on the stack when reaching the end of the program.

```txtIndex     Stack0         1```

Step 9: Load passphrase argument again

Code: `arg 0` (docs)

Loads the passphrase argument passed with the transaction again to the stack.

```txtIndex     Stack0         11         "weather comfort erupt ..."```

Step 10: Calculate the sha256 value for comparing

Code: `sha256` (docs)

Calculates the SHA256 value of the top-element of the stack, in our case the passphrase argument at index 1. Then, it pushes a [32]byte string back to the stack. In the next step, we will compare it against the expected byte array (`[]byte`) for our passphrase to see if they match.

```txtIndex     Stack0         11         "df4013da039178305d2662c13bf0e0be30ba8485e000279c4e914370fd5b2475"```

Step 11: Load the expected byte array for the passphrase

Code: `byte base64 30AT2gOReDBdJmLBO/DgvjC6hIXgACecTpFDcP1bJHU=` (docs)

First, let’s explain where the “30AT2gOReDBdJmLBO/DgvjC6hIXgACecTpFDcP1bJHU=” value comes from. This is the base64 version of the SHA256 of our expected passphrase “weather comfort erupt …”. Next, we tell the `byte` opcode to convert this base64 string to a []byte. As expected, this yields the same byte array (`[]byte`) as the argument we’ve submitted with the transaction.

```txtIndex     Stack0         11         "df4013da039178305d2662c13bf0e0be30ba8485e000279c4e914370fd5b2475"2         "df4013da039178305d2662c13bf0e0be30ba8485e000279c4e914370fd5b2475"```

Step 12: Verify both byte arrays

Code: `==` (docs)

Verifies if the last two values on the stack are equal. It can compare both byte[] and uint64 values.

```txtIndex     Stack0         11         1```

Step 13: Merge values to a single value

Code: `&&` (docs)

Consumes two uint64 values, if both are not zero, then it returns 1 (true).

Note: You can make more advanced logic constructions with logic operators, for instance: `(condition 1 && condition 2) || (condition 3)`.

```txtIndex     Stack0         1```

Here, the program ends. Because there’s only one positive value on the stack left, the program finishes successfully and a spend transaction is approved. As you can see, we’ve created the following logic construction: 

```(condition "transaction fee" && condition "passphrase length" && condition "passphrase string match")```

How to verify your TEAL passphrase code?

We can use goal CLI tool to verify if the transaction succeeds or fails. First, let’s add the `passphrase.teal` file to our sandbox.

```sh./sandbox copyTo ./artifacts/passphrase.teal```

Now, you can compile the address for the smart signature. You’ll need it to fund the smart signature just like a regular account.


```sh./sandbox goal clerk compile passphrase.teal```

And, let’s fund the address using one of the prefunded accounts.

```sh./sandbox goal clerk send -a 250000 -f CFPQQQA5E2CZFCFDWBA6KROPJLNLOC2YRERTJ6YYY5CRBEUQKXTPKDRS4A -t 45L2272USBPOCS3QC6PUVKGJRNVWY5GN4MDP2NJNCZFAZ7UWMVM3J5KJKQ```

Next, let’s generate the payment transaction from the contract to a random account using goal clerk send. Our goal is to provide the correct passphrase and fee to be able to spend the funds held in the compiled `passphrase.teal` account. The `-o` option lets you specify an output file for the transaction in JSON format. Make sure to use a funded sandbox account to send this transaction.

```sh./sandbox goal clerk send -a 30000 --from-program passphrase.teal -c  --argb64 d2VhdGhlciBjb21mb3J0IGVydXB0IHZlcmIgcGV0IHJhbmdlIGVuZG9yc2UgZXhoaWJpdCB0cmVlIGJydXNoIGNyYW5lIG1hbg== -t  -o passphrase.txn```

To view the output `passphrase.txn` file, we can either enter the `Algod` client using `./sandbox enter algod` and use `cat passphrase.txn`. Or, you can copy the file outside the sandbox. However, this step is optional, outputted data is unreadable.

```sh./sandbox copyFrom passphrase.txn```

Next, let’s use the goal clerk dryrun command to verify if the transaction will succeed and our logic works as expected.

```sh./sandbox goal clerk dryrun -t passphrase.txn```

Your output will look like below. As you can see, the transaction succeeds based on the printed `- pass -` value. Try for yourself to generate a failing transaction with a fee set higher than 10,000 or an incorrect passphrase.

```shtx[0] trace:  1 txn Fee => (1000 0x3e8)  3 pushint 10000 => (10000 0x2710)  6 <= => (1 0x1)  7 arg_0 => (7765617468657220636f6d666f72742065727570742076657262207065742072616e676520656e646f72736520657868696269742074726565206272757368206372616e65206d616e)  8 len => (73 0x49)  9 pushint 73 => (73 0x49) 11 == => (1 0x1) 12 && => (1 0x1) 13 arg_0 => (7765617468657220636f6d666f72742065727570742076657262207065742072616e676520656e646f72736520657868696269742074726565206272757368206372616e65206d616e) 14 sha256 => (df4013da039178305d2662c13bf0e0be30ba8485e000279c4e914370fd5b2475) 15 pushbytes 0xdf4013da039178305d2662c13bf0e0be30ba8485e000279c4e914370fd5b2475 // addr 35ABHWQDSF4DAXJGMLATX4HAXYYLVBEF4AACPHCOSFBXB7K3ER2SPJXOSA => (df4013da039178305d2662c13bf0e0be30ba8485e000279c4e914370fd5b2475) 49 == => (1 0x1) 50 && => (1 0x1) - pass -```

You can still send this transaction to the blockchain network using the goal clerk rawsend command.

```sh./sandbox goal clerk rawsend -f passphrase.txn```

You can find more information about smart signatures and smart contracts in the Algorand developer documentation. Make sure to join the Algorand Discord server to learn more about Algorand development and its ecosystem.

Disclaimer

This solution is intended for learning purposes only. It does not cover error checking and other edge cases; therefore, should not be used as a production application.

Credits: Created by Tobias Schmidt, GitHub: @CrypBlockCode

Regulation and Society adoption

Ждем новостей

Нет новых страниц

Следующая новость