Articles
No Results
Article

Using a Smart Contract for Advanced Approvals: Hierarchical Thresholds

Securing money flow is a key design principle for Algorand Smart Contracts (ACS1). Today let’s look at real-world use case for authorizing money flow within the typical corporate structure. Although the department/role structure may be hierarchical, the approval process can be designed as distributed, decentralized and secure. We will use the CLI Tools to build and test an escrow contract account implementing hierarchical threshold approvals. You’ll learn how to use tealsign to add a signature to a LogicSig and use ed25519verify within your contract code.

Let’s start with something concrete, a typical corporate organization and their desire to implement financial controls on spending from their treasury account. It’s a hierarchical organization comprised of several employees with various overlapping signing authorities on that account. Thankfully, each employee has a unique account on the Algorand Network. By designing a secure contract implementing hierarchical threshold evaluation, the organization can build oversight and redundancy into their spending protocol.

Here is the corporate structure:

- C-Level Execs
    - CEO
    - COO
    - CFO
- Accounting Department
    - Accounting Manager
        - Accountant1
        - Accountant2
        - Accountant3

The desired spending protocol must enforce authorization from the majority C-Level Execs and a quorum of the Accounting Department. Specifically, both of the following conditions must be met:

1. A minimum of 2 out of 3 C-Level Execs approve
2. (At least 1 Accountant AND the Account Manager approve) OR (all 3 Accountants approve)

That provides the required details we need to build the treasury account using the TEAL language. Let’s write some code! Open your favorite text editor and create a new file named “hierarchical-threshold.tmpl” then paste this in and save it:

// The following contract implements hierarchical threshold evaluation with the following semantics:
// ( ( Majority C-Level Execs  ) AND (                  Accounting Department Review                   ) )
// ( ( Majority: CEO, COO, CFO ) AND ( ( Any Accountant  AND Accounting Manager ) OR All Accountants ) ) )
// ( (   2of3: CEO, COO, CFO   ) AND ( ( 1of3: ACCT1,2,3 AND     ACCT_MNGR      ) OR 3of3: ACCT1,2,3 ) ) )
// ( (         2 of 3          ) AND ( (     1 of 3      AND         1          ) OR     3 of 3      ) ) )

// *** SIGNATURE VERIFICATIONS ***
// First, verify all signatures from the C-Level Execs
txn TxID       // "data"
arg 0          // "signature"
addr TMPL_CEO  // "public key"
ed25519verify

txn TxID
arg 1
addr TMPL_COO
ed25519verify
+

txn TxID
arg 2
addr TMPL_CFO
ed25519verify
+

// Store sum in scratch space 0
store 0

// Next, verify signatures from the Accountants
txn TxID
arg 3
addr TMPL_ACCT1
ed25519verify

txn TxID
arg 4
addr TMPL_ACCT2
ed25519verify
+

txn TxID
arg 5
addr TMPL_ACCT3
ed25519verify
+

// Store in scratch space 0
store 1

// Next, verify signature from Accounting Manager
txn TxID
arg 6
addr TMPL_ACCT_MNGR
ed25519verify

// Store sum in scratch space 2
store 2

// *** THRESHOLD CALCULATIONS ***
// Calculate C-Level Execs Approvals (need majority, threshold 2)
load 0
int 2
>=

// Calculate Accounting Department (lhs)
// Calculate Accountants (need any, threshold 1)
load 1
int 1
>=

// Calculate Accounting Manager (need this, threshold 1 implicit)
load 2

// *** FIRST LOGIC EVALUATION ***
// Evaluate Accounting Department (lhs)
&&

// *** RESUME THRESHOLD CALCULATIONS ***
// Calculate Accounting Department (rhs)
// Calculate Accountants (need all, threshold 3)
load 2
int 3
>=

// *** RESUME LOGIC EVALUATIONS ***
// Evaluate Accounting Department (either lhs OR rhs)
||

// Evaluate C-Level Execs AND Accounting Department
&&

// *** FEES ***
txn Fee
int TMPL_FEE
<=
&&

That’s a big wall of code, but not to worry, it’s rather simple to follow along. Let’s dive in.

Lines 1-5 provide the semantics we defined above, our business logic for authorization.

// The following contract implements hierarchical threshold evaluation with the following semantics:
// ( ( Majority C-Level Execs  ) AND (                  Accounting Department Review                   ) )
// ( ( Majority: CEO, COO, CFO ) AND ( ( Any Accountant  AND Accounting Manager ) OR All Accountants ) ) )
// ( (   2of3: CEO, COO, CFO   ) AND ( ( 1of3: ACCT1,2,3 AND     ACCT_MNGR      ) OR 3of3: ACCT1,2,3 ) ) )
// ( (         2 of 3          ) AND ( (     1 of 3      AND         1          ) OR     3 of 3      ) ) )

Notice the // *** SECTION HEADER *** lines throughout. There are four basic sections in this contract code:
- Signature Verifications
- Threshold Calculations
- Logic Evaluations
- Fees

Info

There is a really nice visual diagram that illustrates the relationships between the program, arguments, stack, scratch space and transactions. These terms are used throughout this article, so it’s best to have a firm understanding.

Within the signature verification section there are code blocks for C-Level Execs, Accountants and the Accounting Manager. They all function similarly, so start by looking at lines 8-12.

// *** SIGNATURE VERIFICATIONS ***
// First, verify all signatures from the C-Level Execs
txn TxID       // "data"
arg 0          // "signature"
addr TMPL_CEO  // "public key"
ed25519verify

txn TxID reads the transactionID and places it on the stack. Next is arg 0 which reads the first transaction argument and places it on the stack (we’ll use tealsign to add that argument to the transaction below). Next is addr TMPL_CEO which adds an account to the stack as well, for a total of 3 items. The TMPL_CEO is a template placeholder which will be replaced with the CEO’s actual account address generated below. Notice the comments for each of those lines: data, signature and public key. ed25519verify requires all three of those values, in that order, so it pops them from the stack. Now the cryptographic signature verification happens and true or false (1 or 0) is place on the stack as a result. Taken together, those four lines of code tell us if the CEO provided a valid signature for this transaction during evaluation.

txn TxID
arg 2
addr TMPL_CFO
ed25519verify
+

// Store sum in scratch space 0
store 0

Signature verification continues with the COO next. Notice lines 18 has + which will pop the top two items from the stack (signature verification of CEO and COO), sum the values and place the result on top of the stack, similar for CFO further down. In line 27 the final sum is popped from the stack with store 0 and moved to scratch space for use later. At this point the stack is empty and the program continues performing additional signature verifications and storing to scratch space for the Accounting Department personnel.

The next section header is threshold calculations. On line 61 load 0 grabs the C-Level Execs sum value from scratch space and place it on the stack. We need a threshold of 2, so int 2 adds that value to the stack. >= pops both values and evaluates them, then returns true or false (1 or 0) to the stack, similar for the remaining personnel. These calculations will be left on the stack and used in the evaluation section to ensure the required threshold of signatures is achieved by the required personnel.

// Calculate C-Level Execs Approvals (need majority, threshold 2)
load 0
int 2
>=

The first Logic Evaluation happens at line 76. This code block looks at the left-hand side of the Accounting Department evaluation to see if at least one accountant and the manager signed. The && pops these two calculated values from the stack and returns the result.

// *** FIRST LOGIC EVALUATION ***
// Evaluate Accounting Department (lhs)
&&

Next, comes the right-hand side calculation for all accountants (no manager needed). load 2 is the calculated value and int 3 is the threshold. >= returns the results to the stack.

// *** RESUME THRESHOLD CALCULATIONS ***
// Calculate Accounting Department (rhs)
// Calculate Accountants (need all, threshold 3)
load 2
int 3
>=

