Documentation
¶
Overview ¶
Package conformance holds shared receive-path test vectors for Freizone clients (SRV-23, see docs/design/23-shared-client-core.md).
The protocol is currently implemented twice -- cmd/devclient here, and freizone-app's Dart state layer -- on top of the same pkg/ratchet and pkg/wire. The cryptography therefore cannot diverge; the *decisions* around it can, and they are what a vector pins: what a receiver does with an envelope it cannot decrypt, with a redelivered X3DH initial, with a prekey block that arrives while a session already exists.
Expectations here are authored from docs/PROTOCOL.md, deliberately NOT recorded from either implementation. Recording would enshrine whichever behaviour is wrong; authoring means a vector can fail on both sides at once and still be right. A failing vector is therefore a claim about the implementation, not about the vector.
Index ¶
- type Expect
- type Outcome
- type Receiver
- func (r Receiver) DHIdentityKey() (*ecdh.PrivateKey, error)
- func (r Receiver) InboundSession(peer string) (*ratchet.Session, error)
- func (r Receiver) OneTimePrekey(id uint32) (*ecdh.PrivateKey, error)
- func (r Receiver) Session(peer string) (*ratchet.Session, error)
- func (r Receiver) SignedPrekey() (*ecdh.PrivateKey, error)
- type SessionEffect
- type Step
- type Vector
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Expect ¶
type Expect struct {
Outcome Outcome `json:"outcome"`
// Text is the decoded v1 message text, asserted when Outcome is
// OutcomeDecrypted.
Text string `json:"text,omitempty"`
// FailureCode is ratchet.FailureCode's classification of the failure. An
// implementation that wraps the ratchet error and loses the code cannot
// satisfy this, which is itself worth failing on: the code is what tells a
// duplicate apart from a real desync.
FailureCode string `json:"failure_code,omitempty"`
// CountsAsDesyncEvidence: whether this step should push the receiver
// toward an automatic re-key (SRV-03). A duplicate must not.
CountsAsDesyncEvidence *bool `json:"counts_as_desync_evidence,omitempty"`
// SessionEffect on the session used for sending.
SessionEffect SessionEffect `json:"session_effect,omitempty"`
// InboundSessionKept: whether a read-only session was retained for this
// peer after the step.
InboundSessionKept *bool `json:"inbound_session_kept,omitempty"`
// OneTimePrekeysRemaining pins the pool size. A responder attempt that
// fails must not burn a prekey, or a redelivered initial drains the pool.
OneTimePrekeysRemaining *int `json:"one_time_prekeys_remaining,omitempty"`
}
Expect is the observable decision surface. Pointer fields are assertions only when set, so a vector can pin one effect without claiming anything about the others.
type Outcome ¶
type Outcome string
Outcome is what processing one envelope produced for the receiver.
const ( // OutcomeDecrypted: plaintext was recovered and is new. OutcomeDecrypted Outcome = "decrypted" // OutcomeDuplicate: this exact envelope was already processed. Nothing is // wrong -- delivery is at-least-once -- so it must neither be retried nor // counted as evidence of a desync, and the ratchet must not advance again. OutcomeDuplicate Outcome = "duplicate" // OutcomeUndecryptable: no session could read it. Whether that is evidence // of a desync is a separate question, see Expect.CountsAsDesyncEvidence. OutcomeUndecryptable Outcome = "undecryptable" )
type Receiver ¶
type Receiver struct {
AccountID string `json:"account_id"`
DHIdentityPriv []byte `json:"dh_identity_priv"`
SignedPrekeyPriv []byte `json:"signed_prekey_priv"`
OneTimePrekeys map[uint32][]byte `json:"one_time_prekeys,omitempty"`
// Sessions and InboundSessions are keyed by peer account id and hold
// marshalled ratchet.Session blobs. InboundSessions are read-only ones,
// kept for messages still in flight on a session we lost a tie-break for.
Sessions map[string]json.RawMessage `json:"sessions,omitempty"`
InboundSessions map[string]json.RawMessage `json:"inbound_sessions,omitempty"`
// ProcessedMessageIDs are ids already handled. An implementation without
// this concept cannot pass the redelivery vectors, which is the point.
ProcessedMessageIDs []string `json:"processed_message_ids,omitempty"`
}
Receiver is the state an implementation must be primed with before step 1. Deliberately protocol-level rather than shaped after either client's own store, so both can map onto it.
func (Receiver) DHIdentityKey ¶
func (r Receiver) DHIdentityKey() (*ecdh.PrivateKey, error)
DHIdentityKey returns the receiver's X25519 identity private key.
func (Receiver) InboundSession ¶
InboundSession unmarshals the stored read-only session for peer, or nil.
func (Receiver) OneTimePrekey ¶
func (r Receiver) OneTimePrekey(id uint32) (*ecdh.PrivateKey, error)
OneTimePrekey returns the private key for id, or nil if the pool has no such entry -- an initial referencing a prekey we never held, or already consumed, is a normal case and not an error.
func (Receiver) SignedPrekey ¶
func (r Receiver) SignedPrekey() (*ecdh.PrivateKey, error)
SignedPrekey returns the receiver's signed prekey private key.
type SessionEffect ¶
type SessionEffect string
SessionEffect is what processing did to the session the receiver SENDS on -- specifically whether it was *replaced* by one derived from the peer's prekey block, not whether its bytes changed (an ordinary decrypt advances the ratchet and changes them every time).
Observable as the session's X3DH role: a receiver holding its own initiator session that adopts the peer's ends up with a responder session, and decrypting never changes a role. The limit of that: when both the old and the new session are responder sessions -- a redelivered initial rebuilt on top of an established one -- the role is identical either way and cannot give the replacement away. Vectors covering that case assert it functionally instead, with a later message that only still decrypts if nothing was reset.
const ( // SessionEstablished: there was no session and one was created. SessionEstablished SessionEffect = "established" // SessionAdoptedPeer: an existing session was replaced by one built from // the peer's prekey block -- either a deliberate re-key (SRV-17) or the // losing half of a simultaneous establishment. SessionAdoptedPeer SessionEffect = "adopted_peer" // SessionUnchanged: the session the receiver sends on was left alone. The // interesting case: a redelivered or stale prekey block must land here, not // on SessionAdoptedPeer. SessionUnchanged SessionEffect = "unchanged" )
type Step ¶
type Step struct {
Label string `json:"label"`
MessageID string `json:"message_id"`
SenderAccountID string `json:"sender_account_id"`
Payload json.RawMessage `json:"payload"`
Expect Expect `json:"expect"`
}
Step is one envelope handed to the receiver, plus what must come of it.
type Vector ¶
type Vector struct {
Name string `json:"name"`
Description string `json:"description"`
// Reference points at the rule being tested (a PROTOCOL.md section, a
// roadmap code), so a failure can be argued from the spec.
Reference string `json:"reference,omitempty"`
Receiver Receiver `json:"receiver"`
Steps []Step `json:"steps"`
}
Vector is one receive-path case: a receiver's starting state and an ordered list of envelopes with the decision expected for each. Steps share state -- step 2 sees what step 1 did -- which is the only way to express redelivery and duplicate handling at all.