Skip to content

Rekeying accounts

Rekeying is a powerful protocol feature that enables an Algorand account holder to maintain a static public address while dynamically rotating the authoritative private spending key(s). This is accomplished by issuing a transaction with the rekey-to field set the authorized address field within the account object. Future transaction authorization using the account’s public address must be provided by the spending key(s) associated with the authorized address, which may be a single key address, multisignature address, logic signature program address, or post-quantum address. Rekeying an account only affects the authorizing address for that account. An account is distinct from an address, so several essential points may not be obvious:

  • If an account is closed (balance to 0), the rekey setting is lost.
  • Rekeys are not recursively resolved. If A is rekeyed to B and B rekeyed to C, B will authorize A’s transactions, not C.
  • Rekeying members of a multisignature does not affect the multisignature authorization since it’s composed of Addresses, not accounts. If necessary, the multisignature account would need to be rekeyed.

A confirmed rekey-to transaction defines, modifies, or removes the auth-addr field of the account object. Defining or modifying means only the corresponding authorized address’s private spending key(s) may authorize future transactions for this public address. Rekeying an account back to its own address removes the auth-addr field entirely — the protocol never stores an auth-addr equal to the account’s own address.

To provide maximum flexibility in key management options, the auth-addr may be specified within a rekey-to transaction as a distinct foreign address representing a single key address, multisignature address, logic signature program address, or post-quantum address. The protocol does not validate control of the required spending key(s) associated with the authorized address defined by --rekey-to parameter when the rekey-to transaction is sent. This is by design and affords additional privacy features to the new authorized address. It is incumbent upon the user to ensure proper key management practices and --rekey-to assignments.

The balance record of every account includes the auth-addr field, which, when populated, defines the required authorized address to be evaluated during transaction validation. For a standard (non-rekeyed) account, the auth-addr field is empty — signing authority rests with the account’s own address and the private spending key created during account generation. The auth-addr field is only stored and displayed after the network confirms an authorized rekey-to transaction, and it is never stored equal to the account’s own address.

A standard account uses its private spending key to authorize from its public address. A rekeyed account defines the authorized address that references a distinct foreign address and thus requires the private spending key(s) thereof to authorize future transactions.

There are two different AuthAddr fields The auth-addr described above belongs to the account’s balance record, and goal account dump displays it as spend. The ledger is what writes it: a confirmed rekey-to transaction sets or clears it, and closing the account removes it along with the rest of the record.

A signed transaction carries its own, separate authorization field, AuthAddr, encoded as sgnr. It names the address whose spending key authorized that one transaction, and the signer sets it rather than the ledger.

Populate sgnr only when the sender has been rekeyed. When an account authorizes its own transaction, omit sgnr entirely rather than setting it to the sender’s own address. As of consensus v42, the network rejects any transaction whose sgnr is present and equal to its snd. Consensus versions before v42 accepted that redundant value, so tooling that always populates sgnr needs to be updated to omit it for accounts that have not been rekeyed.

Let’s consider a scenario where a single-key account with address A rekeys to a different single-key account with address B. This requires two single key accounts at time t0. The result from time t1 is that transactions for address A must be authorized by address B.

Figure: Rekeying to a Single Address
Figure: Rekeying to a Single Address

Refer to Creating Accounts to generate two accounts and Funding Accounts to fund their addresses using the faucet. This example utilizes the following public addresses:

ADDR_A="UGAGADYHIUGFGRBEPHXRFI6Z73HUFZ25QP32P5FV4H6B3H3DS2JII5ZF3Q"
ADDR_B="LOWE5DE25WOXZB643JSNWPE6MGIJNBLRPU2RBAVUNI4ZU22E3N7PHYYHSY"

Use the following code sample to view initial authorized address for Account A using goal:

goal account dump --address $ADDR_A

Response:

{
"addr": "UGAGADYHIUGFGRBEPHXRFI6Z73HUFZ25QP32P5FV4H6B3H3DS2JII5ZF3Q",
"algo": 100000,
[...]
}

The response includes the addr field, which is the public address. Only the spending key associated with this address may authorize transactions for this account.

Now lets consider another scenario wherein a single key account with public address A rekeys to a multi signature address BC_T1. This scenario reuses both Accounts A and B, adds a third Account C and creates a multisignature Account BC_T1 comprised of addresses B and C with a threshold of 1. The result will be the private spending key for $ADDR_B or $ADDR_C may authorize transaction from $ADDR_A.

Rekey to multisignature Address