Logic evaluations continue at line 87 where we find the || operator which pops the two sides of the Accounting Department calculations and performs an or, then returns the result. The final logic evaluation for our departments is an and of the C-Level Execs, which was left on the stack from line 63, with the Accounting Department. At this point we have successfully evaluated the intended business logic within the contract code.

// *** RESUME LOGIC EVALUATIONS ***
// Evaluate Accounting Department (either lhs OR rhs)
||

// Evaluate C-Level Execs AND Accounting Department
&&

A quick check of the Fees in lines 92-96 (we will replace the template placeholder below) using &&. The contract will complete with only 0 or 1 on the stack. That value equates to either REJECT or PASS during transaction evaluation.

// *** FEES ***
txn Fee
int TMPL_FEE
<=
&&

Let’s see this code in action. We are going to use bash, goal and algokey to perform our tests offline, then you may deploy the smart contract once you are confident everything is defined securely. First, let’s setup your local environment.

# set the path for your temporary directory used to store account keys
TEMPDIR="/TEMPDIR"

# set your wallet name
WALLET_NAME="Your Wallet Name"

# Set the appropriate algod values for your environment. These are the defaults for Sandbox on TestNet.
ALGOD_HOST_PORT="localhost:4001"
ALGOD_HEADER="x-algo-api-token:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

# ensure your PATH is set properly, else modify the following and set -d /path/to/goal and -d /path/to/algokey
GOAL_CMD="goal -w ${WALLET_NAME}"
ALGOKEY_CMD="algokey"

Let’s begin by setting up new accounts for the personnel using algokey. Copy/paste each line into your terminal to execute.

# C-Level Execs
export CEO=$(algokey generate -f $TEMPDIR/CEO | tail -1 | awk '{ print $3 }')
export COO=$(algokey generate -f $TEMPDIR/COO | tail -1 | awk '{ print $3 }')
export CFO=$(algokey generate -f $TEMPDIR/CFO | tail -1 | awk '{ print $3 }')

# Accounting Department
export ACCT1=$(algokey generate -f $TEMPDIR/ACCT1 | tail -1 | awk '{ print $3 }')
export ACCT2=$(algokey generate -f $TEMPDIR/ACCT2 | tail -1 | awk '{ print $3 }')
export ACCT3=$(algokey generate -f $TEMPDIR/ACCT3 | tail -1 | awk '{ print $3 }')
export ACCT_MNGR=$(algokey generate -f $TEMPDIR/ACCT_MNGR | tail -1 | awk '{ print $3 }')

export RECIPIENT="GD64YIY3TWGDMCNPP553DZPPR6LDUSFQOIJVFDPPXWEG3FVOJCCDBBHU5A" # TestNet Faucet

Nothing shows up on screen because we assigned the resulting account identifiers to environment variables. You may echo $CEO to see the account, similar for $ACCT_MNGR and so on. Important to note is the keyfile or private key for each account is stored in your $TEMPDIR. These keyfiles will be accessed by tealsign below. We are just testing here, but in production you must secure those keyfiles properly.

Now we need to update our template code to replace the TMPL_ values with the relevant local variable set above. For example, replace TMPL_CEO with the value from echo $CEO. Don’t forget TMPL_FEE near the bottom. There are 8 values to update. Save as hierarchical-threshold.teal to write out the final TEAL program.

Next, let’s compile that code and assign the resulting address to $CONTRACT.

# Compile contract
export CONTRACT=$(${GOAL_CMD} clerk compile hierarchical-threshold.teal | awk '{ print $2 }')

It’s time to build a transaction. We will use goal to build a transaction with a LogicSig that sends from the contract source code and writes out tosign.tx file.

# Build the unsigned transaction, write out to file:
${GOAL_CMD} clerk send --from-program hierarchical-threshold.teal --to $RECIPIENT --amount 100000 --out tosign.tx

Let’s look at the transaction:

${GOAL_CMD} clerk inspect tosign.tx

Response:

tosign.tx[0]
{
  "lsig": {
    "l": "// version 1\nintcblock 2 1 3\nbytecblock 0x4c33ba3ebce473aa2e17bf35587c4c7279b1c6676aeec4a12efdba689442398e 0xb1f66c928cae970ffc419241f579ae340530d0e1c88cac4bf17cc5b3023eb364 0x09714edc7199e3677755994b38286b11f9e10be0fa5712a596ea99d0aa38f378 0x2ff0d5f30d23cd8ca89120704b3aa9f3d74bbaeeebe1cd6ab94b62117ddefc49 0xd6def622ae4415ec24b8819aa6d86cadd9c1366783b0259a3bed447b67a87a83 0xa569ce551fc700418dc8e62217c384b1feedadbe7673094778eb20c6a6ed7b0c 0xf3eee3bf0d798824187c24e60d70590b5325c11a3c88fcefdb5c83eea08a62aa\ntxn TxID\narg_0\nbytec_0\ned25519verify\ntxn TxID\narg_1\nbytec_1\ned25519verify\n+\ntxn TxID\narg_2\nbytec_2\ned25519verify\n+\nstore 0\ntxn TxID\narg_3\nbytec_3\ned25519verify\ntxn TxID\narg 4\nbytec 4\ned25519verify\n+\ntxn TxID\narg 5\nbytec 5\ned25519verify\n+\nstore 1\ntxn TxID\narg 6\nbytec 6\ned25519verify\nstore 2\nload 0\nintc_0\n>=\nload 1\nintc_1\n>=\nload 2\n&&\nload 2\nintc_2\n>=\n||\n&&\n"
  },
  "txn": {
    "amt": 100000,
    "fee": 1000,
    "fv": 151598,
    "gen": "REKEY-v1",
    "gh": "aT+oawSj/oxSWPNILM7O7EkuVE1rPxD5lLTgY+kvVCA=",
    "lv": 152598,
    "note": "ec5eeJ2d0aI=",
    "rcv": "GD64YIY3TWGDMCNPP553DZPPR6LDUSFQOIJVFDPPXWEG3FVOJCCDBBHU5A",
    "snd": "AFLMTJGTZW2K7BZMNSEX6EP2DUQ7L4GAAIAEBRG5QVM4ONL4QUOPDZ5MYU",
    "type": "pay"
  }
}

Notice the “lsig” section contains the teal program, but it does not contain any arguments. We will use goal clerk dryrun for offline testing:

${GOAL_CMD} clerk dryrun --txfile tosign.tx

Results:

tx[0] cost=13343 trace:
  1 intcblock => <empty stack>
  6 bytecblock => <empty stack>
239 txn 0x17 => (85b1fd66c2fca6df093e46936d00b378dbebaad31261a325c81aaa52c7b3cf34) 
241 arg_0 => (85b1fd66c2fca6df093e46936d00b378dbebaad31261a325c81aaa52c7b3cf34) 
241 cannot load arg[0] of 0

REJECT
ERROR: cannot load arg[0] of 0

Without any signatures passed in as arguments, the contract will fail to evaluate above. Let’s add our first signature to the unsigned transaction using goal clerk tealsign with a few parameters (describe below):

