Skip to main content

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:

  1. Creating the multisig account based on their existing tz4 accounts
  2. Revealing the account and registering it as a delegate
  3. 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:

  1. One participant generates the unsigned operation and shares it with the others.
  2. The participants sign the operation and share their signatures.
  3. One participant combines the participant signatures into a single signature.
  4. 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.

  1. If you don't have three tz4 accounts already, create them as usual with the octez-client gen keys command, but include the -s bls argument to use the BLS signature scheme and create accounts that start with tz4.

    For example, this command creates an account named alice:

    octez-client gen keys alice -s bls
  2. Get the addresses (public key hashes) and public keys of each participant account with the show address command:

    octez-client show address alice
  3. 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.

    note

    Two 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.

  4. 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>" }
  5. 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>
  6. Create the aggregated proof of possession (PoP) for the multisig account by combining proof fragments from the participants:

    1. 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.

    2. 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.

    3. Record the PoP because you will need it later. It is represented later by the placeholder <MULTISIG_POP>.

    note

    The 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.

  1. 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.

  2. 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.

  3. 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
  4. 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>.

    note

    When 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.

  5. 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 replace 023-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.

  6. 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.

  7. In the same way, sign the same bytes as Bob's account and Charlie's account.

  8. 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, not 0x03 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.

  9. 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.

  10. 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.

  11. 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.

  1. If you don't have a consensus key selected already, create one with the octez-client gen keys command and get its address with the octez-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.

  2. 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
  3. 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
  4. 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.

    note

    This 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.

  5. 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
  6. 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, not 0x03 as in a previous command.

    The response is the signature for the operation, which starts with BLsig.

  7. 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.

  8. 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.

  9. 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.

  1. Get the new counter of the multisig account:

    octez-client rpc get chains/main/blocks/head/context/contracts/<MULTISIG_ADDRESS>/counter
  2. 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
  3. 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 the stake entrypoint.

  4. 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
  5. 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, not 0x03 as in a previous command.

  6. 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>" }'
  7. 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.

  8. 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 the get 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 code 0x03.

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 only 03, not 0x03.

  • 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.