Making a Shelley blockchain from scratch

Last validated: 2020/05/10

Preliminaries

This assumes that you already have built and installed the executables from this repository. So cardano-cli and cardano-node should be on your $PATH.

We also assume a Linux system, though it should work fine on OSX too.

$ cardano-cli version
cardano-cli 1.11.0 - linux-x86_64 - ghc-8.6

Everything we’ll be doing uses the shelley sub-command

$ cardano-cli shelley
Usage: cardano-cli shelley COMMAND
  Shelley specific commands

Available options:
  -h,--help                Show this help text

Available commands:
  address                  Shelley address commands
  stake-address            Shelley stake address commands
  transaction              Shelley transaction commands
  node                     Shelley node operaton commands
  stake-pool               Shelley stake pool commands
  query                    Shelley node query commands
  block                    Shelley block commands
  system                   Shelley system commands
  governance               Shelley governance commands
  genesis                  Shelley genesis block commands

We’ll put all files under an example directory.

Making a genesis file manually

To start a new blockchain we of course need a genesis file. A Shelley genesis file is JSON file.

There is a manual method and a semi-automagic method, but we’ll start by explaining the manual method since that makes it easier to explain what the things are and what they are for. So read this section even if you want to use the automagic method.

So when doing it for real, we have to use the manual method since the different steps are run by different people in different locations.

A real chain should use several genesis keys, and they should be created separately by the members of the federation bootstrapping the system. They should be created offline, kept offline and only the verification keys shared.

For a demo we’re going to put all the keys together in once place

To start with we will set up our template directory:

$ cardano-cli shelley genesis create --genesis-dir example/

This gives us