${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/CEO --lsig-txn tosign.tx --set-lsig-arg-idx 0
${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/CEO --lsig-txn tosign.tx --set-lsig-arg-idx 1
${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/CEO --lsig-txn tosign.tx --set-lsig-arg-idx 2
${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/CEO --lsig-txn tosign.tx --set-lsig-arg-idx 3
${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/CEO --lsig-txn tosign.tx --set-lsig-arg-idx 4
${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/CEO --lsig-txn tosign.tx --set-lsig-arg-idx 5
${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/CEO --lsig-txn tosign.tx --set-lsig-arg-idx 6

The first parameter is --signed-txid which says we intend to sign this transaction’s ID (rather than some arbitrary data). It will be used as the “data” when ed25519verify evaluates within the teal code. Next, is --keyfile $TEMPDIR/CEO which is the private key held by the CEO and used to sign here. The --lsig-txn tosign.tx defines the transaction file to add the signature to. Last, the --set-lsig-arg-idx 0 informs which index position the signature will appear. Notice the CEO is adding their signature for all 7 index positions. Let’s test the CEO-only signed transaction using ${GOAL_CMD} clerk dryrun --txfile tosign.tx again.

Results:

tx[0] cost=13343 trace:
  1 intcblock => <empty stack>
  6 bytecblock => <empty stack>
239 txn 0x17 => (bdf1688e1afa7e2bebfd8f6ca84d6b2043950edcf88bb49a32ac52e2c428fcb7) 
241 arg_0 => (f8c137934ea4b498bfa8f87b0c426d46fb47dcb31a900b6641ebbf01c70d139db3f5c75705e939543185dc2778e885aba773c1b217f9534963bf7c2ddafd2403) 
242 bytec_0 => (4c33ba3ebce473aa2e17bf35587c4c7279b1c6676aeec4a12efdba689442398e) 
243 ed25519verify => (1 0x1) 
244 txn 0x17 => (bdf1688e1afa7e2bebfd8f6ca84d6b2043950edcf88bb49a32ac52e2c428fcb7) 
246 arg_1 => (f8c137934ea4b498bfa8f87b0c426d46fb47dcb31a900b6641ebbf01c70d139db3f5c75705e939543185dc2778e885aba773c1b217f9534963bf7c2ddafd2403) 
247 bytec_1 => (b1f66c928cae970ffc419241f579ae340530d0e1c88cac4bf17cc5b3023eb364) 
248 ed25519verify => (0 0x0) 
249 + => (1 0x1) 
250 txn 0x17 => (bdf1688e1afa7e2bebfd8f6ca84d6b2043950edcf88bb49a32ac52e2c428fcb7) 
252 arg_2 => (f8c137934ea4b498bfa8f87b0c426d46fb47dcb31a900b6641ebbf01c70d139db3f5c75705e939543185dc2778e885aba773c1b217f9534963bf7c2ddafd2403) 
253 bytec_2 => (09714edc7199e3677755994b38286b11f9e10be0fa5712a596ea99d0aa38f378) 
254 ed25519verify => (0 0x0) 
255 + => (1 0x1) 
256 store 0x00 => <empty stack>
258 txn 0x17 => (bdf1688e1afa7e2bebfd8f6ca84d6b2043950edcf88bb49a32ac52e2c428fcb7) 
260 arg_3 => (f8c137934ea4b498bfa8f87b0c426d46fb47dcb31a900b6641ebbf01c70d139db3f5c75705e939543185dc2778e885aba773c1b217f9534963bf7c2ddafd2403) 
261 bytec_3 => (2ff0d5f30d23cd8ca89120704b3aa9f3d74bbaeeebe1cd6ab94b62117ddefc49) 
262 ed25519verify => (0 0x0) 
263 txn 0x17 => (bdf1688e1afa7e2bebfd8f6ca84d6b2043950edcf88bb49a32ac52e2c428fcb7) 
265 arg 0x04 => (f8c137934ea4b498bfa8f87b0c426d46fb47dcb31a900b6641ebbf01c70d139db3f5c75705e939543185dc2778e885aba773c1b217f9534963bf7c2ddafd2403) 
267 bytec 0x04 => (d6def622ae4415ec24b8819aa6d86cadd9c1366783b0259a3bed447b67a87a83) 
269 ed25519verify => (0 0x0) 
270 + => (0 0x0) 
271 txn 0x17 => (bdf1688e1afa7e2bebfd8f6ca84d6b2043950edcf88bb49a32ac52e2c428fcb7) 
273 arg 0x05 => (f8c137934ea4b498bfa8f87b0c426d46fb47dcb31a900b6641ebbf01c70d139db3f5c75705e939543185dc2778e885aba773c1b217f9534963bf7c2ddafd2403) 
275 bytec 0x05 => (a569ce551fc700418dc8e62217c384b1feedadbe7673094778eb20c6a6ed7b0c) 
277 ed25519verify => (0 0x0) 
278 + => (0 0x0) 
279 store 0x01 => <empty stack>
281 txn 0x17 => (bdf1688e1afa7e2bebfd8f6ca84d6b2043950edcf88bb49a32ac52e2c428fcb7) 
283 arg 0x06 => (f8c137934ea4b498bfa8f87b0c426d46fb47dcb31a900b6641ebbf01c70d139db3f5c75705e939543185dc2778e885aba773c1b217f9534963bf7c2ddafd2403) 
285 bytec 0x06 => (f3eee3bf0d798824187c24e60d70590b5325c11a3c88fcefdb5c83eea08a62aa) 
287 ed25519verify => (0 0x0) 
288 store 0x02 => <empty stack>
290 load 0x00 => (1 0x1) 
292 intc_0 => (2 0x2) 
293 >= => (0 0x0) 
294 load 0x01 => (0 0x0) 
296 intc_1 => (1 0x1) 
297 >= => (0 0x0) 
298 load 0x02 => (0 0x0) 
300 && => (0 0x0) 
301 load 0x02 => (0 0x0) 
303 intc_2 => (3 0x3) 
304 >= => (0 0x0) 
305 || => (0 0x0) 
306 && => (0 0x0) 

REJECT

Overall, this will fail to evaluate, but at least we now see a complete run of the program code. Notice the highlighted lines near the top leading up to ed25519verify. That evaluated to true, so arg 0 is a valid signature for the CEO. A few lines lower and you will find the signature check for COO, but it will be false, and for all other signature checks. That’s as expected.

Next, let’s simulate passing this partially signed transaction file another C-Level Exec. goal clerk tealsign will overwrite the signature specified by --set-lsig-arg-idx n at that index position, thus replacing the invalid CEO signature.

# Add the signature from the CFO, then dryrun
${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/CFO --lsig-txn tosign.tx --set-lsig-arg-idx 2
${GOAL_CMD} clerk dryrun --txfile tosign.tx

Partial Result:

tx[0] cost=13343 trace:
  1 intcblock => <empty stack>
  6 bytecblock => <empty stack>
239 txn 0x17 => (bdf1688e1afa7e2bebfd8f6ca84d6b2043950edcf88bb49a32ac52e2c428fcb7) 
241 arg_0 => (f8c137934ea4b498bfa8f87b0c426d46fb47dcb31a900b6641ebbf01c70d139db3f5c75705e939543185dc2778e885aba773c1b217f9534963bf7c2ddafd2403) 
242 bytec_0 => (4c33ba3ebce473aa2e17bf35587c4c7279b1c6676aeec4a12efdba689442398e) 
243 ed25519verify => (1 0x1) 
244 txn 0x17 => (bdf1688e1afa7e2bebfd8f6ca84d6b2043950edcf88bb49a32ac52e2c428fcb7) 
246 arg_1 => (f8c137934ea4b498bfa8f87b0c426d46fb47dcb31a900b6641ebbf01c70d139db3f5c75705e939543185dc2778e885aba773c1b217f9534963bf7c2ddafd2403) 
247 bytec_1 => (b1f66c928cae970ffc419241f579ae340530d0e1c88cac4bf17cc5b3023eb364) 
248 ed25519verify => (0 0x0) 
249 + => (1 0x1) 
250 txn 0x17 => (bdf1688e1afa7e2bebfd8f6ca84d6b2043950edcf88bb49a32ac52e2c428fcb7) 
252 arg_2 => (44b24a60aaa259c4833131e54d585b7e45cf625032707f65089c2b714d00ccd86d2df98990a06482ef9a95f70f81c3c08709a23f1ff8edd716ba16a4aed43c01) 
253 bytec_2 => (09714edc7199e3677755994b38286b11f9e10be0fa5712a596ea99d0aa38f378) 
254 ed25519verify => (1 0x1) 
255 + => (2 0x2) 
256 store 0x00 => <empty stack>
[...]

The CEO and CFO have now signed properly. Look for arg_2 from the CFO and see that it validates. Below that notice that the calculation for C-Level Execs is summed to 2 and stored to scratch space (empties the stack)

I’m going to leave this next code block commented out so that you may add the individual signatures you desire. Just remove the # symbol and execute the line to add a particular signature.

# Keep adding signatures
#${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/CFO --lsig-txn tosign.tx --set-lsig-arg-idx 2
#${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/ACCT1 --lsig-txn tosign.tx --set-lsig-arg-idx 3
#${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/ACCT2 --lsig-txn tosign.tx --set-lsig-arg-idx 4
#${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/ACCT3 --lsig-txn tosign.tx --set-lsig-arg-idx 5
#${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/ACCT_MNGR --lsig-txn tosign.tx --set-lsig-arg-idx 6

Test the evaluation using ${GOAL_CMD} clerk dryrun --txfile tosign.tx, then review the output. Keep adding valid signatures for the proper argument index until you PASS:

tx[0] cost=13343 trace:
  1 intcblock => <empty stack>
  6 bytecblock => <empty stack>
239 txn 0x17 => (8d450db9be2e9b923ffe1a98360e55325e59f85d2e366468eab4215135412cc7) 
241 arg_0 => (8aede83c97785491e53b74c40599dd246a2c91074965cbabea1941585cc2e7735936201579c9e83a0274f5ce9ea41b0f64f02ff4f49933ee49ff29e8b9cebe07) 
242 bytec_0 => (4c33ba3ebce473aa2e17bf35587c4c7279b1c6676aeec4a12efdba689442398e) 
243 ed25519verify => (0 0x0) 
244 txn 0x17 => (8d450db9be2e9b923ffe1a98360e55325e59f85d2e366468eab4215135412cc7) 
246 arg_1 => (8aede83c97785491e53b74c40599dd246a2c91074965cbabea1941585cc2e7735936201579c9e83a0274f5ce9ea41b0f64f02ff4f49933ee49ff29e8b9cebe07) 
247 bytec_1 => (b1f66c928cae970ffc419241f579ae340530d0e1c88cac4bf17cc5b3023eb364) 
248 ed25519verify => (1 0x1) 
249 + => (1 0x1) 
250 txn 0x17 => (8d450db9be2e9b923ffe1a98360e55325e59f85d2e366468eab4215135412cc7) 
252 arg_2 => (acd856a4996ef84cfd123920adf874a2e34ff4a9fccc7ea021c934f6fd2096a26f0ac8ed1a194412283f6f35e62a6b766459cbff7737736e5534169d66bb8c0a) 
253 bytec_2 => (09714edc7199e3677755994b38286b11f9e10be0fa5712a596ea99d0aa38f378) 
254 ed25519verify => (1 0x1) 
255 + => (2 0x2) 
256 store 0x00 => <empty stack>
258 txn 0x17 => (8d450db9be2e9b923ffe1a98360e55325e59f85d2e366468eab4215135412cc7) 
260 arg_3 => (8aede83c97785491e53b74c40599dd246a2c91074965cbabea1941585cc2e7735936201579c9e83a0274f5ce9ea41b0f64f02ff4f49933ee49ff29e8b9cebe07) 
261 bytec_3 => (2ff0d5f30d23cd8ca89120704b3aa9f3d74bbaeeebe1cd6ab94b62117ddefc49) 
262 ed25519verify => (0 0x0) 
263 txn 0x17 => (8d450db9be2e9b923ffe1a98360e55325e59f85d2e366468eab4215135412cc7) 
265 arg 0x04 => (8aede83c97785491e53b74c40599dd246a2c91074965cbabea1941585cc2e7735936201579c9e83a0274f5ce9ea41b0f64f02ff4f49933ee49ff29e8b9cebe07) 
267 bytec 0x04 => (d6def622ae4415ec24b8819aa6d86cadd9c1366783b0259a3bed447b67a87a83) 
269 ed25519verify => (0 0x0) 
270 + => (0 0x0) 
271 txn 0x17 => (8d450db9be2e9b923ffe1a98360e55325e59f85d2e366468eab4215135412cc7) 
273 arg 0x05 => (c4a63f2d93ff7c39ba4468626d3e7f41035c1041081fe07d45e1b64a62c84908a042d018aebc0100faea188cdf67aaac912b10124df5cef3c67b3aa05dee7a0c) 
275 bytec 0x05 => (a569ce551fc700418dc8e62217c384b1feedadbe7673094778eb20c6a6ed7b0c) 
277 ed25519verify => (1 0x1) 
278 + => (1 0x1) 
279 store 0x01 => <empty stack>
281 txn 0x17 => (8d450db9be2e9b923ffe1a98360e55325e59f85d2e366468eab4215135412cc7) 
283 arg 0x06 => (6af41a3dbcefe6cb3fa4975e2d4883f53a94c0fcced5f299344966ef36a1cb66eb15a34508cf0e5e5ab82f9f063c4258eed8bf6672c92e77b010a5fbfa0c060d) 
285 bytec 0x06 => (f3eee3bf0d798824187c24e60d70590b5325c11a3c88fcefdb5c83eea08a62aa) 
287 ed25519verify => (1 0x1) 
288 store 0x02 => <empty stack>
290 load 0x00 => (2 0x2) 
292 intc_0 => (2 0x2) 
293 >= => (1 0x1) 
294 load 0x01 => (1 0x1) 
296 intc_1 => (1 0x1) 
297 >= => (1 0x1) 
298 load 0x02 => (1 0x1) 
300 && => (1 0x1) 
301 load 0x02 => (1 0x1) 
303 intc_2 => (3 0x3) 
304 >= => (0 0x0) 
305 || => (1 0x1) 
306 && => (1 0x1) 

 - pass -
 ```

Now that your contract is passing, you may fund the escrow account and begin using it as the company treasury. Keep those keyfiles secure, as they will be required to approve transaction on the network.

```bash
# TODO: Fund the $CONTRACT account so sending really works, else just continue use `goal clerk dryrun` for testing
${GOAL_CMD} clerk send --from $FUNDED_ACCOUNT --to $CONTRACT --amount $AMOUNT

Ready to broadcast a passing transaction? Ensure the escrow account is funded, then:

${GOAL_CMD} clerk rawsend --filename tosign.tx

You may restart at any point by deleting the partially signed transaction file(s), then build a new one to start again:

rm tosign.tx*
${GOAL_CMD} clerk send --from-program hierarchical-threshold.teal --to $RECIPIENT --amount 100000 --out tosign.tx

There are many combinations of personnel that may participate to approve transactions from this escrow contract account. The TEAL program provides assurance to the organization that only the agreed to personnel may authorize a transaction from the treasury account. It provides role redundancy should a single signer be unavailable or lose their key. In case a single key is lost, a new teal program should be generated defining the replacement key for that slot and the remaining authorized signers should approve closing the legacy contract account to the new contract account.

This article demonstrated how to design and implement a TEAL language smart contract account able to function within a real-world organization. Using hierarchical threshold signature evaluation the contract enforces defined personnel collaborate to authorize transactions. Please provide your feedback on the Algorand Developer Forums and sign up for the Developer Newsletter to get the latest feature updates on Algorand.

smart contracts

logicsig

asc1

ed25519

teal

July 23, 2020