Part 1: Baking using an aggregated multisig (N-of-N) account
This tutorial walks you through setting up a multisig account to use to stake tez and become a baker. It uses a multisig account with signature aggregation (N-of-N scheme), in which all 3 of the 3 participants must approve of operations taken on behalf of the multisig account. These accounts can be controlled by multiple people sharing control of the account or by a single person who wants the additional security of spreading control of the account over multiple keys. For more information about staking, see Staking.
For simplicity, the multisig account uses a separate consensus key for the baker's consensus operations, which allows the baker to sign blocks and attestations without needing the signatures of the participants. In principle, it would be possible to use a multisig account as the consensus key, but doing so is impractical because bakers sign consensus operations frequently and signing transactions for multisig accounts takes time. This is why the Octez tooling does not support using a multisig account as a consensus key. For more information about baking and consensus keys, see Run a Tezos node in 5 steps.
For the purposes of the tutorial, imagine that three users, Alice, Bob, and Charlie want to use a multisig account to bake together. The tutorial follows these general steps that Alice, Bob, and Charlie must do:
- Creating the multisig account based on their existing
tz4
accounts - Revealing the account and registering it as a delegate
- Staking with the account
Each operation for the account (in this case including revealing, registering as a delegate, staking, and any transfers or smart contract calls) must be signed by all 3 participants. For this reason, each operation involves these sub-steps:
- One participant generates the unsigned operation and shares it with the others.
- The participants sign the operation and share their signatures.
- One participant combines the participant signatures into a single signature.
- That participant signs the operation and submits it to the Tezos network.
For the purposes of the tutorial, the "participants" are different accounts on your local machine, stored in the Octez client's local wallet. In a real situation you can use accounts that are hosted on different systems and managed by different wallets.
If you have any trouble with the commands on this page, see the Troubleshooting section.
Prerequisites
Before you begin, make sure that you have version 23 or later of the Octez client and octez-codec
program installed.
See Installing the Octez client.
Setting up the multisig account
To create a multisig account with signature aggregation, you use two or more existing tz4
accounts to create a multisig with their aggregated keys.
You cannot change the accounts after the multisig account has been created.
-
If you don't have three
tz4
accounts already, create them as usual with theoctez-client gen keys
command, but include the-s bls
argument to use the BLS signature scheme and create accounts that start withtz4
.For example, this command creates an account named
alice
:octez-client gen keys alice -s bls
-
Get the addresses (public key hashes) and public keys of each participant account with the
show address
command:octez-client show address alice
-
Get the proof of possession (PoP) for each account with the
create bls proof
command, as in this example:octez-client create bls proof for alice
This proof is also generated when the account is revealed, and you can see it in the metadata for the reveal operation. This proof is part of how the accounts provide a public certificate witnessing the shared possession of the tz4 account’s secret key.
noteTwo kinds of proofs of possession are used in multisig accounts that use signature aggregation:
-
PoPs for individual accounts, which are generated with the
create bls proof
command and are used to create the aggregated multisig account in the next step. -
PoP fragments of the multisig account, which are generated with the
create bls proof for ... --override-public-key
command. Later, you use these fragments to create the PoP for the multisig account.
-
-
Aggregate the accounts into the multisig account by passing the public keys and proofs for the participant accounts. This example uses placeholders such as
<ALICE_PK>
and<ALICE_POP>
for the public key and PoP for each account:octez-client aggregate bls public keys '[
{
"public_key": "<ALICE_PK>",
"proof": "<ALICE_POP>"
},
{
"public_key": "<BOB_PK>",
"proof": "<BOB_POP>"
},
{
"public_key": "<CHARLIE_PK>",
"proof": "<CHARLIE_POP>"
}
]'The response includes the public key and address (public key hash) of the multisig account:
{ "public_key": "<MULTISIG_PK>",
"public_key_hash": "<MULTISIG_ADDRESS>" } -
Optional: For convenience, add the multisig account to the Octez client with a local alias such as
multisig_aggregate
, using the address from the previous command:octez-client add address multisig_aggregate <MULTISIG_ADDRESS>
-
Create the aggregated proof of possession (PoP) for the multisig account by combining proof fragments from the participants:
-
Create a proof fragment from each participant account by passing the multisig account's public key to the
create bls proof
command, as in this example:octez-client create bls proof for alice --override-public-key <MULTISIG_PK>
The response is the fragment of the multisig account's PoP from that participant.
-
Combine the proof fragments into the PoP for the multisig account by passing them to the
aggregate BLS proofs
command, as in this example:octez-client aggregate bls proofs '{
"public_key": "<MULTISIG_PK>",
"proofs": [
"<ALICE_POP_FRAGMENT>",
"<BOB_POP_FRAGMENT>",
"<CHARLIE_POP_FRAGMENT>"
]
}'The result is the aggregated proof of possession for the multisig account.
The order of the PoPs in this command is not important.
-
Record the PoP because you will need it later. It is represented later by the placeholder
<MULTISIG_POP>
.
noteThe proof of possession in the response is a feature introduced in protocol proposal S that is related to an underlying change in the usage of BLS in the Tezos protocol necessary to implement both these features and aggregated attestations. For more information, start at tz4: BLS in the Octez and protocol documentation.
-
Now you have a single account controlled by three accounts, none of which can control it independently. In the next section, you use these participant accounts reveal the account and register it as a delegate so it can get baking rights.
Revealing the account and registering as a delegate
Multisig accounts must be revealed and delegated to stake, just like other accounts. However, the process is different, because (as explained at the top of this page) running any operation with the multisig account involves signing the operation with each of the participants. In this section you sign an operation to reveal the account and register as a delegate.
-
Set your installation of the Octez client to use a test network. For example, to use the Ghostnet testnet RPC endpoint at https://rpc.ghostnet.teztnets.com, run this command:
octez-client -E https://rpc.ghostnet.teztnets.com config update
For more information about test networks and other RPC endpoints, see Testing on testnets.
-
From the faucet for the testnet, send at least 6,005 tez to the multisig account's address. The account must have some tez to be revealed and to pay for transaction fees on top of the 6,000 tez that you will stake later.
-
Get the counter of the multisig account by running this command, where
<MULTISIG_ADDRESS>
is the address of the multisig account:octez-client rpc get chains/main/blocks/head/context/contracts/<MULTISIG_ADDRESS>/counter
-
Get the hash of a recent block to act as the branch to base the operation on by running this command:
octez-client rpc get chains/main/blocks/head~2/hash
The resulting hash is referred to in future steps by the placeholder
<BRANCH>
.noteWhen you submit the signed operation, this branch must be a sufficiently recent block or the operation fails. The constant
max_operations_time_to_live
determines how recent the branch must be. For example, on Mainnet, this constant is 450 blocks, which gives you about one hour to get the operation signed by enough participants and submitted.To get the value of this constant on the network you are using, run this command, which requires the
jq
program:octez-client rpc get /chains/main/blocks/head/context/constants | jq | grep max_operations_time_to_live
Then, multiply the value of this constant by the value of the
minimal_block_delay
constant to get the time duration in seconds. -
Generate the operation to reveal the account:
octez-codec encode 023-PtSeouLo.operation.unsigned from '{
"branch": "<BRANCH>",
"contents":
[ { "kind": "reveal",
"source": "<MULTISIG_ADDRESS>", "fee": "735",
"counter": "<COUNTER_PLUS_1>", "gas_limit": "3251", "storage_limit": "0",
"public_key": "<MULTISIG_PK>",
"proof": "<MULTISIG_POP>" } ] }'This command uses values from previous steps, including:
- The address and public key of the multisig account (
<MULTISIG_ADDRESS>
and<MULTISIG_PK>
) - The PoP that was created for the account (
<MULTISIG_POP>
) - The branch (
<BRANCH>
) - The next counter value, which you can get by adding 1 to the current counter value from a previous step (
<COUNTER_PLUS_1>
)
It passes these values to the
octez-codec
program, which encodes them in binary based on the Seoul protocol. You can replace023-PtSeouLo.operation.unsigned
with the schema for the protocol that you want to use; see Encodings in the Octez and protocol documentation.The result is a string of bytes that represents the unsigned operation. These are the bytes that the participant accounts must sign to authorize the operation.
- The address and public key of the multisig account (
-
Sign the operation as Alice by passing these bytes to the
sign bytes
command, which uses<UNSIGNED_BYTES>
as a placeholder for the response from the previous command:octez-client sign bytes "0x03<UNSIGNED_BYTES>" for alice
Note that this command prefixes the bytes with
0x03
because it is an encoded manager operation.The response is Alice's signature of the transaction.
-
In the same way, sign the same bytes as Bob's account and Charlie's account.
-
Combine the signatures to fully sign the operation by running this command, which uses the placeholders
<ALICE_SIG>
,<BOB_SIG>
, and<CHARLIE_SIG>
to represent their signatures:octez-client aggregate BLS signatures '{
"public_key": "<MULTISIG_PK>",
"message": "03<UNSIGNED_BYTES>",
"signature_shares": [
"<ALICE_SIG>",
"<BOB_SIG>",
"<CHARLIE_SIG>"
]
}'Note that the unsigned operation bytes are prefixed with
03
, not0x03
as in a previous command. The order of the signatures is not important.The response is the signature for the operation, which starts with
BLsig
. -
Sign the operation by running this command, which uses the same JSON parameter as the command to generate the operation with the addition of the
signature
field:octez-codec encode 023-PtSeouLo.operation from '{
"branch": "<BRANCH>",
"contents":
[ { "kind": "reveal",
"source": "<MULTISIG_ADDRESS>", "fee": "735",
"counter": "<COUNTER_PLUS_1>", "gas_limit": "3251", "storage_limit": "0",
"public_key": "<MULTISIG_PK>",
"proof": "<MULTISIG_POP>" } ],
"signature": "<COMBINED_SIGNATURE>" }'The value of the
signature
field, represented by the placeholder<COMBINED_SIGNATURE>
, is the output of the previous command.The response is the signed operation as a series of bytes.
-
Submit the operation by passing it to this command:
octez-client rpc post /injection/operation with '"<SIGNED_OPERATION>"'
Note that the signed operation is within both single and double quotes.
The response is the hash of the submitted operation.
-
Check the result of the operation by looking it up in the block explorer or passing its hash to the
get receipt
command:octez-client get receipt for <OPERATION_HASH>
The receipt notes that the operation was signed by the multisig account itself, not by the individual participants. Here is an example receipt:
$ octez-client get receipt for oniCMt7XKe9BEsKVz4Yw8YSSNoDrZmRNdR1d2pWLyqNBt7kseCR
Warning:
This is NOT the Tezos Mainnet.
Do NOT use your fundraiser keys on this network.
Operation found in block: BL5NWAr6MuF1pYDN6p153wFab8SLWmugf7HgGnFLpkGR5XdN88W (pass: 3, offset: 0)
Manager signed operations:
From: tz4EoQW3jWNvn2q4kVQawdnCovmnGxJAofeG
Fee to the baker: ꜩ0.00145
Expected counter: 679669
Gas limit: 5300
Storage limit: 0 bytes
Balance updates:
tz4EoQW3jWNvn2q4kVQawdnCovmnGxJAofeG ... -ꜩ0.00145
payload fees(the block proposer) ....... +ꜩ0.00145
Revelation of manager public key:
Contract: tz4EoQW3jWNvn2q4kVQawdnCovmnGxJAofeG
Key: BLpk1r3rPXZF9w4NohXaPvcR2hEaBEXckVFv2ek6mQdnxr6oxyAdngJED2ueebUHmA6voita4kof
Proof of possession: BLsigBUQMG8ZXgH3UA87hTYQ43NctfJk7rA3vEq2uuYQ8Qe7PPgfCqLpJdWxNYzBi1Dxx5xPnwF2uZPFYd4ZmjwmJh8rZGAikymyzYvnhLYE5L1huycuPVgWpLhSEdC1onbJQWCkvbW2YT
This revelation was successfully applied
Consumed gas: 3249.100
Registering as a delegate
Now you can set up the multisig account as a delegate with a consensus key. As with the reveal operation, all participants must sign the operation.
-
If you don't have a consensus key selected already, create one with the
octez-client gen keys
command and get its address with theoctez-client show address
command. This key is not required to be a BLS key like the participant accounts.The following steps use the placeholder
<CONSENSUS_PK>
as the public key of the consensus key. -
Get the counter of the multisig account by running this command, where
<MULTISIG_ADDRESS>
is the address of the multisig account:octez-client rpc get chains/main/blocks/head/context/contracts/<MULTISIG_ADDRESS>/counter
-
Get the hash of a recent block to act as the branch to base the operation on by running this command:
octez-client rpc get chains/main/blocks/head~2/hash
-
As you did in the previous section, generate the operation. You could do the registration and consensus key in separate operations, but doing it in one step illustrates how you can batch operations:
octez-codec encode 023-PtSeouLo.operation.unsigned from '{
"branch": "<BRANCH>",
"contents":
[ { "kind": "delegation",
"source": "<MULTISIG_ADDRESS>", "fee": "449",
"counter": "<COUNTER_PLUS_1>", "gas_limit": "1676", "storage_limit": "0",
"delegate": "<MULTISIG_ADDRESS>" },
{ "kind": "update_consensus_key",
"source": "<MULTISIG_ADDRESS>", "fee": "183",
"counter": "<COUNTER_PLUS_2>", "gas_limit": "200", "storage_limit": "0",
"pk": "<CONSENSUS_PK>" } ] }'This command uses values from previous steps, including:
- The address the multisig account (
<MULTISIG_ADDRESS>
) - The branch (
<BRANCH>
) - The next two counter values, which you can get by adding 1 and 2 to the current counter value from a previous step (
<COUNTER_PLUS_1>
and<COUNTER_PLUS_2>
) - The public key of the consensus key (
<CONSENSUS_PK>
)
The result is a string of bytes that represents the unsigned operation.
noteThis batched operation is equivalent to the usual
octez-client register key
command to set up a non-multisig account as a delegate with a consensus key. Of course, this command doesn't work with multisig accounts because it can't get signatures from all of the participant accounts. - The address the multisig account (
-
As you did in the previous section, sign the bytes as the participant accounts, prefixing the bytes with
0x03
, as in this example:octez-client sign bytes "0x03<UNSIGNED_BYTES>" for alice
-
Combine the signatures to fully sign the operation by running this command, which uses the placeholders
<ALICE_SIG>
,<BOB_SIG>
, and<CHARLIE_SIG>
to represent their signatures:octez-client aggregate BLS signatures '{
"public_key": "<MULTISIG_PK>",
"message": "03<UNSIGNED_BYTES>",
"signature_shares": [
"<ALICE_SIG>",
"<BOB_SIG>",
"<CHARLIE_SIG>"
]
}'Note that the unsigned operation bytes are prefixed with
03
, not0x03
as in a previous command.The response is the signature for the operation, which starts with
BLsig
. -
Sign the operation by running this command, which uses the same JSON parameter as the command to generate the operation with the addition of the
signature
field:octez-codec encode 023-PtSeouLo.operation from '{
"branch": "<BRANCH>",
"contents":
[ { "kind": "delegation",
"source": "<MULTISIG_ADDRESS>", "fee": "449",
"counter": "<COUNTER_PLUS_1>", "gas_limit": "1676", "storage_limit": "0",
"delegate": "<MULTISIG_ADDRESS>" },
{ "kind": "update_consensus_key",
"source": "<MULTISIG_ADDRESS>", "fee": "183",
"counter": "<COUNTER_PLUS_2>", "gas_limit": "200", "storage_limit": "0",
"pk": "<MULTISIG_PK>" } ],
"signature": "<COMBINED_SIGNATURE>" }'The value of the
signature
field, represented by the placeholder<COMBINED_SIGNATURE>
, is the output of the previous command.The response is the signed operation as a series of bytes.
-
Submit the operation by passing it to this command:
octez-client rpc post /injection/operation with '"<SIGNED_OPERATION>"'
The response is the hash of the submitted operation.
-
Check the result of the operation by looking it up in the block explorer or passing its hash to the
get receipt
command:octez-client get receipt for <OPERATION_HASH>
Now the account is registered as a delegate and can be a baker. To bake, it needs to stake tez, which you do in the next section.
Staking with the multisig account
Signing the staking operation is similar to signing the other operations with the account.
-
Get the new counter of the multisig account:
octez-client rpc get chains/main/blocks/head/context/contracts/<MULTISIG_ADDRESS>/counter
-
Get the hash of a recent block to act as the branch to base the operation on by running this command:
octez-client rpc get chains/main/blocks/head~2/hash
-
Like you did in the previous section, generate the operation to stake with the account:
octez-codec encode 023-PtSeouLo.operation.unsigned from '{
"branch": "<BRANCH>",
"contents":
[ { "kind": "transaction",
"source": "<MULTISIG_ADDRESS>", "fee": "808",
"counter": "<COUNTER_PLUS_1>", "gas_limit": "5134", "storage_limit": "0",
"amount": "<AMOUNT_TO_STAKE>",
"destination": "<MULTISIG_ADDRESS>",
"parameters":
{ "entrypoint": "stake", "value": { "prim": "Unit" } } } ] }'This command includes the counter value plus one and the amount to stake in mutez, in this case
6000000000
for 6,000 tez, the minimum amount that bakers must stake. The stake operation is implemented internally as a transfer from the account to itself via thestake
entrypoint. -
Sign the operation as each account by passing the operation bytes to the
sign bytes
command:octez-client sign bytes "0x03<UNSIGNED_BYTES>" for alice
-
Combine the signatures to fully sign the operation by running this command, which uses the placeholders
<ALICE_SIG>
,<BOB_SIG>
, and<CHARLIE_SIG>
to represent their signatures:octez-client aggregate BLS signatures '{
"public_key": "<MULTISIG_PK>",
"message": "03<UNSIGNED_BYTES>",
"signature_shares": [
"<ALICE_SIG>",
"<BOB_SIG>",
"<CHARLIE_SIG>"
]
}'Note that the unsigned operation bytes are prefixed with
03
, not0x03
as in a previous command. -
Sign the operation by running this command, which uses the same JSON parameter as the command to generate the operation with the addition of the
signature
field:octez-codec encode 023-PtSeouLo.operation from '{
"branch": "<BRANCH>",
"contents":
[ { "kind": "transaction",
"source": "<MULTISIG_ADDRESS>", "fee": "808",
"counter": "<COUNTER_PLUS_1>", "gas_limit": "5134", "storage_limit": "0",
"amount": "<AMOUNT_TO_STAKE>",
"destination": "<MULTISIG_ADDRESS>",
"parameters":
{ "entrypoint": "stake", "value": { "prim": "Unit" } } }],
"signature": "<COMBINED_SIGNATURE>" }' -
Submit the operation by passing it to this command:
octez-client rpc post /injection/operation with '"<SIGNED_OPERATION>"'
The response is the hash of the submitted operation.
-
Check the result of the operation by looking it up in the block explorer, checking your staked balance with the
octez-client get staked balance for
command, or passing its hash to theget receipt
command:octez-client get receipt for <OPERATION_HASH>
Now the tez is staked and the account can become a baker. When it's time to unstake the tez, the participant accounts must create an unstaking operation and sign it as they signed the other operations. Similarly, they must co-sign any transfers or smart contract calls from this account.
To become a baker, see the tutorial Run a Tezos node in 5 steps.
Troubleshooting
Errors with the octez-client sign bytes
command:
- If this command throws the error
Invalid bytes, expecting hexadecimal notation
, ensure that the bytes to be signed are prefixed with the code0x03
.
Errors with the octez-client aggregate bls signatures
command:
-
If this command failed to produce the signature, make sure that the
message
field is prefixed with only03
, not0x03
. -
Make sure you signed with the correct participant accounts and enough participant accounts.
Errors with the command octez-client rpc post /injection/operation with '"<SIGNED_OPERATION>"'
:
-
If this command throws an error that says that the operation failed because it is branched on a block that is too old, the branch that you based the operation on is out of date. You may need to complete these steps more quickly or automate them to complete them in time. You can calculate the allowable time by checking the time to live, as described in Revealing and delegating the multisig account.
-
If the error says that the operation signature is invalid, make sure that the contents of the operations that you passed to the
octez-codec
commands match. -
If the error says that the counter is already used, make sure that you have added one to the current counter value.
The octez-client rpc post /injection/operation
command returns an operation hash but this hash does not exist on the block explorer and the octez-client get receipt for
command fails:
-
Check that the source account has enough tez to pay the fees.
-
The
fee
field for the operation may be too low.
Summary
In this tutorial, starting from existing tz4
accounts, you created a tz4
multisig account with its own public key, address, and PoP.
No one knows the private key for the account, so no one person can control it.
Instead, each subsequent operation must be signed by all participants and aggregated by one participant.
The multi-signature scheme ensures that all participants agree on any change in the configuration of the baker. However, the baker executable automatically signs routine consensus operations using another consensus key, that is not subject to the above agreement procedure, which ensures efficiency and seamless execution.
From here, you can use the account much as an ordinary user account, but with the enhanced security of requiring multiple signatures to create operations.