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 ¶
- Constants
- Variables
- func CreateSenderKeyDistributionMessage(ctx context.Context, sender address.ProtocolAddress, distributionID [16]byte, ...) (*protocol.SenderKeyDistributionMessage, error)
- func Decrypt(ctx context.Context, sender address.ProtocolAddress, skmBytes []byte, ...) ([]byte, error)
- func Encrypt(ctx context.Context, sender address.ProtocolAddress, distributionID [16]byte, ...) (*protocol.SenderKeyMessage, error)
- func ProcessSenderKeyDistributionMessage(ctx context.Context, sender address.ProtocolAddress, ...) error
- type SenderChainKey
- type SenderKeyRecord
- func (r *SenderKeyRecord) AddSenderKeyState(messageVersion uint8, chainID uint32, iteration uint32, chainKey []byte, ...)
- func (r *SenderKeyRecord) SenderKeyState() (*SenderKeyState, bool)
- func (r *SenderKeyRecord) SenderKeyStateForChainID(chainID uint32) *SenderKeyState
- func (r *SenderKeyRecord) Serialize() ([]byte, error)
- func (r *SenderKeyRecord) StateCount() int
- type SenderKeyState
Examples ¶
Constants ¶
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 ¶
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.