Skip to content

Signing Transactions

This section explains how to authorize transactions on the Algorand Network. Transaction signing is a fundamental security feature that proves ownership of an account and authorizes specific actions on the blockchain.

Before a transaction is sent to the network, it must first be authorized by the sender.

There are different transactions signatures to be described in the following sections:

Single Signatures

A single signature corresponds to a signature from the private key of an Algorand public/private key pair.

This is an example of a transaction signed by an Algorand private key displayed with goal clerk inspect command:

{
"sig": "ynA5Hmq+qtMhRVx63pTO2RpDrYiY1wzF/9Rnnlms6NvEQ1ezJI/Ir9nPAT6+u+K8BQ32pplVrj5NTEMZQqy9Dw==",
"txn": {
"amt": 10000000,
"fee": 1000,
"fv": 4694301,
"gen": "testnet-v1.0",
"gh": "SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=",
"lv": 4695301,
"rcv": "QC7XT7QU7X6IHNRJZBR67RBMKCAPH67PCSX4LYH4QKVSQ7DQZ32PG5HSVQ",
"snd": "EW64GC6F24M7NDSC5R3ES4YUVE3ZXXNMARJHDCCCLIHZU6TBEOC7XRSBG4",
"type": "pay"
}
}

This transaction sends 10 Algo from "EW64GC..." to "QC7XT7..." on TestNet. The transaction was signed with the private key that corresponds to the "snd" address of "EW64GC...". The base64 encoded signature is shown as the value of the "sig" field.

How to

The following example will demonstrate how to sign a transaction with an account whiwh originally doesn’t have a signer.

/* When working with account types that do not include a signer,
* you can set the signer using the `setSigner` method.
*/
// Generate an account with no signer. The method returns a secret key and address.
const { sk: accountNoSignerSecretKey, addr: accountNoSignerAddress } = algosdk.generateAccount()
// Ensure the account is funded from the dispenser account
await algorand.account.ensureFunded(accountNoSignerAddress, dispenser, algo(10))
// This payment transaction fails with `Error: No signer found` because the account has no signer
try {
await algorand.send.payment({
sender: accountNoSignerAddress,
receiver: randomAccountA,
amount: algo(1),
note: 'Sending payment with account that has no signer',
})
} catch (e) {
console.log(`Error: ${e}`)
}
// Track the given account for later signing.
// Note: If you are generating accounts via the various methods on AccountManager,
// e.g., `algorand.account.random`, `algorand.account.fromMnemonic`, `algorand.account.logicsig`, etc.,
// then they automatically get tracked.
algorand.account.setSigner(
accountNoSignerAddress,
algosdk.makeBasicAccountTransactionSigner({
sk: accountNoSignerSecretKey,
addr: accountNoSignerAddress,
}),
)
// Now the account is tracked and has the signer set so it can be used to sign transactions
const accountWithSigner = algorand.account.getAccount(accountNoSignerAddress)
// Since we set the signer above, the transaction will be signed by that acccount automatically
// We have included the optional `signer` parameter for clarity
await algorand.send.payment({
sender: accountWithSigner.addr,
receiver: randomAccountA,
amount: algo(1),
signer: accountWithSigner.signer,
note: 'Sending payment with account that has a signer explicitly set',
})

Now let’s dive into an example where the given account has a signer in it.

/**
* When working with account types that include a signer, it can be used to sign transactions.
* There are two ways to sign transactions using the Algorand Client.
*/
//Method 1: Create an unsigned transaction and sign it with the `signer` attribute of the account object.
const unsignedPayTxn = await algorand.createTransaction.payment({
sender: randomAccountA,
receiver: randomAccountB,
amount: algo(1),
note: 'Sending payment with account that has a signer using method 1',
})
await algorand.newGroup().addTransaction(unsignedPayTxn, randomAccountA.signer).send()
// Method 2: Directly send the transaction using the `send` property of the Algorand Client and pass in the signer.
await algorand.send.payment({
sender: randomAccountB,
receiver: randomAccountA,
amount: algo(1),
signer: randomAccountB.signer,
note: 'Sending payment with account that has a signer using method 2',
})
// You can also set a default signer for the Algorand client.
// The default signer, which is now randomAccountA, will be used when no specific signer is provided.
algorand.account.setDefaultSigner(randomAccountA.signer)
// The Algorand Client now knows the default signer and you do not need to pass in the signer.
await algorand.send.payment({
sender: randomAccountA,
receiver: randomAccountB,
amount: algo(1),
note: 'Sending payment with account that has a default signer',
})

Multisignatures

When a transaction’s sender is a multisignature account, authorization requires signatures from multiple private keys. The number of signatures must be equal to or greater than the account’s threshold value.

To sign a multisignature transaction, you need the complete multisignature account details: the list and order of authorized addresses, the threshold value, and the version. This information must be available either in the transaction itself or to the signing agent.

Here is what the same transaction above would look like if sent from a 2/3 multisig account.

