FairRandom
Two-phase commit-reveal protocol for deriving a shared random Long seed.
All peers (including the local peer identified by seam's Seam.selfId) participate. Once every peer has committed and then revealed, every participant derives the same seed:
seed = first 8 bytes of SHA-256(H(id₁ ‖ secret₁) ‖ … ‖ H(idₙ ‖ secretₙ))Per-contributor inputs are hashed as SHA-256(peerId.encodeToByteArray() ‖ secret) before combination, making the seed framing self-describing: the 32-byte hash domain-separates each contributor by identity, preventing length-extension and ambiguous-split attacks. Contributors are sorted by PeerId value (lexicographic) before concatenation so the result is deterministic regardless of reveal-arrival order.
Abort resistance
A last-mover peer can observe all other peers' reveals before deciding whether to reveal, allowing it to abort when the outcome is unfavourable. This is a known limitation of the basic commit-reveal protocol. Applications enforce forfeit semantics for abort at the game layer. Full abort-resistance requires threshold signatures or a VRF — out of scope here.
If the seam collapses mid-round — a transport tear (the seam reaches the terminal us.tractat.kuilt.core.SeamState.Torn) OR a membership drain (a required participant leaves the live peer set while the seam stays Woven) — the missing commit/reveal never arrives. roll detects both via us.tractat.kuilt.core.raceCollapse and throws SeamCollapsedException rather than stalling, so no outer timeout is required. (A peer that stays connected but simply withholds its reveal is a distinct, application-level concern — see "Abort resistance" above — and still requires game-layer forfeit handling.)
Usage
val fairRandom = FairRandom(seam, setOf(aliceId, bobId))
val seed: Long = fairRandom.roll() // suspends through both phasesFairRandom is single-use: each roll is a fresh protocol run. Create a new instance for each roll if repeated rounds are needed.
Parameters
the woven seam shared with all peers.
all participant PeerIds, including seam's own Seam.selfId.