groups

package
v0.3.0 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: 12 Imported by: 0

Documentation

Overview

Package groups implements Signal's sender-key (group messaging) state: the per-sender SenderKeyState/SenderKeyRecord and the sender-key distribution message (SKDM) create/process flow. It mirrors rust/protocol/src/sender_keys.rs and the SKDM portion of rust/protocol/src/group_cipher.rs.

Example (GroupMessaging)

Example_groupMessaging shows the sender-key group flow: a sender distributes its sender key to a group member, then encrypts a message the member decrypts. Each party keeps its own SenderKeyStore keyed by (sender, distributionID).

package main

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

	"github.com/GoCodeAlone/libsignal-go/address"
	"github.com/GoCodeAlone/libsignal-go/groups"
	"github.com/GoCodeAlone/libsignal-go/stores/inmem"
)

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

	// The sender's address and a shared 16-byte distribution id for this group.
	dev, err := address.NewDeviceID(1)
	if err != nil {
		panic(err)
	}
	sender := address.NewProtocolAddress("+15551230001", dev)
	var distributionID [16]byte
	copy(distributionID[:], []byte("example-dist-001"))

	senderStore := inmem.NewSenderKeyStore()
	memberStore := inmem.NewSenderKeyStore()

	// The sender provisions its chain and produces a distribution message.
	skdm, err := groups.CreateSenderKeyDistributionMessage(ctx, sender, distributionID, senderStore, rand.Reader)
	if err != nil {
		panic(err)
	}

	// The group member processes the distribution message into its own store.
	if err := groups.ProcessSenderKeyDistributionMessage(ctx, sender, skdm, memberStore); err != nil {
		panic(err)
	}

	// The sender encrypts; the member decrypts to the identical plaintext.
	plaintext := []byte("hello, group")
	skm, err := groups.Encrypt(ctx, sender, distributionID, plaintext, senderStore, rand.Reader)
	if err != nil {
		panic(err)
	}
	got, err := groups.Decrypt(ctx, sender, skm.Serialized(), memberStore)
	if err != nil {
		panic(err)
	}

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

Index

Examples

Constants

View Source
const (
	// MaxSenderKeyStates bounds the per-record state list; adding past the cap
	// evicts the oldest. Mirrors consts::MAX_SENDER_KEY_STATES.
	MaxSenderKeyStates = 5

	// MaxForwardJumps bounds how far ahead of its current iteration a received
	// sender-key message may be before it is rejected; a larger gap would force
	// an unbounded chain-key ratchet on decrypt. Mirrors consts::MAX_FORWARD_JUMPS.
	MaxForwardJumps = 25_000
)

Variables

View Source
var (
	// ErrNoSenderKeyState is returned when no record exists for the (sender,
	// distribution) pair, or the message's chain id is not in the record.
	// Mirrors SignalProtocolError::NoSenderKeyState.
	ErrNoSenderKeyState = errors.New("groups: no sender key state for distribution")

	// ErrInvalidSenderKeySession is returned when the stored state is corrupt or
	// incomplete (missing chain key, missing/invalid signing key, or AES key/IV
	// rejected). Mirrors SignalProtocolError::InvalidSenderKeySession.
	ErrInvalidSenderKeySession = errors.New("groups: invalid sender key session")

	// ErrUnrecognizedMessageVersion is returned when a message's version does not
	// match the chain's. Mirrors SignalProtocolError::UnrecognizedMessageVersion.
	ErrUnrecognizedMessageVersion = errors.New("groups: unrecognized sender key message version")

	// ErrSignatureInvalid is returned when the message signature does not verify
	// under the chain's signing key. Mirrors
	// SignalProtocolError::SignatureValidationFailed.
	ErrSignatureInvalid = errors.New("groups: sender key signature validation failed")

	// ErrDuplicateMessage is returned when a message's iteration is in the past
	// and its message key is not cached (already consumed). Mirrors
	// SignalProtocolError::DuplicatedMessage.
	ErrDuplicateMessage = errors.New("groups: duplicate sender key message")

	// ErrInvalidMessage is returned when a message is structurally valid but
	// cannot be processed: too far into the future (beyond MaxForwardJumps) or a
	// decryption (padding) failure. Wraps protocol.ErrInvalidMessage so callers
	// matching on the shared protocol sentinel also catch it. Mirrors
	// SignalProtocolError::InvalidMessage(SenderKey, ...).
	ErrInvalidMessage = fmt.Errorf("groups: %w", protocol.ErrInvalidMessage)
)