$ ls example/*
example/genesis.json  example/genesis.spec.json

example/delegate-keys:

example/genesis-keys:

example/utxo-keys:

Note that it created both a genesis.spec.json and a genesis.json. This command can be re-run at any time and it will re-generate the genesis.json based on the genesis.spec.json (which we can edit by hand) and any keys placed in the three sub-directories.

Our next steps will be to create the various keys, adjust the genesis.spec.json to our liking and re-generate the genesis.json.

Genesis keys

Shelley supports decentralised block production but not yet decentralised governance, so we still have genesis keys with special governance powers.

So the first step will be to make the genesis keys.

$ cardano-cli shelley genesis key-gen-genesis
Usage: cardano-cli shelley genesis key-gen-genesis --verification-key-file FILEPATH
                                                   --signing-key-file FILEPATH
  Create a Shelley genesis key pair

Available options:
  --verification-key-file FILEPATH
                           Output filepath of the verification key.
  --signing-key-file FILEPATH
                           Output filepath of the signing key.

So let’s make two example genesis key pairs

$ cardano-cli shelley genesis key-gen-genesis \
    --verification-key-file example/genesis-keys/genesis1.vkey \
    --signing-key-file example/genesis-keys/genesis1.skey
$ cardano-cli shelley genesis key-gen-genesis \
    --verification-key-file example/genesis-keys/genesis2.vkey \
    --signing-key-file example/genesis-keys/genesis2.skey

Semi-readable file formats

You can look at these files, they are semi-readable

$ cat example/genesis-keys/genesis1.vkey
type: Genesis verification key
title: Genesis key
cbor-hex:
 582066361ff2e1b5fdf25bc14bc48424b64fa62cee7692ed7e86216eafd3bdb28cbd

The “type” must not be edited. This is used as a sanity check. The “title” field is free-form and you can use it for whatever purpose you like, such as identifying different keys. Don’t edit the binary data of course, but you can inspect it using any CBOR tool, e.g. cbor.me or the cardano-cli itself:

cardano-cli shelley text-view decode-cbor
Usage: cardano-cli shelley text-view decode-cbor --file FILENAME
  Print a TextView file as decoded CBOR.

Available options:
  --file FILENAME          Input file.

Like so

$ cardano-cli shelley text-view decode-cbor \
    --file example/genesis-keys/genesis1.vkey

58 20 46 4e f4 95 59 f4 e3 6f b7 02 1f cb 12 71
c5 ba 84 f3 66 22 0a 15 0e 66 bb a8 71 87 2f 27
7c ed  # bytes(32)

So we can see this is just a 32 byte string. Not surprising, since this is of course just an ed25519 verification key.

Genesis delegate keys

When we start a Shelley blockchain it will not be in decentralised block production mode. Initially all blocks will be created by designated genesis delegate nodes in the BFT overlay schedule. These genesis delegate nodes are similar to stake pool nodes (but take part in the BFT overlay and don’t get rewards). The genesis file contains a special mapping from genesis keys to genesis delegate keys.

So we need to make genesis delegate keys, as many as you made genesis keys (just two in our example).

$ cardano-cli shelley genesis key-gen-delegate
Usage: cardano-cli shelley genesis key-gen-delegate --verification-key-file FILEPATH
                                                    --signing-key-file FILEPATH
                                                    --operational-certificate-issue-counter FILE
  Create a Shelley genesis delegate key pair

Available options:
  --verification-key-file FILEPATH
                           Output filepath of the verification key.
  --signing-key-file FILEPATH
                           Output filepath of the signing key.
  --operational-certificate-issue-counter FILE
                           The file with the issue counter for the operational
                           certificate.

Much the same as for genesis keys, but there is an additional output, the operational certificate issue counter. We will talk about this later.

Let’s make two genesis delegate key pairs, to use with our two genesis keys

$ cardano-cli shelley genesis key-gen-delegate \
    --verification-key-file example/delegate-keys/delegate1.vkey \
    --signing-key-file example/delegate-keys/delegate1.skey \
    --operational-certificate-issue-counter example/delegate-keys/delegate-opcert1.counter
$ cardano-cli shelley genesis key-gen-delegate \
    --verification-key-file example/delegate-keys/delegate2.vkey \
    --signing-key-file example/delegate-keys/delegate2.skey \
    --operational-certificate-issue-counter example/delegate-keys/delegate-opcert2.counter

Let’s see what’s in that counter file

$ cat example/delegate-keys/delegate-opcert1.counter
type: Node operational certificate issue counter
title: Next certificate issue number: 0
cbor-hex:
 00

Yes, we count from zero. We will talk about what this counter is for later.

Initial UTxO

We need to start the system with some money or it will be very boring. The genesis file can list number of initial addresses and values, but we need keys for those addresses and later to sign transactions to spend the initial UTxO values.

So we need to make genesis initial UTxO keys.

$ cardano-cli shelley genesis key-gen-utxo
Usage: cardano-cli shelley genesis key-gen-utxo --verification-key-file FILEPATH
                                                --signing-key-file FILEPATH
  Create a Shelley genesis UTxO key pair

Available options:
  --verification-key-file FILEPATH
                           Output filepath of the verification key.
  --signing-key-file FILEPATH
                           Output filepath of the signing key.

We can make as many as is useful. Let’s make two.

$ cardano-cli shelley genesis key-gen-utxo \
    --verification-key-file example/utxo-keys/utxo1.vkey \
    --signing-key-file example/utxo-keys/utxo1.skey
$ cardano-cli shelley genesis key-gen-utxo \
    --verification-key-file example/utxo-keys/utxo2.vkey \
    --signing-key-file example/utxo-keys/utxo2.skey

The genesis file itself

When we set up our template using the create command, it generated an example genesis template for us in example/genesis.spec.json:

{
    "activeSlotsCoeff": 5.0e-2,
    "protocolParams": {
        "poolDecayRate": 0,
        "poolDeposit": 0,
        "protocolVersion": {
            "minor": 0,
            "major": 0
        },
        "decentralisationParam": 1,
        "maxTxSize": 16384,
        "minFeeA": 0,
        "maxBlockBodySize": 65536,
        "keyMinRefund": 0,
        "minFeeB": 0,
        "eMax": 0,
        "extraEntropy": {
            "tag": "NeutralNonce"
        },
        "maxBlockHeaderSize": 1000,
        "keyDeposit": 0,
        "keyDecayRate": 0,
        "nOpt": 100,
        "rho": 0,
        "poolMinRefund": 0,
        "tau": 0,
        "a0": 0
    },
    "startTime": "1970-01-01T00:00:00Z",
    "genDelegs": {},
    "updateQuorum": 5,
    "maxMajorPV": 1,
    "initialFunds": {},
    "maxLovelaceSupply": 0,
    "networkMagic": 42,
    "epochLength": 432000,
    "staking": null,
    "slotsPerKESPeriod": 129600,
    "slotLength": 1,
    "maxKESEvolutions": 60,
    "securityParam": 2160
}

TODO: the generated file puts the fields in an unhelpful order.

We will mostly use these defaults for this demo. The meaning of all the ones we do not edit here will be covered elsewhere.

When we regenerate the genesis file it will fill in the:

  • genDelegs

  • initialFunds

  • startTime

  • and optionally it can override the maxLovelaceSupply

Let’s regenerate the genesis file (note, this command does not set an initial Lovelace supply, that will be done later)

$ cardano-cli shelley genesis create --genesis-dir example/

and then look at it and understand what the command has done

$ cat example/genesis.json
{
    "activeSlotsCoeff": 5.0e-2,
    "protocolParams": {
        "poolDecayRate": 0,
        "poolDeposit": 0,
        "protocolVersion": {
            "minor": 0,
            "major": 0
        },
        "decentralisationParam": 1,
        "maxTxSize": 16384,
        "minFeeA": 0,
        "maxBlockBodySize": 65536,
        "keyMinRefund": 0,
        "minFeeB": 0,
        "eMax": 0,
        "extraEntropy": {
            "tag": "NeutralNonce"
        },
        "maxBlockHeaderSize": 1000,
        "keyDeposit": 0,
        "keyDecayRate": 0,
        "nOpt": 100,
        "rho": 0,
        "poolMinRefund": 0,
        "tau": 0,
        "a0": 0
    },
    "startTime": "2020-05-10T16:28:12.17999965Z",
    "genDelegs": {
        "a4d927a8e50c7a51e0f7d41a75057073cd2fc49bfc87a44891a8a9f80800cd8a":
          "b2ee836b2b92fd3dd5e4228c943d8854e673c0510983956ce4f4ea7afcd9f761"
    },
    "updateQuorum": 5,
    "maxMajorPV": 1,
    "initialFunds": {
        "820658207c3c942eaa39dd0aec74415312cf80dfc92b34fa1d0e1c2fd2499ee105219305": 0
        "8206582076cb9794a896ee640dcb54bd932f3a0ca6012aa99cb7d767aec9f4d717c88d7b": 0
    },
    "maxLovelaceSupply": 0,
    "networkMagic": 42,
    "epochLength": 432000,
    "staking": null,
    "slotsPerKESPeriod": 129600,
    "slotLength": 1,
    "maxKESEvolutions": 60,
    "securityParam": 2160
}

The GenDelegs is the mapping from genesis keys to genesis delegates. The representation in the JSON file is between key hashes.

So to understand where it got the key hashes from we can use a command to get the key hash for each key:

$ cardano-cli shelley genesis key-hash
Usage: cardano-cli shelley genesis key-hash --verification-key-file FILEPATH
  Print the identifier (hash) of a public key

Available options:
  --verification-key-file FILEPATH
                           Input filepath of the verification key.

Let’s do that for our genesis key and genesis delegate key

$ cardano-cli shelley genesis key-hash \
    --verification-key-file example/genesis-keys/genesis1.vkey
  a4d927a8e50c7a51e0f7d41a75057073cd2fc49bfc87a44891a8a9f80800cd8a

$ cardano-cli shelley genesis key-hash \
    --verification-key-file example/delegate-keys/delegate1.vkey
  b2ee836b2b92fd3dd5e4228c943d8854e673c0510983956ce4f4ea7afcd9f761

So now we can see where the hashes from the genDelegs came from

  "genDelegs": {
    "a4d927a8e50c7a51e0f7d41a75057073cd2fc49bfc87a44891a8a9f80800cd8a":
      "b2ee836b2b92fd3dd5e4228c943d8854e673c0510983956ce4f4ea7afcd9f761"
  },

Next it’s a similar deal with the initialFunds. This is a mapping from the initial addresses to the initial values at those address. So we need a command to get the address corresponding to an initial UTxO verification key:

$ cardano-cli shelley genesis initial-addr
Usage: cardano-cli shelley genesis initial-addr --verification-key-file FILEPATH
  Get the address for an initial UTxO based on the verification key

Available options:
  --verification-key-file FILEPATH
                           Input filepath of the verification key.

So let’s do that for the UTxO key

$ cardano-cli shelley genesis initial-addr \
    --verification-key-file example/utxo-keys/utxo1.vkey
  820658207c3c942eaa39dd0aec74415312cf80dfc92b34fa1d0e1c2fd2499ee105219305

And if we compare this with the initialFunds from the generated file we see

  "initialFunds": {
    "820658207c3c942eaa39dd0aec74415312cf80dfc92b34fa1d0e1c2fd2499ee105219305": 0
    "8206582076cb9794a896ee640dcb54bd932f3a0ca6012aa99cb7d767aec9f4d717c88d7b": 0
  }

This means we’ll start with 0 lovelace in a special genesis UTxO at that address.

Ok, so zero lovelace is not that useful. We can however edit the genesis.spec.json and set the maxLovelaceSupply there, or we specify the initial supply when we re-generate the genesis file. Either way, it will be split equally between all the utxo keys.

$ cardano-cli shelley genesis create \
    --genesis-dir example/ \
    --supply 1000000

Yes ONE MILLION LOVELACE.

If we look again at the generated genesis file now we’ll see

  "initialFunds": {
    "820658207c3c942eaa39dd0aec74415312cf80dfc92b34fa1d0e1c2fd2499ee105219305": 500000
    "8206582076cb9794a896ee640dcb54bd932f3a0ca6012aa99cb7d767aec9f4d717c88d7b": 500000
  }

So we see the amount split between our two initial addresses.

You will also see that the maxLovelaceSupply is set to this same supply. If you edit this manually note that it has to be at least as big as the sum total from our initialFunds, but it can be bigger to allow for monetary expansion later for stake rewards.

Finally there is the startTime, which is the agreed time of slot zero. By default the create command filled this in to be 30s into the future, but you can also specify this manually with --start-time UTC_TIME or edit it manually afterwards. It needs to be set to a time in the near future or near past. It cannot be too far in the past otherwise the system would start having missed a very large number of slots.

Making a genesis file semi-automagically

If you jumped straight in here, skipping the manual method, do go back and review that section covers the concepts about what these keys are all for.

Also remember: when doing it for real you cannot use the automagic method. It is not secure to because it makes all the keys in one place. When doing it for real the people involved have to follow the manual method where keys are generated separately on secure offline machines.

But for demos it is fine

$ cardano-cli shelley genesis
Usage: cardano-cli shelley genesis COMMAND
  Shelley genesis block commands

Available commands:
  key-gen-genesis          Create a Shelley genesis key pair
  key-gen-delegate         Create a Shelley genesis delegate key pair
  key-gen-utxo             Create a Shelley genesis UTxO key pair
  key-hash                 Print the identifier (hash) of a public key
  get-ver-key              Derive the verification key from a signing key
  initial-txin             Get the TxIn for an initial UTxO based on the
                           verification key
  create                   Create a Shelley genesis file from a genesis template
                           and genesis/delegation/spending keys.

The automagic method uses the last one, create, and all the others are for the manual method.

$ cardano-cli shelley genesis create
Usage: cardano-cli shelley genesis create --genesis-dir DIR
                                          [--gen-genesis-keys INT]
                                          [--gen-utxo-keys INT]
                                          [--start-time UTC_TIME]
                                          [--supply LOVELACE]
  Create a Shelley genesis file from a genesis template and
  genesis/delegation/spending keys.

Available options:
  --genesis-dir DIR        The genesis directory containing the genesis template
                           and required genesis/delegation/spending keys.
  --gen-genesis-keys INT   The number of genesis keys to make [default is 0].
  --gen-utxo-keys INT      The number of UTxO keys to make [default is 0].
  --start-time UTC_TIME    The genesis start time in YYYY-MM-DDThh:mm:ssZ
                           format. If unspecified, will be the current time +30
                           seconds.
  --supply LOVELACE        The initial coin supply in Lovelace which will be
                           evenly distributed across initial stake holders.

This command will generate a genesis file. It can also generate all the keys, or it can pick up keys you created manually.

It follows this file layout convention:

  • ${genesisdir}/genesis.json

  • ${genesisdir}/genesis.spec.json

  • ${genesisdir}/genesis-keys/genesis${N}.{vkey,skey}

  • ${genesisdir}/delegate-keys/delegate${N}.{vkey,skey}

  • ${genesisdir}/delegate-keys/delegate-opcert${N}.counter

  • ${genesisdir}/utxo-keys/utxo${N}.{vkey,skey}

By default it will not create any keys for you, and will pick up any that you have created manually following the file layout convention.

You can set up the directory layout with a default genesis spec file

$ cardano-cli shelley genesis create --genesis-dir example/

The create command can also create all the necessary keys for you. The optional --gen-genesis-keys and --gen-utxo-keys flags can be used to specify the number of keys of each kind to generate.

We still need a genesis spec to start from. Here’s the default genesis spec file example/genesis.spec.json

{
    "activeSlotsCoeff": 5.0e-2,
    "protocolParams": {
        "poolDecayRate": 0,
        "poolDeposit": 0,
        "protocolVersion": {
            "minor": 0,
            "major": 0
        },
        "decentralisationParam": 1,
        "maxTxSize": 16384,
        "minFeeA": 0,
        "maxBlockBodySize": 65536,
        "keyMinRefund": 0,
        "minFeeB": 0,
        "eMax": 0,
        "extraEntropy": {
            "tag": "NeutralNonce"
        },
        "maxBlockHeaderSize": 1000,
        "keyDeposit": 0,
        "keyDecayRate": 0,
        "nOpt": 100,
        "rho": 0,
        "poolMinRefund": 0,
        "tau": 0,
        "a0": 0
    },
    "startTime": "1970-01-01T00:00:00Z",
    "genDelegs": {},
    "updateQuorum": 5,
    "maxMajorPV": 1,
    "initialFunds": {},
    "maxLovelaceSupply": 0,
    "networkMagic": 42,
    "epochLength": 432000,
    "staking": null,
    "slotsPerKESPeriod": 129600,
    "slotLength": 1,
    "maxKESEvolutions": 60,
    "securityParam": 2160
}

The create will read the genesis.spec.json and produce the genesis.json by filling in the:

  • genDelegs

  • initialFunds

  • startTime

  • and optionally it can override the maxLovelaceSupply

Everything else we have to fill in manually, either in the template or afterwards.

So let’s try it:

$ cardano-cli shelley genesis create \
    --genesis-dir example/ \
    --supply 1000000000 \
    --gen-genesis-keys 2 \
    --gen-utxo-keys 2

We’re going for more zeros on our money supply this time, after all why make trillions when we could make billions?

Let’s have a look at the result

$ cat example/genesis.json
{
    "decentralisationParam": 1,
    "activeSlotsCoeff": 5.0e-2,
    "startTime": "2020-05-07T01:46:02.884394538Z",
    "genDelegs": {
        "40a5d0f1db7ec1bcf92758f1909677576b4edf7164c94602bee0f7848495c615":
          "6e8f60c82449be3d6c17415d784881476d81ed99d88108526e5a32c8d087bdc8",
        "60262fd1a9c700730c93a8cc855d840f8a9795d956e8ee1f6980657efa172fbb":
          "6e8f60c82449be3d6c17415d784881476d81ed99d88108526e5a32c8d087bdc8"
    },
    "updateQuorum": 5,
    "maxHeaderSize": 1400,
    "maxMajorPV": 1000,
    "maxBodySize": 16384,
    "maxLovelaceSupply": 1000000000,
    "initialFunds": {
        "8206582080edb9890519e08847aff26f55a076a439b9835baa7113d04ad1ed9b2ea55817": 500000000,
        "8206582076cb9794a896ee640dcb54bd932f3a0ca6012aa99cb7d767aec9f4d717c88d7b": 500000000
    },
    "networkMagic": 42,
    "epochLength": 21600,
    "slotLength": 1,
    "slotsPerKESPeriod": 86400,
    "maxKESEvolutions": 90,
    "securityParam": 2160,
    "protocolParams": {
           "a0": 0,
           "decentralisationParam": 0.99,
           "eMax": 0,
           "extraEntropy": {
               "tag": "NeutralNonce"
           },
           "keyDecayRate": 0,
           "keyDeposit": 0,
           "keyMinRefund": 0,
           "maxBlockBodySize": 2097152,
           "maxBlockHeaderSize": 8192,
           "maxTxSize": 2048,
           "minFeeA": 0,
           "minFeeB": 0,
           "nOpt": 100,
           "poolDecayRate": 0,
           "poolDeposit": 0,
           "poolMinRefund": 0,
           "protocolVersion": {
               "major": 0,
               "minor": 0
           },
           "rho": 0,
           "tau": 0
       }
}

And the files it made

$ ls example/*/
example/delegate-keys/:
delegate1.skey     delegate-opcert1.counter
delegate1.vkey     delegate-opcert2.counter
delegate2.skey
delegate2.vkey

