thin

package
v0.0.56 Latest Latest
Warning

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

Go to latest
Published: Jul 15, 2025 License: AGPL-3.0 Imports: 27 Imported by: 0

Documentation

Overview

Package thin provides a lightweight client API for the Katzenpost mixnet.

Overview

The thin client package implements a client-daemon architecture where the thin client communicates with a separate client daemon process that handles the heavy cryptographic operations and mixnet protocol details. This design allows applications to integrate with Katzenpost without implementing the full complexity of the mixnet protocols.

Architecture

The thin client connects to a client daemon via TCP or Unix domain sockets. The daemon handles:

  • Sphinx packet creation and processing
  • PKI document management and validation
  • Mixnet routing and timing
  • Cryptographic operations (encryption, decryption, signatures)
  • Connection management to the mixnet

The thin client provides a simple API for:

  • Sending and receiving messages
  • Creating and managing communication channels
  • Handling events and status updates

APIs

This package provides two main APIs:

## Legacy API (deprecated for new projects)

The legacy API provides basic message sending functionality:

  • SendMessage: Send a message with optional reply capability
  • SendMessageWithoutReply: Send a fire-and-forget message
  • BlockingSendMessage: Send a message and wait for reply
  • SendReliableMessage: Send with automatic retransmission (ARQ)
  • BlockingSendReliableMessage: Reliable send with blocking reply

## Pigeonhole Channel API (recommended)

The new Pigeonhole protocol provides reliable, ordered communication channels:

  • CreateWriteChannel: Create a new channel for sending messages
  • CreateReadChannel: Create a channel for receiving messages
  • WriteChannel: Prepare a message for transmission
  • ReadChannel: Prepare a query to read the next message
  • SendChannelQuery: Send prepared queries to the mixnet
  • ResumeWriteChannel/ResumeReadChannel: Resume channels after restart

Basic Usage Example

// Load configuration
cfg, err := thin.LoadFile("thinclient.toml")
if err != nil {
	log.Fatal(err)
}

// Create and connect thin client
logging := &config.Logging{Level: "INFO"}
client := thin.NewThinClient(cfg, logging)
err = client.Dial()
if err != nil {
	log.Fatal(err)
}
defer client.Close()

// Create a communication channel (Alice side)
ctx := context.Background()
channelID, readCap, writeCap, err := client.CreateWriteChannel(ctx)
if err != nil {
	log.Fatal(err)
}

// Write a message
message := []byte("Hello, Bob!")
writeReply, err := client.WriteChannel(ctx, channelID, message)
if err != nil {
	log.Fatal(err)
}

// Send the prepared message through the mixnet
destNode, destQueue, err := client.GetCourierDestination()
if err != nil {
	log.Fatal(err)
}
messageID := client.NewMessageID()
_, err = client.SendChannelQueryAwaitReply(ctx, channelID,
	writeReply.SendMessagePayload, destNode, destQueue, messageID)
if err != nil {
	log.Fatal(err)
}

Channel Communication Pattern

Pigeonhole channels use a two-step process:

1. Prepare: Call WriteChannel or ReadChannel to prepare the cryptographic payload 2. Send: Call SendChannelQuery to actually transmit through the mixnet

This separation allows for:

  • State management and persistence
  • Retry logic and error recovery
  • Offline operation (preparation can happen without mixnet connectivity)

Error Handling

The API uses structured error codes defined in thin_messages.go. Check the ErrorCode field in reply events and use ThinClientErrorToString() for human-readable messages.

Event Handling

The thin client provides an event-driven interface:

eventSink := client.EventSink()
defer client.StopEventSink(eventSink)

for event := range eventSink {
	switch e := event.(type) {
	case *MessageReplyEvent:
		// Handle message reply
	case *ConnectionStatusEvent:
		// Handle connection changes
	case *NewDocumentEvent:
		// Handle PKI updates
	}
}

Configuration

The thin client requires configuration specifying:

  • Network and address of the client daemon
  • Sphinx geometry parameters
  • Pigeonhole geometry parameters

See the testdata/thinclient.toml file for an example configuration.

Thread Safety

The ThinClient is safe for concurrent use. Multiple goroutines can call methods simultaneously. However, individual channels and their state should be managed carefully in concurrent environments.

Index

Constants

View Source
const (
	// MessageIDLength is the length of a message ID in bytes.
	MessageIDLength = 16

	// QueryIDLength is the length of a query ID in bytes.
	QueryIDLength = 16
)
View Source
const (
	// ThinClientSuccess indicates that the operation completed successfully
	// with no errors. This is the default success state.
	ThinClientSuccess uint8 = 0

	// ThinClientErrorConnectionLost indicates that the connection to the daemon
	// was lost during the operation. The client should attempt to reconnect.
	ThinClientErrorConnectionLost uint8 = 1

	// ThinClientErrorTimeout indicates that the operation timed out before
	// completion. This may occur during network operations or when waiting
	// for responses from the mixnet.
	ThinClientErrorTimeout uint8 = 2

	// ThinClientErrorInvalidRequest indicates that the request format was
	// invalid or contained malformed data that could not be processed.
	ThinClientErrorInvalidRequest uint8 = 3

	// ThinClientErrorInternalError indicates an internal error occurred within
	// the client daemon or thin client that prevented operation completion.
	ThinClientErrorInternalError uint8 = 4

	// ThinClientErrorMaxRetries indicates that the maximum number of retry
	// attempts was exceeded for a reliable operation (such as ARQ).
	ThinClientErrorMaxRetries uint8 = 5

	// ThinClientErrorInvalidChannel indicates that the specified channel ID
	// is invalid or malformed.
	ThinClientErrorInvalidChannel uint8 = 6

	// ThinClientErrorChannelNotFound indicates that the specified channel
	// does not exist or has been garbage collected.
	ThinClientErrorChannelNotFound uint8 = 7

	// ThinClientErrorPermissionDenied indicates that the operation was denied
	// due to insufficient permissions or capability restrictions.
	ThinClientErrorPermissionDenied uint8 = 8

	// ThinClientErrorInvalidPayload indicates that the message payload was
	// invalid, too large, or otherwise could not be processed.
	ThinClientErrorInvalidPayload uint8 = 9

	// ThinClientErrorServiceUnavailable indicates that the requested service
	// or functionality is currently unavailable.
	ThinClientErrorServiceUnavailable uint8 = 10

	// ThinClientErrorDuplicateCapability indicates that the provided capability
	// (read or write cap) has already been used and is considered a duplicate.
	ThinClientErrorDuplicateCapability uint8 = 11

	// ThinClientErrorCourierCacheCorruption indicates that the courier's cache
	// has detected corruption.
	ThinClientErrorCourierCacheCorruption uint8 = 12

	// ThinClientPropagationError indicates that the request could not be
	// propagated to replicas.
	ThinClientPropagationError uint8 = 13

	// ThinClientErrorInvalidWriteCapability indicates that the provided write
	// capability is invalid.
	ThinClientErrorInvalidWriteCapability uint8 = 14

	// ThinClientErrorInvalidReadCapability indicates that the provided read
	// capability is invalid.
	ThinClientErrorInvalidReadCapability uint8 = 15

	// ThinClientErrorInvalidResumeWriteChannelRequest indicates that the provided
	// ResumeWriteChannel request is invalid.
	ThinClientErrorInvalidResumeWriteChannelRequest uint8 = 16

	// ThinClientErrorInvalidResumeReadChannelRequest indicates that the provided
	// ResumeReadChannel request is invalid.
	ThinClientErrorInvalidResumeReadChannelRequest uint8 = 17

	// ThinClientImpossibleHashError indicates that the provided hash is impossible
	// to compute, such as when the hash of a write capability is provided but
	// the write capability itself is not provided.
	ThinClientImpossibleHashError uint8 = 18

	// ThinClientImpossibleNewWriteCapError indicates that the daemon was unable
	// to create a new write capability.
	ThinClientImpossibleNewWriteCapError uint8 = 19

	// ThinClientImpossibleNewStatefulWriterError indicates that the daemon was unable
	// to create a new stateful writer.
	ThinClientImpossibleNewStatefulWriterError uint8 = 20

	// ThinClientCapabilityAlreadyInUse indicates that the provided capability
	// is already in use.
	ThinClientCapabilityAlreadyInUse uint8 = 21
)

Thin client error codes provide standardized error reporting across the protocol. These codes are used in response messages to indicate the success or failure of operations, allowing applications to handle errors consistently.

View Source
const ChannelIDLength = 32

ChannelIDLength is the length of the channel ID in bytes.

Variables

This section is empty.

Functions

func ThinClientErrorToString added in v0.0.50

func ThinClientErrorToString(errorCode uint8) string

ThinClientErrorToString converts a thin client error code to a human-readable string. This function provides consistent error message formatting across the thin client protocol and is used for logging and error reporting.

Parameters:

  • errorCode: The error code to convert

Returns:

  • string: A human-readable description of the error

Types

type ChannelQueryReplyEvent added in v0.0.55

type ChannelQueryReplyEvent struct {
	// MessageID is the unique identifier for the request associated with the
	// query sent in the SendChannelQuery command.
	MessageID *[MessageIDLength]byte `cbor:"message_id"`

	// Payload is the reply payload if any.
	Payload []byte `cbor:"payload"`

	// ReplyIndex is the index of the reply that was actually used when processing
	// this message. This is particularly relevant for pigeonhole channel reads.
	ReplyIndex *uint8 `cbor:"reply_index"`

	// ErrorCode indicates the success or failure of sending the channel query.
	// A value of ThinClientErrorSuccess indicates the query was successfully sent.
	ErrorCode uint8 `cbor:"error_code"`
}

ChannelQueryReplyEvent is the event sent when a new message is received.

func (*ChannelQueryReplyEvent) String added in v0.0.55

func (e *ChannelQueryReplyEvent) String() string

String returns a string representation of the ChannelQueryReplyEvent.

type ChannelQuerySentEvent added in v0.0.55

type ChannelQuerySentEvent struct {
	// MessageID is the unique identifier for the request associated with the
	// query sent in the SendChannelQuery command.
	MessageID *[MessageIDLength]byte `cbor:"message_id"`

	// SentAt contains the time the message was sent.
	SentAt time.Time `cbor:"sent_at"`

	// ReplyETA is the expected round trip time to receive a response.
	ReplyETA time.Duration `cbor:"reply_eta"`

	// ErrorCode indicates the success or failure of sending the channel query.
	// A value of ThinClientErrorSuccess indicates the query was successfully sent.
	ErrorCode uint8 `cbor:"error_code"`
}

ChannelQuerySentEvent is the event sent when a message has been fully transmitted.

func (*ChannelQuerySentEvent) String added in v0.0.55

func (e *ChannelQuerySentEvent) String() string

String returns a string representation of a MessageSentEvent.

type CloseChannel added in v0.0.52

type CloseChannel struct {
	ChannelID uint16 `cbor:"channel_id"`
}

CloseChannel requests closing a pigeonhole channel. NOTE however that there is no corresponding reply type for this request to tell us if the close failed or not.

type Config added in v0.0.49

type Config struct {
	// SphinxGeometry defines the Sphinx packet format parameters used by the
	// client daemon. This must exactly match the daemon's configuration to
	// ensure proper packet size validation and processing.
	SphinxGeometry *geo.Geometry

	// PigeonholeGeometry defines the Pigeonhole protocol parameters used for
	// channel operations. This must match the daemon's configuration for
	// proper payload size validation and channel operation compatibility.
	PigeonholeGeometry *pigeonholeGeo.Geometry

	// Network specifies the network type for connecting to the client daemon.
	// Supported values: "tcp", "tcp4", "tcp6", "unix"
	Network string

	// Address specifies the address to connect to the client daemon.
	// For TCP: "host:port" (e.g., "localhost:64331")
	// For Unix: path to socket file (e.g., "/tmp/katzenpost.sock")
	Address string
}

Config contains the configuration parameters for a ThinClient.

The configuration specifies how to connect to the client daemon and includes the cryptographic parameters that must match the daemon's configuration.

Configuration can be loaded from a TOML file using LoadFile() or created programmatically. The SphinxGeometry and PigeonholeGeometry parameters must exactly match those used by the client daemon.

Example TOML configuration:

Network = "tcp"
Address = "localhost:64331"

[SphinxGeometry]
  PacketLength = 3082
  NrHops = 5
  UserForwardPayloadLength = 2000
  # ... other Sphinx parameters

[PigeonholeGeometry]
  MaxPlaintextPayloadLength = 1553
  # ... other Pigeonhole parameters

func FromConfig added in v0.0.49

func FromConfig(cfg *config.Config) *Config

FromConfig creates a thin client Config from a client daemon config.Config.

This function extracts the relevant parameters from a full client daemon configuration and creates a thin client configuration that can connect to that daemon. The SphinxGeometry and PigeonholeGeometry are copied directly to ensure compatibility.

Parameters:

  • cfg: The client daemon configuration

Returns:

  • *Config: A thin client configuration compatible with the daemon

Panics:

  • If cfg.SphinxGeometry is nil
  • If cfg.PigeonholeGeometry is nil

func LoadFile added in v0.0.50

func LoadFile(filename string) (*Config, error)

LoadFile loads a thin client configuration from a TOML file.

The TOML file should contain the network connection parameters and cryptographic geometry specifications. See the package documentation for an example configuration format.

Parameters:

  • filename: Path to the TOML configuration file

Returns:

  • *Config: The loaded configuration
  • error: Any error encountered reading or parsing the file

Example:

cfg, err := thin.LoadFile("thinclient.toml")
if err != nil {
	log.Fatal("Failed to load config:", err)
}

type ConnectionStatusEvent

type ConnectionStatusEvent struct {
	// IsConnected indicates whether the daemon is connected to the mixnet.
	// When true, full functionality is available. When false, only offline
	// operations (channel preparation) will work.
	IsConnected bool `cbor:"is_connected"`

	// Err contains any error that caused a disconnection. This field is
	// nil when IsConnected is true or when the disconnection was intentional.
	Err error `cbor:"err"`
}

ConnectionStatusEvent is sent when the daemon's connection status changes.

This event indicates whether the client daemon is currently connected to the mixnet infrastructure. When IsConnected is false, the client operates in "offline mode" where channel preparation operations work but actual message transmission will fail.

Applications can use this event to:

  • Display connection status to users
  • Queue operations for later transmission when offline
  • Implement retry logic for failed operations

func (*ConnectionStatusEvent) String

func (e *ConnectionStatusEvent) String() string

String returns a string representation of the ConnectionStatusEvent.

type CreateReadChannel added in v0.0.50

type CreateReadChannel struct {

	// QueryID is used for correlating this thin client request with the
	// thin client reponse.
	QueryID *[QueryIDLength]byte `cbor:"query_id"`

	// ReadCap is the read capability that grants access to the channel.
	// This capability is typically shared by the channel creator and allows
	// reading messages from the specified channel.
	ReadCap *bacap.ReadCap `cbor:"read_cap"`
}

CreateReadChannel requests the creation of a new pigeonhole read channel from an existing read capability. Read channels allow receiving messages from a communication channel created by the holder of the write capability.

type CreateReadChannelReply added in v0.0.50

type CreateReadChannelReply struct {
	// QueryID is used for correlating this reply with the CreateReadChannel request
	// that created it.
	QueryID *[QueryIDLength]byte `cbor:"query_id"`

	// ChannelID is the unique identifier for the created read channel, used in
	// subsequent ReadChannel operations.
	ChannelID uint16 `cbor:"channel_id"`

	// ErrorCode indicates the success or failure of the channel creation.
	// A value of ThinClientErrorSuccess indicates successful creation.
	ErrorCode uint8 `cbor:"error_code"`
}

CreateReadChannelReply is sent in response to a CreateReadChannel request. It provides the channel ID and current read position for the newly created pigeonhole read channel.

func (*CreateReadChannelReply) String added in v0.0.50

func (e *CreateReadChannelReply) String() string

String returns a string representation of the CreateReadChannelReply.

type CreateWriteChannel added in v0.0.50

type CreateWriteChannel struct {

	// QueryID is used for correlating this thin client request with the
	// thin client reponse.
	QueryID *[QueryIDLength]byte `cbor:"query_id"`
}

CreateWriteChannel requests the creation of a new pigeonhole write channel. For channel resumption, please see the ResumeWriteChannel type below. The reply will contain the channel ID, read capability, write capability, and the current message index, all of which can be used by a clever client to resume the channel in the future even in the face of system reboots etc.

type CreateWriteChannelReply added in v0.0.50

type CreateWriteChannelReply struct {
	// QueryID is used for correlating this reply with the CreateWriteChannel request
	// that created it.
	QueryID *[QueryIDLength]byte `cbor:"query_id"`

	// ChannelID is the unique identifier for the created channel, used in
	// subsequent WriteChannel operations.
	ChannelID uint16 `cbor:"channel_id"`

	// ReadCap is the read capability that can be shared with others to allow
	// them to read messages from this channel.
	ReadCap *bacap.ReadCap `cbor:"read_cap"`

	// WriteCap is the write capability that should be stored for channel
	// persistence and resumption across client restarts.
	WriteCap *bacap.WriteCap `cbor:"write_cap"`

	// ErrorCode indicates the success or failure of the channel creation.
	// A value of ThinClientErrorSuccess indicates successful creation.
	ErrorCode uint8 `cbor:"error_code"`
}

CreateWriteChannelReply is sent in response to a CreateWriteChannel request. It provides the channel ID and capabilities needed to use the newly created or resumed pigeonhole write channel.

func (*CreateWriteChannelReply) String added in v0.0.50

func (e *CreateWriteChannelReply) String() string

String returns a string representation of the CreateWriteChannelReply.

type Event

type Event interface {
	// String returns a human-readable string representation of the Event.
	String() string
}

Event is the generic interface for all events sent by the thin client.

Events are sent through channels returned by EventSink() and provide asynchronous notification of various client operations and status changes. Applications should use type assertions to handle specific event types.

Common event types include:

  • ConnectionStatusEvent: Connection state changes
  • MessageReplyEvent: Replies to sent messages
  • NewDocumentEvent: PKI document updates
  • Channel operation events (CreateWriteChannelReply, etc.)

type MessageIDGarbageCollected

type MessageIDGarbageCollected struct {
	// MessageID is the local unique identifier for the message.
	MessageID *[MessageIDLength]byte `cbor:"message_id"`
}

MessageIDGarbageCollected is the event used to signal when a given message ID has been garbage collected.

func (*MessageIDGarbageCollected) String

func (e *MessageIDGarbageCollected) String() string

String returns a string representation of a MessageIDGarbageCollected.

type MessageReplyEvent

type MessageReplyEvent struct {
	// MessageID is the unique identifier for the request associated with the
	// reply.
	MessageID *[MessageIDLength]byte `cbor:"message_id"`

	// SURBID must be a unique identity for each request.
	// This field should be nil if WithSURB is false.
	SURBID *[constants.SURBIDLength]byte `cbor:"surbid"`

	// Payload is the reply payload if any.
	Payload []byte `cbor:"payload"`

	// ReplyIndex is the index of the reply that was actually used when processing
	// this message. This is particularly relevant for pigeonhole channel reads.
	ReplyIndex *uint8 `cbor:"reply_index,omitempty"`

	// ErrorCode indicates the success or failure of the message operation.
	// A value of ThinClientSuccess (0) indicates no error occurred.
	// Non-zero values indicate specific error conditions.
	ErrorCode uint8 `cbor:"error_code"`
}

MessageReplyEvent is the event sent when a new message is received.

func (*MessageReplyEvent) String

func (e *MessageReplyEvent) String() string

String returns a string representation of the MessageReplyEvent.

type MessageSentEvent

type MessageSentEvent struct {
	// MessageID is the local unique identifier for the message, generated
	// when the message was enqueued.
	MessageID *[MessageIDLength]byte `cbor:"message_id"`

	// SURBID must be a unique identity for each request.
	// This field should be nil if WithSURB is false.
	SURBID *[constants.SURBIDLength]byte `cbor:"surbid"`

	// SentAt contains the time the message was sent.
	SentAt time.Time `cbor:"sent_at"`

	// ReplyETA is the expected round trip time to receive a response.
	ReplyETA time.Duration `cbor:"reply_eta"`

	// Err is the error message if any error was encountered when sending the message.
	// Empty string indicates no error occurred.
	Err string `cbor:"err,omitempty"`
}

MessageSentEvent is the event sent when a message has been fully transmitted.

func (*MessageSentEvent) String

func (e *MessageSentEvent) String() string

String returns a string representation of a MessageSentEvent.

type NewDocumentEvent

type NewDocumentEvent struct {
	Document *cpki.Document `cbor:"document"`
}

NewDocumentEvent is the new document event, signaling that we have received a new document from the PKI.

func (*NewDocumentEvent) String

func (e *NewDocumentEvent) String() string

String returns a string representation of a NewDocumentEvent.

type NewPKIDocumentEvent

type NewPKIDocumentEvent struct {
	Payload []byte `cbor:"payload"`
}

NewPKIDocumentEvent is the unix domain socket protocol message used by the daemon to tell the thin client about new PKI document events. The payload field contains a CBOR encoded PKI document, stripped of signatures.

func (*NewPKIDocumentEvent) String

func (e *NewPKIDocumentEvent) String() string

String returns a string representation of a NewDocumentEvent.

type ReadChannel added in v0.0.50

type ReadChannel struct {
	// ChannelID identifies the source channel for the read operation.
	// This ID was returned when the channel was created.
	ChannelID uint16 `cbor:"channel_id"`

	// QueryID is used for correlating this thin client request with the
	// thin client reponse.
	QueryID *[QueryIDLength]byte `cbor:"query_id"`

	// MessageBoxIndex specifies the starting read position for the channel.
	// If this field is nil, reading will start from the current index in the client daemon's
	// stateful reader, which is what you want most of the time.
	// This field and the next field, ReplyIndex are complicated to use properly, like so:
	//
	// This field is only needed because the next field, ReplyIndex, requires us to
	// specify *which* message should be returned, since presumably the application
	// will perform two read queries *on the same Box* if the first result is not available.
	MessageBoxIndex *bacap.MessageBoxIndex `cbor:"message_box_index,omitempty"`

	// ReplyIndex is the index of the reply to return. It is optional and
	// a default of zero will be used if not specified.
	ReplyIndex *uint8 `cbor:"reply_index"`
}

ReadChannel requests reading the next message from a pigeonhole channel. The daemon will prepare a query for the next available message and return the serialized payload that should be sent via SendChannelQuery. Note that the last two fields are useful if you want to send two read queries to the same Box id in order to retrieve two different replies.

type ReadChannelReply added in v0.0.50

type ReadChannelReply struct {
	// QueryID is used for correlating this reply with the ReadChannel request
	// that created it.
	QueryID *[QueryIDLength]byte `cbor:"query_id"`

	// ChannelID identifies the channel this reply corresponds to.
	ChannelID uint16 `cbor:"channel_id"`

	// ErrorCode indicates the success or failure of preparing the read operation.
	// A value of ThinClientErrorSuccess indicates the query is ready to send.
	ErrorCode uint8 `cbor:"error_code"`

	// SendMessagePayload contains the prepared query that should be sent via
	// SendChannelQuery to retrieve the next message from the channel.
	SendMessagePayload []byte `cbor:"send_message_payload"`

	// CurrentMessageIndex indicates the message index for the current message.
	CurrentMessageIndex *bacap.MessageBoxIndex `cbor:"current_message_index"`

	// NextMessageIndex indicates the message index to use after successfully
	// reading the current message.
	NextMessageIndex *bacap.MessageBoxIndex `cbor:"next_message_index"`

	// ReplyIndex is the index of the reply that was used when creating this ReadChannelReply.
	// This corresponds to the ReplyIndex parameter from the ReadChannel request.
	ReplyIndex *uint8 `cbor:"reply_index"`

	// EnvelopeHash is the hash of the CourierEnvelope that was sent to the
	// mixnet and is used to resume the read operation.
	EnvelopeHash *[32]byte `cbor:"envelope_hash"`

	// EnvelopeDescriptor contains the serialized EnvelopeDescriptor that
	// contains the private key material needed to decrypt the envelope reply.
	EnvelopeDescriptor []byte `cbor:"envelope_descriptor"`
}

ReadChannelReply is sent in response to a ReadChannel request. It provides the prepared query payload that should be sent through the mixnet to retrieve the next message from the channel.

func (*ReadChannelReply) String added in v0.0.50

func (e *ReadChannelReply) String() string

String returns a string representation of the ReadChannelReply.

type Request

type Request struct {

	// CreateWriteChannel is used to create a new Pigeonhole write channel.
	CreateWriteChannel *CreateWriteChannel `cbor:"create_write_channel"`

	// CreateReadChannel is used to create a new Pigeonhole read channel.
	CreateReadChannel *CreateReadChannel `cbor:"create_read_channel"`

	// WriteChannel is used to write to a Pigeonhole channel.
	WriteChannel *WriteChannel `cbor:"write_channel"`

	// ReadChannel is used to read from a Pigeonhole channel.
	ReadChannel *ReadChannel `cbor:"read_channel"`

	// ResumeWriteChannel is used to resume a write operation that was previously
	ResumeWriteChannel *ResumeWriteChannel `cbor:"resume_write_channel"`

	// ResumeWriteChannelQuery is used to resume a write operation that was previously
	ResumeWriteChannelQuery *ResumeWriteChannelQuery `cbor:"resume_write_channel_query"`

	// ResumeReadChannel is used to resume a read operation that was previously
	ResumeReadChannel *ResumeReadChannel `cbor:"resume_read_channel"`

	// ResumeReadChannelQuery is used to resume a read operation that was previously
	ResumeReadChannelQuery *ResumeReadChannelQuery `cbor:"resume_read_channel_query"`

	// CloseChannel is used to close a Pigeonhole channel.
	CloseChannel *CloseChannel `cbor:"close_channel"`

	// ThinClose is used to indicate that the thin client is disconnecting
	// from the daemon.
	ThinClose *ThinClose `cbor:"thin_close"`

	// SendChannelQuery is used to send a message through the mix network
	SendChannelQuery *SendChannelQuery `cbor:"send_channel_query"`

	// SendMessage is used to send a message through the mix network.
	// Note that this is part of the legacy API and should not be used for newer
	// works using the Pigeonhole protocol.
	SendMessage *SendMessage `cbor:"send_message"`

	// SendARQMessage is used to send a message through the mix network
	// using the naive ARQ error correction scheme.
	// Note that this is part of the legacy API and should not be used for newer
	// works using the Pigeonhole protocol.
	SendARQMessage *SendARQMessage `cbor:"send_arq_message"`
}

Request is the thin client's request message to the client daemon. It can result in one or more Response messages being sent back to the thin client.

type Response

type Response struct {

	// ShutdownEvent is sent when the client daemon is shutting down.
	ShutdownEvent *ShutdownEvent `cbor:"shudown_event"`

	// ConnectionStatusEvent is sent when the client daemon's connection status changes.
	ConnectionStatusEvent *ConnectionStatusEvent `cbor:"connection_status_event"`

	// NewPKIDocumentEvent is sent when the client daemon receives a new PKI document.
	NewPKIDocumentEvent *NewPKIDocumentEvent `cbor:"new_pki_document_event"`

	// NewDocumentEvent is sent when the client daemon receives a new PKI document.
	NewDocumentEvent *NewDocumentEvent `cbor:"new_document_event"`

	// MessageSentEvent is sent when the client daemon successfully sends a message.
	MessageSentEvent *MessageSentEvent `cbor:"message_sent_event"`

	// MessageReplyEvent is sent when the client daemon receives a reply to a message.
	MessageReplyEvent *MessageReplyEvent `cbor:"message_reply_event"`

	// MessageIDGarbageCollected is sent when the client daemon garbage collects a message ID.
	MessageIDGarbageCollected *MessageIDGarbageCollected `cbor:"message_id_garbage_collected"`

	// CreateWriteChannelReply is sent when the client daemon successfully creates a write channel.
	CreateWriteChannelReply *CreateWriteChannelReply `cbor:"create_write_channel_reply"`

	// CreateReadChannelReply is sent when the client daemon successfully creates a read channel.
	CreateReadChannelReply *CreateReadChannelReply `cbor:"create_read_channel_reply"`

	// WriteChannelReply is sent when the client daemon successfully writes a message to a channel.
	WriteChannelReply *WriteChannelReply `cbor:"write_channel_reply"`

	// ReadChannelReply is sent when the client daemon successfully reads a message from a channel.
	ReadChannelReply *ReadChannelReply `cbor:"read_channel_reply"`

	// ResumeWriteChannelReply is sent when the client daemon successfully resumes a write channel.
	ResumeWriteChannelReply *ResumeWriteChannelReply `cbor:"resume_write_channel_reply"`

	// ResumeWriteChannelQueryReply is sent when the client daemon successfully resumes a write channel.
	ResumeWriteChannelQueryReply *ResumeWriteChannelQueryReply `cbor:"resume_write_channel_query_reply"`

	// ResumeReadChannelReply is sent when the client daemon successfully resumes a read channel.
	ResumeReadChannelReply *ResumeReadChannelReply `cbor:"resume_read_channel_reply"`

	// ResumeReadChannelQueryReply is sent when the client daemon successfully resumes a read channel.
	ResumeReadChannelQueryReply *ResumeReadChannelQueryReply `cbor:"resume_read_channel_query_reply"`

	// ChannelQuerySentEvent is sent when the client daemon successfully sends a channel query.
	ChannelQuerySentEvent *ChannelQuerySentEvent `cbor:"channel_query_sent_event"`

	// ChannelQueryReplyEvent is sent when the client daemon receives a reply to a channel query.
	ChannelQueryReplyEvent *ChannelQueryReplyEvent `cbor:"channel_query_reply_event"`
}

Response is the client daemon's response message to the thin client.

type ResumeReadChannel added in v0.0.55

type ResumeReadChannel struct {
	// QueryID is used for correlating this thin client request with the
	// thin client reponse.
	QueryID *[QueryIDLength]byte `cbor:"query_id"`

	// ReadCap is the read capability that grants access to the channel.
	// This capability is typically shared by the channel creator and allows
	// reading messages from the specified channel.
	ReadCap *bacap.ReadCap `cbor:"read_cap"`

	// NextMessageIndex indicates the message index to use after successfully
	// reading the current message.
	NextMessageIndex *bacap.MessageBoxIndex `cbor:"next_message_index"`

	// ReplyIndex is the index of the reply to return. It is optional and
	// a default of zero will be used if not specified.
	ReplyIndex *uint8 `cbor:"reply_index"`
}

ResumeReadChannel requests resuming a read operation that was previously initiated but not yet completed.

type ResumeReadChannelQuery added in v0.0.55

type ResumeReadChannelQuery struct {
	// QueryID is used for correlating this thin client request with the
	// thin client reponse.
	QueryID *[QueryIDLength]byte `cbor:"query_id"`

	// ReadCap is the read capability that grants access to the channel.
	// This capability is typically shared by the channel creator and allows
	// reading messages from the specified channel.
	ReadCap *bacap.ReadCap `cbor:"read_cap"`

	// NextMessageIndex indicates the message index to use after successfully
	// reading the current message.
	NextMessageIndex *bacap.MessageBoxIndex `cbor:"next_message_index"`

	// ReplyIndex is the index of the reply to return. It is optional and
	// a default of zero will be used if not specified.
	ReplyIndex *uint8 `cbor:"reply_index"`

	// EnvelopeDescriptor contains the serialized EnvelopeDescriptor that
	// contains the private key material needed to decrypt the envelope reply.
	EnvelopeDescriptor []byte `cbor:"envelope_descriptor"`

	// EnvelopeHash is the hash of the CourierEnvelope that was sent to the
	// mixnet and is used to resume the read operation.
	EnvelopeHash *[32]byte `cbor:"envelope_hash"`
}

ResumeReadChannel requests resuming a read operation that was previously initiated but not yet completed.

type ResumeReadChannelQueryReply added in v0.0.55

type ResumeReadChannelQueryReply struct {
	// QueryID is used for correlating this reply with the ResumeReadChannel request
	// that created it.
	QueryID *[QueryIDLength]byte `cbor:"query_id"`

	// ChannelID is the unique identifier for the resumed channel, used in
	// subsequent ReadChannel operations.
	ChannelID uint16 `cbor:"channel_id"`

	// ErrorCode indicates the success or failure of preparing the read operation
	// resumption. A value of ThinClientErrorSuccess indicates the query is ready
	// to send in a subsequent SendChannelQuery call.
	ErrorCode uint8 `cbor:"error_code"`
}

ResumeReadChannelReply is sent in response to a ResumeReadChannel request. It indicates whether the resume operation was successful or not.

func (*ResumeReadChannelQueryReply) String added in v0.0.55

func (e *ResumeReadChannelQueryReply) String() string

String returns a string representation of the ResumeReadChannelReply.

type ResumeReadChannelReply added in v0.0.55

type ResumeReadChannelReply struct {
	// QueryID is used for correlating this reply with the ResumeReadChannel request
	// that created it.
	QueryID *[QueryIDLength]byte `cbor:"query_id"`

	// ChannelID is the unique identifier for the resumed channel, used in
	// subsequent ReadChannel operations.
	ChannelID uint16 `cbor:"channel_id"`

	// ErrorCode indicates the success or failure of preparing the read operation
	// resumption. A value of ThinClientErrorSuccess indicates the query is ready
	// to send in a subsequent SendChannelQuery call.
	ErrorCode uint8 `cbor:"error_code"`
}

ResumeReadChannelReply is sent in response to a ResumeReadChannel request. It indicates whether the resume operation was successful or not.

func (*ResumeReadChannelReply) String added in v0.0.55

func (e *ResumeReadChannelReply) String() string

String returns a string representation of the ResumeReadChannelReply.

type ResumeWriteChannel added in v0.0.55

type ResumeWriteChannel struct {

	// QueryID is used for correlating this thin client request with the
	// thin client reponse.
	QueryID *[QueryIDLength]byte `cbor:"query_id"`

	// WriteCap is the write capability for resuming an existing channel.
	// If nil, a new channel will be created. If provided, the channel will
	// be resumed from the specified MessageBoxIndex position.
	WriteCap *bacap.WriteCap `cbor:"write_cap,omitempty"`

	// MessageBoxIndex specifies the starting or resume point for the channel.
	// This field is can be nil when resuming a write channel which has not
	// yet been written to. If this field is provided, it is used to resume
	// the write channel from a specific message index. You must populate this
	// field with the NextMessageIndex from the previous WriteChannelReply.
	MessageBoxIndex *bacap.MessageBoxIndex `cbor:"message_box_index,omitempty"`
}

ResumeWriteChannel requests resuming a write channel that was previously either written to or created but not yet written to. This command cannot resume a write operation that was in progress, for that you must used the ResumeWriteChannelQuery command instead.

type ResumeWriteChannelQuery added in v0.0.55

type ResumeWriteChannelQuery struct {

	// QueryID is used for correlating this thin client request with the
	// thin client reponse.
	QueryID *[QueryIDLength]byte `cbor:"query_id"`

	// WriteCap is the write capability for resuming an existing channel.
	// If nil, a new channel will be created. If provided, the channel will
	// be resumed from the specified MessageBoxIndex position.
	WriteCap *bacap.WriteCap `cbor:"write_cap,omitempty"`

	// MessageBoxIndex specifies the resume point for the channel.
	// This field is required.
	MessageBoxIndex *bacap.MessageBoxIndex `cbor:"message_box_index,omitempty"`

	// EnvelopeDescriptor contains the serialized EnvelopeDescriptor that
	// contains the private key material needed to decrypt the envelope reply.
	EnvelopeDescriptor []byte `cbor:"envelope_descriptor"`

	// EnvelopeHash is the hash of the CourierEnvelope that was sent to the
	EnvelopeHash *[32]byte `cbor:"envelope_hash"`
}

ResumeWriteChannel requests resuming a write operation that was previously initiated but not yet completed.

type ResumeWriteChannelQueryReply added in v0.0.55

type ResumeWriteChannelQueryReply struct {
	// QueryID is used for correlating this reply with the ResumeWriteChannel request
	// that created it.
	QueryID *[QueryIDLength]byte `cbor:"query_id"`

	// ChannelID is the unique identifier for the resumed channel, used in
	// subsequent WriteChannel operations.
	ChannelID uint16 `cbor:"channel_id"`

	// ErrorCode indicates the success or failure of preparing the write operation
	// resumption. A value of ThinClientErrorSuccess indicates the payload is ready
	// to send in a subsequent SendChannelQuery call.
	ErrorCode uint8 `cbor:"error_code"`
}

ResumeWriteChannelReply is sent in response to a ResumeWriteChannel request. It indicates whether the resume operation was successful or not.

func (*ResumeWriteChannelQueryReply) String added in v0.0.55

String returns a string representation of the ResumeWriteChannelReply.

type ResumeWriteChannelReply added in v0.0.55

type ResumeWriteChannelReply struct {
	// QueryID is used for correlating this reply with the ResumeWriteChannel request
	// that created it.
	QueryID *[QueryIDLength]byte `cbor:"query_id"`

	// ChannelID is the unique identifier for the resumed channel, used in
	// subsequent WriteChannel operations.
	ChannelID uint16 `cbor:"channel_id"`

	// ErrorCode indicates the success or failure of preparing the write operation
	// resumption. A value of ThinClientErrorSuccess indicates the payload is ready
	// to send in a subsequent SendChannelQuery call.
	ErrorCode uint8 `cbor:"error_code"`
}

ResumeWriteChannelReply is sent in response to a ResumeWriteChannel request. It indicates whether the resume operation was successful or not.

func (*ResumeWriteChannelReply) String added in v0.0.55

func (e *ResumeWriteChannelReply) String() string

String returns a string representation of the ResumeWriteChannelReply.

type SendARQMessage added in v0.0.50

type SendARQMessage struct {
	// ID is the unique identifier with respect to the Payload.
	// This is only used by the ARQ.
	ID *[MessageIDLength]byte `cbor:"id"`

	// WithSURB indicates if the message should be sent with a SURB
	// in the Sphinx payload.
	WithSURB bool `cbor:"with_surb"`

	// SURBID must be a unique identity for each request.
	// This field should be nil if WithSURB is false.
	SURBID *[sConstants.SURBIDLength]byte `cbor:"surbid"`

	// DestinationIdHash is 32 byte hash of the destination Provider's
	// identity public key.
	DestinationIdHash *[hash.HashSize]byte `cbor:"destination_id_hash"`

	// RecipientQueueID is the queue identity which will receive the message.
	RecipientQueueID []byte `cbor:"recipient_queue_id"`

	// Payload is the actual Sphinx packet.
	Payload []byte `cbor:"payload"`
}

SendARQMessage is used to send a message through the mix network using the simple ARQ error correction scheme. It is part of the legacy API and should not be used for newer works using the Pigeonhole protocol.

type SendChannelQuery added in v0.0.55

type SendChannelQuery struct {
	// MessageID is the unique identifier for the request associated with the
	// query reply via the ChannelQueryReplyEvent.
	MessageID *[MessageIDLength]byte `cbor:"message_id"`

	// ChannelID is optional and only used for sending channel messages.
	// For non-channel messages, this field should be nil.
	ChannelID *uint16 `cbor:"channel_id,omitempty"`

	// DestinationIdHash is 32 byte hash of the destination Service's
	// identity public key.
	DestinationIdHash *[hash.HashSize]byte `cbor:"destination_id_hash"`

	// RecipientQueueID is the queue identity which will receive the message.
	// This queue ID is meant to be the queue ID of the Pigeonhole protocol Courier service.
	RecipientQueueID []byte `cbor:"recipient_queue_id"`

	// Payload is the Pigeonole protocol ciphertext payload which will be encapsulated in the Sphinx payload.
	Payload []byte `cbor:"payload"`
}

SendChannelQuery is used to send a Pigeonhole protocol ciphertext query payload through the mix network. The result of sending this message type is two more events: ChannelQuerySentEvent and ChannelQueryReplyEvent both of which can be matched by the MessageID field.

type SendMessage added in v0.0.50

type SendMessage struct {
	// ID is the unique identifier with respect to the Payload.
	// This is only used by the ARQ.
	ID *[MessageIDLength]byte `cbor:"id"`

	// WithSURB indicates if the message should be sent with a SURB
	// in the Sphinx payload.
	WithSURB bool `cbor:"with_surb"`

	// SURBID must be a unique identity for each request.
	// This field should be nil if WithSURB is false.
	SURBID *[constants.SURBIDLength]byte `cbor:"surbid"`

	// DestinationIdHash is 32 byte hash of the destination Provider's
	// identity public key.
	DestinationIdHash *[hash.HashSize]byte `cbor:"destination_id_hash"`

	// RecipientQueueID is the queue identity which will receive the message.
	RecipientQueueID []byte `cbor:"recipient_queue_id"`

	// Payload is the actual Sphinx packet.
	Payload []byte `cbor:"payload"`
}

SendMessage is used to send a message through the mix network it is part of the legacy API and should not be used for newer works using the Pigeonhole protocol.

type ShutdownEvent

type ShutdownEvent struct{}

ShutdownEvent is sent when the client daemon is shutting down.

This event indicates that the daemon is terminating and the thin client connection will be lost. Applications should handle this event by performing cleanup and potentially attempting to reconnect.

func (*ShutdownEvent) String

func (e *ShutdownEvent) String() string

String returns a string representation of the ShutdownEvent.

type ThinClient

type ThinClient struct {
	worker.Worker
	// contains filtered or unexported fields
}

ThinClient handles communication between mixnet applications and the client daemon.

The ThinClient implements a lightweight client architecture where cryptographic operations, PKI management, and mixnet protocol handling are delegated to a separate client daemon process. This design allows applications to integrate with Katzenpost without implementing the full complexity of the mixnet protocols.

Key responsibilities of ThinClient:

  • Maintain connection to the client daemon via TCP or Unix sockets
  • Provide high-level APIs for message sending and channel operations
  • Handle event distribution to application code
  • Manage PKI document caching and epoch transitions
  • Coordinate request/response correlation for blocking operations

The ThinClient is safe for concurrent use by multiple goroutines.

Lifecycle:

  1. Create with NewThinClient()
  2. Connect with Dial()
  3. Use messaging/channel APIs
  4. Clean up with Close()

Example:

cfg, _ := thin.LoadFile("config.toml")
logging := &config.Logging{Level: "INFO"}
client := thin.NewThinClient(cfg, logging)
defer client.Close()

err := client.Dial()
if err != nil {
	log.Fatal(err)
}

// Use client for messaging...

func NewThinClient

func NewThinClient(cfg *Config, logging *config.Logging) *ThinClient

NewThinClient creates a new ThinClient instance.

This function initializes a new thin client with the provided configuration and logging settings. The client is created in a disconnected state; call Dial() to establish connection to the client daemon.

The client will validate that required geometry parameters are present and set up internal channels and workers for event handling.

Parameters:

  • cfg: Configuration specifying daemon connection and crypto parameters
  • logging: Logging configuration for the client

Returns:

  • *ThinClient: A new thin client instance ready for connection

Panics:

  • If cfg.SphinxGeometry is nil
  • If cfg.PigeonholeGeometry is nil
  • If logging configuration is invalid

Example:

cfg, _ := thin.LoadFile("config.toml")
logging := &config.Logging{
	Level: "INFO",
	File:  "", // stdout
}
client := thin.NewThinClient(cfg, logging)

func (*ThinClient) BlockingSendMessage

func (t *ThinClient) BlockingSendMessage(ctx context.Context, payload []byte, destNode *[32]byte, destQueue []byte) ([]byte, error)

BlockingSendMessage sends a message and blocks until a reply is received.

DEPRECATED: This method is part of the legacy API. New applications should use the Pigeonhole Channel API (CreateWriteChannel, WriteChannel, etc.) which provides better reliability, ordering guarantees, and state management.

This method provides a synchronous request-response pattern by automatically generating a SURB ID, sending the message, and waiting for the reply. It blocks until either a reply is received or the context times out.

This is convenient for simple request-response interactions but lacks the advanced features of the Pigeonhole Channel API such as message ordering, channel persistence, and offline operation support.

Requirements:

  • The daemon must be connected to the mixnet (IsConnected() == true)
  • The destination service must be available in the current PKI document
  • A context with appropriate timeout should be provided

Parameters:

  • ctx: Context for cancellation and timeout control (recommended: 30s timeout)
  • payload: Message data to send
  • destNode: Hash of the destination service's identity key
  • destQueue: Queue ID of the destination service

Returns:

  • []byte: Reply payload from the destination service
  • error: Any error encountered during sending or while waiting for reply

Example:

// Send message to echo service and wait for reply
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

echoService, err := client.GetService("echo")
if err != nil {
	return err
}

destNode := hash.Sum256(echoService.MixDescriptor.IdentityKey)
reply, err := client.BlockingSendMessage(ctx, []byte("Hello"),
	&destNode, echoService.RecipientQueueID)
if err != nil {
	return err
}

fmt.Printf("Echo reply: %s\n", reply)

func (*ThinClient) BlockingSendReliableMessage

func (t *ThinClient) BlockingSendReliableMessage(ctx context.Context, messageID *[MessageIDLength]byte, payload []byte, destNode *[32]byte, destQueue []byte) (reply []byte, err error)

BlockingSendReliableMessage sends a message with ARQ and blocks until completion.

DEPRECATED: This method is part of the legacy API. New applications should use the Pigeonhole Channel API (CreateWriteChannel, WriteChannel, etc.) which provides better reliability, ordering guarantees, and state management.

This method combines reliable message sending with synchronous operation by implementing Automatic Repeat reQuest (ARQ) and blocking until either the message is successfully acknowledged or the maximum retry limit is reached. It provides the highest reliability available in the legacy API.

The method blocks until the complete ARQ process finishes, which may take significant time depending on network conditions and retry configuration.

Requirements:

  • The daemon must be connected to the mixnet (IsConnected() == true)
  • The destination service must support ARQ acknowledgments
  • A unique message ID must be provided for tracking
  • A context with appropriate timeout should be provided

Parameters:

  • ctx: Context for cancellation and timeout control (recommended: 60s+ timeout)
  • messageID: Unique identifier for tracking this message (use NewMessageID())
  • payload: Message data to send
  • destNode: Hash of the destination service's identity key
  • destQueue: Queue ID of the destination service

Returns:

  • []byte: Reply payload from the destination service (if any)
  • error: Any error encountered during the reliable send process

Example:

// Send reliable message and wait for completion
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()

messageID := client.NewMessageID()
echoService, err := client.GetService("echo")
if err != nil {
	return err
}

destNode := hash.Sum256(echoService.MixDescriptor.IdentityKey)
reply, err := client.BlockingSendReliableMessage(ctx, messageID,
	[]byte("Critical message"), &destNode, echoService.RecipientQueueID)
if err != nil {
	log.Printf("Reliable send failed: %v", err)
	return err
}

fmt.Printf("Reliable send completed, reply: %s\n", reply)

func (*ThinClient) Close

func (t *ThinClient) Close() error

Close gracefully shuts down the thin client and closes the daemon connection.

This method performs a clean shutdown by:

  1. Sending a close notification to the daemon
  2. Closing the network connection
  3. Stopping all background workers
  4. Closing internal event channels

After calling Close(), the ThinClient instance should not be used further. Any ongoing operations will be interrupted and may return errors.

Returns:

  • error: Any error encountered during shutdown

Example:

defer client.Close() // Ensure cleanup

// Use client...

err := client.Close()
if err != nil {
	log.Printf("Error during shutdown: %v", err)
}

func (*ThinClient) CloseChannel added in v0.0.52

func (t *ThinClient) CloseChannel(ctx context.Context, channelID uint16) error

CloseChannel closes a Pigeonhole channel and releases its resources.

This method cleanly closes a channel that was created with CreateWriteChannel, CreateReadChannel, or any of the Resume methods. Closing a channel releases the associated resources in the client daemon and should be called when the channel is no longer needed.

After closing a channel, the channel ID becomes invalid and should not be used for further operations. Attempting to use a closed channel ID will result in errors.

This operation works in both online and offline modes, as it only affects local state in the client daemon.

Parameters:

  • ctx: Context for cancellation and timeout control
  • channelID: Channel ID to close (from Create or Resume operations)

Returns:

  • error: Any error encountered during channel closure

Example:

// Create a channel
channelID, readCap, writeCap, err := client.CreateWriteChannel(ctx)
if err != nil {
	return err
}

// Use the channel for operations...
// ...

// Clean up when done
err = client.CloseChannel(ctx, channelID)
if err != nil {
	log.Printf("Warning: failed to close channel %d: %v", channelID, err)
}

// Store writeCap and readCap for future resumption if needed

func (*ThinClient) CreateReadChannel added in v0.0.50

func (t *ThinClient) CreateReadChannel(ctx context.Context, readCap *bacap.ReadCap) (uint16, error)

CreateReadChannel creates a read channel from a read capability.

This method creates a channel for reading messages using a read capability that was obtained from the creator of a write channel. The read capability allows access to messages written to the corresponding write channel.

Read channels maintain their own state independent of the write channel, allowing multiple readers to consume messages at their own pace. Each reader tracks its own position in the message sequence.

Like other channel operations, this works in offline mode, allowing applications to set up channels even when the daemon is not connected to the mixnet.

Parameters:

  • ctx: Context for cancellation and timeout control
  • readCap: Read capability obtained from the channel creator

Returns:

  • uint16: Channel ID for subsequent read operations
  • error: Any error encountered during channel creation

Example:

// Bob creates a read channel using Alice's read capability
ctx := context.Background()
channelID, err := client.CreateReadChannel(ctx, readCap)
if err != nil {
	log.Fatal("Failed to create read channel:", err)
}

// Now Bob can read messages from Alice's channel
fmt.Printf("Created read channel %d\n", channelID)

func (*ThinClient) CreateWriteChannel added in v0.0.50

func (t *ThinClient) CreateWriteChannel(ctx context.Context) (uint16, *bacap.ReadCap, *bacap.WriteCap, error)

CreateWriteChannel creates a new Pigeonhole write channel for sending messages.

This method creates a new communication channel using the Pigeonhole protocol, which provides reliable, ordered message delivery. The channel is created with fresh cryptographic capabilities that allow writing messages to the channel and sharing read access with other parties.

The returned capabilities have the following purposes:

  • ReadCap: Can be shared with others to allow them to read messages from this channel
  • WriteCap: Should be stored securely for channel persistence and resumption
  • ChannelID: Used for subsequent operations on this channel

Channel operations work in offline mode (when daemon is not connected to mixnet), allowing applications to prepare messages even without network connectivity.

Parameters:

  • ctx: Context for cancellation and timeout control

Returns:

  • uint16: Channel ID for subsequent operations
  • *bacap.ReadCap: Read capability that can be shared with message recipients
  • *bacap.WriteCap: Write capability for channel persistence and resumption
  • error: Any error encountered during channel creation

Example:

ctx := context.Background()
channelID, readCap, writeCap, err := client.CreateWriteChannel(ctx)
if err != nil {
	log.Fatal("Failed to create write channel:", err)
}

// Share readCap with Bob so he can read messages
// Store writeCap for channel resumption after restart
fmt.Printf("Created channel %d\n", channelID)

func (*ThinClient) Dial

func (t *ThinClient) Dial() error

Dial establishes a connection to the client daemon and initializes the client.

This method performs the complete connection handshake with the client daemon:

  1. Establishes network connection (TCP or Unix socket)
  2. Receives initial connection status from daemon
  3. Receives initial PKI document
  4. Starts background workers for event handling

The client supports both online and offline modes. In offline mode (when the daemon is not connected to the mixnet), channel preparation operations will work but actual message transmission will fail.

After successful connection, the client will automatically handle:

  • PKI document updates
  • Connection status changes
  • Event distribution to application code

Returns:

  • error: Any error encountered during connection or handshake

Example:

client := thin.NewThinClient(cfg, logging)
err := client.Dial()
if err != nil {
	log.Fatal("Failed to connect to daemon:", err)
}
defer client.Close()

if !client.IsConnected() {
	log.Println("Daemon is offline - limited functionality available")
}

func (*ThinClient) EventSink

func (t *ThinClient) EventSink() chan Event

EventSink returns a buffered channel that receives all events from the thin client.

This method creates a new event channel that will receive copies of all events generated by the thin client, including:

  • Connection status changes
  • PKI document updates
  • Message sent confirmations
  • Message replies
  • Channel operation results
  • Error notifications

The returned channel is buffered with capacity 1 to prevent blocking the event distribution system. Applications should process events promptly to avoid missing events.

Important: Always call StopEventSink() when done with the channel to prevent resource leaks and ensure proper cleanup.

Returns:

  • chan Event: A buffered channel that will receive all client events

Example:

eventSink := client.EventSink()
defer client.StopEventSink(eventSink)

for event := range eventSink {
	switch e := event.(type) {
	case *MessageReplyEvent:
		fmt.Printf("Received reply: %s\n", e.Payload)
	case *ConnectionStatusEvent:
		fmt.Printf("Connection status: %v\n", e.IsConnected)
	case *NewDocumentEvent:
		fmt.Printf("New PKI document for epoch %d\n", e.Document.Epoch)
	}
}

func (*ThinClient) GetConfig

func (t *ThinClient) GetConfig() *Config

GetConfig returns the client's configuration.

Returns:

  • *Config: The configuration used to create this client

func (*ThinClient) GetCourierDestination added in v0.0.55

func (t *ThinClient) GetCourierDestination() (*[32]byte, []byte, error)

GetCourierDestination returns a courier service destination for the current epoch.

This method finds and randomly selects a courier service from the current PKI document. Courier services handle Pigeonhole protocol operations, storing and retrieving messages for channels. The random selection provides automatic load balancing across available courier instances.

The returned destination information is used with SendChannelQuery and SendChannelQueryAwaitReply to transmit prepared channel operations to the mixnet.

Returns:

  • *[32]byte: Hash of the courier service's identity key (destination node)
  • []byte: Queue ID for the courier service
  • error: Error if no courier services are available

Example:

// Get courier destination for sending a channel query
destNode, destQueue, err := client.GetCourierDestination()
if err != nil {
	log.Fatal("No courier services available:", err)
}

// Use with SendChannelQuery
messageID := client.NewMessageID()
err = client.SendChannelQuery(ctx, channelID, payload,
	destNode, destQueue, messageID)

func (*ThinClient) GetLogger

func (t *ThinClient) GetLogger(prefix string) *logging.Logger

GetLogger returns a logger instance with the specified prefix.

This allows applications to create loggers that integrate with the thin client's logging system and maintain consistent log formatting.

Parameters:

  • prefix: String prefix for log messages from this logger

Returns:

  • *logging.Logger: A logger instance with the specified prefix

func (*ThinClient) GetService

func (t *ThinClient) GetService(serviceName string) (*common.ServiceDescriptor, error)

GetService returns a randomly selected service matching the specified capability.

This method is a convenience wrapper around GetServices() that randomly selects one service from all available services with the given capability. This provides automatic load balancing across available service instances.

Parameters:

  • serviceName: The name of the service capability to find

Returns:

  • *common.ServiceDescriptor: A randomly selected service descriptor
  • error: Error if no services with the capability are found

Example:

// Get a random courier service for load balancing
courier, err := client.GetService("courier")
if err != nil {
	log.Fatal("No courier service available:", err)
}
fmt.Printf("Using courier: %s\n", courier.Name)

func (*ThinClient) GetServices

func (t *ThinClient) GetServices(capability string) ([]*common.ServiceDescriptor, error)

GetServices returns all services matching the specified capability name.

This method searches the current PKI document for services that provide the specified capability. Services in Katzenpost are identified by their capability names (e.g., "echo", "courier", "keyserver").

Parameters:

  • capability: The name of the service capability to search for

Returns:

  • []*common.ServiceDescriptor: Slice of all matching service descriptors
  • error: Error if no services with the capability are found

Example:

// Find all courier services
couriers, err := client.GetServices("courier")
if err != nil {
	log.Fatal("No courier services available:", err)
}
fmt.Printf("Found %d courier services\n", len(couriers))

func (*ThinClient) IsConnected added in v0.0.53

func (t *ThinClient) IsConnected() bool

IsConnected returns true if the client daemon is connected to the mixnet.

This indicates whether the daemon has an active connection to the mixnet infrastructure. When false, the client is in "offline mode" where channel operations (prepare operations) will work but actual message transmission will fail.

Returns:

  • bool: true if daemon is connected to mixnet, false otherwise

func (*ThinClient) NewMessageID

func (t *ThinClient) NewMessageID() *[MessageIDLength]byte

NewMessageID generates a new cryptographically random message identifier.

Message IDs are used to correlate requests with responses in both legacy and channel APIs. Each message should have a unique ID to prevent confusion and enable proper event correlation.

Returns:

  • *[MessageIDLength]byte: A new random message ID

Panics:

  • If the random number generator fails

func (*ThinClient) NewQueryID added in v0.0.54

func (t *ThinClient) NewQueryID() *[QueryIDLength]byte

NewQueryID generates a new cryptographically random query identifier.

Query IDs are used in the channel API to correlate channel operation requests with their responses. Each query should have a unique ID.

Returns:

  • *[QueryIDLength]byte: A new random query ID

Panics:

  • If the random number generator fails

func (*ThinClient) NewSURBID

func (t *ThinClient) NewSURBID() *[sConstants.SURBIDLength]byte

NewSURBID generates a new Single Use Reply Block identifier.

SURB IDs are used in the legacy API to correlate reply messages with their original requests. Each SURB should have a unique ID.

Returns:

  • *[sConstants.SURBIDLength]byte: A new random SURB ID

func (*ThinClient) PKIDocument

func (t *ThinClient) PKIDocument() *cpki.Document

PKIDocument returns the thin client's current PKI document.

The PKI document contains the current network topology, service information, and cryptographic parameters for the current epoch. This document is automatically updated when the client daemon receives new PKI information.

Returns:

  • *cpki.Document: The current PKI document, or nil if none available

Thread-safe: This method can be called concurrently from multiple goroutines.

func (*ThinClient) PKIDocumentForEpoch added in v0.0.50

func (t *ThinClient) PKIDocumentForEpoch(epoch uint64) (*cpki.Document, error)

PKIDocumentForEpoch returns the PKI document for a specific epoch from cache.

This method provides access to PKI documents from previous epochs that are cached by the client. This is important for maintaining consistency during epoch transitions where different participants might be using PKI documents from different epochs, which can lead to different envelope hashes and communication failures.

The client automatically caches the last 5 epochs of PKI documents. If the requested epoch is not in cache, the current document is returned as a fallback.

Parameters:

  • epoch: The epoch number for which to retrieve the PKI document

Returns:

  • *cpki.Document: The PKI document for the specified epoch
  • error: Error if no document is available for the epoch

Example:

// Get document for a specific epoch during channel operations
doc, err := client.PKIDocumentForEpoch(12345)
if err != nil {
	log.Printf("No PKI document for epoch %d: %v", 12345, err)
	return
}
// Use doc for epoch-specific operations...

func (*ThinClient) ReadChannel added in v0.0.50

func (t *ThinClient) ReadChannel(ctx context.Context, channelID uint16, messageBoxIndex *bacap.MessageBoxIndex, replyIndex *uint8) (*ReadChannelReply, error)

ReadChannel prepares a read query for a Pigeonhole channel.

This method performs the first step of the two-phase channel read process: it prepares the cryptographic query that will be sent through the mixnet to retrieve the next message from the channel. The actual transmission is performed separately using SendChannelQuery() or SendChannelQueryAwaitReply().

Note that the last two parameters are useful if you want to send two read queries to the same Box id in order to retrieve two different replies. Our current sharding scheme ensures that two storage replicas will store a copy of the Box we are interested in reading. Thus we can optionally select the specific storage replica to query.

Parameters:

  • ctx: Context for cancellation and timeout control
  • channelID: Channel ID returned by CreateReadChannel or ResumeReadChannel
  • messageBoxIndex: Optional specific message index to read (nil for next message)
  • replyIndex: Optional specific reply index within the message (nil for default)

Returns:

  • *ReadChannelReply: Contains prepared query payload and state information
  • error: Any error encountered during preparation

Example:

// Read the next message in sequence
readReply, err := client.ReadChannel(ctx, channelID, nil, nil)
if err != nil {
	log.Fatal("Failed to prepare read:", err)
}

// Send the prepared query
destNode, destQueue, _ := client.GetCourierDestination()
messageID := client.NewMessageID()
replyPayload, err := client.SendChannelQueryAwaitReply(ctx, channelID,
	readReply.SendMessagePayload, destNode, destQueue, messageID)

func (*ThinClient) ResumeReadChannel added in v0.0.55

func (t *ThinClient) ResumeReadChannel(
	ctx context.Context,
	readCap *bacap.ReadCap,
	nextMessageIndex *bacap.MessageBoxIndex,
	replyIndex *uint8) (uint16, error)

ResumeReadChannel resumes a read channel from a previous session.

This method allows applications to restore a read channel after a restart or interruption by providing the read capability and position information that were saved from a previous session. This enables persistent communication channels that survive application restarts.

The read capability should be obtained from the channel creator, and the position information should be saved from previous read operations to maintain proper message sequencing.

After resumption, the channel can be used normally with ReadChannel() and other channel operations.

Parameters:

  • ctx: Context for cancellation and timeout control
  • readCap: Read capability obtained from the channel creator
  • nextMessageIndex: Message index to resume from. If set to nil then the channel will start from the beginning index value indicated by the readCap.
  • replyIndex: Reply index within the message (nil for default)

Returns:

  • uint16: Channel ID for subsequent operations on the resumed channel
  • error: Any error encountered during resumption

Example:

// During application shutdown, save these values persistently:
// readCap (from channel creator)
// nextMessageIndex (from last ReadChannelReply)
// replyIndex (from last ReadChannelReply)

// After restart, resume the channel:
channelID, err := client.ResumeReadChannel(ctx, readCap,
	nextMessageIndex, replyIndex)
if err != nil {
	log.Fatal("Failed to resume read channel:", err)
}

// Continue reading messages normally
readReply, err := client.ReadChannel(ctx, channelID, nil, nil)

func (*ThinClient) ResumeReadChannelQuery added in v0.0.55

func (t *ThinClient) ResumeReadChannelQuery(
	ctx context.Context,
	readCap *bacap.ReadCap,
	nextMessageIndex *bacap.MessageBoxIndex,
	replyIndex *uint8,
	envelopeDescriptor []byte,
	envelopeHash *[32]byte) (uint16, error)

ResumeReadChannelQuery resumes a read channel with a specific query state.

This method provides more granular resumption control than ResumeReadChannel by allowing the application to resume from a specific query state, including the envelope descriptor and hash. This is useful when resuming from a partially completed read operation that was interrupted during transmission.

This method is typically used when an application has saved the complete state from a ReadChannelReply and wants to resume from that exact point, including any pending query state.

Most parameters are required for this method. Only replyIndex may be nil, in which case it defaults to 0 (the first reply in the message).

Parameters:

  • ctx: Context for cancellation and timeout control
  • readCap: Read capability obtained from the channel creator
  • nextMessageIndex: Exact message index to resume from (required)
  • replyIndex: Reply index within the message (nil defaults to 0)
  • envelopeDescriptor: Envelope descriptor from the interrupted operation (required)
  • envelopeHash: Hash of the envelope from the interrupted operation (required)

Returns:

  • uint16: Channel ID for subsequent operations on the resumed channel
  • error: Any error encountered during resumption

Example:

// During interruption, save complete state from ReadChannelReply:
// readCap, nextMessageIndex, replyIndex, envelopeDescriptor, envelopeHash

// Resume with complete query state:
channelID, err := client.ResumeReadChannelQuery(ctx, readCap,
	nextMessageIndex, replyIndex, envelopeDescriptor, envelopeHash)
if err != nil {
	log.Fatal("Failed to resume read channel query:", err)
}

// Channel is now ready to continue from the exact interrupted state

func (*ThinClient) ResumeWriteChannel added in v0.0.55

func (t *ThinClient) ResumeWriteChannel(
	ctx context.Context,
	writeCap *bacap.WriteCap,
	messageBoxIndex *bacap.MessageBoxIndex) (uint16, error)

ResumeWriteChannel resumes a write channel from a previous session.

This method allows applications to restore a write channel after a restart or interruption by providing the write capability and message index that were saved from a previous session. This enables persistent communication channels that survive application restarts.

The write capability and message index should be obtained from:

  • CreateWriteChannelReply.WriteCap and CreateWriteChannelReply.NextMessageIndex
  • WriteChannelReply.NextMessageIndex from previous write operations

After resumption, the channel can be used normally with WriteChannel() and other channel operations.

Parameters:

  • ctx: Context for cancellation and timeout control
  • writeCap: Write capability from the original channel creation
  • messageBoxIndex: Message index to resume from (typically the next index to write); if set to nil then the channel will start from the beginning.

Returns:

  • uint16: Channel ID for subsequent operations on the resumed channel
  • error: Any error encountered during resumption

Example:

// During application shutdown, save these values persistently:
// writeCap (from CreateWriteChannelReply)
// nextMessageIndex (from last WriteChannelReply)

// After restart, resume the channel:
channelID, err := client.ResumeWriteChannel(ctx, writeCap, nextMessageIndex)
if err != nil {
	log.Fatal("Failed to resume write channel:", err)
}

// Continue using the channel normally
message := []byte("Resumed channel message")
writeReply, err := client.WriteChannel(ctx, channelID, message)

func (*ThinClient) ResumeWriteChannelQuery added in v0.0.55

func (t *ThinClient) ResumeWriteChannelQuery(
	ctx context.Context,
	writeCap *bacap.WriteCap,
	messageBoxIndex *bacap.MessageBoxIndex,
	envelopeDescriptor []byte,
	envelopeHash *[32]byte) (uint16, error)

ResumeWriteChannelQuery resumes a write channel with a specific query state.

This method provides more granular resumption control than ResumeWriteChannel by allowing the application to resume from a specific query state, including the envelope descriptor and hash. This is useful when resuming from a partially completed write operation that was interrupted during transmission.

This method is typically used when an application has saved the complete state from a WriteChannelReply and wants to resume from that exact point, including any pending query state.

All parameters are required for this method, unlike the basic ResumeWriteChannel which only requires the write capability and message index.

Parameters:

  • ctx: Context for cancellation and timeout control
  • writeCap: Write capability from the original channel creation
  • messageBoxIndex: Exact message index to resume from
  • envelopeDescriptor: Envelope descriptor from the interrupted operation
  • envelopeHash: Hash of the envelope from the interrupted operation

Returns:

  • uint16: Channel ID for subsequent operations on the resumed channel
  • error: Any error encountered during resumption

Example:

// During interruption, save complete state from WriteChannelReply:
// writeCap, messageBoxIndex, envelopeDescriptor, envelopeHash

// Resume with complete query state:
channelID, err := client.ResumeWriteChannelQuery(ctx, writeCap,
	messageBoxIndex, envelopeDescriptor, envelopeHash)
if err != nil {
	log.Fatal("Failed to resume write channel query:", err)
}

// Channel is now ready to continue from the exact interrupted state

func (*ThinClient) SendChannelQuery added in v0.0.50

func (t *ThinClient) SendChannelQuery(
	ctx context.Context,
	channelID uint16,
	payload []byte,
	destNode *[32]byte,
	destQueue []byte,
	messageID *[MessageIDLength]byte,
) error

SendChannelQuery sends a prepared channel query to the mixnet without waiting for a reply.

This method performs the second step of the two-phase channel operation process. It takes a payload prepared by WriteChannel or ReadChannel and transmits it through the mixnet to the specified courier service.

This is a fire-and-forget operation - it does not wait for a reply. Use SendChannelQueryAwaitReply if you need to wait for and receive the response.

Requirements:

  • The daemon must be connected to the mixnet (IsConnected() == true)
  • The payload must be prepared by WriteChannel or ReadChannel
  • The destination must be obtained from GetCourierDestination()

Parameters:

  • ctx: Context for cancellation and timeout control
  • channelID: Channel ID from CreateWriteChannel/CreateReadChannel/Resume operations
  • payload: Prepared payload from WriteChannel or ReadChannel
  • destNode: Courier service node hash from GetCourierDestination()
  • destQueue: Courier service queue ID from GetCourierDestination()
  • messageID: Unique message identifier for correlation

Returns:

  • error: Any error encountered during transmission

Example:

// Prepare a write operation
writeReply, err := client.WriteChannel(ctx, channelID, message)
if err != nil {
	return err
}

// Get courier destination
destNode, destQueue, err := client.GetCourierDestination()
if err != nil {
	return err
}

// Send without waiting for reply
messageID := client.NewMessageID()
err = client.SendChannelQuery(ctx, channelID, writeReply.SendMessagePayload,
	destNode, destQueue, messageID)

func (*ThinClient) SendChannelQueryAwaitReply added in v0.0.55

func (t *ThinClient) SendChannelQueryAwaitReply(
	ctx context.Context,
	channelID uint16,
	payload []byte,
	destNode *[32]byte,
	destQueue []byte,
	messageID *[MessageIDLength]byte,
) ([]byte, error)

SendChannelQueryAwaitReply sends a prepared channel query and waits for the reply.

This method performs the second step of the two-phase channel operation process and blocks until a reply is received or the context times out. It combines sending the prepared payload with waiting for and returning the response.

This is the most commonly used method for channel operations as it provides a complete request-response cycle. For fire-and-forget operations, use SendChannelQuery instead.

Requirements:

  • The daemon must be connected to the mixnet (IsConnected() == true)
  • The payload must be prepared by WriteChannel or ReadChannel
  • The destination must be obtained from GetCourierDestination()

Parameters:

  • ctx: Context for cancellation and timeout control (recommended: 30s timeout)
  • channelID: Channel ID from CreateWriteChannel/CreateReadChannel/Resume operations
  • payload: Prepared payload from WriteChannel or ReadChannel
  • destNode: Courier service node hash from GetCourierDestination()
  • destQueue: Courier service queue ID from GetCourierDestination()
  • messageID: Unique message identifier for correlation

Returns:

  • []byte: Response payload from the courier service
  • error: Any error encountered during transmission or while waiting for reply

Example:

// Prepare a read operation
readReply, err := client.ReadChannel(ctx, channelID, nil, nil)
if err != nil {
	return err
}

// Get courier destination
destNode, destQueue, err := client.GetCourierDestination()
if err != nil {
	return err
}

// Send and wait for reply
messageID := client.NewMessageID()
replyPayload, err := client.SendChannelQueryAwaitReply(ctx, channelID,
	readReply.SendMessagePayload, destNode, destQueue, messageID)
if err != nil {
	return err
}

// Process the received message
fmt.Printf("Received: %s\n", replyPayload)

func (*ThinClient) SendMessage

func (t *ThinClient) SendMessage(surbID *[sConstants.SURBIDLength]byte, payload []byte, destNode *[32]byte, destQueue []byte) error

