Nordhavn / specs / convene / core / KeyRing

convene / coredata

KeyRing

Purpose

A member's client-side secret store — the epoch keys they were let into, and nothing anyone else can open. At rest the ring is sealed under a key derived from the member's passkey, so the content is unreadable even with the device in hand until the passkey is presented; the keys never leave the device in the clear. The passkey ceremony itself is the boundary's carried concern — the domain owns that a ring at rest yields nothing, and opens only through the ceremony. Asking the ring for a key it does not hold is not an error — it is the access model working: an epoch you were never let into is an epoch you cannot read.

Attributes

Invariants

(none) — the one invariant this draft carried ("a ring whose state is 'sealed' yields nothing") is now two rows of the Machine below, each of which gates its operation directly. An authored invariant that restates a machine row is redundant by construction.

Operations

unlock

Produces a new KeyRing, opened — the same keys, unreadable no longer. Only the owner's passkey ceremony reaches this operation; how the passkey is presented and verified is the boundary's carried concern. Result: self

seal

Produces a new KeyRing, sealed — at rest, its keys unreadable until the owner's passkey opens it again. Result: self

add group key

Produces a new KeyRing also holding a given GroupKey. It fails when the ring is at rest — a ring at rest takes no key. Result: self

key for

Reports the GroupKey, if any, the ring holds for a given GroupRevision — that epoch's key; nothing when the owner was never let into the epoch. Result: the GroupKey the ring holds for that epoch, if any

Machine

The ring's rest/use lifecycle — every operation's legality in one table. A ring begins sealed.

fromondotoreason (refuse only)
sealedunlockpermitopened
sealedsealpermitsealed
sealedadd group keyrefusea ring at rest takes no key
sealedkey foranswer nothing
openedunlockpermitopened
openedsealpermitsealed
openedadd group keypermitopened
openedkey forpermitopened

The examples

Context: a KeyRing owned by karin.dahl holding the GroupKey "k-0163", and the GroupRevision "g-7421.3" under key id "k-0163", created by karin.dahl and naming karin.dahl among its members; a GroupKey "k-0150" for the adding rows.

Scenarios

An opened ring yields the epoch's key

Given: an opened KeyRing owned by karin.dahl holding the GroupKey "k-0163", and the GroupRevision "g-7421.3" under key id "k-0163" When: key for that revision is asked Then: the ring yields the GroupKey "k-0163"

An epoch never let into yields nothing

Given: an opened KeyRing owned by karin.dahl holding the GroupKey "k-0163", and the GroupRevision "g-7421.4" under key id "k-0264" When: key for that revision is asked Then: the ring reports nothing — an epoch she was never let into is an epoch she cannot read

An opened ring gains a key

Given: an opened KeyRing owned by anne.bruun holding the GroupKey "k-0142" When: add group key is performed with the GroupKey "k-0150" Then: the result is a ring holding both "k-0142" and "k-0150"

spoken of by