example/genesis-keys/:
genesis1.skey  genesis2.skey
genesis1.vkey  genesis2.vkey

example/utxo-keys/:
utxo1.skey  utxo2.skey
utxo1.vkey  utxo2.vkey

You’ll notice that the automagic method has divided the total supply amongst the initial UTxO keys, but you can still edit this file manually to adjust that if you want.

Node operational keys and certificates

Armed just with the genesis file we could now start a node, however it would raise difficult philosophical questions about the nature of a blockchain with no blocks. To avoid such questions, let’s aim to have some nodes that can create blocks. Of course this means everyone’s favourite: more keys.

Shelley uses a “hot key / cold key” scheme for block producing nodes:

  • the cold key is intended to be kept securely offline (hence “cold”),

  • while the hot key is kept on the node itself and used to sign block headers.

Rather than cold and hot, we typically refer to the operator’s offline key (cold) and their operational key (hot).

The basic idea of such a scheme is that if the operational key is compromised then a new one can be issued and the old one invalidated. This involves establishing the link between the operator’s offline key and their operational key. This is done by means of a certificate. The certificate identifies the current operational key, and is signed by the offline key. The certificate also contains an issue counter number so that all other nodes can see when a new certificate is being used and old certificates should be considered invalid.

The act of “issuing” a new certificate simply means the act of signing a new certificate using the offline key.

