conformance

package
v0.24.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 23, 2026 License: AGPL-3.0 Imports: 10 Imported by: 0

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

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

func (r Receiver) InboundSession(peer string) (*ratchet.Session, error)

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) Session

func (r Receiver) Session(peer string) (*ratchet.Session, error)

Session unmarshals the stored session for peer, or nil if there is none.

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.

func Generate

func Generate() ([]Vector, error)

Generate builds every vector from scratch. Called by the -update path of TestVectorsAreGenerated; the committed testdata is what tests actually run against, so regeneration is a deliberate act.

func Load

func Load(dir string) ([]Vector, error)

Load reads every *.json vector from dir, sorted by filename so a failing run is reproducible.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL