Documentation
¶
Index ¶
- Constants
- Variables
- func Address(identityPub ed25519.PublicKey) [AddressSize]byte
- func BuildChatAckPlain(peer [ChatPeerHandleSize]byte, upToSeq uint32, ...) []byte
- func BuildChatAuthBootstrapPlain(addr [AddressSize]byte, ts uint32, proof [ChatAccountProofSize]byte) []byte
- func BuildChatDataPlain(idx uint8, chunk []byte) ([]byte, error)
- func BuildChatFetchPlain(peer [ChatPeerHandleSize]byte, seq uint32, block uint8) []byte
- func BuildChatFinPlain(crc uint32) []byte
- func BuildChatFragPlain(idx, total uint8, chunk []byte) []byte
- func BuildChatHandshakeStream(ephPub []byte, protoVer, kind byte, sealedBootstrap []byte) []byte
- func BuildChatKeyFetchPlain(addr [AddressSize]byte) []byte
- func BuildChatSendStartPlain(dst [AddressSize]byte, totalLen uint16) []byte
- func BuildChatSendStatusPlain(peer [AddressSize]byte) []byte
- func BuildChatStatusPlain() []byte
- func ChannelEligibleForSharedCache(channel uint16) bool
- func ChatAccountProof(kss [KeySize]byte, ephPub []byte, addr [AddressSize]byte, ts uint32, ...) [ChatAccountProofSize]byte
- func ChatAvailableFromBlock0(block0 []byte) (available, ok bool)
- func ChatBootstrapCounter() uint32
- func ChatCellJitter(queryKey [KeySize]byte, selector [chatSelectorSize]byte, counter uint32) int
- func ChatChunkMAC(macKey [KeySize]byte, sessionID uint32, index uint8, chunk []byte) [ChatChunkMACSize]byte
- func ChatClearHandshakeSelector(sel *[chatSelectorSize]byte)
- func ChatContentKey(own *ecdh.PrivateKey, peerEncPub []byte, src, dst [AddressSize]byte, ...) ([KeySize]byte, error)
- func ChatIsHandshakeSelector(sel [chatSelectorSize]byte) bool
- func ChatIsSessionLost(resp []byte) bool
- func ChatMarkHandshakeSelector(sel *[chatSelectorSize]byte)
- func ChatPeerHandle(addr [AddressSize]byte) [ChatPeerHandleSize]byte
- func ChatPlainOp(pt []byte) byte
- func ChatPlainVersion(pt []byte) byte
- func ChatReceiptKey(own *ecdh.PrivateKey, peerEncPub []byte) ([KeySize]byte, error)
- func ChatReceiptMAC(receiptKey [KeySize]byte, sender, recipient [AddressSize]byte, upToSeq uint32) [ChatReceiptMACSize]byte
- func ChatServerMAC(kss [KeySize]byte, src, dst [AddressSize]byte, seq uint32, ciphertext []byte) [ChatSrvMACSize]byte
- func ChatServerSharedKey(priv *ecdh.PrivateKey, peerPub, clientEncPub, serverEkPub []byte) ([KeySize]byte, error)
- func ChatSessionKey(own *ecdh.PrivateKey, peerPub []byte, protoVer byte, queryKey [KeySize]byte) ([KeySize]byte, error)
- func ChatSessionKeys(priv *ecdh.PrivateKey, peerPub, ephPub, serverEkPub []byte) (routing, mac [KeySize]byte, err error)
- func CompressMessages(data []byte) []byte
- func ContentDigest(canonical []byte) []byte
- func ContentHashOf(msgs []Message) uint32
- func DecodeChatCell(queryKey [KeySize]byte, qname, domain string) (selector [chatSelectorSize]byte, counter uint32, payload []byte, err error)
- func DecodeQuery(queryKey [KeySize]byte, qname, domain string) (channel, block uint16, err error)
- func DecodeResponse(responseKey [KeySize]byte, encoded string) ([]byte, error)
- func DecodeSendQuery(queryKey [KeySize]byte, qname, domain string) (targetChannel uint16, message []byte, err error)
- func DecodeTitlesData(data []byte) (map[string]string, error)
- func DecodeUpstreamBlockQuery(queryKey [KeySize]byte, qname, domain string) (sessionID uint16, index uint8, chunk []byte, err error)
- func DecodeVersionData(block []byte) (string, error)
- func DecompressMessages(data []byte) ([]byte, error)
- func DecompressMessagesLimited(data []byte, maxOut int) ([]byte, error)
- func Decrypt(key [KeySize]byte, ciphertext []byte) ([]byte, error)
- func DecryptRelayBlob(key [KeySize]byte, blob []byte) ([]byte, error)
- func DecryptWithNonce(key [KeySize]byte, nonce, ct []byte) ([]byte, error)
- func DeriveEncryptionKey(seed []byte) (*ecdh.PrivateKey, error)
- func DeriveIdentityKey(seed []byte) (ed25519.PrivateKey, error)
- func DeriveKeys(passphrase string) (queryKey, responseKey [KeySize]byte, err error)
- func DeriveRelayKey(passphrase string) ([KeySize]byte, error)
- func EncodeAdminQuery(queryKey [KeySize]byte, cmd AdminCmd, arg []byte, domain string, ...) (string, error)
- func EncodeChatCell(queryKey [KeySize]byte, mode QueryEncoding, selector [chatSelectorSize]byte, ...) (string, error)
- func EncodeChatInfo(info ChatInfo) []byte
- func EncodeChatMessage(contentKey, kss [KeySize]byte, src, dst [AddressSize]byte, seq uint32, ...) ([]byte, error)
- func EncodeExtraBlock(priv ed25519.PrivateKey, channelID uint16, digest []byte, ts int64) ([]byte, error)
- func EncodeMediaBlockHeader(h MediaBlockHeader) []byte
- func EncodeMediaText(meta MediaMeta, caption string) string
- func EncodeMetadataExtended(m *Metadata) ([][]byte, error)
- func EncodeProfilePicsBundle(b ProfilePicsBundle) []byte
- func EncodeQuery(queryKey [KeySize]byte, channel, block uint16, domain string, ...) (string, error)
- func EncodeQueryDeterministic(queryKey [KeySize]byte, channel, block uint16, domain string, ...) (string, error)
- func EncodeRegisterEnvelope(identity ed25519.PrivateKey, encPub []byte, timestamp uint32) ([]byte, error)
- func EncodeResponse(responseKey [KeySize]byte, data []byte, maxPadding int) (string, error)
- func EncodeSendQuery(queryKey [KeySize]byte, targetChannel uint16, message []byte, domain string, ...) (string, error)
- func EncodeTitlesData(titles map[string]string) []byte
- func EncodeUpstreamBlockQuery(queryKey [KeySize]byte, sessionID uint16, index uint8, chunk []byte, ...) (string, error)
- func EncodeUpstreamInitQuery(queryKey [KeySize]byte, init UpstreamInit, domain string, mode QueryEncoding) (string, error)
- func EncodeVersionData(version string) ([]byte, error)
- func Encrypt(key [KeySize]byte, plaintext []byte) ([]byte, error)
- func EncryptRelayBlob(key [KeySize]byte, plaintext []byte) ([]byte, error)
- func EncryptWithNonce(key [KeySize]byte, nonce, plaintext []byte) ([]byte, error)
- func GenerateEphemeralKey() (*ecdh.PrivateKey, error)
- func GenerateSeed() ([]byte, error)
- func IsMediaChannel(ch uint16) bool
- func OpenChat(ksession [KeySize]byte, selector []byte, counter uint32, sealed []byte) ([]byte, error)
- func OpenChatCellPayload(ksession [KeySize]byte, selector [chatSelectorSize]byte, counter uint32, ...) ([]byte, error)
- func OpenChatResponse(ksession [KeySize]byte, selector [chatSelectorSize]byte, reqCounter uint32, ...) (status byte, body []byte, err error)
- func ParseChatHandshakeStream(stream []byte) (ephPub []byte, protoVer, kind byte, sealedBootstrap []byte, err error)
- func PeekExtendedHeader(block0 []byte) (extended bool, blockCount uint8, hash [EMHHashLen]byte, err error)
- func RelayDomainSegment(domain, passphrase string) string
- func RelayObjectName(size int64, crc uint32, passphrase string) string
- func SanitiseMediaFilename(s string) string
- func SealChat(ksession [KeySize]byte, selector []byte, counter uint32, plaintext []byte) []byte
- func SealChatCellPayload(ksession [KeySize]byte, selector [chatSelectorSize]byte, counter uint32, ...) ([]byte, error)
- func SealChatCellPayloadN(ksession [KeySize]byte, selector [chatSelectorSize]byte, counter uint32, ...) ([]byte, error)
- func SealChatResponse(ksession [KeySize]byte, selector [chatSelectorSize]byte, reqCounter uint32, ...) []byte
- func SerializeMessages(msgs []Message) []byte
- func SerializeMetadata(m *Metadata) []byte
- func SplitChunks(data []byte, chunkSize int) [][]byte
- func SplitIntoBlocks(data []byte) [][]byte
- func VerifyEntry(bundle []byte, entry ProfilePicEntry) ([]byte, error)
- func VerifyExtendedHash(hash [EMHHashLen]byte, body []byte) error
- func VerifyExtraBlock(pub ed25519.PublicKey, channelID uint16, eb *ExtraBlock) error
- type AdminCmd
- type ChannelInfo
- type ChatAck
- type ChatAuthBootstrap
- type ChatData
- type ChatFetch
- type ChatFin
- type ChatFrag
- type ChatInfo
- type ChatKeyFetch
- type ChatLimits
- type ChatMessage
- type ChatSendStart
- type ChatSendStatus
- type ChatType
- type ChunkReassembler
- type ExtraBlock
- type MediaBlockHeader
- type MediaCompression
- type MediaMeta
- type Message
- type Metadata
- type ProfilePicEntry
- type ProfilePicsBundle
- type ProfilePicsBundleHeader
- type QueryEncoding
- type RegisterEnvelope
- type UpstreamInit
- type UpstreamKind
Constants ¶
const ( // SeedSize is the length of a chat identity seed in bytes (256-bit, a // 24-word recovery mnemonic). SeedSize = 32 // AddressSize is the length of a chat address in bytes. AddressSize = 12 // X25519KeySize is the length of an x25519 public or private key. X25519KeySize = 32 // ChatSrvMACSize is the truncated HMAC length authenticating a message // envelope to the server. ChatSrvMACSize = 8 // ChatChunkMACSize is the truncated HMAC length authenticating one // upload chunk to the session. ChatChunkMACSize = 4 // ChatReceiptMACSize is the truncated HMAC length of an end-to-end delivery // receipt. 6 bytes keeps the ACK and SEND_STATUS response in one cell, and // the MAC is verified offline by the sender (no online forgery oracle), so a // malicious server still cannot fabricate a ✓✓ — only withhold a real one. ChatReceiptMACSize = 6 )
const ( // ChatMessageVersion is the wire version of message envelopes. ChatMessageVersion = 1 // ChatRegisterVersion is the wire version of registration records. ChatRegisterVersion = 1 // ChatMaxPlaintextBytes caps a decompressed message body. Real messages are // well under MaxMsgBytes; the cap only bounds memory if a hostile peer sends // a deflate bomb (a tiny ciphertext that inflates ~1000x). 64 KiB is far // above any legitimate text and far below a memory-DoS. ChatMaxPlaintextBytes = 64 * 1024 )
const ( // ChatChannel is a reserved feed-channel number that feed domains refuse. // Chat is served only on its own sub-domains (dispatched by domain), so it // is no longer carried in chat queries — it just stays reserved. ChatChannel uint16 = 0xFFF6 // ChatInfoChannel serves the signed chat capability payload on the feed // metadata path (block-count-prefixed, like TitlesChannel). ChatInfoChannel uint16 = 0xFFF5 // ChatProtocolVersion is the chat request/response wire version (the high // nibble of an op byte). Kept for future changes; not bumped (chat is // unreleased, so there is no prior wire to stay compatible with). ChatProtocolVersion = 1 // ChatCellPayloadSize is the per-cell payload after selector+counter. ChatCellPayloadSize = chatCellLen - chatSelectorSize - chatCounterSize // 19 // ChatCellPlainSize is the max sealed plaintext (op + fields) per in-context // cell (payload minus the seal tag). ChatCellPlainSize = ChatCellPayloadSize - ChatSealTagSize // 15 // ChatDataChunkSize is the message-body bytes carried by one DATA cell at the // default budget. ChatDataChunkSize = ChatCellPlainSize - 2 // op(1)+idx(1)+chunk → 13 // ChatCellPlainMin / ChatCellPlainMax bound the configurable per-cell op // plaintext budget B (RFC §8.2 — v1 constant, client-chosen). The default is // ChatCellPlainSize (15); a client may pick any B in [min,max] to trade query // length against query count. Floor = room for the smallest framing; ceiling = // fits a single DNS label. ChatCellPlainMin = 6 ChatCellPlainMax = 21 // ChatCellPayloadMax is the largest sealed cell payload (max budget + jitter + // tag). ChatCellPayloadMax = ChatCellPlainMax + ChatJitterMax + ChatSealTagSize // ChatJitterMax bounds the deterministic per-cell length jitter (RFC §8.2): // 0..ChatJitterMax extra zero-pad bytes inside the seal, so cells vary in // length like the feed's 0-4 suffix instead of sitting at a single point. The // pad is keyed by (selector, counter) so a retransmit reproduces the exact // query name (resolver caches stay warm). ChatJitterMax = 4 // ChatSelectorSize is exported for the session/selector layer. ChatSelectorSize = chatSelectorSize )
const ( ChatOpStatus = 1 ChatOpFetch = 2 ChatOpAck = 3 ChatOpKeyFetch = 4 ChatOpSendStatus = 5 ChatOpSendStart = 6 ChatOpData = 7 ChatOpFin = 8 // ChatOpFrag carries one fragment of a control op too large for the current // cell budget B (RFC §8.2): plaintext = op(1) ‖ idx(1) ‖ total(1) ‖ chunk. // The server reassembles, then dispatches the inner op. ChatOpFrag = 9 )
Chat opcodes (low nibble of the first sealed-plaintext byte).
const ( ChatHandshakeAuth = 1 // bootstrap = addr ‖ ts ‖ account proof ChatHandshakeRegister = 2 // bootstrap = full self-signed register record )
Handshake kinds (the cleartext byte after the eph key in the stream).
const ( ChatStatusOK = 0 ChatStatusUnknownRecipient = 1 ChatStatusInboxFull = 2 ChatStatusPairQuota = 3 ChatStatusRateLimited = 4 ChatStatusUnknownSender = 5 ChatStatusBadVersion = 6 ChatStatusBusy = 7 ChatStatusUnknownSession = 8 ChatStatusBadAuth = 9 // ChatStatusFragMore acks a non-final OP_FRAG cell: "fragment received, keep // sending"; the cell that completes the op returns the inner op's status. ChatStatusFragMore = 10 ChatStatusNotFound = 11 ChatStatusIncomplete = 12 ChatStatusReplay = 13 ChatStatusBadRequest = 14 ChatStatusDisabled = 15 )
Chat response status codes.
const ( KeySize = 32 // AES-256 NonceSize = 12 // GCM nonce )
const ( // SendChannel is the special channel number used for upstream message sending. // When a query has channel == SendChannel, the block field encodes the target // channel number, and additional data labels carry the encrypted message text. SendChannel uint16 = 0xFFFE // AdminChannel is the special channel number for admin commands (add/remove // channels, hard refresh). The encrypted payload is "password\ncmd\narg". AdminChannel uint16 = 0xFFFD // UpstreamInitChannel starts a chunked upstream session for admin/send payloads. UpstreamInitChannel uint16 = 0xFFFC // UpstreamDataChannel carries one chunk of a chunked upstream session. UpstreamDataChannel uint16 = 0xFFFB // VersionChannel serves latest release version with random suffix. VersionChannel uint16 = 0xFFFA // TitlesChannel serves per-channel human-readable display names. TitlesChannel uint16 = 0xFFF9 // RelayInfoChannel serves the relay-discovery payload (GitHub // owner/repo + domain segment). Block 0 carries it. RelayInfoChannel uint16 = 0xFFF8 // ProfilePicsChannel serves the per-channel profile-picture index: // for every Telegram channel that has a profile photo we emit // (username, mediaCh, size, crc32). Bytes themselves live on the // referenced mediaCh and are fetched via the regular media path. // Off by default on the client. ProfilePicsChannel uint16 = 0xFFF7 // MaxUpstreamBlockPayload keeps uploaded query chunks comfortably below DNS // name limits across typical domains and resolver paths. MaxUpstreamBlockPayload = 8 // MaxUpstreamBlocks bounds the amount of server-side session state. MaxUpstreamBlocks = 128 )
const ( ExtraBlockVersion = 1 // ExtraDigestSize is the truncated-SHA-256 length used for content // digests. 16 bytes (128-bit) is ample second-preimage resistance for // authenticating feed content while keeping the block small. ExtraDigestSize = 16 )
const ( ExtraTLVTimestamp = 0x01 // int64 unix seconds, 8 bytes big-endian ExtraTLVDigest = 0x02 // ExtraDigestSize bytes ExtraTLVSignature = 0x03 // ed25519 signature, ed25519.SignatureSize bytes )
TLV entry types inside an ExtraBlock.
const ( RelayDNS = 0 // slow path — bytes assembled from DNS blocks RelayGitHub = 1 // fast path — bytes pulled from a GitHub repo )
Relay indices: each MediaMeta.Relays[N] flags whether the file is reachable via that relay. Order is fixed so the wire format is positional. Future relays append to this list; older clients ignore unknown trailing flags.
const ( EMHMagic0 byte = 0xFE EMHMagic1 byte = 0xED EMHHeaderLen = MarkerSize + 4 // 3 (Marker) + 4 (Timestamp) = 7 EMHHashLen = 3 EMHMaxBlocks = 255 // EMHFlagNewerAvailable, when bit-set in the flags byte at offset [2], // signals "a newer metadata format exists; if you support it, switch". // Reserved for future use — current encoders never set it; current // decoders never read it. Bits 1..7 are reserved for future flags. EMHFlagNewerAvailable byte = 1 << 0 )
Extended metadata header packs block_count + content_hash into the Marker[3] and Timestamp[4] fields of V0 metadata. Those fields are serialized + deserialized but never read by clients, so old clients keep working with no schema change while new clients use the embedded header to fetch all metadata blocks in parallel and validate the assembled payload was not mixed across server snapshots.
Wire layout (occupies the first 7 bytes of V0-serialized metadata, in the slots Marker[3] and Timestamp[4]):
[0] EMHMagic0 = 0xFE [1] EMHMagic1 = 0xED [2] flags uint8 — reserved (see EMHFlag*) [3] block_count uint8 (1..255 total metadata blocks) [4..6] content_hash 3 bytes — first 3 bytes of SHA-256(payload[7:])
The hash covers the body only — everything after the 7-byte header — so it changes whenever the actual metadata content changes (channel list, NextFetch, flags, etc.) but is independent of the block split. Magic bytes. 2 bytes of magic → 1/65536 probability that an old server's random 3-byte Marker happens to start with FE ED. False-positive cost is bounded: hash verify fails → retry → eventually cooldown for 10 min and fall back to the legacy fetch path. Acceptable; reduce by adding a third magic byte if we ever observe this misbehaviour in the wild.
const ( ProfilePicMimeJPEG uint8 = 0 ProfilePicMimePNG uint8 = 1 ProfilePicMimeWebP uint8 = 2 )
MIME tag values.
const ( // MinBlockPayload is the minimum decrypted payload per DNS TXT block. MinBlockPayload = 200 // MaxBlockPayload is the maximum decrypted payload per DNS TXT block. MaxBlockPayload = 600 // DefaultBlockPayload is kept for compatibility; equals MaxBlockPayload. DefaultBlockPayload = MaxBlockPayload // DefaultMaxPadding is the default random padding added to responses to vary DNS response size. DefaultMaxPadding = 32 // PadLengthSize is the 2-byte length prefix added before real data when padding is used. PadLengthSize = 2 // MetadataChannel is the special channel number for server metadata. MetadataChannel = 0 // MediaChannelStart and MediaChannelEnd bound the channel-number range // reserved for cached binary media (images, files, ...). Each cached file // occupies one channel; bytes are split into raw blocks served via the // usual DNS TXT path. The range is well above typical feed channel counts // and well below the special control channels at the top of uint16 space. MediaChannelStart uint16 = 10000 MediaChannelEnd uint16 = 60000 // inclusive // MarkerSize is the random marker in metadata to verify data freshness. MarkerSize = 3 // Query payload structure sizes. QueryPaddingSize = 4 QueryChannelSize = 2 QueryBlockSize = 2 QueryPayloadSize = QueryPaddingSize + QueryChannelSize + QueryBlockSize // 8 // Message header sizes (in the serialized message stream). MsgIDSize = 4 MsgTimestampSize = 4 MsgLengthSize = 2 MsgHeaderSize = MsgIDSize + MsgTimestampSize + MsgLengthSize // 10 MsgContentHashSize = 4 )
const ( MediaImage = "[IMAGE]" MediaVideo = "[VIDEO]" MediaFile = "[FILE]" MediaAudio = "[AUDIO]" MediaSticker = "[STICKER]" MediaGIF = "[GIF]" MediaPoll = "[POLL]" MediaContact = "[CONTACT]" MediaLocation = "[LOCATION]" MediaReply = "[REPLY]" // MediaMe marks an outgoing private-chat message — sent by the // authenticated user. The client renders these right-aligned with // a [YOU] label instead of the sender-name prefix. MediaMe = "[ME]" )
Media placeholder strings for non-text content.
const ChatAccountProofSize = 8
ChatAccountProofSize is the truncated HMAC proving account control in an auth handshake.
const ChatPeerHandleSize = 4
ChatPeerHandleSize is the short reference to a peer used in ACK/SENDSTATUS instead of the full 12-byte address.
const (
// ChatSealTagSize is the truncated per-query MAC length.
ChatSealTagSize = 4
)
const GCMNonceSize = 12
GCMNonceSize is the AES-GCM nonce length (carried in the chat envelope).
const MediaBlockHeaderLen = 16
MediaBlockHeaderLen is the fixed length of the metadata prefix that the server prepends to a cached media file's bytes before splitting into blocks. Block 0 of every media channel begins with these bytes.
Layout (big-endian where multi-byte):
[0:4] CRC32(IEEE) of the DECOMPRESSED file content
[4] header version (currently 1)
[5] compression byte (MediaCompression*)
[6:16] reserved (zero) — room for future protocol fields without
bumping the version byte
const MediaHeaderVersion uint8 = 1
MediaHeaderVersion is the current header version. Bumped when the layout changes incompatibly; until then, the reserved bytes carry future fields.
const RegisterEnvelopeLen = 1 + ed25519.PublicKeySize + X25519KeySize + 4 + ed25519.SignatureSize
RegisterEnvelopeLen is the fixed registration-record length.
Variables ¶
var ChatSessionLostResp = []byte{0xE5}
ChatSessionLostResp is the 1-byte unsealed sentinel a server returns for an in-context cell whose session it no longer knows (TTL expiry or reboot). The client can't open a sealed reply for a dead session, so this length-1 marker (a sealed reply is always ≥1+ChatSealTagSize) tells it to re-handshake.
Functions ¶
func Address ¶ added in v0.25.9
func Address(identityPub ed25519.PublicKey) [AddressSize]byte
Address returns the chat address for an ed25519 identity public key: the first AddressSize bytes of its SHA-256 hash.
func BuildChatAckPlain ¶ added in v0.25.9
func BuildChatAckPlain(peer [ChatPeerHandleSize]byte, upToSeq uint32, receipt [ChatReceiptMACSize]byte) []byte
BuildChatAckPlain: ACK peer's messages up to upToSeq (peer by handle). The receipt is the recipient's E2E proof of delivery (ChatReceiptMAC); it rides the spare cell bytes (op1+handle4+seq3+receipt6 = 14 ≤ 15). An all-zero receipt means "no proof" — the ack still frees quota, the sender just won't see a verified ✓✓.
func BuildChatAuthBootstrapPlain ¶ added in v0.25.9
func BuildChatAuthBootstrapPlain(addr [AddressSize]byte, ts uint32, proof [ChatAccountProofSize]byte) []byte
BuildChatAuthBootstrapPlain builds an auth-handshake bootstrap plaintext.
func BuildChatDataPlain ¶ added in v0.25.9
BuildChatDataPlain: one body chunk at index.
func BuildChatFetchPlain ¶ added in v0.25.9
func BuildChatFetchPlain(peer [ChatPeerHandleSize]byte, seq uint32, block uint8) []byte
BuildChatFetchPlain: INBOX_FETCH for (peer, seq, block). The peer handle disambiguates the sender — seq is per-pair, so two senders can both have a pending message at the same seq; the server resolves the handle to the full address within the caller's known pairs (as ACK does). 10 bytes, fits a cell.
func BuildChatFinPlain ¶ added in v0.25.9
BuildChatFinPlain: commit the upload (crc over the assembled body).
func BuildChatFragPlain ¶ added in v0.25.9
BuildChatFragPlain: one fragment (idx of total) of an inner op too big for the budget. chunk ≤ budget-3 (op+idx+total).
func BuildChatHandshakeStream ¶ added in v0.25.9
BuildChatHandshakeStream assembles the reassembled handshake stream: eph(32) ‖ proto_ver(1) ‖ kind(1) ‖ sealedBootstrap.
proto_ver is cleartext (the server must read it before deriving Ksession to pick the version's derivation) but tamper-evident: it is bound into Ksession (see ChatSessionKey), so flipping it just breaks the bootstrap seal.
func BuildChatKeyFetchPlain ¶ added in v0.25.9
func BuildChatKeyFetchPlain(addr [AddressSize]byte) []byte
BuildChatKeyFetchPlain: fetch a peer's registration record (full addr).
func BuildChatSendStartPlain ¶ added in v0.25.9
func BuildChatSendStartPlain(dst [AddressSize]byte, totalLen uint16) []byte
BuildChatSendStartPlain: start a message upload to dst (src is the session).
func BuildChatSendStatusPlain ¶ added in v0.25.9
func BuildChatSendStatusPlain(peer [AddressSize]byte) []byte
BuildChatSendStatusPlain: ✓/✓✓ counters for own messages to peer. The full address is sent (the recipient may not be in the caller's known pairs, so a handle can't be resolved server-side) — it still fits one cell.
func BuildChatStatusPlain ¶ added in v0.25.9
func BuildChatStatusPlain() []byte
BuildChatStatusPlain: INBOX_STATUS (freshness comes from the cell counter).
func ChannelEligibleForSharedCache ¶ added in v0.19.0
ChannelEligibleForSharedCache reports whether queries on a channel may use a deterministic suffix. False for metadata (clients must always see fresh metadata) and for write/admin channels (each write is unique by design).
func ChatAccountProof ¶ added in v0.25.9
func ChatAccountProof(kss [KeySize]byte, ephPub []byte, addr [AddressSize]byte, ts uint32, domain string) [ChatAccountProofSize]byte
ChatAccountProof binds an auth handshake to the account: HMAC under kss (the client↔server shared key, computable only by the account holder and the server) over the ephemeral key, address, timestamp and chat domain. The domain binding stops cross-server replay.
func ChatAvailableFromBlock0 ¶ added in v0.25.9
ChatAvailableFromBlock0 reads the ChatAvailable advertisement (flags bit 0x02) from metadata block 0 alone — no need to fetch or verify the rest of the metadata. The flags byte sits at a fixed offset (marker+timestamp+nextFetch) in every metadata format, so block 0 is enough. ok is false if block 0 is too short to contain it. This is an optimization hint only: it lets a client skip the (retry-prone) ChatInfo probe on a server that advertises no messenger; the authoritative, signed capability check is still ChatInfo.
func ChatBootstrapCounter ¶ added in v0.25.9
func ChatBootstrapCounter() uint32
ChatBootstrapCounter is the counter the bootstrap blob is sealed under.
func ChatCellJitter ¶ added in v0.25.9
ChatCellJitter returns the deterministic extra-pad length (0..ChatJitterMax) for a cell, keyed by the query key and the cell's (selector, counter). Both ends compute it identically, so the client pads to budget+jitter and the server recovers the real budget by subtracting it — and a byte-identical retransmit reproduces the same query name. OP_FRAG cells pass jitter=0 (a fragment's chunk is concatenated, so trailing pad can't ride along).
func ChatChunkMAC ¶ added in v0.25.9
func ChatChunkMAC(macKey [KeySize]byte, sessionID uint32, index uint8, chunk []byte) [ChatChunkMACSize]byte
ChatChunkMAC authenticates one upload chunk to its session so an attacker who learned the session id still cannot poison the upload.
func ChatClearHandshakeSelector ¶ added in v0.25.9
func ChatClearHandshakeSelector(sel *[chatSelectorSize]byte)
ChatClearHandshakeSelector clears the handshake flag (server session refs).
func ChatContentKey ¶ added in v0.25.9
func ChatContentKey(own *ecdh.PrivateKey, peerEncPub []byte, src, dst [AddressSize]byte, seq uint32) ([KeySize]byte, error)
ChatContentKey derives the AES-256 key sealing one message body. Both ends compute the same key: the sender from its enc private key and the recipient's published enc key, the recipient from its enc private key and the sender's published enc key. The key is bound to the message's (src, dst, seq) so it is valid for exactly one message slot.
func ChatIsHandshakeSelector ¶ added in v0.25.9
ChatIsHandshakeSelector reports whether the handshake flag is set.
func ChatIsSessionLost ¶ added in v0.25.9
ChatIsSessionLost reports whether a decoded response is the session-lost sentinel.
func ChatMarkHandshakeSelector ¶ added in v0.25.9
func ChatMarkHandshakeSelector(sel *[chatSelectorSize]byte)
ChatMarkHandshakeSelector sets the handshake flag (client setup tags).
func ChatPeerHandle ¶ added in v0.25.9
func ChatPeerHandle(addr [AddressSize]byte) [ChatPeerHandleSize]byte
ChatPeerHandle is the first bytes of a peer address; the server disambiguates it within the caller account's known pairs. A collision is 2^-32 with a handful of contacts and only ever names one of YOUR own contacts.
func ChatPlainOp ¶ added in v0.25.9
ChatPlainOp / ChatPlainVersion read the op / version from a sealed-plaintext.
func ChatPlainVersion ¶ added in v0.25.9
func ChatReceiptKey ¶ added in v0.25.9
ChatReceiptKey derives the pair key that authenticates delivery receipts. Like ChatContentKey it comes from the two peers' static ECDH — so only the two ends can compute it, never the relaying server — but it is independent of any single message (no seq), so one key covers the whole conversation. The recipient signs its ACK watermark with it; the sender verifies, turning ✓✓ from a server claim into a fact the server cannot forge.
func ChatReceiptMAC ¶ added in v0.25.9
func ChatReceiptMAC(receiptKey [KeySize]byte, sender, recipient [AddressSize]byte, upToSeq uint32) [ChatReceiptMACSize]byte
ChatReceiptMAC is the recipient's proof that it acknowledged sender→recipient messages up to upToSeq. The (sender, recipient) order is fixed by the message direction (originator first), so both ends bind the same tuple and a receipt can't be reflected onto the reverse direction.
func ChatServerMAC ¶ added in v0.25.9
func ChatServerMAC(kss [KeySize]byte, src, dst [AddressSize]byte, seq uint32, ciphertext []byte) [ChatSrvMACSize]byte
ChatServerMAC authenticates a message envelope to the server: only the registered holder of the sender enc key can produce it, and it binds the routing pair, the sequence, and the exact ciphertext.
func ChatServerSharedKey ¶ added in v0.25.9
func ChatServerSharedKey(priv *ecdh.PrivateKey, peerPub, clientEncPub, serverEkPub []byte) ([KeySize]byte, error)
ChatServerSharedKey derives the long-lived client↔server key (used for the envelope server MAC). The client calls it with its enc private key and the server's published ek; the server with its ek private key and the client's registered enc key. clientEncPub and serverEkPub fix the info ordering so both sides derive identical bytes.
func ChatSessionKey ¶ added in v0.25.9
func ChatSessionKey(own *ecdh.PrivateKey, peerPub []byte, protoVer byte, queryKey [KeySize]byte) ([KeySize]byte, error)
ChatSessionKey derives the per-connection session key from an eph↔ek ECDH, mixing the query key into the HKDF info so the public passphrase is required, and the negotiated protocol version so a tampered/cleartext version byte in the handshake can't downgrade: a different version yields a different key, so the sealed bootstrap fails to open (fail-closed) rather than being accepted under another version.
func ChatSessionKeys ¶ added in v0.25.9
func ChatSessionKeys(priv *ecdh.PrivateKey, peerPub, ephPub, serverEkPub []byte) (routing, mac [KeySize]byte, err error)
ChatSessionKeys derives the routing-encryption key and the chunk-MAC key for one upload session from a fresh ephemeral↔ek ECDH. The client calls it with the ephemeral private key and the server ek; the server with the ek private key and the ephemeral public key from INIT.
func CompressMessages ¶
CompressMessages compresses serialized message data using deflate. The output has a 1-byte header (compression type) followed by the payload. If compression doesn't reduce size, the raw data is stored instead.
func ContentDigest ¶ added in v0.25.9
ContentDigest returns the truncated SHA-256 of canonical channel content. The caller is responsible for producing the canonical bytes (e.g. SerializeMessages / SerializeMetadata) identically on server and client.
func ContentHashOf ¶
ContentHashOf computes a CRC32 hash of serialized message data. This changes when any message is edited, even if IDs stay the same.
func DecodeChatCell ¶ added in v0.25.9
func DecodeChatCell(queryKey [KeySize]byte, qname, domain string) (selector [chatSelectorSize]byte, counter uint32, payload []byte, err error)
DecodeChatCell splits a query name into its selector, counter, and the fixed-length payload (still sealed, for in-context cells), un-masking the selector+counter first.
func DecodeQuery ¶
DecodeQuery parses and decrypts a DNS query subdomain. Auto-detects plain-text (c<N>b<M>), single-label base32, or multi-label hex encoding.
func DecodeResponse ¶
DecodeResponse base64-decodes and decrypts a DNS TXT response, stripping padding.
func DecodeSendQuery ¶
func DecodeSendQuery(queryKey [KeySize]byte, qname, domain string) (targetChannel uint16, message []byte, err error)
DecodeSendQuery decodes a send-message DNS query. Returns the target channel number and decrypted message text.
func DecodeTitlesData ¶ added in v0.11.0
DecodeTitlesData decodes a name→title map from bytes produced by EncodeTitlesData.
func DecodeUpstreamBlockQuery ¶
func DecodeUpstreamBlockQuery(queryKey [KeySize]byte, qname, domain string) (sessionID uint16, index uint8, chunk []byte, err error)
DecodeUpstreamBlockQuery decodes one chunk of a chunked upstream payload. The first 2 bytes of chunk data live in the encrypted header[6:8]; any remaining bytes follow the 16-byte ciphertext as raw bytes.
func DecodeVersionData ¶ added in v0.7.0
DecodeVersionData extracts the version string from a block produced by EncodeVersionData.
func DecompressMessages ¶
DecompressMessages decompresses data produced by CompressMessages. Reads the 1-byte header to determine the compression type.
func DecompressMessagesLimited ¶ added in v0.25.9
DecompressMessagesLimited is DecompressMessages with a cap on the inflated output (maxOut <= 0 means unbounded). A caller that decompresses data from an untrusted source must pass a sane cap: deflate expands up to ~1000x, so a small ciphertext can otherwise inflate into a memory-exhausting "zip bomb".
func DecryptRelayBlob ¶ added in v0.13.0
DecryptRelayBlob is the inverse of EncryptRelayBlob.
func DecryptWithNonce ¶ added in v0.25.9
DecryptWithNonce reverses EncryptWithNonce.
func DeriveEncryptionKey ¶ added in v0.25.9
func DeriveEncryptionKey(seed []byte) (*ecdh.PrivateKey, error)
DeriveEncryptionKey derives the x25519 encryption key from a seed.
func DeriveIdentityKey ¶ added in v0.25.9
func DeriveIdentityKey(seed []byte) (ed25519.PrivateKey, error)
DeriveIdentityKey derives the ed25519 identity key from a seed.
func DeriveKeys ¶
DeriveKeys derives separate query and response AES-256 keys from a passphrase using HKDF.
func DeriveRelayKey ¶ added in v0.13.0
DeriveRelayKey derives the AES-256 key used to encrypt blobs uploaded to a shared relay (e.g. a public GitHub repo) and to HMAC the path segments. Returns an error only if HKDF fails; the result is deterministic for a given passphrase.
func EncodeAdminQuery ¶
func EncodeAdminQuery(queryKey [KeySize]byte, cmd AdminCmd, arg []byte, domain string, mode QueryEncoding) (string, error)
EncodeAdminQuery creates a DNS query that carries an admin command to the server. The payload is a single AdminCmd byte followed by optional argument bytes, GCM-encrypted and split across DNS labels.
func EncodeChatCell ¶ added in v0.25.9
func EncodeChatCell(queryKey [KeySize]byte, mode QueryEncoding, selector [chatSelectorSize]byte, counter uint32, payload []byte, domain string) (string, error)
EncodeChatCell packs one uniform cell into a query name. payload must be ≤ ChatCellPayloadSize; it is zero-padded to the fixed cell length and the selector+counter are masked so the whole name looks random.
func EncodeChatInfo ¶ added in v0.25.9
EncodeChatInfo encodes a ChatInfo payload (TLV; unknown types are skipped by old parsers, so fields can be added later).
func EncodeChatMessage ¶ added in v0.25.9
func EncodeChatMessage(contentKey, kss [KeySize]byte, src, dst [AddressSize]byte, seq uint32, text string) ([]byte, error)
EncodeChatMessage seals text into a message envelope. The text is deflate-compressed (store-raw if not smaller) before encryption. contentKey is the pair key from ChatContentKey; kss the client↔server key from ChatServerSharedKey.
func EncodeExtraBlock ¶ added in v0.25.9
func EncodeExtraBlock(priv ed25519.PrivateKey, channelID uint16, digest []byte, ts int64) ([]byte, error)
EncodeExtraBlock builds a single signed ExtraBlock (count=1, index=0) for a channel and pads it to a random size in [MinBlockPayload, MaxBlockPayload] so it is indistinguishable from a normal content block. ts is unix seconds; digest must be ExtraDigestSize bytes (see ContentDigest). The returned bytes are plaintext — encrypt them with EncodeResponse like any other block.
func EncodeMediaBlockHeader ¶ added in v0.13.0
func EncodeMediaBlockHeader(h MediaBlockHeader) []byte
EncodeMediaBlockHeader writes the binary header into a fresh slice of length MediaBlockHeaderLen. Reserved bytes are zero-padded.
func EncodeMediaText ¶ added in v0.13.0
EncodeMediaText prepends the metadata line to an optional caption and returns the combined message text. A nil/empty caption yields just the tag + metadata + trailing newline-less string (the caption split is by the metadata line's trailing \n, so an empty caption simply has no extra body).
func EncodeMetadataExtended ¶ added in v0.19.0
EncodeMetadataExtended serializes metadata with the extended header embedded in Marker/Timestamp, splits into blocks, and patches the final block_count into block 0. Callers feed plain *Metadata; the Marker and Timestamp fields on the input are overwritten with the embedded header.
func EncodeProfilePicsBundle ¶ added in v0.16.0
func EncodeProfilePicsBundle(b ProfilePicsBundle) []byte
EncodeProfilePicsBundle serialises the directory.
func EncodeQuery ¶
func EncodeQuery(queryKey [KeySize]byte, channel, block uint16, domain string, mode QueryEncoding) (string, error)
EncodeQuery creates a DNS query subdomain for the given channel and block. Single-label (default): [base32_encrypted].domain Multi-label: [hex_part1].[hex_part2].domain All queries are encrypted to prevent DPI detection.
func EncodeQueryDeterministic ¶ added in v0.19.0
func EncodeQueryDeterministic(queryKey [KeySize]byte, channel, block uint16, domain string, mode QueryEncoding, seed []byte) (string, error)
EncodeQueryDeterministic produces the same DNS query subdomain every time it is called with the same (key, channel, block, mode, seed). Lets public DNS resolvers cache the response across users who share the seed.
Caller is responsible for excluding channels that must never be cached (MetadataChannel and the write channels). Use ChannelEligibleForSharedCache to gate this.
func EncodeRegisterEnvelope ¶ added in v0.25.9
func EncodeRegisterEnvelope(identity ed25519.PrivateKey, encPub []byte, timestamp uint32) ([]byte, error)
EncodeRegisterEnvelope builds a registration record binding an x25519 encryption key to an identity, signed by the identity key. timestamp is unix seconds (newest record wins on re-registration).
func EncodeResponse ¶
EncodeResponse encrypts and base64-encodes a block payload for a DNS TXT response. Adds a 2-byte length prefix and random padding to vary response size for anti-DPI.
func EncodeSendQuery ¶
func EncodeSendQuery(queryKey [KeySize]byte, targetChannel uint16, message []byte, domain string, mode QueryEncoding) (string, error)
EncodeSendQuery creates a DNS query that carries an upstream message. Format: [header_b32].[data_b32].domain The header is a normal encrypted 8-byte query with channel=SendChannel and block=targetChannel. The data label contains GCM-encrypted message text. Returns an error if the message is too long for a single DNS query.
func EncodeTitlesData ¶ added in v0.11.0
EncodeTitlesData encodes a name→title map into bytes for TitlesChannel blocks. Format: count(2) + [nameLen(1)+name+titleLen(1)+title]*count
func EncodeUpstreamBlockQuery ¶
func EncodeUpstreamBlockQuery(queryKey [KeySize]byte, sessionID uint16, index uint8, chunk []byte, domain string, mode QueryEncoding) (string, error)
EncodeUpstreamBlockQuery encodes one chunk of a chunked upstream payload into a single DNS label. The first min(2, len(chunk)) bytes are embedded in the AES-ECB header at [6:8] (not covered by integrity check); any remaining bytes are appended raw after the 16-byte ciphertext. The upstream payload is already GCM-encrypted, so confidentiality is preserved; tampering is caught by GCM on reassembly.
Header: [0:2] session_id, [2] index, [3] chunk_len,
[4:6] channel=UpstreamDataChannel, [6:8] chunk prefix
Suffix: chunk[2:] (raw, up to 6 bytes)
Max label: 16 + 6 = 22 bytes → 36 base32 chars (fits in 63-char DNS label).
func EncodeUpstreamInitQuery ¶
func EncodeUpstreamInitQuery(queryKey [KeySize]byte, init UpstreamInit, domain string, mode QueryEncoding) (string, error)
EncodeUpstreamInitQuery creates a compact single-label query that registers a chunked upstream session. All init data is packed into the AES-ECB header:
[0:2] session_id, [2] total_blocks, [3] kind, [4:6] channel=UpstreamInitChannel, [6] target_channel, [7] 0
No GCM data labels — just one 26-char base32 label + domain.
func EncodeVersionData ¶ added in v0.7.0
EncodeVersionData encodes a version string into a single block padded to a random size in [MinBlockPayload, MaxBlockPayload], making it indistinguishable in size from regular content blocks for DPI resistance. Format:
[2 bytes: version byte length][version bytes][random padding]
func EncryptRelayBlob ¶ added in v0.13.0
EncryptRelayBlob seals plaintext with the relay key. Output is nonce||ciphertext||tag, identical framing to the DNS response cipher so clients can reuse Decrypt for both paths.
func EncryptWithNonce ¶ added in v0.25.9
EncryptWithNonce seals plaintext under key with an explicit (caller-supplied) nonce. The nonce MUST be unique per key; the chat envelope uses a fresh random one per message so a repeated (src,dst,seq) — e.g. the same recipient on two servers — never reuses the keystream. Output is ciphertext+tag (no nonce).
func GenerateEphemeralKey ¶ added in v0.25.9
func GenerateEphemeralKey() (*ecdh.PrivateKey, error)
GenerateEphemeralKey returns a fresh x25519 key for one upload session.
func GenerateSeed ¶ added in v0.25.9
GenerateSeed returns a new random chat identity seed.
func IsMediaChannel ¶ added in v0.13.0
IsMediaChannel reports whether ch falls inside the reserved media-blob channel range. Media channels are not enumerated in Metadata; the client learns each (channel, blocks, hash) tuple from the corresponding feed message text via [TAG]<size>:<dl>:<ch>:<blk>:<crc32hex>.
func OpenChat ¶ added in v0.25.9
func OpenChat(ksession [KeySize]byte, selector []byte, counter uint32, sealed []byte) ([]byte, error)
OpenChat verifies the tag and decrypts. Fail-closed on any mismatch.
func OpenChatCellPayload ¶ added in v0.25.9
func OpenChatCellPayload(ksession [KeySize]byte, selector [chatSelectorSize]byte, counter uint32, payload []byte) ([]byte, error)
OpenChatCellPayload reverses SealChatCellPayload, returning the fixed-size plaintext (caller reads op + fields, ignores trailing pad).
func OpenChatResponse ¶ added in v0.25.9
func OpenChatResponse(ksession [KeySize]byte, selector [chatSelectorSize]byte, reqCounter uint32, sealed []byte) (status byte, body []byte, err error)
OpenChatResponse reverses SealChatResponse.
func ParseChatHandshakeStream ¶ added in v0.25.9
func ParseChatHandshakeStream(stream []byte) (ephPub []byte, protoVer, kind byte, sealedBootstrap []byte, err error)
ParseChatHandshakeStream splits a reassembled handshake stream.
func PeekExtendedHeader ¶ added in v0.19.0
func PeekExtendedHeader(block0 []byte) (extended bool, blockCount uint8, hash [EMHHashLen]byte, err error)
PeekExtendedHeader inspects the first EMHHeaderLen bytes of a metadata block 0. Returns (extended=true, count, hash, nil) when the magic + version match; (extended=false, …, nil) when this is a legacy V0 payload from an old server. Errors are reserved for genuinely malformed input (block too short or block_count=0 with magic present).
func RelayDomainSegment ¶ added in v0.13.0
RelayDomainSegment returns the path segment that scopes a deployment's files inside a shared relay repo. Computed as HMAC-SHA256 over the domain, keyed by a passphrase-derived secret, then truncated. Without the passphrase an observer cannot tell which deployment a folder belongs to.
func RelayObjectName ¶ added in v0.13.0
RelayObjectName returns the per-file path segment under the domain folder. Computed from (size, crc) so the same content always lives at the same path (dedup), but HMAC'd with the passphrase so an observer can't probe "is a known file present in this repo?".
func SanitiseMediaFilename ¶ added in v0.13.0
SanitiseMediaFilename returns a filename safe to embed in the wire metadata line. The output uses a restricted alphabet ([A-Za-z0-9._-]) so no path separator, colon, newline, or control char can ever survive. When the input is too long the base name is replaced with a short hash-derived id but the extension is preserved so other OSes still recognise the file type.
func SealChat ¶ added in v0.25.9
SealChat returns ct‖tag(4): AES-CTR of plaintext, then a truncated HMAC over nonce‖ct. Deterministic for the same (key,selector,counter,plaintext).
func SealChatCellPayload ¶ added in v0.25.9
func SealChatCellPayload(ksession [KeySize]byte, selector [chatSelectorSize]byte, counter uint32, plaintext []byte) ([]byte, error)
SealChatCellPayload seals an op plaintext into a default-budget (15-byte plain, 19-byte payload) cell. Equivalent to SealChatCellPayloadN at the default budget.
func SealChatCellPayloadN ¶ added in v0.25.9
func SealChatCellPayloadN(ksession [KeySize]byte, selector [chatSelectorSize]byte, counter uint32, plaintext []byte, budget int) ([]byte, error)
SealChatCellPayloadN seals an op plaintext into a cell of the given budget B (op plaintext bytes per cell): the plaintext is zero-padded to B before sealing, so every cell at a given budget is the same length. B must be in [ChatCellPlainMin, ChatCellPlainMax].
func SealChatResponse ¶ added in v0.25.9
func SealChatResponse(ksession [KeySize]byte, selector [chatSelectorSize]byte, reqCounter uint32, status byte, body []byte) []byte
SealChatResponse seals status‖body under the session key with a response-side counter (distinct from any request nonce).
func SerializeMessages ¶
SerializeMessages encodes messages into a byte stream for data channel blocks.
func SerializeMetadata ¶
SerializeMetadata encodes metadata into bytes for channel 0 blocks. Format: marker(3) + timestamp(4) + nextFetch(4) + flags(1) + channelCount(2) + per-channel data Per-channel: nameLen(1) + name + blocks(2) + lastMsgID(4) + contentHash(4) + chatType(1) + flags(1)
New servers wrap the same payload with an extended header that reuses the otherwise-unread Marker + Timestamp fields — see EncodeMetadataExtended in metadata_ext.go. Old clients keep parsing this format unchanged and just ignore those fields.
func SplitChunks ¶ added in v0.25.9
SplitChunks splits data into fixed-size chunks; the final chunk may be shorter. Returns one empty chunk for empty data so a session always has at least one block.
func SplitIntoBlocks ¶
SplitIntoBlocks splits data into blocks of randomly varying size in [MinBlockPayload, MaxBlockPayload]. Random sizes make traffic analysis harder; the client just concatenates all blocks to reassemble.
func VerifyEntry ¶ added in v0.16.0
func VerifyEntry(bundle []byte, entry ProfilePicEntry) ([]byte, error)
VerifyEntry returns bundle[entry.Offset:entry.Offset+entry.Size] if the slice is in-range and its CRC32-IEEE matches entry.CRC. The hash check is what stops a misaligned bundle from serving the wrong avatar under a username.
func VerifyExtendedHash ¶ added in v0.19.0
func VerifyExtendedHash(hash [EMHHashLen]byte, body []byte) error
VerifyExtendedHash returns nil when the first 3 bytes of SHA-256(body) match the hash embedded in the header. body must be the assembled bytes AFTER the 7-byte EMH header.
func VerifyExtraBlock ¶ added in v0.25.9
func VerifyExtraBlock(pub ed25519.PublicKey, channelID uint16, eb *ExtraBlock) error
VerifyExtraBlock checks the signature against the server public key, binding it to channelID. channelID is the channel the client actually requested — if a resolver served a different channel's extra block, the signed channel id will not match and verification fails.
Types ¶
type AdminCmd ¶
type AdminCmd byte
AdminCmd identifies admin commands carried in upstream admin payloads.
type ChannelInfo ¶
type ChannelInfo struct {
Name string
DisplayName string // human-readable title; empty means fall back to Name
Blocks uint16
LastMsgID uint32
ContentHash uint32 // CRC32 of serialized message data; changes on edits
ChatType ChatType // 0=Telegram channel, 1=private chat, 2=X account
CanSend bool // true if server allows sending messages to this chat
}
ChannelInfo describes a single feed channel.
type ChatAck ¶ added in v0.25.9
type ChatAck struct {
Peer [ChatPeerHandleSize]byte
UpToSeq uint32
Receipt [ChatReceiptMACSize]byte // E2E delivery proof; zero if none
}
ChatAck is a parsed ACK.
func ParseChatAckPlain ¶ added in v0.25.9
ParseChatAckPlain parses an ACK plaintext. The receipt is optional (a zero-value Receipt is returned if absent) so the op stays parseable even without proof.
type ChatAuthBootstrap ¶ added in v0.25.9
type ChatAuthBootstrap struct {
Addr [AddressSize]byte
TS uint32
Proof [ChatAccountProofSize]byte
}
ChatAuthBootstrap is a parsed auth-handshake bootstrap.
func ParseChatAuthBootstrapPlain ¶ added in v0.25.9
func ParseChatAuthBootstrapPlain(pt []byte) (*ChatAuthBootstrap, error)
ParseChatAuthBootstrapPlain parses an auth-handshake bootstrap plaintext.
type ChatData ¶ added in v0.25.9
ChatData is a parsed DATA chunk. Chunk is the fixed-size slice; the server trims it to the real length using the upload's known total length.
func ParseChatDataPlain ¶ added in v0.25.9
ParseChatDataPlain parses a DATA plaintext.
type ChatFetch ¶ added in v0.25.9
type ChatFetch struct {
Peer [ChatPeerHandleSize]byte
Seq uint32
Block uint8
}
ChatFetch is a parsed INBOX_FETCH.
func ParseChatFetchPlain ¶ added in v0.25.9
ParseChatFetchPlain parses an INBOX_FETCH plaintext.
type ChatFin ¶ added in v0.25.9
type ChatFin struct {
CRC32 uint32
}
ChatFin is a parsed FIN.
func ParseChatFinPlain ¶ added in v0.25.9
ParseChatFinPlain parses a FIN plaintext.
type ChatFrag ¶ added in v0.25.9
ChatFrag is a parsed OP_FRAG.
func ParseChatFragPlain ¶ added in v0.25.9
ParseChatFragPlain parses an OP_FRAG plaintext (op already padded to budget, so trailing pad is harmless — the reassembler trims to the inner op length).
type ChatInfo ¶ added in v0.25.9
type ChatInfo struct {
MinVersion uint8
MaxVersion uint8
Enabled bool
Domains []string
EkPub []byte
Limits ChatLimits
}
ChatInfo is the chat capability payload served (signed) on the feed metadata path. EkPub is the server x25519 key clients run the session handshake against — delivered here, under the feed signing key, instead of in the import URI. Enabled is false when the operator has chat domains configured but has turned chat off.
func ParseChatInfo ¶ added in v0.25.9
ParseChatInfo decodes a ChatInfo payload.
type ChatKeyFetch ¶ added in v0.25.9
type ChatKeyFetch struct {
Addr [AddressSize]byte
}
ChatKeyFetch is a parsed KEYFETCH.
func ParseChatKeyFetchPlain ¶ added in v0.25.9
func ParseChatKeyFetchPlain(pt []byte) (*ChatKeyFetch, error)
ParseChatKeyFetchPlain parses a KEYFETCH plaintext.
type ChatLimits ¶ added in v0.25.9
type ChatLimits struct {
ChunkSize uint8
MaxMsgBytes uint16
InboxCap uint16
PerPairCap uint16
SendPerHour uint16
SessionIdleSec uint16
SessionHardSec uint16
TTLHours uint16
}
ChatLimits are the server-advertised chat limits.
func DefaultChatLimits ¶ added in v0.25.9
func DefaultChatLimits() ChatLimits
DefaultChatLimits returns the default server limits.
type ChatMessage ¶ added in v0.25.9
type ChatMessage struct {
Seq uint32
Nonce []byte
Ciphertext []byte
SrvMAC [ChatSrvMACSize]byte
}
ChatMessage is a parsed message envelope.
Wire layout:
ver(1) msg_seq(4 BE) nonce(12) ciphertext(N) srvmac(8)
ciphertext seals the inner payload with the pair content key (AES-256-GCM) under a fresh RANDOM nonce carried in the envelope. The random nonce is what guarantees no keystream reuse even if the same (src,dst,seq) recurs — e.g. the same recipient on two servers (seq is per-server) — so confidentiality does not hinge on seq uniqueness. Inner layout:
cflag(1) body // body = deflate(text) or raw text; cflag says which
The sender address is NOT carried: the recipient derives the content key from the inbox entry's src, so a wrong src yields a wrong key and AEAD fails — misattribution is impossible without an inner copy.
func ParseChatMessage ¶ added in v0.25.9
func ParseChatMessage(data []byte) (*ChatMessage, error)
ParseChatMessage parses a message envelope. It never panics; any malformation returns an error.
func (*ChatMessage) Open ¶ added in v0.25.9
func (m *ChatMessage) Open(contentKey [KeySize]byte) (string, error)
Open decrypts and decompresses the body with the pair content key. AEAD success itself authenticates the sender (only the pair can compute contentKey, which is bound to src,dst,seq), so no inner address check is needed.
func (*ChatMessage) VerifyServerMAC ¶ added in v0.25.9
func (m *ChatMessage) VerifyServerMAC(kss [KeySize]byte, src, dst [AddressSize]byte) error
VerifyServerMAC checks the sender-to-server MAC. The server calls this at FIN with the session's routing pair and the sender's registered enc key.
type ChatSendStart ¶ added in v0.25.9
type ChatSendStart struct {
Dst [AddressSize]byte
TotalLen uint16
}
ChatSendStart is a parsed SEND_START.
func ParseChatSendStartPlain ¶ added in v0.25.9
func ParseChatSendStartPlain(pt []byte) (*ChatSendStart, error)
ParseChatSendStartPlain parses a SEND_START plaintext.
type ChatSendStatus ¶ added in v0.25.9
type ChatSendStatus struct {
Peer [AddressSize]byte
}
ChatSendStatus is a parsed SENDSTATUS.
func ParseChatSendStatusPlain ¶ added in v0.25.9
func ParseChatSendStatusPlain(pt []byte) (*ChatSendStatus, error)
ParseChatSendStatusPlain parses a SENDSTATUS plaintext.
type ChunkReassembler ¶ added in v0.25.9
type ChunkReassembler struct {
// contains filtered or unexported fields
}
ChunkReassembler reassembles a fixed number of chunks arriving out of order, tracking which have been received. The bitmap is returned to the client so it can retransmit gaps.
func NewChunkReassembler ¶ added in v0.25.9
func NewChunkReassembler(total int) *ChunkReassembler
NewChunkReassembler creates a reassembler expecting total chunks.
func (*ChunkReassembler) Add ¶ added in v0.25.9
func (r *ChunkReassembler) Add(index int, chunk []byte) bool
Add stores a chunk at index; returns false if index is out of range. Re-adding an index is idempotent.
func (*ChunkReassembler) Assemble ¶ added in v0.25.9
func (r *ChunkReassembler) Assemble() []byte
Assemble concatenates the received chunks. Meaningful only when Complete.
func (*ChunkReassembler) Bitmap ¶ added in v0.25.9
func (r *ChunkReassembler) Bitmap() []byte
Bitmap returns the received set as a big-endian bit field: chunk i is bit (7 - i%8) of byte i/8.
func (*ChunkReassembler) Complete ¶ added in v0.25.9
func (r *ChunkReassembler) Complete() bool
Complete reports whether every chunk has been received.
type ExtraBlock ¶ added in v0.25.9
type ExtraBlock struct {
Version uint8
Count uint8
Index uint8
Timestamp int64
Digest []byte
Signature []byte
}
ExtraBlock is a parsed ExtraBlock.
func ParseExtraBlock ¶ added in v0.25.9
func ParseExtraBlock(data []byte) (*ExtraBlock, error)
ParseExtraBlock parses a decrypted ExtraBlock plaintext. It returns an error (never panics) on any malformation, so a caller can treat a spoofed or stale block — or an old server's unrelated response — as "no valid extra block".
func (*ExtraBlock) VerifyChannelContent ¶ added in v0.25.9
func (eb *ExtraBlock) VerifyChannelContent(canonical []byte) error
VerifyChannelContent confirms canonical content matches the signed digest. Call only after VerifyExtraBlock has succeeded.
type MediaBlockHeader ¶ added in v0.13.0
type MediaBlockHeader struct {
CRC32 uint32
Version uint8
Compression MediaCompression
}
MediaBlockHeader is the parsed form of a media-channel block-0 header.
func DecodeMediaBlockHeader ¶ added in v0.13.0
func DecodeMediaBlockHeader(b []byte) (MediaBlockHeader, error)
DecodeMediaBlockHeader parses the first MediaBlockHeaderLen bytes of a media block. Errors on truncation or unknown header version.
type MediaCompression ¶ added in v0.13.0
type MediaCompression byte
MediaCompression names a compression method applied to a cached media file's bytes before they're split into DNS blocks.
const ( MediaCompressionNone MediaCompression = 0 MediaCompressionGzip MediaCompression = 1 MediaCompressionDeflate MediaCompression = 2 )
func ParseMediaCompressionName ¶ added in v0.13.0
func ParseMediaCompressionName(s string) (MediaCompression, error)
ParseMediaCompressionName returns the MediaCompression matching one of "none", "gzip", "deflate" (case-insensitive). Used by the CLI flag to translate user input.
func (MediaCompression) String ¶ added in v0.13.0
func (c MediaCompression) String() string
String returns the canonical name of the compression value.
type MediaMeta ¶ added in v0.13.0
type MediaMeta struct {
Tag string // e.g. MediaImage, MediaVideo, MediaFile
Size int64
Relays []bool // index = relay constant, value = availability
Channel uint16 // DNS channel (when Relays[RelayDNS])
Blocks uint16 // DNS block count (when Relays[RelayDNS])
CRC32 uint32
Filename string
}
MediaMeta describes a downloadable media blob attached to a feed message.
Wire format (immediately after the media tag, before any caption):
[IMAGE]<size>:<f0>,<f1>,...:<dnsCh>:<dnsBlk>:<crc32hex>[:<filename>]
where each <fN> is "1" or "0" indicating availability via relay N. <dnsCh>:<dnsBlk> are only meaningful when f0 (RelayDNS) is set.
func ParseMediaText ¶ added in v0.13.0
ParseMediaText parses a message body that begins with a known media tag. Returns metadata + remaining caption. Legacy "[TAG]\ncaption" bodies parse with empty Relays (HasAnyRelay()==false). Unknown tags return ok=false.
func (MediaMeta) HasAnyRelay ¶ added in v0.13.0
HasAnyRelay reports whether at least one relay can serve this file.
type Message ¶
Message represents a single feed message in a channel.
func ParseMessages ¶
ParseMessages decodes messages from concatenated data channel block data.
type Metadata ¶
type Metadata struct {
// Marker is the 3 bytes at the start of the serialized metadata
// payload. Legacy encoder fills it with a random per-server marker;
// the extended encoder fills it with EMH magic + flags. Treat it as
// opaque on the wire and use PeekExtendedHeader to interpret.
Marker [MarkerSize]byte
// Timestamp is the 4 bytes immediately after Marker. Legacy encoder
// fills it with a unix timestamp; the extended encoder fills it with
// EMH block_count + content_hash. Treat it as opaque and use
// PeekExtendedHeader to interpret.
Timestamp uint32
NextFetch uint32 // unix timestamp of next server-side fetch (0 = unknown)
TelegramLoggedIn bool // true if server has an active Telegram session
// ChatAvailable advertises that this server has the messenger configured
// (flags bit 0x02). Lets clients skip the ChatInfo probe on chatless
// servers and tell apart "no chat" from "chat, but you lack the key".
ChatAvailable bool
Channels []ChannelInfo
}
Metadata holds channel 0 data: server info + channel list.
func ParseMetadata ¶
ParseMetadata decodes metadata from concatenated channel 0 block data.
New servers may embed an extended header in the Marker + Timestamp fields of the same wire format — see PeekExtendedHeader for the magic check. This parser ignores those fields by design; clients that care about the embedded block_count / hash check them explicitly.
type ProfilePicEntry ¶ added in v0.16.0
type ProfilePicEntry struct {
Username string
Offset uint32
Size uint32
CRC uint32
MIME uint8
DNSChannel uint16
DNSBlocks uint16
}
ProfilePicEntry points at one avatar via either the GitHub bundle (Offset/Size into the concatenated blob) or its own DNS channel (DNSChannel/DNSBlocks). Both paths verify the same Size + CRC.
type ProfilePicsBundle ¶ added in v0.16.0
type ProfilePicsBundle struct {
Header ProfilePicsBundleHeader
Entries []ProfilePicEntry
}
ProfilePicsBundle is the directory (header + entries). The bundle bytes themselves live in the referenced media channel / relay.
func DecodeProfilePicsBundle ¶ added in v0.16.0
func DecodeProfilePicsBundle(data []byte) (ProfilePicsBundle, error)
DecodeProfilePicsBundle parses bytes produced by EncodeProfilePicsBundle.
type ProfilePicsBundleHeader ¶ added in v0.16.0
type ProfilePicsBundleHeader struct {
BundleSize uint32
BundleCRC uint32
// One bool per relay constant. RelayGitHub here means the bundle
// is on GitHub; RelayDNS for the bundle is rare (per-entry DNS
// channels handle the DNS path).
Relays []bool
}
Profile pictures use a hybrid layout: every avatar is concatenated into one bundle uploaded to the GitHub relay (one file → no per-file rate limit), and each avatar also gets its own DNS media channel so partial fetches over DNS still display.
Wire layout of ProfilePicsChannel (after the block-count prefix the Feed layer adds):
bundleSize uint32
bundleCRC uint32
relayCount uint8 — N
relays [N]u8 — bool per relay (RelayDNS=0, RelayGitHub=1, …)
count uint16
entries:
usernameLen uint8
username [usernameLen]byte
offset uint32 — within the GitHub bundle
size uint32
crc uint32 — CRC32 of bundle[offset:offset+size]
mime uint8 — 0=jpeg, 1=png, 2=webp
dnsChannel uint16 — 0 if not on DNS
dnsBlocks uint16
func (ProfilePicsBundleHeader) HasRelay ¶ added in v0.16.0
func (h ProfilePicsBundleHeader) HasRelay(idx int) bool
HasRelay reports whether the relay at idx is set. Out of range returns false.
type QueryEncoding ¶
type QueryEncoding int
QueryEncoding controls how DNS query subdomains are encoded.
const ( // QuerySingleLabel uses base32 in a single DNS label (default, stealthier). QuerySingleLabel QueryEncoding = iota // QueryMultiLabel uses hex split across multiple DNS labels. QueryMultiLabel )
type RegisterEnvelope ¶ added in v0.25.9
type RegisterEnvelope struct {
IdentityPub ed25519.PublicKey
EncPub []byte
Timestamp uint32
Signature []byte
// contains filtered or unexported fields
}
RegisterEnvelope is a parsed key-registration record. Wire layout:
ver(1) identity_pub(32) enc_pub(32) timestamp(4 BE) sig(64)
func ParseRegisterEnvelope ¶ added in v0.25.9
func ParseRegisterEnvelope(data []byte) (*RegisterEnvelope, error)
ParseRegisterEnvelope parses a registration record. It never panics.
func (*RegisterEnvelope) Address ¶ added in v0.25.9
func (e *RegisterEnvelope) Address() [AddressSize]byte
Address returns the chat address bound to this registration.
func (*RegisterEnvelope) EncKey ¶ added in v0.25.9
func (e *RegisterEnvelope) EncKey() (*ecdh.PublicKey, error)
EncKey returns the registered x25519 encryption key, validated.
func (*RegisterEnvelope) Verify ¶ added in v0.25.9
func (e *RegisterEnvelope) Verify() error
Verify checks the identity signature. The caller must separately confirm Address(IdentityPub) equals the expected address.
type UpstreamInit ¶
type UpstreamInit struct {
SessionID uint16
TotalBlocks uint8
Kind UpstreamKind
TargetChannel uint8
}
UpstreamInit describes a chunked upstream session.
func DecodeUpstreamInitQuery ¶
func DecodeUpstreamInitQuery(queryKey [KeySize]byte, qname, domain string) (*UpstreamInit, error)
DecodeUpstreamInitQuery parses a compact single-label upstream init query.
type UpstreamKind ¶
type UpstreamKind byte
UpstreamKind identifies the payload carried by a chunked upstream session.
const ( UpstreamKindSend UpstreamKind = 1 UpstreamKindAdmin UpstreamKind = 2 )