{
"msig": {
"subsig": [
{
"pk": "SYGHTA2DR5DYFWJE6D4T34P4AWGCG7JTNMY4VI6EDUVRMX7NG4KTA2WMDA"
},
{
"pk": "VBDMPQACQCH5M6SBXKQXRWQIL7QSR4FH2UI6EYI4RCJSB2T2ZYF2JDHZ2Q"
},
{
"pk": "W3KONPXCGFNUGXGDCOCQYVD64KZOLUMHZ7BNM2ZBK5FSSARRDEXINLYHPI"
}
],
"thr": 2,
"v": 1
},
"txn": {
"amt": 10000000,
"fee": 1000,
"fv": 4694301,
"gen": "testnet-v1.0",
"gh": "SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=",
"lv": 4695301,
"rcv": "QC7XT7QU7X6IHNRJZBR67RBMKCAPH67PCSX4LYH4QKVSQ7DQZ32PG5HSVQ",
"snd": "GQ3QPLJL4VKVGQCHPXT5UZTNZIJAGVJPXUHCJLRWQMFRVL4REVW7LJ3FGY",
"type": "pay"
}
}

The difference between this transaction and the one above is the form of its signature component. For multisignature accounts, an "msig" struct is added which contains the 3 public addresses ("pk"), the threshold value ("thr") and the multisig version "v". Although this transaction is still unsigned, the addition of the correct "msig" struct indicates that the transaction is “aware” of its multisig sender and will accept sub-signatures from single keys even if the signing agent does not contain information about its multisignature properties.

It is highly recommended to include the "msig" template in the transaction. This is especially important when the transaction will be signed by multiple parties or offline. Without the template, the signing agent must already know the multisignature account details. For example, goal can only sign a multisig transaction without an "msig" template if the multisig address exists in its wallet. In this case, goal will add the "msig" template during signing.

Sub-signatures can be added to the transaction one at a time, cumulatively, or merged together from multiple transactions.

Here is the same transaction above, fully authorized:

{
"msig": {
"subsig": [
{
"pk": "SYGHTA2DR5DYFWJE6D4T34P4AWGCG7JTNMY4VI6EDUVRMX7NG4KTA2WMDA",
"s": "xoQkPyyqCPEhodngmOTP2930Y2GgdmhU/YRQaxQXOwh775gyVSlb1NWn70KFRZvZU96cMtq6TXW+r4sK/lXBCQ=="
},
{
"pk": "VBDMPQACQCH5M6SBXKQXRWQIL7QSR4FH2UI6EYI4RCJSB2T2ZYF2JDHZ2Q"
},
{
"pk": "W3KONPXCGFNUGXGDCOCQYVD64KZOLUMHZ7BNM2ZBK5FSSARRDEXINLYHPI",
"s": "p1ynP9+LZSOZCBcrFwt5JZB2F+zqw3qpLMY5vJBN83A+55cXDYp5uz/0b+vC0VKEKw+j+bL2TzKSL6aTESlDDw=="
}
],
"thr": 2,
"v": 1
},
"txn": {
"amt": 10000000,
"fee": 1000,
"fv": 4694301,
"gen": "testnet-v1.0",
"gh": "SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=",
"lv": 4695301,
"rcv": "QC7XT7QU7X6IHNRJZBR67RBMKCAPH67PCSX4LYH4QKVSQ7DQZ32PG5HSVQ",
"snd": "GQ3QPLJL4VKVGQCHPXT5UZTNZIJAGVJPXUHCJLRWQMFRVL4REVW7LJ3FGY",
"type": "pay"
}
}

The two signatures are added underneath their respective addresses. Since this meets the required threshold of 2, the transaction is now fully authorized and can be sent to the network. While adding more sub-signatures than the threshold requires is unnecessary, it is perfectly valid.

How-To

The following code example demonstrates how to execute a transaction signed by a multisig account.

// Create a 2-of-3 multisig account that requires
// only 2 signature from the 3 possible signers to authorize transactions
const multisigAccount = algorand.account.multisig(
{ version: 1, threshold: 2, addrs: [randomAccountA, randomAccountB, randomAccountC] },
[randomAccountA.account, randomAccountB.account, randomAccountC.account],
)
await algorand.account.ensureFunded(multisigAccount, dispenser, algo(10))
// Method 1: Create an unsigned transaction from the multisig account and sign it
// with the `signer` attribute of the multisig account.
const unsignedMultisigPayTxn = await algorand.createTransaction.payment({
sender: multisigAccount,
receiver: randomAccountA,
amount: algo(1),
note: 'Sending payment with multisig account using method 1',
})
await algorand.newGroup().addTransaction(unsignedMultisigPayTxn, multisigAccount.signer).send()
// Method 2: Send a payment transaction from the multisig account
// which will automatically collect the required number of signatures
// from the signing accounts provided when creating the multisig account
await algorand.send.payment({
sender: multisigAccount,
receiver: randomAccountA,
amount: algo(1),
note: 'Sending payment with multisig account using method 2',
})