To create a new multisignature account, refer to Generate a Multisignature Account. Ensure it uses both $ADDR_B and the new $ADDR_C with a threshold of 1 (so either B or C may authorize). Set the resulting account address to the $ADDR_BC_T1 environment variable for use below.

A rekey-to transaction allows an account holder to change the spending authority of their account without changing the account’s public address. A rekey-to transaction enables an account owner to delegate their spending authority to a different private key while maintaining the same public address. This means the original account can transfer its authorization to sign and approve transactions to a new key without creating a new account or changing the account’s address. The existing authorized address must provide authorization for this transaction.

Account A intends to rekey its authorized address to $ADDR_B, which is the public address of Account B. This can be accomplished in a single goal command:

goal clerk send --from $ADDR_A --to $ADDR_A --amount 0 --rekey-to $ADDR_B

Now, if we view account A using the command:

goal account dump --address $ADDR_A

Response:

{
"addr": "UGAGADYHIUGFGRBEPHXRFI6Z73HUFZ25QP32P5FV4H6B3H3DS2JII5ZF3Q",
"algo": 199000,
[...]
"spend": "LOWE5DE25WOXZB643JSNWPE6MGIJNBLRPU2RBAVUNI4ZU22E3N7PHYYHSY"
}

The populated spend field instructs the validation protocol to only approve transactions for this account object when authorized by that address’s spending key(s). Validators will ignore all other attempted authorizations, including those from the public address defined in the addr field.

The following transaction will fail because, by default, goal attempts to add the authorization using the --from parameter. However, the protocol will reject this because it is expecting the authorization from $ADDR_B due to the confirmed rekeying transaction above.

goal clerk send --from $ADDR_A --to $ADDR_B --amount 100000

The rekey-to transaction workflow is as follows:

  • Construct a transaction that specifies an address for the rekey-to parameter
  • Add the required signature(s) from the current authorized address
  • Send and confirm the transaction on the network

We will construct an unsigned transaction using goal with the --out flag to write the unsigned transaction to a file:

goal clerk send --from $ADDR_A --to $ADDR_B --amount 100000 --out send-single.txn

For multisignature account, the rekey transaction constructed requires authorization from $ADDR_B.

goal clerk send --from $ADDR_A --to $ADDR_A --amount 0 --rekey-to $ADDR_BC_T1 --out rekey-multisig.txn

Next, locate the wallet containing the private spending key for Account B. The goal clerk sign command provides the flag --signer, which specifies the proper required authorized address $ADDR_B. Notice the infile flag reads in the unsigned transaction file from above and the --outfile flag writes the signed transaction to a separate file.

goal clerk sign --signer $ADDR_B --infile send-single.txn --outfile send-single.stxn

Use the following command to sign rekey transaction in multisignature account:

goal clerk sign --signer $ADDR_B --infile rekey-multisig.txn --outfile rekey-multisig.stxn

We will send the signed transaction file using the following command:

goal clerk rawsend --filename send-single.stxn

This will succeed, sending the 100000 microAlgos from $ADDR_A to $ADDR_B using the private spending key of Account B.

Next, send and Confirm Rekey to multisignature account using the following command:

goal clerk rawsend --filename rekey-multisig.stxn
goal account dump --address $ADDR_A

The rekey transaction will confirm, resulting in the spend field update within the account object:

{
"addr": "UGAGADYHIUGFGRBEPHXRFI6Z73HUFZ25QP32P5FV4H6B3H3DS2JII5ZF3Q",
"algo": 199000,
[...]
"spend": "NEWMULTISIGADDRESSBCT1..."
}

Now we will send with Auth BC_T1 using the following command:

goal clerk send --from $ADDR_A --to $ADDR_B --amount 100000 --msig-params="1 $ADDR_B $ADDR_C" --out send-multisig-bct1.txn
goal clerk multisig sign --tx send-multisig-bct1.txn --address $ADDR_C
goal clerk rawsend --filename send-multisig-bct1.txn

This transaction will succeed as a private spending key for $ADDR_C provided the authorization and meets the threshold requirement for the multisignature account.

Rekeying can also be acheived using Algokit Utils. In the following example, account_a is rekeyed to account_b. The code then illustrates that signing a transaction from account_a will fail if signed with account_a’s private key and succeed if signed with account_b’s private key.

/**
* Rekey an account to use a different address for signing.
* This allows account A to be controlled by account B's private key.
*/
await algorand.account.rekeyAccount(randomAccountA, randomAccountB)
// Send a payment transaction from account A
// which will automatically sign the transaction with account B's private key
await algorand.send.payment({
sender: randomAccountA,
receiver: randomAccountC,
amount: algo(1),
})