Error sentinels returned by Encrypt/Decrypt, wrapped with %w so callers can match them with errors.Is. They mirror the relevant SignalProtocolError variants from rust/protocol/src/group_cipher.rs.

Functions

func CreateSenderKeyDistributionMessage

func CreateSenderKeyDistributionMessage(
	ctx context.Context,
	sender address.ProtocolAddress,
	distributionID [16]byte,
	store stores.SenderKeyStore,
	rng io.Reader,
) (*protocol.SenderKeyDistributionMessage, error)

CreateSenderKeyDistributionMessage builds the SKDM that announces sender's sender-key chain for distributionID to other group members. If the store has no record yet, it provisions a fresh chain (random 31-bit chain id, random 32-byte chain key at iteration 0, fresh signing key pair) and persists it before building the message; otherwise it reuses the existing head state. Mirrors group_cipher.rs create_sender_key_distribution_message.

rng supplies the chain key and signing-key entropy (use crypto/rand.Reader in production); store holds the opaque serialized SenderKeyRecord.

func Decrypt

func Decrypt(
	ctx context.Context,
	sender address.ProtocolAddress,
	skmBytes []byte,
	store stores.SenderKeyStore,
) ([]byte, error)

Decrypt parses, authenticates, and decrypts a serialized SenderKeyMessage from sender, returning the plaintext. It handles out-of-order delivery via the per-state skipped-message-key cache (bounded by MaxForwardJumps ahead and maxMessageKeys retained), rejects replays and signature failures, and persists the advanced state. Mirrors group_cipher.rs group_decrypt.

func Encrypt

func Encrypt(
	ctx context.Context,
	sender address.ProtocolAddress,
	distributionID [16]byte,
	plaintext []byte,
	store stores.SenderKeyStore,
	rng io.Reader,
) (*protocol.SenderKeyMessage, error)

Encrypt produces a signed SenderKeyMessage for plaintext on sender's sender-key chain for distributionID, advancing the chain by one and persisting it. The chain must already exist in store (created by CreateSenderKeyDistributionMessage). Mirrors group_cipher.rs group_encrypt.

rng supplies the XEdDSA signing nonce (use crypto/rand.Reader in production).

func ProcessSenderKeyDistributionMessage

func ProcessSenderKeyDistributionMessage(
	ctx context.Context,
	sender address.ProtocolAddress,
	skdm *protocol.SenderKeyDistributionMessage,
	store stores.SenderKeyStore,
) error

ProcessSenderKeyDistributionMessage records the sender-key chain announced by skdm into the store under (sender, skdm.DistributionID). The receiver stores only the public signing key (no private key). Mirrors group_cipher.rs process_sender_key_distribution_message.

Types

type SenderChainKey

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

SenderChainKey is one link in a sender chain: an iteration counter and the 32-byte chain key seed. It advances by HMAC and derives per-message keys. Mirrors SenderChainKey in sender_keys.rs.

func (SenderChainKey) Iteration

func (c SenderChainKey) Iteration() uint32

Iteration returns the chain key's iteration counter.

func (SenderChainKey) Seed

func (c SenderChainKey) Seed() []byte

Seed returns the 32-byte chain key material. The slice aliases internal state; callers must not mutate it.

type SenderKeyRecord

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

