Changing the board keys

Part of Board instructions. How the board changes the keys that sign sporks, online against a node or offline in a spork database.

A change lists the keys active after it and carries the approvals of the keys before it; what the chain holds and the rules a node applies to it are on Grid sporks. A change of the board keys is signed and co-signed by the keys it makes active, so gridspork-set board-keys and gridspork-cosign accept a key that a pending change adds.

The chain grows one change at a time. In this example the board adds a fifth key, E, and then switches off A:

flowchart LR
    R["Root<br/>built-in keys A B C D<br/>and the epoch"] --> C0["Change 0<br/>active: A B C D E<br/>approved by 3 of A B C D,<br/>and by E"]
    C0 --> C1["Change 1<br/>active: B C D E<br/>approved by 3 of A B C D E"]

A change is made online against a node, or offline in a spork database file:

flowchart TD
    P["util board-keys propose<br/>writes the change file"] --> A["util board-keys approve<br/>by a quorum of the keys before it,<br/>and by every key it adds"]
    A -->|online| S["gridspork-set board-keys<br/>-D change file<br/>with a key the change makes active"]
    S --> C["gridspork-cosign<br/>with a second such key"]
    A -->|offline| AP["util board-keys apply"]
    AP --> SG["util spork-db sign,<br/>by two members"]
    SG --> I["util spork-db import"]
    C --> N["Every node goes by the new keys"]
    I --> N

Making a change online

The change file holds no secret: it lists the keys before and after and the approvals, and passes from one board member to the next.

  1. A member proposes the change on a machine with a copy of a spork database: hedgehog util board-keys propose --spork-db <file> --activate <key> --deactivate <key> --output <change file>. --activate and --deactivate repeat. Without --spork-db the change follows on the chain that the release ships, or on the keys built into it when it ships none, as this release does; a private network with --network-keys follows on its own keys. A change over a spork database that holds no board keys spork follows on the built-in keys. Give --spork-db once the network holds a board keys spork. The command prints sequence: <n>, each key before the change as before: <key>, each key after it as after: <key>, quorum: <q> of the <n> keys active before it, each added key as approve too: <key> and output: <file>. It refuses through Nothing was proposed: <reason> a chain that does not hold from the root of this machine, a change that changes nothing, one leaving fewer than 2 or more than 16 keys, an added key already active and a key to switch off that is not: The board keys of <file> do not hold from the root of this machine: <reason>, with the release in place of <file> without --spork-db, The change changes nothing; give --activate or --deactivate, A change leaves 2 to 16 keys active, not <n>, <16 hex> is already active, <16 hex> is not active. The output file must not exist: --output <file> exists and is not overwritten.
  2. Every member who is to approve runs hedgehog util board-keys approve <change file> --spork-db <file> -k <key>, or gives the key with --key-file or $HEDGEHOG_BOARD_KEY. The command verifies the chain the file follows on from the root and takes the keys active before the change from that chain, not from the file, so a file that hides a key is refused: The change of the board keys is refused: the keys active before it are <keys>, not <keys> as the change file says. The key must be active before the change or added by it, or The board key is neither active before the change nor added by it, so it does not approve it. The approval is written into the file, in place of an earlier approval by the same key, and the command prints approvals: <n> of <q> that the keys before it need and added keys that approved it: <a> of <b>.
  3. A member proposes the spork with the change against a node: hedgehog cli gridspork-set board-keys -D <change file> -k <key>. The key must be one of the keys the change makes active. Add --first-version when the node holds no spork of the type. The command appends the change to the stored chain, verifies the whole chain and signs the head with the key. A change that switches off a key which signed a spork the node holds and counts is refused unless --allow-uncounted is given, since that spork would count for nothing until keys that stay sign it again: the change switches off keys that signed sporks the node holds, which would count for nothing until keys that stay sign them again: <TYPE> signed by <16 hex> and <16 hex>; ... . Renew them first with keys that stay, or give --allow-uncounted.
  4. A second member co-signs with hedgehog cli gridspork-cosign -k <key of B> <digest>, where the key is another key of the new active set. The node stores the spork once both signatures hold, goes by the new chain and passes the spork on to its peers.

Both read the result with gridspork-get board-keys. Every cli command that signs installs the chain of its node before it judges a key, and refuses a node whose chain does not hold from the root of this machine or that its own keys did not sign: The board keys that the node holds do not hold from the root of this machine, or two of their keys did not sign them (see --network-keys).

Making a change offline

