How Roost signs
Voters and quorum
Each voter derives a per-key secret from its own root, which it regenerates from its PUF at power-up. A seal is HMAC-SHA256 over the message digest and the voter's counter. The proxy asks every enrolled voter and returns a receipt once at least two agree.
K_key = HMAC-SHA256(K_root, key_hash)
sig = HMAC-SHA256(K_key, data_hash || ctr) # ctr: 64-bit, +1 per seal, never rewinds
kid = SHA256(HMAC-SHA256(K_root, "axowl-kid"))[0:8]Seals and signatures
A seal (an IRON3 receipt) is confirmed by the voters that made it: it proves to Roost and to you that a record was sealed by the quorum. A signature meant for a third party, such as a code signature, a certificate or a document signature, uses a public-key algorithm from Supported algorithms, so anyone with the public key can verify it without asking the box.
Receipts
An IRON3: receipt lists, for each signing voter, its key id, counter and seal, sorted by key id. Because the counter is part of each seal, the same input sealed twice gives two different receipts, and an old receipt cannot be replayed as a new seal.
Verification
Verification asks the voters again. It succeeds when enough of the original voters confirm their seals. A receipt that names a voter the proxy does not know is rejected (UNKNOWN_KID).
Enrollment and hot swap
A new voter is not counted until it is enrolled. A missing voter is listed in missing and the quorum is judged on the rest, so a failed board can be replaced while signing continues.