To make things even more fun, Shelley uses two operational keys:

  • A KES key, using magic crypto;

  • A VRF key, using even more magic crypto.

They are both used in block headers. The KES key is used to prove that the node is who it says it is, just like a normal signature. The VRF key is used to prove that the node has the right to create a block in this slot.

The use of a VRF key is special to Ouroboros Praos. In a “normal” proof-of-stake blockchain (like Ouroboros Classic or BFT) one simply knows who has the right to make the block in each slot, because we know what the slot leader schedule is: that is the slot leader schedule is public. So in that case you only have to prove you are who you say you are, and everyone can check that the slot leader schedule says if you’re the slot leader or not. Ouroboros Praos has a private slot leader schedule. This means that nobody knows in advance who is going to be the slot leader, but once someone is, they can prove to everyone else that they are. And that is what the VRF key is for: proving that.

KES stands for Key Evolving Signature. It is like a normal signature scheme, but with the “forward-security” property. The signing key is “evolved” after a number of slots (e.g. the number of slots equivalent to 24 hours) to give a new signing key, and the old key is forgotten. It means that if someone breaks into a server running a node, while they can steal the current signing key, they should not be able to recover the signing keys from earlier periods. This means that an attacker cannot sign blocks for this node for the past, only for the current and future. This helps to prevent the creation of large false alternative histories of the blockchain.

As is normal security practice, hot or operational keys should be cycled after a while, and new operational keys issued. There is also a technical limitation that KES signing keys can only be evolved a finite number of times. The larger that choice of the maximum number of evolutions, the larger the signatures become. The signatures are included in block headers, and for good performance we want to keep block headers small. In Shelley, the KES keys will need to be reissued before 90 days, but they can always be reissued earlier.

So in order to run a Shelley node we will need to:

  • generate an operator’s offline key;

  • generate a KES operational key;

  • generate a VRF operational key; and

  • issue an operational certificate

The latter three are needed by the node itself, and issuing the operational certificate needs the operator’s offline key.

Now if you followed the previous section on constructing a genesis file, then you have already generated operator offline keys: the genesis delegates are exactly that. The other operator offline keys are stake pool keys. For most purposes the genesis delegate and stake pool operator offline keys are the same: both get used to issue operational certs.

We can create stake pool operator keys using:

$ cardano-cli shelley node key-gen
Usage: cardano-cli shelley node key-gen --verification-key-file FILEPATH
                                        --signing-key-file FILEPATH
                                        --operational-certificate-issue-counter FILE
  Create a key pair for a node operator's offline key and a new certificate
  issue counter

Available options:
  --verification-key-file FILEPATH
                           Output filepath of the verification key.
  --signing-key-file FILEPATH
                           Output filepath of the signing key.
  --operational-certificate-issue-counter FILE
                           The file with the issue counter for the operational
                           certificate.

For now we will ignore stake pools however since we need to get the system bootstrapped with the BFT overlay.

KES Keys

There’s a command to generate new KES keys

cardano-cli shelley node key-gen-KES
Usage: cardano-cli shelley node key-gen-KES --verification-key-file FILEPATH
                                            --signing-key-file FILEPATH
  Create a key pair for a node KES operational key

Available options:
  --verification-key-file FILEPATH
                           Output filepath of the verification key.
  --signing-key-file FILEPATH
                           Output filepath of the signing key.

So let’s go ahead and create a KES key for our first two nodes

$ mkdir example/{node1,node2}

$ cardano-cli shelley node key-gen-KES \
    --verification-key-file example/node1/kes.vkey \
    --signing-key-file example/node1/kes.skey

$ cardano-cli shelley node key-gen-KES \
    --verification-key-file example/node2/kes.vkey \
    --signing-key-file example/node2/kes.skey

If you look at these files, you’ll see that KES signing keys are quite chunky, especially compared to our normal ed25519 keys.

VRF Keys

And there’s a command to generate new VRF keys

$ cardano-cli shelley node key-gen-VRF
Usage: cardano-cli shelley node key-gen-VRF --verification-key-file FILEPATH
                                            --signing-key-file FILEPATH
  Create a key pair for a node VRF operational key

Available options:
  --verification-key-file FILEPATH
                           Output filepath of the verification key.
  --signing-key-file FILEPATH
                           Output filepath of the signing key.

So let’s do that too

$ cardano-cli shelley node key-gen-VRF \
    --verification-key-file example/node1/vrf.vkey \
    --signing-key-file example/node1/vrf.skey

$ cardano-cli shelley node key-gen-VRF \
    --verification-key-file example/node2/vrf.vkey \
    --signing-key-file example/node2/vrf.skey

Issuing an operational certificate

Now we get to the stage of wanting to issue an operational certificate.

When doing this for real, the operator’s offline key should of course be offline, so we would issue the certificate on the offline machine with the offline key, and copy the resulting certificate to the operational machine.

$ cardano-cli shelley node issue-op-cert
Usage: cardano-cli shelley node issue-op-cert --hot-kes-verification-key-file FILEPATH
                                              --cold-signing-key-file FILEPATH
                                              --operational-certificate-issue-counter FILE
                                              --kes-period NATURAL
                                              --out-file FILE
  Issue a node operational certificate

Available options:
  --hot-kes-verification-key-file FILEPATH
                           Filepath of the hot KES verification key.
  --cold-signing-key-file FILEPATH
                           Filepath of the cold signing key.
  --operational-certificate-issue-counter FILE
                           The file with the issue counter for the operational
                           certificate.
  --kes-period NATURAL     The start of the KES key validity period.
  --out-file FILE          The output file.

There’s a few things here to understand.

As discussed above, a certificate identifies an operational KES key that we will be using to sign block headers, so we need its verification key. It is signed by the operator’s offline key so we need that signing key.

As mentioned, certificates have an issue counter number that is used to inform other nodes that older certificates are now invalid. This certificate issue counter must be kept with the operator’s offline key. You’ll notice they got created when we created the genesis delegate keys, or if you create a new stake pool key.

Finally we have a confusing flag for the KES period. Each operational certificate specifies when the certificate is valid from. This is like a date but is specified in terms of KES periods, which is some number of slots long. Frankly, this needs improving in the CLI tools and/or documentation. For now we are creating a system from scratch so we can start with period 0.

So let’s go ahead and issue ourselves an operational certificate. We will sign using use the genesis delegate keys we created earlier.

$ cardano-cli shelley node issue-op-cert \
    --hot-kes-verification-key-file example/node1/kes.vkey \
    --cold-signing-key-file example/delegate-keys/delegate1.skey \
    --operational-certificate-issue-counter example/delegate-keys/delegate-opcert1.counter \
    --kes-period 0 \
    --out-file example/node1/cert

$ cardano-cli shelley node issue-op-cert \
    --hot-kes-verification-key-file example/node2/kes.vkey \
    --cold-signing-key-file example/delegate-keys/delegate2.skey \
    --operational-certificate-issue-counter example/delegate-keys/delegate-opcert2.counter \
    --kes-period 0 \
    --out-file example/node2/cert

Starting a node

Now that we have generated our genesis.json, KES and VRF operational keys and issued ourselves operational certificates, we are close to being able to run our nodes.

The last things we need are node configuration and topology files.

For the configuration file, we can start with the default Byron mainnet configuration

$ cp cardano-node/configuration/defaults/byron-mainnet/configuration.yaml \
     example/

and make a couple tweaks

$ sed -i 's/Protocol: RealPBFT/Protocol: TPraos/' example/configuration.yaml
$ sed -i 's/minSeverity: Info/minSeverity: Debug/' example/configuration.yaml

We will share this configuration.yaml file between both nodes we run.

The topology files tell nodes which other nodes to talk to. We will need one for each node we run. For this demo we will be running two nodes on the same machine, one on port 3001 and the other on 3002. We will configure them to talk to each other.

So create the following two files as example/node1/topology.json and example/node2/topology.json.

{
  "Producers": [
    {
      "addr": "127.0.0.1",
      "port": 3002,
      "valency": 1
    }
  ]
}
{
  "Producers": [
    {
      "addr": "127.0.0.1",
      "port": 3001,
      "valency": 1
    }
  ]
}

So node1 will listen on port 3001 and contact node2 on port 3002, and the other way around for node2.

Now we are ready to run our two nodes. With the way we have set them up should both be block-producing nodes that take part in the BFT overlay schedule.

One final tweak before we start: it has probably been some time since we generated the genesis.json, so the start time is probably now some time in the past. It is a bit nicer if we set the start time to be shortly in the future and then start up our nodes. That way they will all wait for the start time and then make blocks from the beginning.

So let’s just re-generate our genesis.json which will set the start time to be 30 seconds into the future, and then we can start our nodes. This assumes you did not do any manual tweaking of the generated genesis.json, as it will be overwritten.

$ cardano-cli shelley genesis create --genesis-dir example/

So, now in two separate terminal windows we can launch our nodes. Node 1:

$ cardano-node run \
    --config example/configuration.yaml \
    --topology example/node1/topology.json \
    --database-path example/node1/db \
    --socket-path example/node1/node.sock \
    --shelley-kes-key example/node1/kes.skey \
    --shelley-vrf-key example/node1/vrf.skey \
    --shelley-operational-certificate example/node1/cert \
    --port 3001

And node 2:

$ cardano-node run \
    --config example/configuration.yaml \
    --topology example/node2/topology.json \
    --database-path example/node2/db \
    --socket-path example/node2/node.sock \
    --shelley-kes-key example/node2/kes.skey \
    --shelley-vrf-key example/node2/vrf.skey \
    --shelley-operational-certificate example/node2/cert \
    --port 3002

The default configuration will log everything to stdout, and we turned the log level up so we’ll see even debug messages, so it will be pretty voluminous.

If you did manage to start the nodes before the start time, you’ll see them waiting:

[localhost:cardano.node:Debug:5] [2020-05-10 21:46:40.77 UTC]
  Waiting 23.921899468s until genesis start time at 2020-05-10 21:47:04.701779756 UTC

After that, you should see the nodes start to alternately create blocks and adopt each others blocks. Remember that we have configured slots to be 1 second long but only 1 in 20 slots will have a block in it.

Querying the node

Now that our nodes are running, let’s poke them and see if they’re doing what we expect. We’ll need a third terminal.

Querying protocol parameters

We’ll start with querying the node to see the current set of protocol parameters.

$ cardano-cli shelley query protocol-parameters
Usage: cardano-cli shelley query protocol-parameters (--mainnet |
                                                       --testnet-magic INT)
                                                     [--out-file FILE]
  Get the node's current protocol parameters

Available options:
  --mainnet                Use the mainnet magic id.
  --testnet-magic INT      Specify a testnet magic id.
  --out-file FILE          Optional output file. Default is to write to stdout.

The only surprising extra flag is the “network magic”. This is the networkMagic from the genesis.json. In the default we generated, that was given as 42. The network “magic” number is used as a simple sanity check (not a security measure of course) when nodes connect to each other, to stop nodes accidentally connecting to nodes running different blockchains, e.g. testnet vs mainnet. We have the same sanity check when we connect to the local node. So we have to specify --testnet-magic 1097911063, otherwise it defaults to mainnet and then the handshake would fail.

