Verify Signatures and Signed Data within Algorand Smart Contracts
While developing smart contracts it is often required to verify a transaction or some arbitrary data was signed by an authorized signer. In this article, we will cover the mechanisms Algorand provides to solve these scenarios while writing Algorand smart contracts.
The ed25519verify Opcode
Algorand smart contracts are written in TEAL. If you are not familiar with TEAL make sure to take a look at the smart contract documentation to get up to speed. TEAL is a stack-based language that provides many opcodes for operating on data that is pushed onto the stack. One of the opcodes, ed25519verify, can be used to verify ed25519 signatures, which Algorand uses, against a public key. The opcode takes three parameters, the data that was signed, the signature bytes, and the public key of the signer. The data can be essentially any arbitrary data. The public key is a 32 byte public key of the signer. This method does not support smart contract addresses or multisig addresses. The signature is a 64 byte signature of the string “ProgData” concatenated with the hash of the program and the data that was signed. The primary reason for this concatenated string is to maintain domain separation and guarantee this signature is only valid for this specific TEAL program.
ed25519verify and tealsign relationship
The tealsign Command
The goal command-line tool provides methods to generate the signature used in the ed25519verify
opcode, via the goal clerk tealsign
command. Listed below are the flags that can be set while running goal clerk tealsign
.
--account string Address of account to sign with
--contract-addr string Contract address to sign data for. not necessary if --lsig-txn is provided
--data-b32 string base32 data to sign
--data-b64 string base64 data to sign
--data-file string Data file to sign
-h, --help help for tealsign
--keyfile string algokey private key file to sign with
--lsig-txn string Transaction with logicsig to sign data for
--set-lsig-arg-idx int If --lsig-txn is also specified, set the lsig arg at this index to the raw signature bytes.
Overwrites any existing argument at this index. Updates --lsig-txn file in place. nil args will be appended until index is valid. (default -1)
--sign-txid Use the txid of --lsig-txn as the data to sign`
From the flags you can see that you can sign data formatted in base32 (--data-b32
), base64 (--data-b64
), or within a file (--data-file
). Additionally you can use it to just sign the transaction id for a specific transaction (--sign-txid
). The --sign-txid
flag can be useful when just wanting to verify a signature of a transaction within a TEAL program. You specify the private key file to sign the data using the --keyfile
flag. You can easily export your account key using the following goal command:
$ goal account export youraccount -d data -w yourwallet
This will display your mnemonic which you can then use with the algokey command tool to generate a private key file.
$ algokey import -m "your mnemonic" --keyfile keyfile.sk
You can then use this keyfile with the tealsign command. The --account
flag will be enabled in the future but currently is not supported. The --contract-addr
and --set-lsig-arg-idx
flags will be explained shortly.
The tealsign command returns the base64 encoded signature. The tealsign capability will also be available in the SDKs in the future.
Simple Examples
In this section we will cover some samples using tealsign and ed25519verify. The following TEAL samples are shown as whole programs but should never be used alone. Additional checks and guidelines should be followed for good practice and safety.
You can create a simple contract account by using the following TEAL code.
// filename:verify_contract.teal
txn TxID
arg 0
addr DROUIZXGT3WFJR3QYVZWTR5OJJXJCMOLS7G4FUGZDSJM5PNOVOREH6HIZE
ed25519verify
If this code is compiled using goal clerk compile
it will generate a contract address that can be funded using the dispenser on testnet. These funds will be locked in this contract account until the address specified in the contract (DROUIZXGT3WFJR3QYVZWTR5OJJXJCMOLS7G4FUGZDSJM5PNOVOREH6HIZE) signs the transaction ID of a transaction that withdrawals the funds.
This can be done with the following goal commands.
$ goal clerk send --from-program verify_contract.teal -t tx_receiver_address -o tosign.tx -a 123400 -d ~/node/data
$ goal clerk tealsign --sign-txid --keyfile keyfile.sk --lsig-txn tosign.tx --set-lsig-arg-idx 0
$ goal clerk rawsend -f tosign.tx -d ~/node/data || true
The first line creates a transaction from the contract account to some receiver (you will need to set an address) and writes it out to a file. The second line uses the tealsign command with the --sign-txid
flag to sign the transaction id of the transaction that was created in the first line. The key file supplied (keyfile.sk) has to be the secret key for the account provided in the contract code (DROUIZXGT3WFJR3QYVZWTR5OJJXJCMOLS7G4FUGZDSJM5PNOVOREH6HIZE). The --set-lsig-arg-idx
flag takes the returned signature and places it as the first argument to the TEAL program (Arg 0). You can use this flag to set the signature to any of the argument indexes, but keep in mind that it will override any argument already in the specified location. The final line just submits the transaction to your local node.
If you want to sign some specific data you can put it in a file or encode it as base64 or base32 and supply that data to the tealsign command. For example if we modify the contract in the previous example to look like the following code.
// filename:verify_contract_string.teal
// python -c "import os, base64; print(base64.b64encode('this is a test').decode('utf-8'))"
byte base64(dGhpcyBpcyBhIHRlc3Q=)
// signature bytes
arg 0
addr DROUIZXGT3WFJR3QYVZWTR5OJJXJCMOLS7G4FUGZDSJM5PNOVOREH6HIZE
ed25519verify
This contract is specifically looking for the string “this is a test” to be signed by the address DROUIZXGT3WFJR3QYVZWTR5OJJXJCMOLS7G4FUGZDSJM5PNOVOREH6HIZE. The python command in the comments is just an example of how to encode the string to base64 using python. The following goal commands can now be used to create a transaction and use tealsign to sign the required data.
$ goal clerk send --from-program verify_contract_string.teal -t tx_receiver_address -o tosign.tx -a 123400 -d ~/node/data
$ goal clerk tealsign --data-b64 dGhpcyBpcyBhIHRlc3Q= --keyfile keyfile.sk --lsig-txn tosign.tx --set-lsig-arg-idx 0
$ goal clerk rawsend -f tosign.tx -d ~/node/data || true
The only difference between this example and the previous is that we specify specific data by using the --data-b64
flag and we do not use the --sign-txid
flag as we are not signing the transaction id.
If you do not want to write out the transaction file you can use the --contract-addr
flag to specify the address of the compiled TEAL code with tealsign to create the signature. This signature can and then be passed as a parameter to the goal clerk send command as shown below. This uses the example TEAL code as in the previous example.
$ goal clerk tealsign --data-b64 dGhpcyBpcyBhIHRlc3Q= --keyfile keyfile.sk --contract-addr 6HUUXXZDSDGUN45PHL4PUNL4W5RBS6CMTAYB6AEFCJ4DW2JFI4IL5JYE3Y
This will output the base64 encoded signature.
Generated signature: d/J79gY9JgZpJCsAusb3ApmqMOXnDMYKHAsJaY6G4AtLOjD1KxdF0L316b4TSPRTUcBHExbZXBr5Tdim9MJaCA==
The --contract-addr
flag is set to the address that is returned when compiling the TEAL program with the goal clerk compile
command. Note that you will only use the --contract-addr
flag if you are not using the --lsign-txn
flag and vice versa.
This signature can now be passed as a TEAL argument using the --argb64
flag with the goal clerk send
command. See TEAL Arguments for more details on passing arguments to smart contracts.
$ goal clerk send --from-program verify_contract_string.teal -t tx_receiver_address --argb64 "d/J79gY9JgZpJCsAusb3ApmqMOXnDMYKHAsJaY6G4AtLOjD1KxdF0L316b4TSPRTUcBHExbZXBr5Tdim9MJaCA==" -a 123400 -d ~/node/data
The --from-program
flag specifies that the transaction is coming from the contract address of the second example. The --argb64
flag is used to specify that this is a TEAL argument and contains the signature generated with the last command.
If you prefer to write either of the two smart contracts in this article in pyteal they would have syntax similar to the following.
Example 1
#contract_verify
from pyteal import *
signer = Addr("DROUIZXGT3WFJR3QYVZWTR5OJJXJCMOLS7G4FUGZDSJM5PNOVOREH6HIZE")
def simple_verify():
verify_cond = Ed25519Verify(Txn.tx_id(), Arg(0), signer)
return verify_cond
print(simple_verify().teal())
Example 2
#contract_verify_string
from pyteal import *
signer = Addr("DROUIZXGT3WFJR3QYVZWTR5OJJXJCMOLS7G4FUGZDSJM5PNOVOREH6HIZE")
mydata = Bytes("base64", "dGhpcyBpcyBhIHRlc3Q=")
def simple_verify_data():
verify_cond = Ed25519Verify(mydata, Arg(0), signer)
return verify_cond
print(simple_verify_data().teal())
Conclusion
While these examples are fairly trivial the combination of ed25519verify and tealsign can be used to create some powerful smart contracts. For example, a coded version of a multisig of a multisig contract could be created to allow some very specific transaction approval schemes. To make sure you stay on top of the many new features on the horizon, signup for the developer newsletter!