From bec3734676faf3868ea225e0316e77ad5a7e4421 Mon Sep 17 00:00:00 2001 From: Gab Virebent Date: Sat, 11 Jul 2026 13:37:23 +0200 Subject: Switch Nym WS transport to nym-client's binary protocol, drop second base64 layer Client.Send now speaks nym-client's native binary websocket protocol (tag || recipient || conn_id || data_len || data) instead of wrapping the ciphertext in a JSON text message, which required base64-encoding it a second time on top of the browser's own base64 layer. The reader's self-address query also switched to the binary protocol: nym-client picks text-vs-binary for every later "received" push based on the format of the last request seen on a connection, so leaving the reader in JSON/text mode would have made nym-client run a lossy UTF-8 conversion over the now-unencoded binary payload, corrupting it. Fixed the binary Received-frame parsing along the way (previous code assumed a fixed 16-byte tag with no length prefix, which never matched the real protocol and was never exercised while the connection stayed text-mode). Verified end-to-end against the real embedded nym-client 1.1.76 binary, with the exact wire format cross-checked against upstream nym source at the pinned build commit. --- internal/nym/client.go | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) (limited to 'internal/nym/client.go') diff --git a/internal/nym/client.go b/internal/nym/client.go index 89fcf52..e0b14d2 100644 --- a/internal/nym/client.go +++ b/internal/nym/client.go @@ -1,8 +1,7 @@ package nym import ( - "encoding/base64" - "encoding/json" + "encoding/binary" "fmt" "io" "io/fs" @@ -18,6 +17,7 @@ import ( // Client sends payloads through the Nym mixnet via an embedded nym-client subprocess. type Client struct { journalistAddr string + recipient []byte // parsed 96-byte wire form of journalistAddr wsPort int dryRun bool process *exec.Cmd @@ -26,8 +26,12 @@ type Client struct { configDir string } -func New(journalistAddr string, binFS fs.FS) *Client { - return &Client{journalistAddr: journalistAddr, wsPort: 1978, binFS: binFS} +func New(journalistAddr string, binFS fs.FS) (*Client, error) { + recipient, err := ParseRecipient(journalistAddr) + if err != nil { + return nil, fmt.Errorf("parse journalist nym address: %w", err) + } + return &Client{journalistAddr: journalistAddr, recipient: recipient, wsPort: 1978, binFS: binFS}, nil } func NewDryRun(journalistAddr string) *Client { @@ -75,31 +79,26 @@ func (c *Client) Start() error { return fmt.Errorf("nym-client websocket did not start in time") } -type sendMsg struct { - Type string `json:"type"` - Message string `json:"message"` - Recipient string `json:"recipient"` - WithReplySurb bool `json:"withReplySurb"` -} - -// Send delivers payload to the journalist's Nym address through the mixnet. +// Send delivers payload to the journalist's Nym address through the mixnet +// using nym-client's binary websocket protocol (tag || recipient || conn_id || +// data_len || data). This avoids the JSON+base64 wrapping of the old text +// protocol, which inflated every payload by ~4/3 before it ever reached the +// nym-client's own 16MB hardcoded websocket message limit. func (c *Client) Send(payload []byte) error { if c.dryRun { fmt.Printf("[dry-run] payload received: %d bytes — would send to %s\n", len(payload), c.journalistAddr) return nil } - msg := sendMsg{ - Type: "send", - Message: base64.StdEncoding.EncodeToString(payload), - Recipient: c.journalistAddr, - WithReplySurb: false, - } - raw, err := json.Marshal(msg) - if err != nil { - return fmt.Errorf("marshal: %w", err) - } - if err := websocket.Message.Send(c.ws, string(raw)); err != nil { + + req := make([]byte, 0, 1+len(c.recipient)+8+8+len(payload)) + req = append(req, ReqSend) + req = append(req, c.recipient...) + req = binary.BigEndian.AppendUint64(req, 0) // connection_id: none + req = binary.BigEndian.AppendUint64(req, uint64(len(payload))) + req = append(req, payload...) + + if err := websocket.Message.Send(c.ws, req); err != nil { return fmt.Errorf("ws send: %w", err) } return nil -- cgit v1.2.3