This command of course connects to a local node. The socket for the local node is set via an environment variable CARDANO_NODE_SOCKET_PATH. Typically one only runs one node on a machine, and so it would make sense to set this for the whole terminal session:

export CARDANO_NODE_SOCKET_PATH=$PWD/example/node1/node.sock

In this demo we are running two nodes however so we’ll specify the env var each time in the command.

$ CARDANO_NODE_SOCKET_PATH=example/node1/node.sock \
    cardano-cli shelley query protocol-parameters \
    --testnet-magic 1097911063
{
    "poolDecayRate": 0,
    "poolDeposit": 0,
    "protocolVersion": {
        "minor": 0,
        "major": 0
    },
    "decentralisationParam": 1,
    "maxTxSize": 16384,
    "minFeeA": 0,
    "maxBlockBodySize": 65536,
    "keyMinRefund": 0,
    "minFeeB": 0,
    "eMax": 0,
    "extraEntropy": {
        "tag": "NeutralNonce"
    },
    "maxBlockHeaderSize": 1000,
    "keyDeposit": 0,
    "keyDecayRate": 0,
    "nOpt": 100,
    "rho": 0,
    "poolMinRefund": 0,
    "tau": 0,
    "a0": 0
}

As we can see, it spits out the same set of protocol parameters from the genesis.json file we started with.

Querying the UTxO

For our next trick we will inspect the UTxO. Of course we have not done any transactions yet so we expect just the initial UTxO as specified in the genesis.json.

There is a command to query the UTxO and return all the UTxOs at a given address.

$ cardano-cli shelley query filtered-utxo
Usage: cardano-cli shelley query filtered-utxo --address ADDRESS
                                               (--mainnet | --testnet-magic INT)
                                               [--out-file FILE]
  Get the node's current UTxO filtered by address

Available options:
  --address ADDRESS        A hex-encoded Cardano address.
  --mainnet                Use the mainnet magic id.
  --testnet-magic INT      Specify a testnet magic id.
  --out-file FILE          Optional output file. Default is to write to stdout.

So what address do we need? The one(s) from the initialFunds in the genesis.json of course. So let’s try it. Obviously you will have to adjust this command to use the right address(es) from your genesis.json.

$ CARDANO_NODE_SOCKET_PATH=example/node1/node.sock \
    cardano-cli shelley query filtered-utxo \
    --testnet-magic 1097911063 \
    --address 8206582080edb9890519e08847aff26f55a076a439b9835baa7113d04ad1ed9b2ea55817

                           TxHash                                 TxIx        Lovelace
----------------------------------------------------------------------------------------
80edb9890519e08847aff26f55a076a439b9835baa7113d04ad1ed9b2ea55817     0         500000000

So this tells us that there is exactly one UTxO entry at that address, with one unspent output (output zero).

The next step will be to spend this.

Submitting a Genesis initial UTxO transaction

Preparing the ingredients

There are a few things we need to make our first transaction:

  • A UTxO to spend

  • The signing key for the UTxO we’re spending

  • An address to spend to

Spending the Genesis initial UTxOs is a little special because of the way the initial UTxO is constructed. But we have seen above that we can find the UTxOs that we want to spend.

We also have the signing key from when we constructed the genesis, e.g. in example/utxo-keys/utxo1.skey.

For an output address, we will need to make one.

Finding the initial UTxO TxIn

To avoid confusion about which UTxO goes with which key, we have this handy command

$ cardano-cli shelley genesis initial-txin
Usage: cardano-cli shelley genesis initial-txin --verification-key-file FILEPATH
  Get the TxIn for an initial UTxO based on the verification key

Available options:
  --verification-key-file FILEPATH
                           Input filepath of the verification key.

Which we can use with our example/utxo-keys/utxo1.vkey

$ cardano-cli shelley genesis initial-txin \
    --verification-key-file example/utxo-keys/utxo1.vkey
80edb9890519e08847aff26f55a076a439b9835baa7113d04ad1ed9b2ea55817#0

Note the TxIn syntax of “long hash # ix”, here with index 0.

Making new keys and addresses

We will make a key pair and then build an address from that. Let’s start with the keypair.

$ cardano-cli shelley address key-gen
Usage: cardano-cli shelley address key-gen --verification-key-file FILEPATH
                                           --signing-key-file FILEPATH
  Create a single address key pair

Available options:
  --verification-key-file FILEPATH
                           Input filepath of the verification key.
  --signing-key-file FILEPATH
                           Input filepath of the signing key.

So let’s do it

$ cardano-cli shelley address key-gen \
    --verification-key-file example/addr1.vkey \
    --signing-key-file example/addr1.skey

We can now build an address from our key.

$ cardano-cli shelley address build
Usage: cardano-cli shelley address build --payment-verification-key-file FILEPATH
                                         [--staking-verification-key-file FILEPATH]
  Build a Shelley payment addres, with optional delegation to a stake address.

Available options:
  --payment-verification-key-file FILEPATH
                           Filepath of the payment verification key.
  --staking-verification-key-file FILEPATH
                           Filepath of the staking verification key.

This command can also build payment addresses that are associated with stake addresses, and thus have the ability to delegate the stake rights.

For now however let’s see our boring address with no stake rights:

$ cardano-cli shelley address build \
    --payment-verification-key-file example/addr1.vkey
82065820cd44104b49b97dae659dabf040cc7d588ea28e52addffc66fd126bb23be87451

The CLI uses a low-tech hex encoding for addresses, rather than fancy bech32.

Building an unsigned transaction body

We will be using the low level build-raw command because a more convenient command is not available yet, and because for spending a Genesis UTxO we have to use the low level command to select the exact UTxO to spend.