A board that does not build the spork on a node writes it into a spork database file instead:

  1. hedgehog util board-keys apply <spork database> --change <change file> --output <new file> writes the database with the change appended. Its board keys spork is a first version that no one signed. The command prints board keys: <n> changes, each active key as active: <key> and output: <file>. It refuses a change file it cannot read with Cannot read the change of the board keys <file>: <reason>, a change that does not follow on the chain with The change of the board keys is refused: <reason>, and one that breaks the rules of the chain with Nothing was applied: <reason>.
  2. Two members run hedgehog util spork-db sign on the output, one after the other, with different keys that the change makes active, see Signing a spork database.
  3. hedgehog util spork-db import <file> installs the result on the node, which then publishes the spork to its peers. The import refuses a file whose chain does not carry on the chain of the node, see Signing a spork database.

The record of the longest chain

A command line keeps the longest chain it verified in board-keys-<16 hex of the root>.seen in its user data directory, with board-keys-<16 hex of the root>.seen.lock beside it. A longer chain takes the record’s place, and the command prints each change it added:

Recorded in /home/user/.local/share/hedgehog/board-keys-7b1e04aa9d2c8f31.seen the 1 change of the board keys that the node holds; a node or a file that holds less of the chain, or another chain, is refused from now on:
  change 0, made at 2026-10-24T10:00:00Z, adds 3f2a5b7c9d1e4f60, switches off none, approved by 44f21a0c9b7e6d18 7c0e35b9a1d24f68

From then on a node or a file that holds no chain is refused with The node holds no board keys, while this machine verified <n> changes before (<file>): it is behind the board or holds changes of it back, and nothing goes by the keys it makes active. Use one that holds the whole chain, or delete <file> only to start over from the chain that the release ships., with The file <file> holds in place of The node holds when a command reads a file. A chain with fewer changes is refused in the same words, beginning The node holds 1 change of the board keys, fewer than the 2 that this machine verified before (<file>); one that parts from the record names the change:

The node holds a chain of the board keys that parts at change <i> from the one that this machine verified before (<file>):
  verified here: change <i>, made at <time>, adds <keys|none>, switches off <keys|none>, approved by <keys|none>
  held there: change <i>, made at <time>, adds <keys|none>, switches off <keys|none>, approved by <keys|none>
Nothing goes by the keys of a chain that parts from the one verified here. Find out which of the two the board made, and delete <file> only to start over from the chain that the release ships.

A record that cannot be read ends the command with `The record of the board keys cannot be read:

; delete it only to start over from the keys built into the release`. Delete a record only to start over; with no chain shipped by this release, that is the built-in keys. The `cli` commands that sign a spork read the chain of the node and keep the record, and so do `util board-keys propose` and `approve` with `--spork-db`, `util board-keys apply` and `util spork-db sign`. A private network has a root of its own and a record of its own beside it. ## Releases and forks A release may ship a chain as `/board-keys/release.chain`. A node, a command line and a util command go by it before anything else, and every chain they take holds it; a release whose chain does not hold from its root does not start. No release ships one yet. A private network has its own root and goes without it. Until a release ships a chain or counts the epoch up, a quorum of any key set that the board ever had can sign a chain from the built-in root that a fresh node takes, as it has no record yet. A key that the board switched off must therefore be destroyed, and a release made after a change of the board keys ships the chain or counts the epoch up. A new epoch starts from new built-in keys and makes every change before it void. A node that holds a chain and receives another chain of which neither holds the other keeps its own, and logs once for each such chain `The chain of board keys of the spork is a fork of the chain this node holds, which it keeps; a release with a new root, or an import of the chain of the board on each node, mends a fork`. Two stolen built-in keys can sign any ordinary spork, as any two active keys can, but cannot change the board keys: a change needs three of the four. If two of the four are stolen, the board cannot switch them off either; a release with a new root is needed to remove them. ## A known limit An attacker who holds at least eight distinct /16 networks, enough of them that a node dials, can delay sporks over P2P: those connections share three quarters of the node's checks, and eight of the attacker's networks can spend that pool's node ceiling. The board can then submit the spork over REST to more nodes; REST submissions are not limited by that budget. ## In the source - `application/src/main/java/org/unigrid/hedgehog/command/util/BoardKeysCommand.java`, `BoardKeysPropose.java`, `BoardKeysApprove.java`, `BoardKeysApply.java` and `ChangeFile.java`: the change file and the three `util board-keys` commands - `application/src/main/java/org/unigrid/hedgehog/command/cli/spork/BoardKeysSpork.java`: `gridspork-set board-keys` - `application/src/main/java/org/unigrid/hedgehog/model/spork/BoardKeys.java`, `KeyChange.java`, `BoardKeyChain.java`, `ProposedKeyChange.java` and `model/crypto/ActiveKeys.java`: the chain of board keys and the rules a change holds by - `application/src/main/java/org/unigrid/hedgehog/model/spork/BoardKeysAuthority.java` and `BoardKeysRecord.java`: taking, installing and recording a chain - `application/src/main/java/org/unigrid/hedgehog/model/spork/BoardKeysEncoder.java` and `BoardKeysDecoder.java`: the chain in wire form