Skip to main content

Signing a Transaction

Signing a transaction for Flow is a multi-step process that can involve one or more accounts, each of which signs for a different purpose.

Signer Roles

  • Proposer: the account that specifies a proposal key.
  • Payer: the account paying for the transaction fees.
  • Authorizers: zero or more accounts authorizing the transaction to mutate their state.

Proposal Key

Each transaction must declare a proposal key, which can be an account key from any Flow account. The account that owns the proposal key is referred to as the proposer.

A proposal key definition declares the address, key ID, and up-to-date sequence number for the account key.


_10
{
_10
// other transaction fields
_10
// ...
_10
"proposalKey": {
_10
"address": "0x01",
_10
"keyId": 7,
_10
"sequenceNumber": 42
_10
}
_10
}

Sequence Numbers

Flow uses sequence numbers to ensure that each transaction executes at most once. This prevents many unwanted situations such as transaction replay attacks.

Sequence numbers work similarly to transaction nonces in Ethereum, but with several key differences:

  • Each key in an account has a dedicated sequence number associated with it. Unlike Ethereum, there is no sequence number for the entire account.
  • When creating a transaction, only the proposer must specify a sequence number. Payers and authorizers are not required to.

The transaction proposer is only required to specify a sequence number for a single account key, even if it signs with multiple keys. This key is referred to as the proposal key.

Each time an account key is used as a proposal key, its sequence number is incremented by 1. The sequence number is updated after execution, even if the transaction fails (reverts) during execution.

A transaction is failed if its proposal key does not specify a sequence number equal to the sequence number stored on the account at execution time.

Example

After the below transaction is executed, the sequence number for Key 7 on Account 0x01 will increase to 43.


_11
{
_11
// other transaction fields
_11
// ...
_11
"proposalKey": {
_11
"address": "0x01",
_11
"keyId": 7,
_11
"sequenceNumber": 42
_11
},
_11
"payer": "0x02",
_11
"authorizers": [ "0x01" ],
_11
}

Anatomy of a Transaction

Due to the existence of weighted keys and split signing roles, Flow transactions sometimes need to be signed multiple times by one or more parties. That is, multiple unique signatures may be needed to authorize a single transaction.

A transaction can contain two types of signatures: payload signatures and envelope signatures.

Transaction Anatomy

Payload

The transaction payload is the innermost portion of a transaction and contains the data that uniquely identifies the operations applied by the transaction. In Flow, two transactions with the same payload will never be executed more than once.

The transaction proposer and authorizer are only required to sign the transaction payload. These signatures are the payload signatures.

Authorization Envelope

The transaction authorization envelope contains both the transaction payload and the payload signatures.

The transaction payer is required to sign the authorization envelope. These signatures are the envelope signatures.

❗ Special case: if an account is both the payer and either a proposer or authorizer, it is required only to sign the envelope.

Payment Envelope

The outermost portion of the transaction, which contains the payload and envelope signatures, is referred to as the payment envelope.

Payer Signs Last

The payer must sign the portion of the transaction that contains the payload signatures, which means that the payer must always sign last. This allows the payer to ensure that they are signing a valid transaction with all of the required payload signatures.

Signature Structure

A transaction signature is a composite structure containing three fields:

  • Address
  • Key ID
  • Signature Data

The address and key ID fields declare the account key that generated the signature, which is required in order to verify the signature against the correct public key.

Common Signing Scenarios

Below are several scenarios in which different signature combinations are required to authorize a transaction.

Single party, single signature

The simplest Flow transaction declares a single account as the proposer, payer and authorizer. In this case, the account can sign the transaction with a single signature.

This scenario is only possible if the signature is generated by a key with full signing weight.

AccountKey IDWeight
0x0111000

_19
{
_19
"payload": {
_19
"proposalKey": {
_19
"address": "0x01",
_19
"keyId": 1,
_19
"sequenceNumber": 42
_19
},
_19
"payer": "0x01",
_19
"authorizers": [ "0x01" ]
_19
},
_19
"payloadSignatures": [], // 0x01 is the payer, so only needs to sign envelope
_19
"envelopeSignatures": [
_19
{
_19
"address": "0x01",
_19
"keyId": 1,
_19
"sig": "0xabc123"
_19
}
_19
]
_19
}

Single party, multiple signatures

A transaction that declares a single account as the proposer, payer and authorizer may still specify multiple signatures if the account uses weighted keys to achieve multi-sig functionality.

AccountKey IDWeight
0x011500
0x012500

_24
{
_24
"payload": {
_24
"proposalKey": {
_24
"address": "0x01",
_24
"keyId": 1,
_24
"sequenceNumber": 42
_24
},
_24
"payer": "0x01",
_24
"authorizers": [ "0x01" ]
_24
},
_24
"payloadSignatures": [], // 0x01 is the payer, so only needs to sign envelope
_24
"envelopeSignatures": [
_24
{
_24
"address": "0x01",
_24
"keyId": 1,
_24
"sig": "0xabc123"
_24
},
_24
{
_24
"address": "0x01",
_24
"keyId": 2,
_24
"sig": "0xdef456"
_24
}
_24
]
_24
}

Multiple parties

A transaction that declares different accounts for each signing role will require at least one signature from each account.

AccountKey IDWeight
0x0111000
0x0211000

_25
{
_25
"payload": {
_25
"proposalKey": {
_25
"address": "0x01",
_25
"keyId": 1,
_25
"sequenceNumber": 42
_25
},
_25
"payer": "0x02",
_25
"authorizers": [ "0x01" ]
_25
},
_25
"payloadSignatures": [
_25
{
_25
"address": "0x01", // 0x01 is not payer, so only signs payload
_25
"keyId": 1,
_25
"sig": "0xabc123"
_25
}
_25
],
_25
"envelopeSignatures": [
_25
{
_25
"address": "0x02",
_25
"keyId": 1,
_25
"sig": "0xdef456"
_25
},
_25
]
_25
}

Multiple parties, multiple signatures

A transaction that declares different accounts for each signing role may require more than one signature per account if those accounts use weighted keys to achieve multi-sig functionality.

AccountKey IDWeight
0x011500
0x012500
0x021500
0x022500

_35
{
_35
"payload": {
_35
"proposalKey": {
_35
"address": "0x01",
_35
"keyId": 1,
_35
"sequenceNumber": 42
_35
},
_35
"payer": "0x02",
_35
"authorizers": [ "0x01" ]
_35
},
_35
"payloadSignatures": [
_35
{
_35
"address": "0x01", // 0x01 is not payer, so only signs payload
_35
"keyId": 1,
_35
"sig": "0xabc123"
_35
},
_35
{
_35
"address": "0x01", // 0x01 is not payer, so only signs payload
_35
"keyId": 2,
_35
"sig": "0x123abc"
_35
}
_35
],
_35
"envelopeSignatures": [
_35
{
_35
"address": "0x02",
_35
"keyId": 1,
_35
"sig": "0xdef456"
_35
},
_35
{
_35
"address": "0x02",
_35
"keyId": 2,
_35
"sig": "0x456def"
_35
},
_35
]
_35
}

Thanks for reading and happy hacking! 🚀