session

package
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Jun 30, 2026 License: AGPL-3.0 Imports: 15 Imported by: 0

Documentation

Overview

Package session models the Double Ratchet session state and the PQXDH establishment + message cipher: SessionState (a thin wrapper over the generated proto.SessionStructure), SessionRecord (the current state plus a bounded list of archived states), ProcessPreKeyBundle / InitializeBobSession (handshake), and Encrypt / Decrypt (the message cipher). It is a pure-Go port of rust/protocol/src/state/session.rs, session.rs, and ratchet.rs, and serializes to the same SessionStructure / RecordStructure protobufs as upstream libsignal v0.91.0.

Compatibility staging: sessions negotiate at the v0.91.0 surface, which ships the Sparse Post-Quantum Ratchet (SPQR) as an optional layer. The session drives SPQR through PQRatchetSend / PQRatchetRecv (the pq_ratchet message field and pq_ratchet_state), mixing the SPQR key into the Double Ratchet message keys; min_version V0 means a peer that does not speak SPQR still interoperates (the key contribution is empty and the derivation is unchanged). See decisions/0001-spqr-staged-compat.md and the README scope matrix.

Example (SessionRoundTrip)

Example_sessionRoundTrip shows a PQXDH handshake and the first message. Bob publishes a pre-key bundle; Alice processes it and encrypts an initial message (a PreKeySignalMessage carrying the inner SignalMessage); Bob establishes his side from the same handshake material and decrypts.

