Contact Voucher Protocol Narration
Introduction
The voucher spec was very difficult for me to read and it overloads variable names and doesn’t make certain facts explicit enough. I wrote this spec for my own understanding of the design as an implementation guide for myself.
Streams
- MessageStream — Bob writes; group reads.
- ReplyStream — Alice seals; Bob reads.
- VoucherStream — the rendezvous; read+write derivable from
Voucher.
Stream Objects
- MessageStream —
.WriteCap(Bob keeps),.ReadCap(travels in payload, insideSignedPleaseAdd). - ReplyStream —
.rootSK(Bob keeps),.rootPK(travels in payload). - VoucherStream — read and write caps derivable by anyone holding the
Voucher.
Messages
SignedPleaseAdd— Bob’s signed self-introduction: a serializedPleaseAdd(hisDisplayNameandMessageStream.ReadCap) plus a signature over it produced byMessageStream.WriteCap.
// PleaseAdd is a member's request to join, carrying their display name
// and the read cap that lets others read their messages.
type PleaseAdd struct {
// DisplayName is the party's name to be displayed in chat clients.
DisplayName string
// UniversalReadCap is the BACAP read cap for this member's
// MessageStream, letting others read all messages posted by them.
UniversalReadCap *bacap.UniversalReadCap
}
// SignedPleaseAdd binds a PleaseAdd to a signature made by the member's
// write cap, so any party can verify the name-and-cap binding.
type SignedPleaseAdd struct {
// PleaseAdd contains the CBOR serialized PleaseAdd struct.
PleaseAdd []byte
// Signature contains the cryptographic signature over the PleaseAdd field.
Signature []byte
}
WhoReply:= the existing members’ read caps, sealed to Bob.Introduction:=SignedPleaseAdd + VoucherSalt, published to the group.
VoucherPayload := SignedPleaseAdd || ReplyStream.rootPK
Voucher := Hash(VoucherPayload)
Steps
- Bob creates two streams. He generates MessageStream and ReplyStream, keeping
MessageStream.WriteCapandReplyStream.rootSK. - Bob builds the voucher. He forms
SignedPleaseAdd = {DisplayName, MessageStream.ReadCap}signed byMessageStream.WriteCap; setsVoucherPayload := SignedPleaseAdd || ReplyStream.rootPK;Voucher := Hash(VoucherPayload). - Bob publishes. The
Voucherderives VoucherStream; Bob writesVoucherPayloadto box 0. - Bob → Alice (OOB). He hands over only the
Voucher. - Alice reads and verifies. From
Vouchershe derives VoucherStream, reads box 0, checksHash(VoucherPayload) == Voucher, and verifies theSignedPleaseAddsignature against its read cap’s rootPK. - Alice replies. She picks
VoucherSalt, sealsWhoReply + VoucherSalttoReplyStream.rootPK, and begins readingMessageStreamunderctx = VoucherSalt. - Alice commits (all-or-nothing COPY). In one operation: write the sealed
WhoReplyto VoucherStream box 1; publishIntroduction(SignedPleaseAdd + VoucherSalt) to her group; tombstone box 0 against reuse. - Bob finishes. He polls VoucherStream box 1, opens
WhoReplywithReplyStream.rootSK, and recoversVoucherSalt(thectx) and the members’ read caps. Both now share the live, salted streams.