SenderKeyRecord is the persisted unit for a (sender, distribution) pair: an ordered list of SenderKeyStates, newest first, capped at MaxSenderKeyStates. It serializes to a SenderKeyRecordStructure proto. Mirrors SenderKeyRecord in sender_keys.rs.

func DeserializeSenderKeyRecord

func DeserializeSenderKeyRecord(b []byte) (*SenderKeyRecord, error)

DeserializeSenderKeyRecord decodes a SenderKeyRecordStructure protobuf into a SenderKeyRecord. Malformed input returns an error and never panics.

func NewSenderKeyRecord

func NewSenderKeyRecord() *SenderKeyRecord

NewSenderKeyRecord returns an empty record.

func (*SenderKeyRecord) AddSenderKeyState

func (r *SenderKeyRecord) AddSenderKeyState(
	messageVersion uint8,
	chainID uint32,
	iteration uint32,
	chainKey []byte,
	signatureKey curve.PublicKey,
	signaturePrivate *curve.PrivateKey,
)

AddSenderKeyState inserts a state for (chainID, signatureKey) at the front (most recent), capped at MaxSenderKeyStates. Mirrors SenderKeyRecord::add_sender_key_state:

  • if a state with the same (chainID, signatureKey) already exists, it is removed and reused unchanged (preserving its chain key), then moved to the front — so re-processing an SKDM does not reset the chain;
  • any other states sharing chainID (different signing key) are dropped;
  • once at the cap, the oldest (tail) state is evicted before inserting.

func (*SenderKeyRecord) SenderKeyState

func (r *SenderKeyRecord) SenderKeyState() (*SenderKeyState, bool)

SenderKeyState returns the head (most recent) state, or ok=false if the record is empty. Mirrors SenderKeyRecord::sender_key_state.

func (*SenderKeyRecord) SenderKeyStateForChainID

func (r *SenderKeyRecord) SenderKeyStateForChainID(chainID uint32) *SenderKeyState

SenderKeyStateForChainID returns the state matching chainID, or nil if none. Mirrors SenderKeyRecord::sender_key_state_for_chain_id.

func (*SenderKeyRecord) Serialize

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

Serialize encodes the record to its SenderKeyRecordStructure protobuf bytes. Mirrors SenderKeyRecord::serialize.

func (*SenderKeyRecord) StateCount

func (r *SenderKeyRecord) StateCount() int

StateCount returns the number of states in the record.

type SenderKeyState

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

SenderKeyState wraps a single sender-key state proto: a chain key plus a signing key (public always, private only for the local sender). Mirrors SenderKeyState in sender_keys.rs.

Like session.SessionState, it stores its secrets in the wrapped proto rather than in redacting wrapper types; the proto-backed state is the codebase's accepted exception to the chain/root-key redaction convention.

func (*SenderKeyState) ChainID

func (s *SenderKeyState) ChainID() uint32

ChainID returns the state's chain id.

func (*SenderKeyState) ChainKey

func (s *SenderKeyState) ChainKey() (SenderChainKey, bool)

ChainKey returns the current sender chain key, or ok=false if the state has none. Mirrors SenderKeyState::sender_chain_key.

func (*SenderKeyState) MessageVersion

func (s *SenderKeyState) MessageVersion() uint32

MessageVersion returns the state's SenderKey message version, mapping a stored 0 to 3 (the first SenderKey version), matching upstream.

func (*SenderKeyState) SigningKeyPrivate

func (s *SenderKeyState) SigningKeyPrivate() (curve.PrivateKey, bool)

SigningKeyPrivate returns the signing private key, or ok=false if it is absent (receiver-side states carry no private key) or malformed.

func (*SenderKeyState) SigningKeyPublic

func (s *SenderKeyState) SigningKeyPublic() (curve.PublicKey, bool)

SigningKeyPublic returns the signing public key, or ok=false if it is missing or malformed.

Jump to

Keyboard shortcuts

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