From ba833c706b7d6052559dc12be2372fa6d10eec7f Mon Sep 17 00:00:00 2001 From: David Stainton Date: Fri, 12 Jun 2026 11:45:57 +0000 Subject: [PATCH] Document full thin client API for v0.0.81/0.0.18 Bump the pinned tags and add groups.yaml entries for the twelve hitherto-undocumented ThinClient methods: the four voucher operations, the Sphinx and Pigeonhole geometry accessors, the directory-authority and per-epoch PKI accessors, and the message-box-counter, config, shutdown, and logger utilities. The reference is regenerated accordingly. --- .../en/docs/misc/thin_client_api_reference.md | 414 ++++++++++++++---- tools/thin-client-api-gen/groups.yaml | 144 +++++- tools/thin-client-api-gen/pinned-versions.env | 4 +- 3 files changed, 458 insertions(+), 104 deletions(-) diff --git a/content/en/docs/misc/thin_client_api_reference.md b/content/en/docs/misc/thin_client_api_reference.md index d3c60a2c..fe6d2187 100644 --- a/content/en/docs/misc/thin_client_api_reference.md +++ b/content/en/docs/misc/thin_client_api_reference.md @@ -37,9 +37,9 @@ This reference describes the following pinned binding versions: | Binding | Repository | Tag | | --- | --- | --- | -| Go reference | [katzenpost/client/thin](https://github.com/katzenpost/katzenpost/tree/v0.0.80/client/thin) | [v0.0.80](https://github.com/katzenpost/katzenpost/releases/tag/v0.0.80) | -| Rust | [thin_client/src](https://github.com/katzenpost/thin_client/tree/0.0.17/src) | [0.0.17](https://github.com/katzenpost/thin_client/releases/tag/0.0.17) | -| Python | [thin_client/katzenpost_thinclient](https://github.com/katzenpost/thin_client/tree/0.0.17/katzenpost_thinclient) | [0.0.17](https://github.com/katzenpost/thin_client/releases/tag/0.0.17) | +| Go reference | [katzenpost/client/thin](https://github.com/katzenpost/katzenpost/tree/v0.0.81/client/thin) | [v0.0.81](https://github.com/katzenpost/katzenpost/releases/tag/v0.0.81) | +| Rust | [thin_client/src](https://github.com/katzenpost/thin_client/tree/0.0.18/src) | [0.0.18](https://github.com/katzenpost/thin_client/releases/tag/0.0.18) | +| Python | [thin_client/katzenpost_thinclient](https://github.com/katzenpost/thin_client/tree/0.0.18/katzenpost_thinclient) | [0.0.18](https://github.com/katzenpost/thin_client/releases/tag/0.0.18) | For pinned versions of the full stack (including `kpclientd`, `katzenqt`, and the server-side components), see [Build from source](/docs/build_from_source/). @@ -106,11 +106,11 @@ The Go `ThinClient` is safe for concurrent use by multiple goroutines. Because i 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 +This method performs the complete connection handshake with the client daemon with the following actions. + 1. It establishes a network connection (TCP or Unix socket). + 2. It receives an initial connection status from daemon. + 3. It receives an initial PKI document. + 4. It 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 @@ -118,8 +118,8 @@ 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 + - connection status changes + - event distribution to application code The Rust binding folds the connect step into its constructor, so `ThinClient::new` returns an already-connected handle. Go and @@ -143,10 +143,10 @@ async def start(self, loop: asyncio.AbstractEventLoop) -> None: 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 +This method performs a clean shutdown with the following actions. + 1. It sends a close notification to the daemon. + 2. It closes the network connection. + 3. It stops all background workers. After calling Close(), the ThinClient instance should not be used further. Any ongoing operations will be interrupted and may return errors. @@ -304,21 +304,19 @@ client = ThinClient(config) 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 +This method creates a new event channel that will receive copies of all +events generated by the thin client, including the following. - PKI document updates - - Message sent confirmations - - Message replies - - Channel operation results - - Error notifications + - message sent confirmations + - message replies + - channel operation results The returned channel is buffered with capacity 1. Events are never -silently dropped: the fan-out worker blocks until the subscriber -accepts each event, matching the "no loss" contract the Rust and +silently dropped; the fan-out worker blocks until the subscriber +accepts each event, matching the "no loss" contract that the Rust and Python thin clients uphold. Consequently an application that stops consuming from its sink will stall the entire fan-out -(including events destined for other subscribers); applications +(including events destined for other subscribers). Applications must drain promptly or call StopEventSink() to release their subscription. @@ -389,11 +387,11 @@ def pki_document(self) -> 'Dict[str,Any] | None': GetPKIDocumentRaw returns the cert.Certificate-wrapped signed PKI document for the requested epoch, with every directory authority -signature intact. Pass epoch == 0 to request the document the daemon +signature intact. Pass epoch == 0 to request the document that the daemon believes is current. The thin client receives the stripped PKI document by default (as -pushed in NewPKIDocumentEvent); use this method when the caller +pushed in NewPKIDocumentEvent). Use this method when the caller needs to verify the directory authority signatures itself. The payload can be deserialized and verified with core/pki.FromPayload. @@ -412,6 +410,58 @@ async def get_pki_document_raw(self, epoch: int = 0) -> 'Tuple[bytes,int]': {{< /tab >}} {{< /tabpane >}} +### GetDirectoryAuthorities / get_directory_authorities + +GetDirectoryAuthorities returns the directory authority descriptors the +client daemon is configured with. + +A thin client holds only its dial transport configuration and never sees +the daemon's voting authority peer list. This method surfaces it, so a +caller may, for instance, map a PKI document's signature fingerprints (the +keys of its Signatures map) to human-readable authority identifiers via +each descriptor's IdentityKeyHash. + +{{< tabpane >}} +{{< tab header="Go" lang="go" >}} +func (t *ThinClient) GetDirectoryAuthorities() ([]*DirectoryAuthority, error) +{{< /tab >}} +{{< tab header="Rust" lang="rust" >}} +pub async fn get_directory_authorities( + &self, +) -> Result, ThinClientError> +{{< /tab >}} +{{< tab header="Python" lang="python" >}} +async def get_directory_authorities(self) -> 'List[Dict[str,Any]]': +{{< /tab >}} +{{< /tabpane >}} + +### PKIDocumentForEpoch / pki_document_for_epoch + +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. + +The Rust binding does not expose a per-epoch accessor; Rust callers +use `pki_document` for the current document, or `get_pki_document_raw` +for the signed document at a given epoch. + +{{< tabpane >}} +{{< tab header="Go" lang="go" >}} +func (t *ThinClient) PKIDocumentForEpoch(epoch uint64) (*cpki.Document, error) +{{< /tab >}} +{{< tab header="Python" lang="python" >}} +def pki_document_for_epoch(self, epoch: int) -> 'Dict[str,Any]': +{{< /tab >}} +{{< /tabpane >}} + ### GetService / get_service GetService returns a randomly selected service matching the specified capability. @@ -463,7 +513,7 @@ def get_services(self, capability: str) -> 'List[ServiceDescriptor]': SendMessage sends a message with reply capability using the legacy API. 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 +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. @@ -522,7 +572,7 @@ 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, +advanced features of the Pigeonhole channel API such as message ordering, channel persistence, and offline operation support. {{< tabpane >}} @@ -550,7 +600,7 @@ async def blocking_send_message(self, payload: bytes | str, dest_node: bytes, de NewKeypair creates a new keypair for use with the Pigeonhole protocol. This method generates a WriteCap and ReadCap from the provided seed using -the BACAP (Blinding-and-Capability) protocol. The WriteCap should be stored +the BACAP (blinding-and-capability) protocol. The WriteCap should be stored securely for writing messages, while the ReadCap can be shared with others to allow them to read messages. @@ -576,10 +626,11 @@ async def new_keypair(self, seed: bytes) -> KeypairResult: NextMessageBoxIndex increments a MessageBoxIndex using the BACAP NextIndex method. This method is used when sending multiple messages to different mailboxes using -the same WriteCap or ReadCap. It properly advances the cryptographic state by: - - Incrementing the Idx64 counter - - Deriving new encryption and blinding keys using HKDF - - Updating the HKDF state for the next iteration +the same WriteCap or ReadCap. It properly advances the cryptographic state with +the following actions. + - Incrementing the Idx64 counter. + - Deriving new encryption and blinding keys using HKDF. + - Updating the HKDF state for the next iteration. The client daemon handles the cryptographic operations using our BACAP library documented here: https://pkg.go.dev/github.com/katzenpost/hpqc/bacap @@ -606,7 +657,7 @@ async def next_message_box_index(self, message_box_index: bytes) -> bytes: EncryptRead encrypts a read operation for a given read capability. This method prepares an encrypted read request that can be sent to the -courier service to retrieve a message from a pigeonhole box. The returned +courier service to retrieve a message from a Pigeonhole box. The returned ciphertext should be sent via StartResendingEncryptedMessage. {{< tabpane >}} @@ -630,7 +681,7 @@ async def encrypt_read(self, read_cap: bytes, message_box_index: bytes) -> Encry EncryptWrite encrypts a write operation for a given write capability. This method prepares an encrypted write request that can be sent to the -courier service to store a message in a pigeonhole box. The returned +courier service to store a message in a Pigeonhole box. The returned ciphertext should be sent via StartResendingEncryptedMessage. {{< tabpane >}} @@ -656,21 +707,22 @@ async def encrypt_write(self, plaintext: bytes, write_cap: bytes, message_box_in StartResendingEncryptedMessage sends an encrypted message via ARQ and blocks until completion. -This method BLOCKS until a reply is received. CancelResendingEncryptedMessage is only +This method blocks until a reply is received. CancelResendingEncryptedMessage is only useful when called from another goroutine to interrupt this blocking call. -The message will be resent periodically until either: - - A reply is received from the courier (this method returns) - - The message is cancelled via CancelResendingEncryptedMessage (from another goroutine) - - The client is shut down +The message will be resent periodically until until one of the following is true: + - A reply is received from the courier (this method returns). + - The message is canceled via CancelResendingEncryptedMessage (from another goroutine). + - The client is shut down. This is used for both read and write operations in the new Pigeonhole API. -The daemon implements a finite state machine (FSM) for handling the stop-and-wait ARQ protocol: +The daemon implements a finite state machine (FSM) for handling the stop-and-wait +ARQ protocol in the following cases. - For default write operations (writeCap != nil, readCap == nil, noIdempotentBoxAlreadyExists == false): The method waits for an ACK from the courier and returns immediately. - The ACK confirms the courier received the envelope and will dispatch it + The ACK confirms that the courier received the envelope and will dispatch it to both shard replicas. This requires only a single round-trip through the mixnet. - For BoxAlreadyExists-aware writes (noIdempotentBoxAlreadyExists == true): @@ -709,7 +761,7 @@ StartResendingEncryptedMessageReturnBoxExists behaves exactly like StartResendingEncryptedMessage save that it returns ErrBoxAlreadyExists when the replica reports that the destination box has already been written, rather than swallowing the condition -as idempotent success. Use it when one needs to distinguish a +as idempotent success. Use it to distinguish a fresh write from a repeat: for instance, when implementing optimistic concurrency on top of the channel, or when establishing whether a particular call actually caused a state change at the @@ -753,7 +805,7 @@ the box is absent rather than waiting for replication to settle. Use it when polling a box that may not yet have been written, for instance when a reader peeks ahead at a peer's next message before -that peer has produced it; the regular variant would block until +that peer has produced it. The regular variant would block until the box appeared, which can be many round trips. As with StartResendingEncryptedMessage, an in-flight call may be @@ -790,7 +842,7 @@ acknowledgements time out, so a multi-box payload is no longer serialised one round trip per box. A `window` of zero asks the daemon to choose a default derived from the send rate and round-trip time. -It blocks until every box has been acknowledged (success) or the transfer +WriteStrea blocks until every box has been acknowledged (success) or the transfer fails, returning the message box index immediately after the last box written, ready to seed a subsequent write on the same channel. @@ -818,7 +870,7 @@ ReadStream reads boxCount sequential boxes from a channel using the daemon's windowed selective-ack (SACK) ARQ, the read counterpart of WriteStream. The daemon keeps up to `window` boxes in flight at once, retransmitting only those whose payloads time out, decrypts each box, and -reassembles them in order. A `window` of zero asks the daemon to choose a +reassembles them in order. A `window` of zero causes the daemon to choose a default. It blocks until every box has been read (success) or the transfer fails, @@ -847,11 +899,11 @@ async def read_stream(self, read_cap, start_index, box_count, window = 0): CancelResendingEncryptedMessage cancels ARQ resending for an encrypted message. -This method stops the automatic repeat request (ARQ) for a previously started -encrypted message transmission. This is useful when: - - A reply has been received through another channel - - The operation should be aborted - - The message is no longer needed +This method stops the ARQ for a previously started +encrypted message transmission. This is useful in the following cases. + - A reply has been received through another channel. + - The operation should be aborted. + - The message is no longer needed. {{< tabpane >}} {{< tab header="Go" lang="go" >}} @@ -873,9 +925,9 @@ async def cancel_resending_encrypted_message(self, envelope_hash: bytes) -> None ### TombstoneRange / tombstone_range TombstoneRange prepares the encrypted envelopes needed to -tombstone a consecutive range of pigeonhole boxes beginning at the +tombstone a consecutive range of Pigeonhole boxes beginning at the supplied MessageBoxIndex. A tombstone is a signed empty payload -that the replica recognises as a deletion marker; the daemon +that the replica recognizes as a deletion marker; the daemon constructs one by signing rather than encrypting whenever EncryptWrite is invoked with an empty plaintext. @@ -911,17 +963,17 @@ async def tombstone_range(self, write_cap: bytes, start: bytes, max_count: int) CreateCourierEnvelopesFromPayload packs a payload of arbitrary size (up to 10 MB) into properly sized CopyStreamElement chunks -for one destination channel. Each chunk is a serialised +for one destination channel. Each chunk is a serialized CopyStreamElement, ready to be written to a box via EncryptWrite -followed by StartResendingEncryptedMessage; the caller marks the +followed by StartResendingEncryptedMessage. The caller marks the boundaries of the stream with the isStart and isLast flags. -This method is stateless: no daemon state is kept between calls, +This method is stateless: no daemon state is kept between calls, and each invocation runs a fresh encoder and flushes before returning. The 10 MB cap guards against accidental memory exhaustion. Once the chunks have been written to a temporary copy stream, a -copy command (StartResendingCopyCommand) is despatched to a +copy command (StartResendingCopyCommand) is dispatched to a courier with the WriteCap for that temporary stream; the courier reads the chunks back and dispatches each envelope to its destination box. @@ -954,9 +1006,9 @@ calling CreateCourierEnvelopesFromPayload once per destination, because the shared encoder runs all envelopes together rather than padding the final box of each destination independently. -This method is stateless: the buffer argument carries any residual +This method is stateless; the buffer argument carries any residual encoder state across calls in place of daemon-side bookkeeping. -Pass nil for buffer on the first call and the Buffer returned by +Pass nil for buffer on the first call and the buffer returned by the previous call thereafter; set isLast on the final call so that the encoder flushes its tail. @@ -1024,17 +1076,18 @@ async def create_courier_envelopes_from_tombstone_range(self, dest_write_cap: by StartResendingCopyCommand sends a copy command via ARQ and blocks until completion. -This method BLOCKS until a reply is received. It uses the ARQ (Automatic Repeat reQuest) +This method blocks until a reply is received. It uses the ARQ mechanism to reliably send copy commands to the courier, automatically retrying if the reply is not received in time. The copy command instructs the courier to read from a temporary copy stream channel -and write the parsed envelopes to their destination channels. The courier: - 1. Derives a ReadCap from the WriteCap - 2. Reads boxes from the temporary channel - 3. Parses boxes into CourierEnvelopes - 4. Sends each envelope to intermediate replicas for replication - 5. Writes tombstones to clean up the temporary channel +and write the parsed envelopes to their destination channels. The courier performs +the following actions. + 1. Derives a ReadCap from the WriteCap. + 2. Reads boxes from the temporary channel. + 3. Parses boxes into CourierEnvelopes. + 4. Sends each envelope to intermediate replicas for replication. + 5. Writes tombstones to clean up the temporary channel. The Rust and Python bindings accept optional `courier_identity_hash` and `courier_queue_id` arguments to pin the command to a particular @@ -1093,11 +1146,11 @@ func (t *ThinClient) StartResendingCopyCommandWithCourier( CancelResendingCopyCommand cancels ARQ resending for a copy command. -This method stops the automatic repeat request (ARQ) for a previously started +This method stops the ARQ for a previously started copy command. This is useful when: - - A reply has been received through another channel - - The operation should be aborted - - The copy command is no longer needed + - A reply has been received through another channel. + - The operation should be aborted. + - The copy command is no longer needed. {{< tabpane >}} {{< tab header="Go" lang="go" >}} @@ -1183,22 +1236,130 @@ pub async fn get_courier_destination( {{< /tab >}} {{< /tabpane >}} -### pigeonhole_geometry (Rust only) +## Vouchers + +### VoucherMint / voucher_mint + +VoucherMint mints a Voucher from the joiner's MessageStream write cap. The +returned reply carries the Voucher to hand over out of band, the payload to +publish to VoucherStream box 0, the rendezvous stream caps, and the reply +keypair; persist VoucherSecretKey to open the inductor's reply later. + +{{< tabpane >}} +{{< tab header="Go" lang="go" >}} +func (t *ThinClient) VoucherMint(messageWriteCap []byte, displayName string) (*VoucherMintReply, error) +{{< /tab >}} +{{< tab header="Rust" lang="rust" >}} +pub async fn voucher_mint( + &self, + message_write_cap: &[u8], + display_name: &str, +) -> Result +{{< /tab >}} +{{< tab header="Python" lang="python" >}} +async def voucher_mint(self, message_write_cap: bytes, display_name: str) -> VoucherMintResult: +{{< /tab >}} +{{< /tabpane >}} + +### VoucherInduct / voucher_induct + +VoucherInduct verifies a published VoucherPayload and seals a reply to the +joiner. The returned reply carries the joiner's salt-mutated read cap (the +live read cap to hand the group), the sealed reply to write to VoucherStream +box 1, and the salt the inductor minted. + +{{< tabpane >}} +{{< tab header="Go" lang="go" >}} +func (t *ThinClient) VoucherInduct(voucher, voucherPayload, whoReply []byte) (*VoucherInductReply, error) +{{< /tab >}} +{{< tab header="Rust" lang="rust" >}} +pub async fn voucher_induct( + &self, + voucher: &[u8], + voucher_payload: &[u8], + who_reply: &[u8], +) -> Result +{{< /tab >}} +{{< tab header="Python" lang="python" >}} +async def voucher_induct(self, voucher: bytes, voucher_payload: bytes, who_reply: bytes) -> VoucherInductResult: +{{< /tab >}} +{{< /tabpane >}} + +### VoucherOpen / voucher_open -Returns the pigeonhole geometry the daemon supplied during the -connection handshake. This geometry defines the payload sizes and -envelope formats for the pigeonhole protocol. +VoucherOpen opens the inductor's sealed reply with the joiner's voucher +secret key, recovers the salt, and mutates the joiner's MessageStream write +cap by it. The returned reply carries the opaque WhoReply, the salt, and the +salt-mutated write cap with which the joiner writes real messages. + +{{< tabpane >}} +{{< tab header="Go" lang="go" >}} +func (t *ThinClient) VoucherOpen(voucherSecretKey, sealedReply, messageWriteCap []byte) (*VoucherOpenReply, error) +{{< /tab >}} +{{< tab header="Rust" lang="rust" >}} +pub async fn voucher_open( + &self, + voucher_secret_key: &[u8], + sealed_reply: &[u8], + message_write_cap: &[u8], +) -> Result +{{< /tab >}} +{{< tab header="Python" lang="python" >}} +async def voucher_open(self, voucher_secret_key: bytes, sealed_reply: bytes, message_write_cap: bytes) -> VoucherOpenResult: +{{< /tab >}} +{{< /tabpane >}} -Panics if called before the daemon's first ConnectionStatusEvent -has been processed, or if the daemon did not supply the geometry -(an incompatible daemon). +### VoucherDeriveStream / voucher_derive_stream -Go callers retrieve the same value through -`GetConfig().PigeonholeGeometry`. The Python binding stores the -geometry internally but does not at present expose a public -accessor. +VoucherDeriveStream derives the VoucherStream caps from the Voucher, which +the inductor needs to read box 0 before inducting. {{< tabpane >}} +{{< tab header="Go" lang="go" >}} +func (t *ThinClient) VoucherDeriveStream(voucher []byte) (*VoucherDeriveStreamReply, error) +{{< /tab >}} +{{< tab header="Rust" lang="rust" >}} +pub async fn voucher_derive_stream( + &self, + voucher: &[u8], +) -> Result +{{< /tab >}} +{{< tab header="Python" lang="python" >}} +async def voucher_derive_stream(self, voucher: bytes) -> VoucherStreamResult: +{{< /tab >}} +{{< /tabpane >}} + +## Geometry + +### GetSphinxGeometry / sphinx_geometry + +GetSphinxGeometry returns the Sphinx geometry the daemon supplied during +the connection handshake. It is nil until Dial() has completed. + +The Python binding stores the Sphinx geometry internally but does not +at present expose a public accessor. + +{{< tabpane >}} +{{< tab header="Go" lang="go" >}} +func (t *ThinClient) GetSphinxGeometry() *geo.Geometry +{{< /tab >}} +{{< tab header="Rust" lang="rust" >}} +pub fn sphinx_geometry(&self) -> Geometry +{{< /tab >}} +{{< /tabpane >}} + +### GetPigeonholeGeometry / pigeonhole_geometry + +GetPigeonholeGeometry returns the Pigeonhole geometry the daemon supplied +during the connection handshake. It is nil until Dial() has completed. + +The Python binding stores the geometry internally but does not at +present expose a public accessor. + +{{< tabpane >}} +{{< tab header="Go" lang="go" >}} +func (t *ThinClient) GetPigeonholeGeometry() *pigeonholeGeo.Geometry +{{< /tab >}} {{< tab header="Rust" lang="rust" >}} pub fn pigeonhole_geometry(&self) -> PigeonholeGeometry {{< /tab >}} @@ -1228,7 +1389,7 @@ def new_message_id() -> bytes: ### NewSURBID / new_surb_id -NewSURBID generates a new Single Use Reply Block identifier. +NewSURBID generates a new SURB. SURB IDs are used in the legacy API to correlate reply messages with their original requests. Each SURB should have a unique ID. @@ -1264,6 +1425,77 @@ def new_query_id(self) -> bytes: {{< /tab >}} {{< /tabpane >}} +### GetMessageBoxIndexCounter / get_message_box_index_counter + +GetMessageBoxIndexCounter returns the BACAP Idx64 counter embedded in a +MessageBoxIndex. Callers can use this to order or compare two indexes +without having to know bacap.MessageBoxIndex's binary layout. + +{{< tabpane >}} +{{< tab header="Go" lang="go" >}} +func (t *ThinClient) GetMessageBoxIndexCounter(messageBoxIndex *bacap.MessageBoxIndex) (uint64, error) +{{< /tab >}} +{{< tab header="Rust" lang="rust" >}} +pub async fn get_message_box_index_counter( + &self, + message_box_index: &[u8], +) -> Result +{{< /tab >}} +{{< tab header="Python" lang="python" >}} +async def get_message_box_index_counter(self, message_box_index: bytes) -> int: +{{< /tab >}} +{{< /tabpane >}} + +### GetConfig / get_config + +GetConfig returns the client's configuration. + +The Rust binding exposes no configuration accessor; Rust callers read +the negotiated geometries through the dedicated `sphinx_geometry` and +`pigeonhole_geometry` methods. + +{{< tabpane >}} +{{< tab header="Go" lang="go" >}} +func (t *ThinClient) GetConfig() *Config +{{< /tab >}} +{{< tab header="Python" lang="python" >}} +def get_config(self) -> Config: +{{< /tab >}} +{{< /tabpane >}} + +### Shutdown (Go only) + +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(). + +The Rust and Python bindings expose a single teardown method, `stop`, +documented under Close / stop above. + +{{< tabpane >}} +{{< tab header="Go" lang="go" >}} +func (t *ThinClient) Shutdown() +{{< /tab >}} +{{< /tabpane >}} + +### GetLogger (Go only) + +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. + +The Rust and Python bindings leave logging to the host application +and expose no equivalent accessor. + +{{< tabpane >}} +{{< tab header="Go" lang="go" >}} +func (t *ThinClient) GetLogger(prefix string) *logging.Logger +{{< /tab >}} +{{< /tabpane >}} + ## Data Types The Pigeonhole methods return structured results whose fields are @@ -1291,8 +1523,8 @@ NewKeypairReply is the reply to a NewKeypair request. | Field | Type | Description | |---|---|---| -| `QueryID` | `*[QueryIDLength]byte` | QueryID is used for correlating this reply with the NewKeypair request | -| `WriteCap` | `*bacap.WriteCap` | WriteCap is the write capability that should be stored for channel | +| `QueryID` | `*[QueryIDLength]byte` | QueryID is used for correlating this reply with the NewKeypair request. | +| `WriteCap` | `*bacap.WriteCap` | WriteCap is the write capability that should be stored for the channel. | | `ReadCap` | `*bacap.ReadCap` | ReadCap is the read capability that can be shared with others to allow them to read messages from this channel. | | `FirstMessageIndex` | `*bacap.MessageBoxIndex` | FirstMessageIndex is the first message index that should be used when writing messages to the channel. | | `ErrorCode` | `uint8` | ErrorCode indicates the reason for a failure to create a new keypair if any. Otherwise it is set to zero for success. | @@ -1304,7 +1536,7 @@ EncryptWriteReply is the reply to an EncryptWrite request. | Field | Type | Description | |---|---|---| | `QueryID` | `*[QueryIDLength]byte` | QueryID is used for correlating this reply with the EncryptWrite request | -| `MessageCiphertext` | `[]byte` | MessageCiphertext is the encrypted message ciphertext that should be sent to the Courier service. | +| `MessageCiphertext` | `[]byte` | MessageCiphertext is the encrypted message ciphertext that should be sent to the courier service. | | `EnvelopeDescriptor` | `[]byte` | EnvelopeDescriptor contains the serialized EnvelopeDescriptor that contains the private key material needed to decrypt the envelope reply. | | `EnvelopeHash` | `*[32]byte` | EnvelopeHash is the hash of the CourierEnvelope that was sent to the mixnet and is used to resume the write operation. | | `NextMessageBoxIndex` | `*bacap.MessageBoxIndex` | NextMessageBoxIndex is the next message box index to use for subsequent write operations. This is computed by the daemon using BACAP's NextIndex. | @@ -1316,8 +1548,8 @@ EncryptReadReply is the reply to an EncryptRead request. | Field | Type | Description | |---|---|---| -| `QueryID` | `*[QueryIDLength]byte` | QueryID is used for correlating this reply with the EncryptRead request | -| `MessageCiphertext` | `[]byte` | MessageCiphertext is the encrypted message ciphertext that should be sent to the Courier service. | +| `QueryID` | `*[QueryIDLength]byte` | QueryID is used for correlating this reply with the EncryptRead request. | +| `MessageCiphertext` | `[]byte` | MessageCiphertext is the encrypted message ciphertext that should be sent to the courier service. | | `EnvelopeDescriptor` | `[]byte` | EnvelopeDescriptor contains the serialized EnvelopeDescriptor that contains the private key material needed to decrypt the envelope reply. | | `EnvelopeHash` | `*[32]byte` | EnvelopeHash is the hash of the CourierEnvelope that was sent to the mixnet and is used to resume the read operation. | | `NextMessageBoxIndex` | `*bacap.MessageBoxIndex` | NextMessageBoxIndex is the next message box index to use for subsequent read operations. This is computed by the daemon using BACAP's NextIndex. | @@ -1329,7 +1561,7 @@ StartResendingEncryptedMessageReply is the reply to a StartResendingEncryptedMes | Field | Type | Description | |---|---|---| -| `QueryID` | `*[QueryIDLength]byte` | QueryID is used for correlating this reply with the StartResendingEncryptedMessage request | +| `QueryID` | `*[QueryIDLength]byte` | QueryID is used for correlating this reply with the StartResendingEncryptedMessage request. | | `Plaintext` | `[]byte` | Plaintext is the plaintext message that was read from the channel. | | `ErrorCode` | `uint8` | ErrorCode indicates the reason for a failure to start resending the encrypted message if any. Otherwise it is set to zero for success. | | `CourierIdentityHash` | `*[32]byte` | CourierIdentityHash is the 32-byte hash of the identity key of the courier that was selected to handle this message. Callers can watch PKI document updates for this courier disappearing from consensus and cancel+re-encrypt if it does. | @@ -1341,9 +1573,9 @@ StartResendingCopyCommandReply is the reply to a StartResendingCopyCommand reque | Field | Type | Description | |---|---|---| -| `QueryID` | `*[QueryIDLength]byte` | QueryID is used for correlating this reply with the StartResendingCopyCommand request | +| `QueryID` | `*[QueryIDLength]byte` | QueryID is used for correlating this reply with the StartResendingCopyCommand request. | | `ErrorCode` | `uint8` | ErrorCode indicates the reason for a failure to execute the copy command if any. Otherwise it is set to zero for success. | -| `ReplicaErrorCode` | `uint8` | ReplicaErrorCode is the pigeonhole replica ErrorCode that caused the Copy command to abort on the courier. Meaningful only when ErrorCode indicates a Copy failure and the courier identified a specific replica-side reason (e.g. ReplicaErrorBoxAlreadyExists). | +| `ReplicaErrorCode` | `uint8` | ReplicaErrorCode is the Pigeonhole replica ErrorCode that caused the Copy command to abort on the courier. Meaningful only when ErrorCode indicates a Copy failure and the courier identified a specific replica-side reason (e.g., ReplicaErrorBoxAlreadyExists). | | `FailedEnvelopeIndex` | `uint64` | FailedEnvelopeIndex is the 1-based sequential position in the copy stream of the envelope whose write triggered the abort. 0 if not applicable. Not a BACAP message index. | ### NextMessageBoxIndex result @@ -1352,7 +1584,7 @@ NextMessageBoxIndexReply is the reply to a NextMessageBoxIndex request. | Field | Type | Description | |---|---|---| -| `QueryID` | `*[QueryIDLength]byte` | QueryID is used for correlating this reply with the NextMessageBoxIndex request | +| `QueryID` | `*[QueryIDLength]byte` | QueryID is used for correlating this reply with the NextMessageBoxIndex request. | | `NextMessageBoxIndex` | `*bacap.MessageBoxIndex` | NextMessageBoxIndex is the incremented message box index. | | `ErrorCode` | `uint8` | ErrorCode indicates the reason for a failure to increment the index if any. Otherwise it is set to zero for success. | diff --git a/tools/thin-client-api-gen/groups.yaml b/tools/thin-client-api-gen/groups.yaml index adb56da3..9a3149b5 100644 --- a/tools/thin-client-api-gen/groups.yaml +++ b/tools/thin-client-api-gen/groups.yaml @@ -125,6 +125,29 @@ rust: "ThinClient::get_pki_document_raw" python: "ThinClient.get_pki_document_raw" +- group: get_directory_authorities + section: "PKI and Service Discovery" + title: "GetDirectoryAuthorities / get_directory_authorities" + summary: | + Returns the directory authority descriptors the client daemon is + configured to trust, including their identity keys and addresses. + go: "ThinClient.GetDirectoryAuthorities" + rust: "ThinClient::get_directory_authorities" + python: "ThinClient.get_directory_authorities" + +- group: pki_document_for_epoch + section: "PKI and Service Discovery" + title: "PKIDocumentForEpoch / pki_document_for_epoch" + summary: | + Returns the cached PKI document for a specific epoch, or an error if + the daemon has not retained a document for that epoch. + notes: | + The Rust binding does not expose a per-epoch accessor; Rust callers + use `pki_document` for the current document, or `get_pki_document_raw` + for the signed document at a given epoch. + go: "ThinClient.PKIDocumentForEpoch" + python: "ThinClient.pki_document_for_epoch" + - group: get_service section: "PKI and Service Discovery" title: "GetService / get_service" @@ -470,20 +493,73 @@ the first element of the returned slice. rust: "ThinClient::get_courier_destination" +# --------------------------------------------------------------------------- +# Vouchers +# --------------------------------------------------------------------------- + +- group: voucher_mint + section: "Vouchers" + title: "VoucherMint / voucher_mint" + summary: | + Mints a Voucher from the joiner's MessageStream write capability. + go: "ThinClient.VoucherMint" + rust: "ThinClient::voucher_mint" + python: "ThinClient.voucher_mint" + +- group: voucher_induct + section: "Vouchers" + title: "VoucherInduct / voucher_induct" + summary: | + Verifies a published VoucherPayload and seals a reply to the joiner. + go: "ThinClient.VoucherInduct" + rust: "ThinClient::voucher_induct" + python: "ThinClient.voucher_induct" + +- group: voucher_open + section: "Vouchers" + title: "VoucherOpen / voucher_open" + summary: | + Opens the inductor's sealed reply with the joiner's voucher secret key. + go: "ThinClient.VoucherOpen" + rust: "ThinClient::voucher_open" + python: "ThinClient.voucher_open" + +- group: voucher_derive_stream + section: "Vouchers" + title: "VoucherDeriveStream / voucher_derive_stream" + summary: | + Derives the VoucherStream capabilities from the Voucher. + go: "ThinClient.VoucherDeriveStream" + rust: "ThinClient::voucher_derive_stream" + python: "ThinClient.voucher_derive_stream" + +# --------------------------------------------------------------------------- +# Geometry +# --------------------------------------------------------------------------- + +- group: sphinx_geometry + section: "Geometry" + title: "GetSphinxGeometry / sphinx_geometry" + summary: | + Returns the Sphinx geometry the daemon supplied during the connection + handshake, describing the packet and payload sizes of the mixnet's + Sphinx packet format. + notes: | + The Python binding stores the Sphinx geometry internally but does not + at present expose a public accessor. + go: "ThinClient.GetSphinxGeometry" + rust: "ThinClient::sphinx_geometry" + - group: pigeonhole_geometry - section: "Pigeonhole: Courier Discovery" - title: "pigeonhole_geometry (Rust only)" + section: "Geometry" + title: "GetPigeonholeGeometry / pigeonhole_geometry" summary: | - Returns a reference to the negotiated Pigeonhole geometry, so that - callers can size payloads to its `max_plaintext_payload_length`. - Go callers obtain this via `GetConfig().PigeonholeGeometry`; the - Python binding stores it internally but does not currently expose - an accessor. + Returns the negotiated Pigeonhole geometry, so that callers can size + payloads to its maximum plaintext payload length. notes: | - Go callers retrieve the same value through - `GetConfig().PigeonholeGeometry`. The Python binding stores the - geometry internally but does not at present expose a public - accessor. + The Python binding stores the geometry internally but does not at + present expose a public accessor. + go: "ThinClient.GetPigeonholeGeometry" rust: "ThinClient::pigeonhole_geometry" # --------------------------------------------------------------------------- @@ -517,3 +593,49 @@ go: "ThinClient.NewQueryID" rust: "ThinClient::new_query_id" python: "ThinClient.new_query_id" + +- group: get_message_box_index_counter + section: "Utility" + title: "GetMessageBoxIndexCounter / get_message_box_index_counter" + summary: | + Returns the BACAP Idx64 counter embedded in a MessageBoxIndex, the + sequence number of a box within its stream. + go: "ThinClient.GetMessageBoxIndexCounter" + rust: "ThinClient::get_message_box_index_counter" + python: "ThinClient.get_message_box_index_counter" + +- group: get_config + section: "Utility" + title: "GetConfig / get_config" + summary: | + Returns the client's configuration object, including the Sphinx and + Pigeonhole geometries negotiated with the daemon. + notes: | + The Rust binding exposes no configuration accessor; Rust callers read + the negotiated geometries through the dedicated `sphinx_geometry` and + `pigeonhole_geometry` methods. + go: "ThinClient.GetConfig" + python: "ThinClient.get_config" + +- group: shutdown + section: "Utility" + title: "Shutdown (Go only)" + summary: | + Cleanly shuts down the ThinClient instance and stops its background + workers. Unlike Close, it does not send a close notification to the + daemon. + notes: | + The Rust and Python bindings expose a single teardown method, `stop`, + documented under Close / stop above. + go: "ThinClient.Shutdown" + +- group: get_logger + section: "Utility" + title: "GetLogger (Go only)" + summary: | + Returns a logger instance with the given prefix, using the thin + client's configured logging backend. + notes: | + The Rust and Python bindings leave logging to the host application + and expose no equivalent accessor. + go: "ThinClient.GetLogger" diff --git a/tools/thin-client-api-gen/pinned-versions.env b/tools/thin-client-api-gen/pinned-versions.env index 112a2870..d46010dd 100644 --- a/tools/thin-client-api-gen/pinned-versions.env +++ b/tools/thin-client-api-gen/pinned-versions.env @@ -7,8 +7,8 @@ # to run unchanged on a workstation whose checkouts live elsewhere by # setting the relevant variables in the shell. -KATZENPOST_TAG ?= v0.0.80 -THINCLIENT_TAG ?= 0.0.17 +KATZENPOST_TAG ?= v0.0.81 +THINCLIENT_TAG ?= 0.0.18 KATZENPOST_PATH ?= /home/human/katzenpost THINCLIENT_PATH ?= /home/human/thin_client