$ cardano-cli shelley transaction build-raw
Usage: cardano-cli shelley transaction build-raw --tx-in TX_IN --tx-out TX_OUT
                                                 --ttl SLOT_COUNT --fee LOVELACE
                                                 --tx-body-file FILEPATH
                                                 [--certificate FILEPATH]
  Build a transaction (low-level, inconvenient)

Available options:
  --tx-in TX_IN            The input transaction as TxId#TxIx where TxId is the
                           transaction hash and TxIx is the index.
  --tx-out TX_OUT          The ouput transaction as TxOut+Lovelace where TxOut
                           is the hex encoded address followed by the amount in
                           Lovelace.
  --ttl SLOT_COUNT         Time to live (in slots).
  --fee LOVELACE           The fee amount in Lovelace.
  --tx-body-file FILEPATH  Output filepath of the TxBody.
  --certificate FILEPATH   Filepath of the certificate. This encompasses all
                           types of certificates (stake pool certificates, stake
                           key certificates etc)

Yes, this is a very inconvenient way to build transactions, but at least you’ll be able to say you understand the UTxO model a little better.

So we have the transaction input and output. The TxIn uses “TxId#TxIx” syntax. That will be the TxIn we got from the initial-txin command. The TxOut uses “TxOut+Lovelace” syntax. We’ll build that by taking the address from the address build command from the previous section and adding “+500000000” for the value we want to move to that address.

Next we have the TTL. In Shelley every transaction has an expiry slot. If the transaction does not make it into the chain by this slot then it will be considered invalid and will never be added to the chain (on that fork). This is generally helpful to avoid potentially valid transactions from floating around for ever. It let’s us guarantee that after some point we definitely do not have to worry about them any more. For boring technical reasons it is required rather than optional. Pick a slot number that is sufficiently far into the future. You can look at the node log files or query the node’s tip to find the current slot number.

Finally we have the fee. In Shelley the transaction fee is explicit, rather than implicitly being the difference between the inputs and the outputs. Furthermore the inputs and outputs + fee, must balance exactly. You can still choose the fee, it just has to be above the minimum fee. With the demo genesis we made, the fees are zero so we can ignore this complication for now.

So we build the unsigned transaction and place it in example/tx1.txbody

$ cardano-cli shelley transaction build-raw \
    --tx-in  76cb9794a896ee640dcb54bd932f3a0ca6012aa99cb7d767aec9f4d717c88d7b#0 \
    --tx-out 82065820cd44104b49b97dae659dabf040cc7d588ea28e52addffc66fd126bb23be87451+500000000 \
    --ttl 3600 \
    --fee 0 \
    --tx-body-file example/tx1.txbody

Making a signed transaction

The next step is to sign it. Shelley transactions sometimes need lots of signatures, e.g. when registering stake pools, or spending from multi-sig addresses. Typically however we just need one signature for each address we use in inputs (that’s one per address, not one per input UTxO, if there are multiple inputs from the same address).

$ cardano-cli shelley transaction sign
Usage: cardano-cli shelley transaction sign --tx-body-file FILEPATH
                                            --signing-key-file FILEPATH
                                            (--mainnet | --testnet-magic INT)
                                            --tx-file FILEPATH
  Sign a transaction

Available options:
  --tx-body-file FILEPATH  Input filepath of the TxBody.
  --signing-key-file FILEPATH
                           Input filepath of the signing key (one or more).
  --mainnet                Use the mainnet magic id.
  --testnet-magic INT      Specify a testnet magic id.
  --tx-file FILEPATH       Output filepath of the Tx.

In our example we need just the one signature, using the utxo1.skey.

$ cardano-cli shelley transaction sign \
  --tx-body-file example/tx1.txbody \
  --signing-key-file example/utxo-keys/utxo1.skey \
  --testnet-magic 1097911063 \
  --tx-file example/tx1.tx

Submitting the signed transaction

Finally we need to submit the signed transaction

$ cardano-cli shelley transaction submit
Usage: cardano-cli shelley transaction submit --tx-file FILEPATH
                                              (--mainnet | --testnet-magic INT)
  Submit a transaction to the local node whose Unix domain socket is obtained
  from the CARDANO_NODE_SOCKET_PATH enviromnent variable.

Available options:
  --tx-file FILEPATH       Filepath of the transaction you intend to submit.
  --mainnet                Use the mainnet magic id.
  --testnet-magic INT      Specify a testnet magic id.

This command also needs the CARDANO_NODE_SOCKET_PATH like the other commands that need to talk to a local node. And as mentioned above in the section on querying the node, we have to specify --testnet-magic 1097911063, otherwise it defaults to mainnet and then the handshake with the node would fail.

So let’s do it.

CARDANO_NODE_SOCKET_PATH=example/node1/node.sock \
    cardano-cli shelley transaction submit \
      --tx-file example/tx1.tx \
      --testnet-magic 1097911063

If we now go look at the node logs (or stdout) for node1 we should see that the transaction was accepted into the mempool, and some seconds later we should see that the next block included the transaction.

We can also check using the UTxO query, but now using the new address we moved the funds to

CARDANO_NODE_SOCKET_PATH=example/node1/node.sock \
    cardano-cli shelley query filtered-utxo \
      --testnet-magic 1097911063 \
      --address 82065820cd44104b49b97dae659dabf040cc7d588ea28e52addffc66fd126bb23be87451

                           TxHash                                 TxIx        Lovelace
----------------------------------------------------------------------------------------
a961d5e14a3a2cd886787d04a2c14721a4ca6d7f15b6ed52ddc0900659200046     0         500000000

So since the fees were zero we move the full amount, and we have a new UTxO entry at our target address.

From here we can do more “normal” transactions to move funds to other addresses or build and submit transactions with special certificates in them.

Submitting a “normal” transaction

TODO: normal txs, much like above, then certs, stake addresses etc.