Bob's session is established here via InitializeBobSession, the recipient seam: it consumes Alice's base key and the Kyber ciphertext (recovered from Alice's pending pre-key message) together with Bob's own pre-key private keys.

package main

import (
	"context"
	"crypto/rand"
	"fmt"

	"github.com/GoCodeAlone/libsignal-go/address"
	"github.com/GoCodeAlone/libsignal-go/curve"
	"github.com/GoCodeAlone/libsignal-go/kem"
	"github.com/GoCodeAlone/libsignal-go/session"
	"github.com/GoCodeAlone/libsignal-go/stores/inmem"
)

func main() {
	ctx := context.Background()

	// Small helpers so the example handles every error rather than discarding it,
	// while staying readable. In production these errors propagate normally.
	genKey := func() curve.KeyPair {
		kp, err := curve.GenerateKeyPair(rand.Reader)
		if err != nil {
			panic(err)
		}
		return kp
	}
	sign := func(signer curve.PrivateKey, msg []byte) []byte {
		sig, err := signer.CalculateSignature(rand.Reader, msg)
		if err != nil {
			panic(err)
		}
		return sig
	}

	dev, err := address.NewDeviceID(1)
	if err != nil {
		panic(err)
	}
	aliceAddr := address.NewProtocolAddress("+15551230001", dev)
	bobAddr := address.NewProtocolAddress("+15551230002", dev)

	// --- Bob's long-lived + pre-key material ---
	bobIdentity := genKey()
	bobSignedPre := genKey()
	bobOneTime := genKey()
	bobKyber, err := kem.GenerateKeyPair(kem.KeyTypeKyber1024, rand.Reader)
	if err != nil {
		panic(err)
	}

	// Bob signs his signed-pre-key and Kyber pre-key with his identity key.
	signedSig := sign(bobIdentity.PrivateKey, bobSignedPre.PublicKey.Serialize())
	kyberSig := sign(bobIdentity.PrivateKey, bobKyber.PublicKey.Serialize())

	oneTimeID := uint32(31)
	oneTimePub := bobOneTime.PublicKey
	bundle, err := session.NewPreKeyBundle(session.PreKeyBundleParams{
		RegistrationID:  4242,
		DeviceID:        1,
		PreKeyID:        &oneTimeID,
		PreKey:          &oneTimePub,
		SignedPreKeyID:  55,
		SignedPreKey:    bobSignedPre.PublicKey,
		SignedPreKeySig: signedSig,
		KyberPreKeyID:   66,
		KyberPreKey:     bobKyber.PublicKey,
		KyberPreKeySig:  kyberSig,
		IdentityKey:     bobIdentity.PublicKey,
	})
	if err != nil {
		panic(err)
	}

	// --- Alice establishes a session from Bob's bundle and encrypts ---
	aliceIdentity := genKey()
	aliceID := inmem.NewIdentityKeyStore(aliceIdentity, 1001)
	aliceSess := inmem.NewSessionStore()

	if err := session.ProcessPreKeyBundle(ctx, rand.Reader, bobAddr, bundle, aliceSess, aliceID); err != nil {
		panic(err)
	}

	plaintext := []byte("hello, bob")
	// On the first (unacknowledged) message Alice wraps the SignalMessage in a
	// PreKeySignalMessage; the inner SignalMessage is what Bob's session decrypts.
	signalMsg, preKeyMsg, err := session.Encrypt(ctx, plaintext, bobAddr, aliceSess, aliceID, nil, rand.Reader)
	if err != nil {
		panic(err)
	}
	if preKeyMsg != nil {
		signalMsg = preKeyMsg.Message()
	}

	// --- Bob establishes his side from the handshake material and decrypts ---
	// Recover Alice's base key + Kyber ciphertext from her pending pre-key state.
	aliceRec, err := aliceSess.LoadSession(ctx, bobAddr)
	if err != nil {
		panic(err)
	}
	pending, ok := aliceRec.CurrentState().PendingPreKeyMessage()
	if !ok {
		panic("alice has no pending pre-key message")
	}
	aliceBaseKey, err := curve.DeserializePublicKey(pending.BaseKey)
	if err != nil {
		panic(err)
	}

	bobState, err := session.InitializeBobSession(session.BobParams{
		OurIdentity:   bobIdentity,
		OurSignedPre:  bobSignedPre,
		OurOneTime:    &bobOneTime,
		OurKyber:      bobKyber,
		TheirIdentity: aliceIdentity.PublicKey,
		TheirBaseKey:  aliceBaseKey,
		KyberCipher:   pending.KyberCiphertext,
	})
	if err != nil {
		panic(err)
	}
	bobSess := inmem.NewSessionStore()
	if err := bobSess.StoreSession(ctx, aliceAddr, session.NewSessionRecord(bobState)); err != nil {
		panic(err)
	}

	got, err := session.Decrypt(ctx, signalMsg, aliceAddr, bobSess, rand.Reader)
	if err != nil {
		panic(err)
	}

	fmt.Println(string(got))
}
Output:
hello, bob

Index

Examples

Constants

View Source
const (
	// MaxMessageKeys bounds the skipped-message-key cache per receiver chain.
	MaxMessageKeys = 2000
	// MaxReceiverChains bounds how many receiver (DH ratchet) chains are kept.
	MaxReceiverChains = 5
	// ArchivedStatesMaxLength bounds the archived (previous) session list.
	ArchivedStatesMaxLength = 40
)

Bounds from rust/protocol/src/consts.rs. These cap unbounded growth of the receiver-chain list, the per-chain skipped-message-key cache, and the archived-states list, matching upstream eviction exactly.

View Source
const MaxForwardJumps = 25000

MaxForwardJumps caps how many message keys a receive may skip ahead in one chain before rejecting the message (MAX_FORWARD_JUMPS in consts.rs). It bounds work and the skipped-key cache growth from a forged far-future counter.

View Source
const MaxUnacknowledgedSessionAge = 30 * 24 * time.Hour

MaxUnacknowledgedSessionAge is how long an initiator session may sit with an unacknowledged pre-key message before encrypting to it fails as stale (MAX_UNACKNOWLEDGED_SESSION_AGE in consts.rs: 30 days).

Variables

View Source
var (
	// ErrUntrustedIdentity is returned when the remote identity key is not
	// trusted for the address per the IdentityKeyStore.
	ErrUntrustedIdentity = errors.New("session: untrusted identity")
	// ErrInvalidSignature is returned when a signed pre-key or Kyber pre-key
	// signature fails to verify under the remote identity key.
	ErrInvalidSignature = errors.New("session: pre-key signature verification failed")
	// ErrNoKyberPreKey is returned when a bundle or pre-key message lacks the
	// Kyber pre-key material required at the v4 protocol surface.
	ErrNoKyberPreKey = errors.New("session: missing Kyber pre-key")
	// ErrInvalidPreKeyBundle is returned for a structurally invalid bundle
	// (e.g. a one-time pre-key id without its key, or vice versa).
	ErrInvalidPreKeyBundle = errors.New("session: invalid pre-key bundle")
	// ErrInvalidKey is returned when supplied key material fails to deserialize
	// or is otherwise unusable.
	ErrInvalidKey = errors.New("session: invalid key material")
)

Errors returned by session establishment. All are %w-wrappable and errors.Is-matchable.

View Source
var (
	// ErrSessionNotFound is returned when no usable session exists for the
	// address (none stored, or the unacknowledged session is stale).
	ErrSessionNotFound = errors.New("session: no session for address")
	// ErrDuplicateMessage is returned when a message's counter has already been
	// decrypted (its message keys are no longer cached).
	ErrDuplicateMessage = errors.New("session: duplicate message")
	// ErrInvalidMessage is returned for a structurally valid but undecryptable
	// message (MAC failure, too-far-future counter, corrupt body).
	ErrInvalidMessage = errors.New("session: invalid message")
)

Cipher errors. All are %w-wrappable and errors.Is-matchable.

Functions

func Decrypt

func Decrypt(
	ctx context.Context,
	ciphertext *protocol.SignalMessage,
	remoteAddress address.ProtocolAddress,
	sessionStore Store,
	rng io.Reader,
) ([]byte, error)

Decrypt decrypts a SignalMessage from remoteAddress against the stored session, using the clone-then-commit discipline: the session state is mutated on a clone and only persisted if decryption succeeds, so a failed decrypt leaves the stored record byte-identical. Mirrors message_decrypt_signal + try_decrypt_from_record (current state only — previous-session fallback is a later refinement; PreKey messages always target the current state).

func Encrypt

func Encrypt(
	ctx context.Context,
	plaintext []byte,
	remoteAddress address.ProtocolAddress,
	sessionStore Store,
	identityStore stores.IdentityKeyStore,
	clock Clock,
	rng io.Reader,
) (*protocol.SignalMessage, *protocol.PreKeySignalMessage, error)

Encrypt encrypts plaintext for remoteAddress using the stored session, advancing the sending chain by one step and persisting the mutated session. While the session's pre-key message is unacknowledged it returns a PreKeySignalMessage (wrapping the SignalMessage); afterward a plain SignalMessage. A stale unacknowledged session (older than MaxUnacknowledgedSessionAge) yields ErrSessionNotFound.

Mirrors message_encrypt: derive message keys from the sender chain, AES-256- CBC encrypt, build the (pre-key) signal message MAC'd over the identities and versioned body, then advance the sender chain key and store.

func ProcessPreKeyBundle

func ProcessPreKeyBundle(
	ctx context.Context,
	rng io.Reader,
	remoteAddress address.ProtocolAddress,
	bundle *PreKeyBundle,
	sessionStore Store,
	identityStore stores.IdentityKeyStore,
) error

ProcessPreKeyBundle performs the initiator (Alice) side of session establishment from a recipient's PreKeyBundle, mirroring session::process_prekey_bundle. On success it stores a fresh alice session (with the unacknowledged pre-key message + Kyber ciphertext recorded) under remoteAddress and saves the recipient's identity.

Steps (in upstream order): trust-check the identity, verify the signed pre-key and Kyber pre-key signatures under that identity, run the PQXDH initiator agreement (4 DH + Kyber encapsulation), initialize the Double Ratchet alice session, record the pending pre-key state + registration ids, save the identity, and store the session.

Types

type BobParams

type BobParams struct {
	OurIdentity   curve.KeyPair
	OurSignedPre  curve.KeyPair
	OurOneTime    *curve.KeyPair // optional; present iff the message used a one-time pre-key
	OurKyber      kem.KeyPair
	TheirIdentity curve.PublicKey
	TheirBaseKey  curve.PublicKey
	KyberCipher   []byte
}

BobParams carries the resolved key material for the recipient agreement. The session cipher (T18) supplies these from the recipient's stores plus the incoming PreKeySignalMessage's base key and Kyber ciphertext.

type Clock

type Clock func() time.Time

Clock returns the current time; injectable so tests can drive the stale-unacknowledged-session check deterministically.

type PendingPreKeyMessage

type PendingPreKeyMessage struct {
	PreKeyID        *uint32 // nil when no one-time pre-key was used
	SignedPreKeyID  uint32
	KyberPreKeyID   *uint32 // nil when no Kyber pre-key is pending (should not happen at v4)
	KyberCiphertext []byte
	BaseKey         []byte
	UnixSeconds     uint64
}

PendingPreKeyMessage is the unacknowledged pre-key message state an initiator session carries until the recipient's first reply: the optional one-time pre-key id, the signed pre-key id, the Kyber pre-key id + ciphertext, the initiator's base public key, and the creation time in Unix seconds.

type PreKeyBundle

type PreKeyBundle struct {
	// contains filtered or unexported fields
}

PreKeyBundle is the set of public key material a server hands an initiator so it can start a session with a recipient without the recipient online: the recipient's registration id, device id, identity key, a signed pre-key (with its XEdDSA signature), a signed Kyber pre-key (with its signature), and an optional one-time EC pre-key. Mirrors PreKeyBundle in rust/protocol/src/state/bundle.rs.

At the v4 protocol surface the Kyber pre-key is mandatory; a bundle without one is rejected at processing time (ErrNoKyberPreKey).

func NewPreKeyBundle

func NewPreKeyBundle(p PreKeyBundleParams) (*PreKeyBundle, error)

NewPreKeyBundle assembles a PreKeyBundle from its parts. The Kyber pre-key is required (v4); the one-time EC pre-key is optional (set both PreKey and PreKeyID, or neither).

func (*PreKeyBundle) DeviceID

func (b *PreKeyBundle) DeviceID() uint32

DeviceID returns the recipient's device id.

func (*PreKeyBundle) IdentityKey

func (b *PreKeyBundle) IdentityKey() curve.PublicKey

IdentityKey returns the recipient's identity public key.

func (*PreKeyBundle) KyberPreKey

func (b *PreKeyBundle) KyberPreKey() kem.PublicKey

KyberPreKey returns the Kyber pre-key public key.

func (*PreKeyBundle) KyberPreKeyID

func (b *PreKeyBundle) KyberPreKeyID() uint32

KyberPreKeyID returns the Kyber pre-key id.

func (*PreKeyBundle) KyberPreKeySignature

func (b *PreKeyBundle) KyberPreKeySignature() []byte

KyberPreKeySignature returns the XEdDSA signature over the serialized Kyber pre-key, produced by the recipient's identity key.

func (*PreKeyBundle) PreKey

func (b *PreKeyBundle) PreKey() (uint32, curve.PublicKey, bool)

PreKey returns the optional one-time EC pre-key (id, public key) and whether one is present.

func (*PreKeyBundle) RegistrationID

func (b *PreKeyBundle) RegistrationID() uint32

RegistrationID returns the recipient's registration id.

func (*PreKeyBundle) SignedPreKey

func (b *PreKeyBundle) SignedPreKey() curve.PublicKey

SignedPreKey returns the signed pre-key public key.

func (*PreKeyBundle) SignedPreKeyID

func (b *PreKeyBundle) SignedPreKeyID() uint32

SignedPreKeyID returns the signed pre-key id.

func (*PreKeyBundle) SignedPreKeySignature

func (b *PreKeyBundle) SignedPreKeySignature() []byte

SignedPreKeySignature returns the XEdDSA signature over the serialized signed pre-key, produced by the recipient's identity key.

type PreKeyBundleParams

type PreKeyBundleParams struct {
	RegistrationID uint32
	DeviceID       uint32

	// PreKey is the optional one-time EC pre-key. When set, PreKeyID must also
	// be set; leave PreKey nil to omit the one-time pre-key.
	PreKeyID *uint32
	PreKey   *curve.PublicKey

	SignedPreKeyID  uint32
	SignedPreKey    curve.PublicKey
	SignedPreKeySig []byte

	KyberPreKeyID  uint32
	KyberPreKey    kem.PublicKey
	KyberPreKeySig []byte

	IdentityKey curve.PublicKey
}

PreKeyBundleParams carries the fields for NewPreKeyBundle. A nil PreKey/ PreKeyID (both must agree) means no one-time pre-key is offered.

type SessionRecord

type SessionRecord struct {
	// contains filtered or unexported fields
}

SessionRecord is the persisted unit for a peer: the current SessionState plus a bounded list of archived (previous) sessions, each stored as the serialized SessionStructure bytes. Mirrors SessionRecord in rust/protocol/src/state/session.rs; it serializes to a RecordStructure proto.

func DeserializeSessionRecord

func DeserializeSessionRecord(b []byte) (*SessionRecord, error)

DeserializeSessionRecord decodes a RecordStructure protobuf into a SessionRecord (SessionRecord::deserialize). Malformed input returns an error and never panics. Archived session bytes are retained opaquely; they are only decoded on demand (PreviousStates / PromoteOldSession).

func NewFreshSessionRecord

func NewFreshSessionRecord() *SessionRecord

NewFreshSessionRecord returns a record with no current session and no archives (SessionRecord::new_fresh).

func NewSessionRecord

func NewSessionRecord(state *SessionState) *SessionRecord

NewSessionRecord returns a record whose current session is the given state (SessionRecord::new).

func (*SessionRecord) ArchiveCurrentState

func (r *SessionRecord) ArchiveCurrentState() error

ArchiveCurrentState moves the current session into the archived list (SessionRecord::archive_current_state). It is a no-op when the current session is already absent. The current session is encoded and prepended to the archive; when the archive is already at ArchivedStatesMaxLength the oldest (tail) entry is dropped first, matching upstream's pop-then-insert(0).

Before encoding, the unacknowledged pre-key message (pending pre-key and pending Kyber pre-key) is cleared, matching archive_current_state_inner -> clear_unacknowledged_pre_key_message in session.rs: an archived session must not retain pending pre-key state.

func (*SessionRecord) CurrentState

func (r *SessionRecord) CurrentState() *SessionState

CurrentState returns the current SessionState, or nil if the record is fresh.

func (*SessionRecord) HasCurrentState

func (r *SessionRecord) HasCurrentState() bool

HasCurrentState reports whether a current session is set.

func (*SessionRecord) PreviousSessionCount

func (r *SessionRecord) PreviousSessionCount() int

PreviousSessionCount returns the number of archived sessions.

func (*SessionRecord) PreviousStates

func (r *SessionRecord) PreviousStates() ([]*SessionState, error)

PreviousStates decodes and returns the archived SessionStates, newest first. A malformed archived entry returns an error.

func (*SessionRecord) PromoteOldSession

func (r *SessionRecord) PromoteOldSession(oldIndex int) error

PromoteOldSession moves the archived session at index oldIndex to be the current session, archiving whatever is currently current (SessionRecord::promote_old_session). oldIndex is into the newest-first archived list.

func (*SessionRecord) PromoteState

func (r *SessionRecord) PromoteState(newState *SessionState) error

PromoteState archives the current session (if any) and installs newState as the new current session (SessionRecord::promote_state).

func (*SessionRecord) Serialize

func (r *SessionRecord) Serialize() ([]byte, error)

Serialize encodes the record to its RecordStructure protobuf bytes (SessionRecord::serialize). The archived sessions are passed through verbatim (they are already-encoded SessionStructure bytes).

func (*SessionRecord) SetCurrentState

func (r *SessionRecord) SetCurrentState(state *SessionState)

SetCurrentState replaces the current session without archiving the previous one. Use PromoteState to archive-then-replace.

type SessionState

type SessionState struct {
	// contains filtered or unexported fields
}

SessionState wraps a *proto.SessionStructure. It does not re-model the session; every field lives in the proto so serialization is exactly the upstream wire form. A nil structure is never valid for an initialized state; constructors always allocate one.

func InitializeBobSession

func InitializeBobSession(p BobParams) (*SessionState, error)

InitializeBobSession performs the recipient (Bob) side of the PQXDH agreement and Double Ratchet bob init (ratchet::initialize_bob_session, minus SPQR per ADR 0001 Stage 1). It is the seam the decrypt path (T18) calls once it has resolved the recipient's pre-keys from its stores and the initiator's base key + Kyber ciphertext from the PreKeySignalMessage. The returned state has no receiver chain (the first incoming message establishes it) and a sender chain off the recipient's signed pre-key.

func NewEmptySessionState

func NewEmptySessionState() *SessionState

NewEmptySessionState returns a state backed by a freshly allocated, zero-value SessionStructure.

func NewSessionState

func NewSessionState(s *proto.SessionStructure) *SessionState

NewSessionState wraps an existing SessionStructure. The structure is taken by reference (not copied); callers that need isolation should Clone first.

A nil structure is replaced with a freshly allocated zero-value one, so the returned state is always backed by a non-nil structure and every method stays panic-free (a setter on a nil structure would otherwise panic). Callers passing a real structure are unaffected.

func (*SessionState) AddReceiverChain

func (s *SessionState) AddReceiverChain(senderRatchetKey curve.PublicKey, chainKey ratchet.ChainKey)

AddReceiverChain appends a receiving chain for the given remote ratchet key, evicting the oldest chain when the count exceeds MaxReceiverChains (SessionState::add_receiver_chain: push then remove(0) over the cap).

func (*SessionState) AliceBaseKey

func (s *SessionState) AliceBaseKey() []byte

AliceBaseKey returns the recorded Alice base key (used to match sessions).

func (*SessionState) CacheMessageKeys

func (s *SessionState) CacheMessageKeys(senderRatchetKey curve.PublicKey, gen ratchet.MessageKeyGenerator) error

CacheMessageKeys inserts a skipped message's key generator at the front of the matching receiver chain's cache, evicting the oldest (tail) when the count exceeds MaxMessageKeys (SessionState::set_message_keys: insert(0) then pop over cap). The generator is stored in its proto form — the SEED for a deferred (Seed) generator (so the Sparse Post-Quantum Ratchet key for that specific skipped message can be mixed in when it later arrives) or the derived keys for a materialized (Keys) generator. Mirrors MessageKeyGenerator::into_pb.

func (*SessionState) ClearUnacknowledgedPreKeyMessage

func (s *SessionState) ClearUnacknowledgedPreKeyMessage()

ClearUnacknowledgedPreKeyMessage clears the pending pre-key and pending Kyber pre-key, mirroring SessionState::clear_unacknowledged_pre_key_message in rust/protocol/src/state/session.rs. Upstream calls this when archiving a session (archive_current_state_inner) so an archived state never retains an unacknowledged pre-key message; it carries an IMPORTANT banner reminding that any future pending field must be cleared here too.

func (*SessionState) Clone

func (s *SessionState) Clone() *SessionState

Clone returns a deep copy of the state (independent proto).

func (*SessionState) LocalIdentityPublic

func (s *SessionState) LocalIdentityPublic() []byte

LocalIdentityPublic returns the bound local identity public key bytes.

func (*SessionState) LocalRegistrationID

func (s *SessionState) LocalRegistrationID() uint32

LocalRegistrationID returns the local registration id.

func (*SessionState) PQRatchetRecv

func (s *SessionState) PQRatchetRecv(msg []byte) (key []byte, err error)

PQRatchetRecv advances the SPQR receive ratchet one step from an inbound SPQR message (the SignalMessage pq_ratchet field), returning the SPQR message key to mix into the Double Ratchet message keys and updating the stored SPQR state in place. A nil/empty returned key means SPQR contributed no key. Mirrors SessionState::pq_ratchet_recv (spqr::recv).

func (*SessionState) PQRatchetSend

func (s *SessionState) PQRatchetSend(rng io.Reader) (msg []byte, key []byte, err error)

PQRatchetSend advances the SPQR send ratchet one step: it produces the outbound SPQR message to attach to the ciphertext (the SignalMessage pq_ratchet field) and the SPQR message key to mix into the Double Ratchet message keys, updating the stored SPQR state in place. A nil/empty returned key means SPQR contributed no key this message (V0 / still negotiating), in which case the message-key derivation is unchanged. Mirrors SessionState::pq_ratchet_send (spqr::send). rng must be a CSPRNG.

func (*SessionState) PQRatchetState

func (s *SessionState) PQRatchetState() []byte

PQRatchetState returns the serialized Sparse Post-Quantum Ratchet (SPQR) state bytes. The session layer drives it through PQRatchetSend / PQRatchetRecv; it is also preserved verbatim through serialize/deserialize.

func (*SessionState) PendingPreKey

func (s *SessionState) PendingPreKey() bool

PendingPreKey reports whether an unacknowledged pre-key message is pending (either the X25519 pending pre-key or the Kyber pending pre-key is set).

func (*SessionState) PendingPreKeyMessage

func (s *SessionState) PendingPreKeyMessage() (PendingPreKeyMessage, bool)

PendingPreKeyMessage returns the unacknowledged pre-key message state and whether one is pending. The EC pending pre-key record is the sole gate: when it is absent there is no pending message (ok=false). The Kyber pending record is OPTIONAL and read opportunistically — its id and ciphertext fill in only when present. This mirrors upstream SessionState:: unacknowledged_pre_key_message_items (rust/protocol/src/state/session.rs:536), which keys solely on `pending_pre_key` and passes `pending_kyber_pre_key` as an Option (so kyber_pre_key_id is Option<KyberPreKeyId>). At v4 the Kyber record is expected present (PreKeyID nil "should not happen at v4"), but its absence is not treated as "no pending message" here, matching upstream.

func (*SessionState) PreviousCounter

func (s *SessionState) PreviousCounter() uint32

PreviousCounter returns the previous sending-chain counter.

func (*SessionState) ReceiverChainKey

func (s *SessionState) ReceiverChainKey(senderRatchetKey curve.PublicKey) (ratchet.ChainKey, bool, error)

ReceiverChainKey returns the chain key for the receiver chain matching the given ratchet key, and whether such a chain exists.

func (*SessionState) RemoteIdentityPublic

func (s *SessionState) RemoteIdentityPublic() []byte

RemoteIdentityPublic returns the bound remote identity public key bytes.

func (*SessionState) RemoteRegistrationID

func (s *SessionState) RemoteRegistrationID() uint32

RemoteRegistrationID returns the remote registration id.

func (*SessionState) RootKey

func (s *SessionState) RootKey() []byte

RootKey returns the current root key bytes (may be nil on a fresh state).

func (*SessionState) SenderChainKey

func (s *SessionState) SenderChainKey() (ratchet.ChainKey, error)

SenderChainKey returns the current sending chain key, or an error if no sender chain is set or its chain key is malformed.

func (*SessionState) SenderRatchetKey

func (s *SessionState) SenderRatchetKey() (curve.PublicKey, error)

SenderRatchetKey returns the public sending ratchet key, or an error if unset.

func (*SessionState) SenderRatchetKeyPair

func (s *SessionState) SenderRatchetKeyPair() (curve.KeyPair, error)

SenderRatchetKeyPair returns the local sending ratchet key pair (public + private), needed to compute the next DH ratchet step on the receive path. It errors if no sender chain is set or its keys are malformed.

func (*SessionState) SessionVersion

func (s *SessionState) SessionVersion() uint32

SessionVersion reports the session/ciphertext version.

func (*SessionState) SetAliceBaseKey

func (s *SessionState) SetAliceBaseKey(key []byte)

SetAliceBaseKey records the Alice base key used to match sessions.

func (*SessionState) SetKyberCiphertext

func (s *SessionState) SetKyberCiphertext(ciphertext []byte)

SetKyberCiphertext stores the initiator's Kyber ciphertext as a pending Kyber pre-key, with the pre-key id left at its sentinel until SetUnacknowledgedKyberPreKeyID sets the real id (mirrors SessionState::set_kyber_ciphertext, which uses u32::MAX as the placeholder).

func (*SessionState) SetLocalIdentityPublic

func (s *SessionState) SetLocalIdentityPublic(pk curve.PublicKey)

SetLocalIdentityPublic stores the bound local identity public key.

func (*SessionState) SetLocalRegistrationID

func (s *SessionState) SetLocalRegistrationID(id uint32)

SetLocalRegistrationID sets the local registration id.

func (*SessionState) SetPQRatchetState

func (s *SessionState) SetPQRatchetState(b []byte)

SetPQRatchetState stores the serialized SPQR state bytes.

func (*SessionState) SetPreviousCounter

func (s *SessionState) SetPreviousCounter(c uint32)

SetPreviousCounter sets the previous sending-chain counter.

func (*SessionState) SetReceiverChainKey

func (s *SessionState) SetReceiverChainKey(senderRatchetKey curve.PublicKey, chainKey ratchet.ChainKey) error

SetReceiverChainKey replaces the chain key on the matching receiver chain.

func (*SessionState) SetRemoteIdentityPublic

func (s *SessionState) SetRemoteIdentityPublic(pk curve.PublicKey)

SetRemoteIdentityPublic stores the bound remote identity public key.

func (*SessionState) SetRemoteRegistrationID

func (s *SessionState) SetRemoteRegistrationID(id uint32)

SetRemoteRegistrationID sets the remote registration id.

func (*SessionState) SetRootKey

func (s *SessionState) SetRootKey(rk ratchet.RootKey)

SetRootKey stores the root key bytes.

func (*SessionState) SetSenderChain

func (s *SessionState) SetSenderChain(senderRatchet curve.KeyPair, chainKey ratchet.ChainKey)

SetSenderChain installs the sending chain from the local ratchet key pair and its chain key (SessionState::set_sender_chain). The public + private ratchet key are both stored (the local sender owns the private key).

func (*SessionState) SetSenderChainKey

func (s *SessionState) SetSenderChainKey(chainKey ratchet.ChainKey) error

SetSenderChainKey replaces the sending chain key, keeping the ratchet keys.

func (*SessionState) SetSessionVersion

func (s *SessionState) SetSessionVersion(v uint32)

SetSessionVersion sets the session/ciphertext version.

func (*SessionState) SetUnacknowledgedKyberPreKeyID

func (s *SessionState) SetUnacknowledgedKyberPreKeyID(id uint32) error

SetUnacknowledgedKyberPreKeyID sets the pending Kyber pre-key id, which must already have been created by SetKyberCiphertext. Mirrors SessionState::set_unacknowledged_kyber_pre_key_id.

func (*SessionState) SetUnacknowledgedPreKeyMessage

func (s *SessionState) SetUnacknowledgedPreKeyMessage(preKeyID *uint32, signedPreKeyID uint32, baseKey curve.PublicKey, unixSeconds uint64)

SetUnacknowledgedPreKeyMessage records the pending (unacknowledged) pre-key message on an initiator session: the optional one-time pre-key id, the signed pre-key id, the initiator's base (ephemeral) public key, and the creation time in whole seconds since the Unix epoch. Mirrors SessionState::set_unacknowledged_pre_key_message in session.rs.

func (*SessionState) Structure

func (s *SessionState) Structure() *proto.SessionStructure

Structure returns the underlying proto. Mutating it mutates the state.

func (*SessionState) TakeMessageKeys

func (s *SessionState) TakeMessageKeys(senderRatchetKey curve.PublicKey, index uint32) (ratchet.MessageKeyGenerator, bool, error)

TakeMessageKeys removes and returns the cached message-key generator at the given index on the matching receiver chain, if present. The second return is false when no matching cached entry exists. Mirrors get_message_keys + MessageKeyGenerator::from_pb (the caller derives the final keys, mixing in the per-message SPQR key).

func (*SessionState) UnacknowledgedKyberCiphertext

func (s *SessionState) UnacknowledgedKyberCiphertext() ([]byte, bool)

UnacknowledgedKyberCiphertext returns the pending Kyber ciphertext (the initiator's KEM ciphertext, to be relayed in the PreKeySignalMessage), and whether one is pending.

type Store

type Store interface {
	// LoadSession returns the session record for address, or (nil, nil) when no
	// session is stored — mirroring upstream's Option<SessionRecord> return,
	// where a nil record means "absent" rather than an error.
	LoadSession(ctx context.Context, address address.ProtocolAddress) (*SessionRecord, error)

	// StoreSession sets the session record for address, overwriting any existing
	// entry. record must be non-nil; implementations reject a nil record, since a
	// nil stored record would be indistinguishable from the (nil, nil) "absent"
	// result of LoadSession (mirroring the stores.SenderKeyStore convention).
	// stores/inmem's SessionStore returns an error on a nil record.
	StoreSession(ctx context.Context, address address.ProtocolAddress, record *SessionRecord) error
}

Store is the session store interface. It lives here rather than in stores/ because it is the only store that references *SessionRecord — keeping it in stores/ would make stores/ import session/ and cycle. The remaining store interfaces (identity, pre-key, etc.) stay in stores/, which is now a leaf; stores/inmem provides an InMemSessionStore that satisfies this interface.

Jump to

Keyboard shortcuts

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