# Quorum evaluation

This file documents the way the quorum of [Sigsum policy][] is
represented, compiled, and evaluated.

It is intended to enable verification of Sigsum proofs on small
constrained devices. It aims to:

* Be reasonably compact.
* Be easy to process and apply with a minimum of code and memory.
* Have a well defined bit-exact compilation from an input ASCII policy.

In this context, a quorum is "the rule that determines whether or not
a subset of witnesses that have cosigned a tree head is strong enough
to consider the tree head to be valid". The quorum is only one part of
the machinery needed for verifying a [Sigsum proof][] according to a
given policy. Details of representation of the policy public keys and
the components of the proof are out of scope for this document.

[Sigsum policy]: https://git.glasklar.is/sigsum/core/sigsum-go/-/blob/main/doc/policy.md
[Sigsum proof]: https://git.glasklar.is/sigsum/core/sigsum-go/-/blob/main/doc/sigsum-proof.md

## The quorum stack machine

A Sigsum policy defines the quorum as a tree where leaves are
witnesses and the internal nodes are k-of-n groups, where each member
of a group is either a witness or a group. To avoid having to
represent this structure during evaluation, we instead represent the
quorum as a program for a very simple stack machine.

Evaluating the quorum depends on three pieces of data:

* A read-only bool array with an entry for each known witness, where
  each entry is true if the witness has cosigned the tree head of
  interest, otherwise false.

* A bytecode program, i.e., an array of bytecode instructions. As the
  name implies, each instruction is one byte (uint8_t), with encoding
  and semantics described below.

* A stack of unsigned values. The stack is modified as the bytecode
  is executed. Initially, it is empty, and at the end, there should be
  exactly one entry on the stack.

A bytecode instruction is structured with the two most significant
bits representing a top-level opcode. The remaining six bits are
either an argument to the operation (an "immediate" argument in
assembly language terminology), or a code identifying a stack
operation with no additional argument. Instructions are executed in
sequence; there are no jump instructions. The instructions are `ADD`,
`TRUE`, `X?`, and `>=K`. The latter two take an immediate argument,
encoded in the instruction.

* `ADD`: Pop two values, push their sum. Encoding: `0b00000001`.

* `TRUE`: Push the value 1 onto the stack. Encoding: `0b00000002`.

* `X?`: Push 1 if we have a valid cosignature from witness `X`,
  otherwise push 0. Encoding: `0b01xxxxxx`.

* `>=K`: Pop one value. Push 1 if that value is >= `K`, otherwise
  push 0. Encoding: `0b10kkkkkk`.

To allow larger immediates, also define a prefix instruction
`0b11pppppp`, with additional high bits of an immediate value. E.g.,
`X?` with `X = 43981 = 0b1010 101111 001101` would be represented as
the three bytes `0b11001010 0b11101111 0b01001101`, and similarly for
`>=K` with large `K`. Unnecessary leading zeros are not allowed, i.e.,
a prefix sequence must not start with `0b11000000`. A prefix
instruction must be followed by an instruction that includes an
immediate argument, i.e., `X?` or `>=K`.

## Evaluating the quorum

The quorum is evaluated as part of verifying a Sigsum proof, after
verifying cosignatures and constructing the bool array with an entry
for each known witness. The stack is initialized to empty, and the
bytecode program is executed. 

An invalid program may run into an exception, due to

* Stack overflow from `TRUE` or `X?`.

* Stack underflow from `ADD` or `>=K`.

* Out of range argument to `X?`.

* Arithmetic overflow from `ADD`.

If any of these conditions occur, execution is terminated and the
quorum is not satisfied. At normal termination of the program, the
quorum is satisfied if and only if the stack contains a single entry
with the value one.

## Compiling the quorum

It's desirable that transforming an ASCII policy into compiled form
(including both the quorum bytecode, the topic of this document, and
serialization of additional data such as the public keys, which is
out of scope) is reproducible. It is also desirable that the output is
in a canonical form that is unchanged for semantically uninteresting
changes to the input file, e.g., reordering lines or renaming a group.
That makes it more useful, e.g., to measure the compiled policy used
to control the boot or other actions taken by a device.

For the quorum, we need a deterministic order of the witnesses. To the
quorum evaluation itself, the choice is arbitrary, but other parts of
this library sorts the witness public keys lexicographically by
*key hash*. The reason for this choice is that a Sigsum proof
identifies keys by key hash, which is facilitated by this ordering.

To compile the quorum, we assume that parsing of the input (ASCII)
policy file has produced a properly sorted list of witnesses and a
tree of witnesses and groups. To compile this, the tree is processed
in depth-first order, starting with the witness or group identified by
the quorum line.

* To compile a witness, find the witness' index, `X`, and emit an `X?`
  instruction.

* A group with a single member must have threshold = 1, and is
  trivial. It is compiled by compiling its only member.

* To compile a group with more than one member, first recursively
  compile each of the `n` members, producing an array of bytecode
  fragments. Then sort the resulting bytecode fragments, first by
  increasing size, and then lexicographically for fragments of the
  same size. Output the first fragment, then output remaining
  fragments, each followed by an `ADD` instruction. Finally, emit a
  `>=K` instruction, with `K` being the group's threshold.

The compiler should also output the stack size required for executing
the program.

## Example

Let's look at the policy named `sigsum-test2-2025`. It defines a
quorum via these witnesses and groups:

```
witness poc.sigsum.org/nisse            1c25f8a44c635457e2e391d1efbca7d4c2951a0aef06225a881e46b98962ac6c
witness rgdd.se/poc-witness             28c92a5a3a054d317c86fc2eeb6a7ab2054d6217100d0be67ded5b74323c5806
witness witness1.smartit.nu/witness1    f4855a0f46e8a3e23bb40faf260ee57ab8a18249fa402f2ca2d28a60e1a3130e
witness navigli.sunlight.geomys.org/dev b20196b937a30d19f0cb7dfa2d5388bc8ddde9cc3df4c24fcf63db27a573d3f2
witness remora.n621.de                  ebcdeb78e7fdb2ef9227b2c1ef11e94600b55b4d6d9a57877e31ee89e59adc36
witness witness.stagemole.eu            4a921b7caef58ae670cdc11ef4184f1c058f7b9259a9107a969f69fa54aa496f
witness tillitis.se/test-witness-1      5c35281928e9da396beede5f22a2251a589e6ac6de52a4de85de8634ffccaf6d

group glasklar-test-witnesses 2 poc.sigsum.org/nisse rgdd.se/poc-witness witness1.smartit.nu/witness1

group quorum-rule 4 glasklar-test-witnesses navigli.sunlight.geomys.org/dev remora.n621.de witness.stagemole.eu tillitis.se/test-witness-1

quorum quorum-rule
```

In key hash ordering, the witnesses are:

| index | name                            | pub key | hash    |
|-------|---------------------------------|---------|---------|
|     0 | tillitis.se/test-witness-1      | 5c35... | 0d4f... |
|     1 | poc.sigsum.org/nisse            | 1c25... | 1c99... |
|     2 | witness1.smartit.nu/witness1    | f485... | 4235... |
|     3 | witness.stagemole.eu            | 4a92... | 49c4... |
|     4 | rgdd.se/poc-witness             | 28c9... | 70b8... |
|     5 | remora.n621.de                  | ebcd... | 86b5... |
|     6 | navigli.sunlight.geomys.org/dev | b201... | b95e... |

The three witnesses in the `glasklar-test-witnesses` group thus get
indices 1, 2, and 4. The quorum tree (with witnesses represented by their
indices) is

```
   Group (threshold = 4)
  /| | |\
 / | | | \ 
0  3 5 6  Group (threshold = 2)
         /|\
        1 2 4
```

The resulting quorum bytecode is the 15 hex bytes `40 43 01 45 01 46
01 41 42 01 44 01 82 01 84`. Assuming we had cosignatures from
witnesses 0, 1, 2, and 3, this would be evaluated as

| byte | instr | stack after execution                               |
|------|-------|-----------------------------------------------------|
|  40  |  0?   | 1													 |
|  43  |  3?   | 1 1												 |
|  01  |  ADD  | 2													 |
|  45  |  5?   | 2 0												 |
|  01  |  ADD  | 2													 |
|  46  |  6?   | 2 0												 |
|  01  |  ADD  | 2													 |
|  41  |  1?   | 2 1    # Start of the glasklar-test-witnesses group |
|  42  |  2?   | 2 1 1												 |
|  01  |  ADD  | 2 2												 |
|  44  |  4?   | 2 2 0												 |
|  01  |  ADD  | 2 2												 |
|  82  |  >=2  | 2 1    # End of the glasklar-test-witnesses group   |
|  01  |  ADD  | 3													 |
|  84  |  >=4  | 0													 |


So the quorum is not satisfied for this subset of witnesses (we would
need one more cosignature, from some witness outside of the
`glasklar-test-witnesses` group).

## Implementation limits

This library represents the stack entries as `uint8_t`. This implies
that groups with more than 255 direct members are not supported (the `ADD`
instruction would overflow if they were all present), and should be
rejected at compile time.

Applications using the quorum bytecode may impose additional
implementation limits on the number of witnesses, the size of the byte
code program, and on the stack size.