SendMessage sends a message with reply capability using the legacy API.

DEPRECATED: This method is part of the legacy API. New applications should use the Pigeonhole Channel API (CreateWriteChannel, WriteChannel, etc.) which provides better reliability, ordering guarantees, and state management.

This method sends a message with a Single Use Reply Block (SURB) that allows the destination to send a reply. The method is asynchronous - it only blocks until the daemon receives the send request, not until the message is actually transmitted or a reply is received.

To receive replies, applications must monitor events from EventSink() and look for MessageReplyEvent instances with matching SURB IDs.

Requirements:

  • The daemon must be connected to the mixnet (IsConnected() == true)
  • The destination service must be available in the current PKI document
  • A unique SURB ID must be provided for reply correlation

Parameters:

  • surbID: Unique identifier for correlating replies (use NewSURBID())
  • payload: Message data to send
  • destNode: Hash of the destination service's identity key
  • destQueue: Queue ID of the destination service

Returns:

  • error: Any error encountered during message preparation or sending

Example:

// Create event sink to receive replies
eventSink := client.EventSink()
defer client.StopEventSink(eventSink)

// Send message with reply capability
surbID := client.NewSURBID()
echoService, _ := client.GetService("echo")
destNode := hash.Sum256(echoService.MixDescriptor.IdentityKey)
err := client.SendMessage(surbID, []byte("Hello"), &destNode, echoService.RecipientQueueID)

// Wait for reply in event loop
for event := range eventSink {
	if reply, ok := event.(*MessageReplyEvent); ok {
		if bytes.Equal(reply.SURBID[:], surbID[:]) {
			fmt.Printf("Reply: %s\n", reply.Payload)
			break
		}
	}
}

func (*ThinClient) SendMessageWithoutReply

func (t *ThinClient) SendMessageWithoutReply(payload []byte, destNode *[32]byte, destQueue []byte) error

SendMessageWithoutReply sends a fire-and-forget message using the legacy API.

DEPRECATED: This method is part of the legacy API. New applications should use the Pigeonhole Channel API (CreateWriteChannel, WriteChannel, etc.) which provides better reliability, ordering guarantees, and state management.

This method sends a message without any reply capability. The message is encapsulated in a Sphinx packet and sent through the mixnet, but no response can be received. This is suitable for notifications or one-way communication.

Requirements:

  • The daemon must be connected to the mixnet (IsConnected() == true)
  • The destination service must be available in the current PKI document

Parameters:

  • payload: Message data to send
  • destNode: Hash of the destination service's identity key
  • destQueue: Queue ID of the destination service

Returns:

  • error: Any error encountered during message preparation or sending

Example:

// Find an echo service
echoService, err := client.GetService("echo")
if err != nil {
	return err
}

// Send a fire-and-forget message
destNode := hash.Sum256(echoService.MixDescriptor.IdentityKey)
destQueue := echoService.RecipientQueueID
err = client.SendMessageWithoutReply([]byte("Hello"), &destNode, destQueue)

func (*ThinClient) SendReliableMessage

func (t *ThinClient) SendReliableMessage(messageID *[MessageIDLength]byte, payload []byte, destNode *[32]byte, destQueue []byte) error

SendReliableMessage sends a message with automatic retransmission (ARQ).

DEPRECATED: This method is part of the legacy API. New applications should use the Pigeonhole Channel API (CreateWriteChannel, WriteChannel, etc.) which provides better reliability, ordering guarantees, and state management.

This method implements Automatic Repeat reQuest (ARQ) functionality, where the message is automatically retransmitted until an acknowledgment is received or the maximum retry limit is reached. This provides better reliability than basic SendMessage but is still inferior to the Pigeonhole Channel API.

The method is asynchronous - it returns immediately after initiating the reliable send process. Applications should monitor events to track the final outcome of the transmission.

Requirements:

  • The daemon must be connected to the mixnet (IsConnected() == true)
  • The destination service must support ARQ acknowledgments
  • A unique message ID must be provided for tracking

Parameters:

  • messageID: Unique identifier for tracking this message (use NewMessageID())
  • payload: Message data to send
  • destNode: Hash of the destination service's identity key
  • destQueue: Queue ID of the destination service

Returns:

  • error: Any error encountered during message preparation or initial sending

Example:

// Send reliable message with ARQ
messageID := client.NewMessageID()
echoService, err := client.GetService("echo")
if err != nil {
	return err
}

destNode := hash.Sum256(echoService.MixDescriptor.IdentityKey)
err = client.SendReliableMessage(messageID, []byte("Important message"),
	&destNode, echoService.RecipientQueueID)

// Monitor events for final outcome
eventSink := client.EventSink()
defer client.StopEventSink(eventSink)
// ... handle MessageSentEvent and MessageReplyEvent

func (*ThinClient) Shutdown

func (t *ThinClient) Shutdown()

Shutdown cleanly shuts down the ThinClient instance.

This method stops all background workers and closes the connection to the client daemon. It is equivalent to calling Halt() and is provided for compatibility. For proper cleanup, prefer using Close().

func (*ThinClient) StopEventSink added in v0.0.43

func (t *ThinClient) StopEventSink(ch chan Event)

StopEventSink stops sending events to the specified channel and cleans up resources.

This method removes the channel from the event distribution system and should be called when the application is done processing events from a channel returned by EventSink(). Failure to call this method may result in resource leaks and continued event processing overhead.

Parameters:

  • ch: The event channel returned by EventSink() to stop

Example:

eventSink := client.EventSink()
defer client.StopEventSink(eventSink) // Ensure cleanup

// Process events...

func (*ThinClient) WriteChannel added in v0.0.50

func (t *ThinClient) WriteChannel(ctx context.Context, channelID uint16, payload []byte) (*WriteChannelReply, error)

WriteChannel prepares a message for writing to a Pigeonhole channel.

This method performs the first step of the two-phase channel write process: it prepares the cryptographic payload that will be sent through the mixnet. The actual transmission is performed separately using SendChannelQuery().

This separation allows for:

  • State management and persistence between preparation and transmission
  • Retry logic and error recovery
  • Offline operation (preparation works without mixnet connectivity)

The method validates the payload size against the configured Pigeonhole geometry limits and returns all information needed to complete the write operation, including state for resumption after interruption.

Parameters:

  • ctx: Context for cancellation and timeout control
  • channelID: Channel ID returned by CreateWriteChannel or ResumeWriteChannel
  • payload: Message data to write (must not exceed MaxPlaintextPayloadLength)

Returns:

  • *WriteChannelReply: Contains prepared payload and state information
  • error: Any error encountered during preparation

Example:

message := []byte("Hello, Bob!")
writeReply, err := client.WriteChannel(ctx, channelID, message)
if err != nil {
	log.Fatal("Failed to prepare write:", err)
}

// Now send the prepared message
destNode, destQueue, _ := client.GetCourierDestination()
messageID := client.NewMessageID()
_, err = client.SendChannelQueryAwaitReply(ctx, channelID,
	writeReply.SendMessagePayload, destNode, destQueue, messageID)

type ThinClose added in v0.0.50

type ThinClose struct {
}

ThinClose is used to indicate that the thin client is disconnecting from the daemon.

type ThinResponse

type ThinResponse struct {
	// SURBID is a unique identifier for this response that should precisely
	// match the application's chosen SURBID of the sent message. This allows
	// the application to correlate responses with requests when using the
	// legacy SendMessage API.
	SURBID *[sConstants.SURBIDLength]byte

	// ID is the unique identifier for the corresponding sent message.
	// This is used for message correlation in the legacy API.
	ID *[MessageIDLength]byte

	// Payload contains the decrypted response data from the destination service.
	// The format and content depend on the service being contacted.
	Payload []byte
}

ThinResponse encapsulates a message response from the mixnet that is passed to the client application. This is part of the legacy API.

ThinResponse contains the decrypted reply payload along with identifiers that allow the application to correlate responses with the original requests.

type WriteChannel added in v0.0.50

type WriteChannel struct {

	// QueryID is used for correlating this thin client request with the
	// thin client reponse.
	QueryID *[QueryIDLength]byte `cbor:"query_id"`

	// ChannelID identifies the target channel for the write operation.
	// This ID was returned when the channel was created.
	ChannelID uint16 `cbor:"channel_id"`

	// Payload contains the message data to write to the channel.
	// The payload size must not exceed the channel's configured limits.
	Payload []byte `cbor:"payload"`
}

WriteChannel requests writing a message to an existing pigeonhole channel. The daemon will prepare the message for transmission and return the serialized payload that should be sent via SendChannelQuery.

type WriteChannelReply added in v0.0.50

type WriteChannelReply struct {
	// QueryID is used for correlating this reply with the WriteChannel request
	// that created it.
	QueryID *[QueryIDLength]byte `cbor:"query_id"`

	// ChannelID identifies the channel this reply corresponds to.
	ChannelID uint16 `cbor:"channel_id"`

	// SendMessagePayload contains the prepared Sphinx packet that should be
	// sent via SendChannelQuery to complete the write operation.
	SendMessagePayload []byte `cbor:"send_message_payload"`

	// CurrentMessageIndex indicates the message index for the current message.
	CurrentMessageIndex *bacap.MessageBoxIndex `cbor:"current_message_index"`

	// NextMessageIndex indicates the message index to use after the courier
	// acknowledges successful delivery of this message.
	NextMessageIndex *bacap.MessageBoxIndex `cbor:"next_message_index"`

	// EnvelopeHash is the hash of the CourierEnvelope that was sent to the
	EnvelopeHash *[32]byte `cbor:"envelope_hash"`

	// EnvelopeDescriptor contains the serialized EnvelopeDescriptor that
	// contains the private key material needed to decrypt the envelope reply.
	EnvelopeDescriptor []byte `cbor:"envelope_descriptor"`

	// ErrorCode indicates the success or failure of preparing the write operation.
	// A value of ThinClientErrorSuccess indicates the payload is ready to send.
	ErrorCode uint8 `cbor:"error_code"`
}

WriteChannelReply is sent in response to a WriteChannel request. It provides the prepared message payload that should be sent through the mixnet to complete the channel write operation.

func (*WriteChannelReply) String added in v0.0.50

func (e *WriteChannelReply) String() string

String returns a string representation of the WriteChannelReply.

Jump to

Keyboard shortcuts

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