Licence. This specification is published without restriction. It may be reproduced, redistributed, translated and implemented, in whole or in part, for any purpose, without royalty, permission or notice. It is provided without warranty of any kind.
Status. Draft. Version 9 is implemented and deployed, but the protocol is still evolving and versions are not interoperable (§9.3, §13.2).
1Introduction
The AnyShare Transfer Protocol moves files and short text messages directly between two devices. It provides confidentiality and integrity of transferred content, mutual authentication of both endpoints to long-term identity keys, and forward secrecy, without trusting the network or any intermediary.
In scope. Transport selection, session establishment, cipher negotiation, key derivation, message framing, chunk encryption, integrity verification, resumption, the QUIC transport, and the candidate exchange and relaying that reach a peer across a NAT.
Out of scope. Account management, the operation of the discovery and rendezvous services described in §14 and §15, and user interface behaviour. These operate outside the secured session and cannot affect its security properties.
1.1 Two transports, one protocol
AnyShare carries the same offers, the same file content and the same integrity digests over two different transports, and which one a pair of devices uses is decided before any of it is sent. §4 states the rule.
| Transport | Security | Framing |
|---|---|---|
| Application-layer over any reliable byte stream | Established by the protocol itself: the signed Diffie-Hellman exchange of §5 and the per-chunk AEAD of §11.2. | The 32-byte message header of §9, one message stream. |
| QUIC, between native peers | Established by the transport: TLS 1.3 mutual authentication to the same Ed25519 identity key, carried as an RFC 7250 raw public key. §5, §6, §7 and §11.2 do not run. | The 13-byte control-frame header of §13.2, one control stream and one stream per file. |
The application-layer construction assumes a reliable, ordered, bidirectional byte stream. It does not assume that stream is private, authenticated or delivered intact, and it makes no further demands on the transport: a direct TCP connection, a WebRTC data channel, a Wi-Fi Aware link and a relayed byte stream are all equivalent to it. The QUIC construction demands more - a UDP path and a QUIC endpoint - and gives more in return: authentication that completes during the transport handshake rather than after it, and no second layer of encryption over content the transport has already sealed.
Nothing below the two framings is invented here. The QUIC transport is assembled from published standards - QUIC (RFC 9000, RFC 9001), TLS 1.3 (RFC 8446) and raw public keys (RFC 7250) - with the Ed25519 identity key of §3 as the TLS credential, and it introduces no cryptographic construction of its own. What §13 specifies is the framing above it.
An implementation MAY implement the application-layer transport alone and remains complete and interoperable: a peer that does not advertise the QUIC capability of §15 is transferred to exactly as specified in §5 onwards, and an implementation that never advertises the capability itself is never dialled over QUIC.
2Conventions
The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as described in RFC 2119.
‖ denotes concatenation. u8, u16, u32 and u64 denote unsigned integers of that width in bits, little-endian unless a layout says otherwise. Quoted strings are ASCII with no terminating byte: "ESHR" is exactly four bytes. Bracketed numbers are byte counts. All key material, hashes and signatures are fixed-width byte strings, never text-encoded on the wire.
Structured payloads use one binary encoding throughout, on both transports. Fields appear in the order given, with no names, no framing and no padding between them, and the reader knows where each ends from its type alone:
- Integers are fixed-width little-endian -
u8,u16,u32,u64. There are no variable-length integers anywhere. - A variable-length field - a string, or a sequence of any element type - is preceded by a
u64giving its length in bytes for a string, or its element count for a sequence. Strings are UTF-8, unterminated. - A fixed-width byte array carries no length prefix: a 16-byte field is 16 bytes.
- An optional field is preceded by one byte:
0x00means absent and nothing follows,0x01means present and the value follows. - An enumerated value with no associated data is a
u32holding its index in the order this document lists its variants, counting from zero. A reader MUST reject an index it does not define. - A UUID is encoded as a variable-length byte string: the
u64value 16, then the sixteen bytes in RFC 4122 field order. The length is redundant and always the same; it is nonetheless on the wire and MUST be written and read.
An implementation conforms to this document if it satisfies every MUST and interoperates at the version it advertises. Any condition described here as an error MUST terminate the session; an implementation MUST NOT continue in a degraded or unauthenticated mode.
3Cryptographic primitives
| Purpose | Primitive | Reference |
|---|---|---|
| Device identity and signatures | Ed25519 | RFC 8032 |
| Key agreement | X25519 | RFC 7748 |
| Content encryption | ChaCha20-Poly1305 or AES-256-GCM | RFC 8439, NIST SP 800-38D |
| Key derivation | HKDF-SHA-256 | RFC 5869 |
| Transcript hashing | SHA-256 | FIPS 180-4 |
| Content integrity | BLAKE3 | BLAKE3 specification |
| Optional content compression | Zstandard | RFC 8878 |
Each device holds one long-term Ed25519 key pair, its identity key, generated on first use. The private half MUST NOTleave the device and is never transmitted by this protocol. The same key pair is the peer’s identity on both transports: on the application-layer transport it signs the handshake transcript of §5.2, and on the QUIC transport it is the TLS credential of §13.1.
The table above is exhaustive for the constructions this document defines. The QUIC transport additionally uses whatever TLS 1.3 itself specifies - the key schedule of RFC 8446 §7.1, the record protection of RFC 8446 §5.2 and the packet protection of RFC 9001 §5 - under the cipher suites TLS negotiates for itself. Those are not choices this protocol makes, and an implementer follows the RFCs rather than this page for them. AnyShare constrains the TLS negotiation in exactly two ways, both stated in §13.1: TLS 1.3 is the only permitted version, and Ed25519 is the only permitted signature scheme.
4Transport selection
A sender chooses one transport before it sends anything, from what discovery (§15) has told it about the peer. The choice is not renegotiated, and the two transports are never raced against each other.
4.1 The rule
A sender classifies a peer from its discovery records alone, before it opens anything, and the classification decides the transport outright. There are three kinds of peer.
| Peer | Recognised by | Transport |
|---|---|---|
| Browser | a rendezvous registration whose platform is web, and which advertises no quic capability and no transfer address | A WebRTC data channel through the rendezvous service, carrying the application-layer protocol of §5 onwards (§4.2). |
| QUIC-capable | every discovery source currently reporting it advertises the quic capability (§15.2) | QUIC (§13), by whichever of the three routes of §4.3 reaches it. No other transport is attempted. |
| Byte-stream only | anything else - a peer that advertises an address but no capability | The application-layer protocol (§5 onwards) over a direct TCP connection to an advertised address, or over a link-local radio transport presented to the protocol as a byte stream. |
A QUIC selection has no fallback. If every QUIC attempt fails, the send fails; an implementation MUST NOT silently retry such a peer over the application-layer transport, and MUST NOT offer a QUIC-capable peer a WebRTC data channel. Falling back would make a broken QUIC path indistinguishable from a slow one.
A sender that has no QUIC endpoint of its own, and no way to gather one, has no QUIC path to attempt; it uses the byte-stream transport whatever the peer advertises. This is not a fallback after a failure - nothing was tried.
4.2 Browsers
A peer running in a web browser has no access to a UDP socket and cannot serve or dial QUIC; it also cannot accept an inbound connection of any kind, so it publishes no address. A WebRTC data channel through the rendezvous service is the only way to reach it, and it is the only peer for which that is true. Data channels are used in both directions: a browser offers SDP to a native peer to send, and a native peer offers SDP to a browser to send. Every claim this document makes about the application-layer transport holds over a data channel unchanged.
The three conditions in the table above are conjunctive on purpose. The platform field is a self-description and no part of this protocol authenticates it - identity is established by the handshake, not by a discovery record. Requiring the two observable properties alongside it means a peer is offered a data channel only when it is indistinguishable from a browser in every respect a sender can check: to be treated as one it must also give up advertising QUIC and give up publishing an address, which is to give up every other way of being reached. An implementation SHOULD NOT key this decision on the platform field alone.
4.3 Reaching a QUIC peer
The capability says “a QUIC listener is bound at the endpoint this record names”. It does not say that endpoint is reachable from the sender, and the addresses the rendezvous service reports for a peer are the private addresses that peer is listening on - exactly the ones an off-link dialer cannot use. So the capability decides whether QUIC is used and the addresses decide only how. Three routes, in order:
- Direct. When at least one address came from a localdiscovery source - multicast DNS on the sender’s own link, not the rendezvous service - those addresses are dialled from the sender’s own listening endpoint. Only those: an address the rendezvous service supplied is a claim about where the peer listens, not evidence it is reachable from here.
- Punched. Otherwise the sender gathers candidates, exchanges them through the rendezvous service and punches (§14). This is the route that reaches a peer behind a NAT.
- Relayed. A candidate offered in that exchange may be a relayed address (§14.5), so a peer no direct or punched path reaches is still reached - over QUIC, not over a data channel.
A direct selection is deliberately not escalated to the exchange when it fails: a peer a local source can see and the sender cannot reach is a broken QUIC path, and punching around it would hide that behind a slower success.
Where the peer has several addresses, they are dialled concurrently - at most eight, filtered by §14.3, from the sender’s own listening endpoint, each carrying the expected peer identity so that a wrong device fails its own handshake rather than winning the race. The first completed handshake wins and the rest are closed. The whole attempt SHOULD be abandoned after eight seconds. A sender SHOULD also bound how often one peer can draw a fan-out from it; sixteen fan-outs in thirty seconds is generous against a person sending files and tight against a loop. A punch is the more expensive of the two and SHOULD carry a tighter bound of its own; whatever that bound is, it MUST sit above what one person retrying a failing send can spend, or an ordinary retry is refused as if it were an attack.
4.4 Ports
A device that serves both transports SHOULD serve them on the same port number - TCP for the byte stream, UDP for QUIC - and, when it cannot, MUST NOT advertise the capability on a record that names the other. A discovery record carries one port (§15.1), so a device whose two listeners disagree can honestly advertise only the transport that port serves.
5Session establishment
Applies to: the application-layer transport. On QUIC this exchange does not run - TLS 1.3 authenticates both peers to the same identity keys before any of the framing in §13 begins.
Every session begins with an authenticated Diffie-Hellman exchange. The peer that opens the connection is the initiator; the peer that accepts it is the responder. Both MUST generate a fresh X25519 key pair per session and MUST NOT reuse it.
initiator responder
│ │
│ KeyExchange { eph_pub, id_pub, ciphers } │
│───────────────────────────────────────────────▶│
│ │ select cipher suite,
│ │ compute transcript,
│ │ sign as responder
│ KeyExchange { eph_pub, id_pub, cipher, sig } │
│◀───────────────────────────────────────────────│
│ verify responder signature │
│ Auth { sig } │
│───────────────────────────────────────────────▶│
│ │ verify initiator signature
│ ── session established ── │5.1 Transcript
Both peers compute an identical 32-byte transcript:
transcript = SHA-256(
"anyshare-handshake-v3" [21]
‖ initiator_ephemeral_public [32]
‖ responder_ephemeral_public [32]
‖ initiator_identity_public [32]
‖ responder_identity_public [32]
‖ u16(length of offered) [ 2]
‖ offered [ n] cipher suite tags, §6
‖ selected [ 1] cipher suite tag, §6
)Fields are ordered by role, not by transmission order, so both peers hash identical bytes. Including both identity keys binds the session to that pair of devices.
5.2 Signatures
Each peer signs the transcript prefixed with its role:
signing_input = "anyshare-handshake-sig" ‖ role ‖ transcript [55]
role = 0x01 initiator
0x02 responderThe role byte MUSTbe included. Without it both peers sign identical bytes, and a signature produced by one can be replayed as the other’s proof.
A peer MUSTverify its counterpart’s signature against the identity key carried in that peer’s own key exchange message, and MUST abort the session if verification fails, if a message arrives out of order, or if any key is malformed. No content is transmitted or written before verification succeeds.
The identity key proven here is the only authenticated identifier in the protocol. Device identifiers and display names carried in later messages are self-asserted and MUST NOT be used for any access-control decision.
6Cipher suite negotiation
| Tag | Suite | Support |
|---|---|---|
0x00 | ChaCha20-Poly1305 | Mandatory |
0x01 | AES-256-GCM | Optional |
The initiator sends offered, a list of suite tags in descending order of preference. It MUST include the mandatory suite. The responder MUST select exactly one tag from that list, and MUST abort if it implements none of them. The initiator MUST abort if the selection is absent from what it offered. There is no fallback.
Both offered and selected are hashed into the transcript of §5.1, which both peers sign. Modifying either value in transit causes the two peers to derive different transcripts, so signature verification fails and the session is abandoned before any content is transmitted.
Both suites take a 256-bit key and a 96-bit nonce and produce a 128-bit authentication tag. Framing, nonce construction and key derivation are therefore identical for either selection.
Note. A peer without hardware AES acceleration offers only the mandatory suite. AES-GCM is therefore selected exactly when both peers can accelerate it, with no separate capability exchange.
7Key derivation
The X25519 shared secret MUST NOT be used as a key directly. It is the input keying material to HKDF-SHA-256, salted with the transcript:
salt = transcript (§5.1)
prk = HKDF-Extract(salt, X25519(own_ephemeral_private,
peer_ephemeral_public))
key_a = HKDF-Expand(prk, "anyshare-send", 32)
key_b = HKDF-Expand(prk, "anyshare-recv", 32)
initiator: send = key_a, receive = key_b
responder: send = key_b, receive = key_aEach direction uses a distinct key, so a message cannot be replayed toward its originator. Salting with the transcript binds the traffic keys to the authenticated identities and to the negotiated suite.
8PIN pairing
Where two devices cannot discover each other locally, a user may pair them with a short numeric PIN displayed on one device and entered on the other. The PIN MUST NOT be transmitted, and it is NOT an input to the key schedule of §7: pairing runs to completion before any transfer session exists, and what it leaves behind is a recorded peer identity key that §5 then authenticates on every later session.
Pairing is a balanced password-authenticated key exchange whose password is the PIN and whose two identities are the two devices’ long-term Ed25519 identity keys, followed by explicit key confirmation.
Note. Which SPAKE2. This is the Abdalla-Pointcheval construction of draft-ladd-spake2-01, wire-compatible with python-spake2 - not RFC 9382. The two differ in ways that prevent interoperation: this construction uses M = 15cfd18e3859… where the RFC uses d048032c6ea0…, and its transcript is the unprefixed SHA-256(H(pw) ‖ H(idA) ‖ H(idB) ‖ X ‖ Y ‖ K) rather than the RFC’s length-prefixed TT. An implementation built to RFC 9382 will complete the exchange and derive a different key.
The initiator is party A and the responder party B, and both MUST pass the identities in that order regardless of role. The group is Ed25519. Completing it proves the peer knew the PIN andthat both sides hold the same view of each other’s identity key, so a relay that substitutes a key cannot complete it. A failed confirmation MUST abort the run rather than retry, which is what limits an attacker to one online guess per PIN.
The exchange is four messages, and the first is a commitment. The initiator sends only a hash of its SPAKE2 message, and reveals the message itself after the responder has sent theirs. The responder MUST verify the commitment against the revealed message before using that message for anything.
initiator -> responder: commit = SHA-256("anyshare pake commit v1" ‖ X)
responder -> initiator: Y
initiator -> responder: X, confirm_initiator
responder -> initiator: confirm_responderThe commitment exists because SPAKE2 is linear in the ephemeral scalar: an intermediary that learned one side’s contribution before choosing its own could search for a value making both devices display the same short authentication string, at a cost of one curve addition per candidate. Committing first removes that ordering advantage, so such a search is impossible rather than merely expensive, and the attempt is reduced to a single blind guess.
Key confirmation and the short authentication string are all derived from the shared key K by HKDF-SHA-256 with an empty salt:
confirm_initiator = HKDF(K, "anyshare pake v1 confirm initiator")[0..32] confirm_responder = HKDF(K, "anyshare pake v1 confirm responder")[0..32] sas_bytes = HKDF(K, "anyshare pake v1 sas")[0..4]
The two confirmation labels differ so that a value reflected back at its sender cannot verify. sas_bytes is read big-endian as a u32; the low 30 bits are taken as three 10-bit groups, most significant first, each indexing a 1024-entry word list. Implementations MUST use the same list and MUST NOT localise it - two devices in different locales showing different words for an honest pairing is indistinguishable from an attack.
Both peers display those three words, and an implementation MUST NOT record the pairing until the user has confirmed that the two devices show the same words. Byte strings on this exchange are encoded as JSON arrays of numbers.
Note. The confirmation step is not decoration. Where the PIN is generated by, or transmitted to, the same party that relays the pairing messages - as it is in AnyShare, whose joiner sends the PIN to the room server to find the room - that party knows the password and can run the exchange from the middle - one session toward each device. The two sessions have independent ephemeral randomness, so the two devices derive different keys and display different words, and the human comparison is what catches it.
9Message framing
Applies to: the application-layer transport. QUIC uses the 13-byte control-frame header of §13.2 instead, and carries file content on streams of its own rather than in messages.
Every message is a 32-byte header followed by its payload.
offset size field
0 4 magic "ESHR"
4 2 version u16
6 2 type u16 §10
8 16 transfer_id UUID, as laid out
24 8 payload_len u64
32 n payload9.1 Validation
A receiver MUST reject a header whose magic does not match, and MUST reject an unrecognised message type rather than skipping the message.
9.2 Payload limit
payload_len MUST be rejected if it exceeds 4 MiB, and this check MUST occur before any buffer is allocated. The field is read from an unauthenticated peer before any signature has been verified; the limit bounds the allocation an unauthenticated peer can compel while remaining well above every valid payload.
9.3 Versioning
A receiver MUST reject a header whose version differs from the version it implements. There is no version negotiation and no backward compatibility: each version is a distinct, incompatible protocol, and peers at different versions cannot interoperate.
10Message types
| Type | Name | From | Purpose |
|---|---|---|---|
0x01 | Offer | sender | Describes one file: name, size, chunk geometry, optional preview and batch manifest. |
0x02 | Accept | receiver | Transfer may proceed. |
0x03 | Reject | receiver | Transfer is declined. |
0x04 | Chunk | sender | One encrypted chunk. |
0x05 | Ack | receiver | Highest contiguous chunk durably stored. |
0x06 | Resume | receiver | Proposes skipping a prefix, with its hash (§12). |
0x07 | Done | sender | Content digest of the complete file. |
0x08 | Cancel | either | Terminates the transfer. |
0x09 | KeyExchange | either | Ephemeral key, identity key, cipher offer or selection, responder signature. |
0x0A | Auth | initiator | Initiator transcript signature. |
0x0B | ResumeAck | sender | Chunk index the sender will start from (§12). |
Only Chunk payloads are encrypted. Control messages are unencrypted within the session and carry no file content, though they do carry file names and sizes; see §16.
The Offerpayload is the offer structure laid out field by field in §13.6. It is the same structure on both transports, encoded the same way; QUIC differs only in carrying every file’s offer in one frame instead of one offer per message.
11File transfer
After the session is established the sender issues an Offer and waits. The receiver replies Accept, Reject, or Resume (§12). Content follows only after Accept.
11.1 Chunking
Content is divided into chunks of equal size, the last of which may be shorter. The sender states the chunk size in the Offer; the default is 256 KiB. Chunk sequence numbers begin at zero and increase by one.
11.2 Chunk encryption
Applies to: the application-layer transport only. On QUIC there is no chunk AEAD - see §13.8 for why, and for what it means for an implementer.
Each chunk is sealed independently under the negotiated suite with the sender’s send key and a nonce derived from its sequence number:
nonce[0..8] = u64(sequence_number) nonce[8..12] = 0x00 0x00 0x00 0x00 payload = Seal(send_key, nonce, chunk) chunk ‖ tag [16]
Sequence numbers are unique within a session, so nonces are unique under a key without shared counter state. Keys are session-specific, so an identical nonce in another session is used under a different key. A chunk that fails authentication MUST terminate the transfer, and unauthenticated content MUST NOT be written to durable storage.
11.3 Compression
A sender MAY compress chunks, indicating this in the Offer. Compression is applied per chunk before encryption, so nonce construction and resumption arithmetic are unaffected. The digest of §11.4 is always computed over uncompressed content.
11.4 Completion and integrity
After the final chunk the sender sends Done carrying the BLAKE3 digest of the complete uncompressed content. The receiver MUST compare it against the content it stored and MUST discard the content on mismatch.
Both peers compute this digest incrementally as content streams, and neither re-reads stored content to produce it. The Offer therefore carries no digest: it is not known when the offer is sent.
12Resumption
Applies to: the application-layer transport. QUIC negotiates the same thing per file, in bytes rather than chunks, on the file’s own stream - §13.9. Partial content is interchangeable between the two: a transfer interrupted on one transport resumes on the other.
An interrupted transfer may resume over any transport, not only the one that failed. A receiver that holds partial content for an offer MAY reply Resume instead of Accept.
receiver → Resume { last_chunk, prefix_hash }
prefix_hash = BLAKE3 of exactly the
content the receiver proposes to skip
sender → ResumeAck { start_chunk }
start_chunk = agreed index, or 0 to decline
chunks resume from start_chunkThe sender MUST compute the digest of its own leading content and MUST reply with start_chunk of zero unless it matches prefix_hash exactly. A sender MUST answer every Resume with a ResumeAck, including when it declines.
Verifying the digest of the exact prefix, rather than trusting a stored offset, detects a checkpoint that refers to different content, a truncated write or an unflushed buffer, in each case restarting cleanly rather than producing a corrupt file.
13The QUIC transport
Between two native peers selected by §4, AnyShare runs over QUIC version 1 (RFC 9000, RFC 9001) rather than over a byte stream. The transport authenticates both devices to their identity keys and encrypts everything it carries, so the application-layer handshake of §5, the cipher negotiation of §6, the key schedule of §7 and the chunk AEAD of §11.2 do not run. What remains - the offers, the file content, the resume negotiation and the integrity digests - is carried by the framing specified here.
Note. What this transport costs, for an implementer choosing it. QUIC is materially more expensive in processor time than a byte stream, and the shape of the cost is more useful than its size. Measured sender-side over 100 MiB transfers between two desktop-class hosts: per byte it is about 1.4 times a TCP byte stream, which is the framing and packet-level encryption; the remainder is a fixed cost per unit of time a connection is open, of roughly 0.07 of one core. Cost per byte transferred therefore rises as the link slows - about 1.4 times on a loopback path, about 5.7 times on a 35 Mbit/s wide-area path. The dominant term is not the cipher; it is timer-driven work and the wakeups it causes.
This is an observation about implementations, not a requirement of the protocol, and it is offered because the trade is not obvious: the round trips this transport removes (§4.3, §13.1) are paid for in processor time that is largest exactly where links are slowest. It has not been measured on a battery-powered device, and the receiving side has not been measured at all.
13.1 Identity and authentication
A QUIC connection is a TLS 1.3 handshake (RFC 8446) inside QUIC’s own packet protection (RFC 9001). Both peers present a credential and both verify the other’s: the handshake is mutual, and an anonymous peer is refused.
| Parameter | Value |
|---|---|
| ALPN protocol name | anyshare/1 |
| TLS version | 1.3 only. A peer offering only earlier versions is refused. |
| Certificate type, both directions | RawPublicKey (RFC 7250). X.509 is neither offered nor accepted. |
| Signature scheme | Ed25519 (0x0807) only |
| Client authentication | Required by the listener. |
| Server name (SNI) | The fixed placeholder anyshare, sent by every dialer and inspected by no listener. |
A dialer offers QUIC version 1 (0x00000001) and exactly one ALPN protocol. A peer whose ALPN does not match MUST be refused during the handshake, with the TLS alert no_application_protocol (120), so that another QUIC service sharing an address never reaches the identity check.
Both certificate-type extensions of RFC 7250 are used, in both directions: a ClientHello carries client_certificate_type and server_certificate_type, each a one-element list containing only RawPublicKey (2), and the server echoes both as single values. There is no X509 fallback in either list. A peer that does not negotiate raw public keys for both MUST be refused with handshake_failure (40); an ordinary TLS peer therefore cannot complete a handshake with an AnyShare device, which is intended.
The credential is the device’s Ed25519 identity key of §3 encoded as a bare SubjectPublicKeyInfo - not a certificate, and not a self-signed certificate. RFC 8410 §4 fixes the encoding, so it is 44 bytes with a constant 12-byte prefix:
SubjectPublicKeyInfo, Ed25519 [44]
30 2a SEQUENCE, 42 bytes
30 05 SEQUENCE, 5 bytes (AlgorithmIdentifier)
06 03 2b 65 70 OBJECT IDENTIFIER 1.3.101.112 (id-Ed25519)
03 21 00 BIT STRING, 33 bytes, 0 unused bits
<32 bytes> the raw Ed25519 public keyBoth roles present the same 44 bytes. A peer MUST reject a credential that is not exactly this - wrong length, wrong prefix, or a different algorithm identifier - because such a peer cannot be authenticated at all.
Having recovered the peer’s key, an implementation MUST apply the following policy, in this order, and MUST fail the handshake on any rejection:
- Not an Ed25519 raw public key. Reject.
- The peer presented our own identity key. Reject. Identity public keys are advertised in the clear by discovery (§15), so anyone can copy one; a device that accepted its own key back would extend to a stranger whatever trust it grants itself.
- A specific peer was expected and this is not it. Reject. A dialer that knows which device it means SHOULD say so before the handshake, so that reaching the wrong device fails inside the handshake rather than after it.
All three rejections are signalled identically on the wire: the alert is access_denied (49), carrying no indication of which rule fired. An implementation MUST NOT distinguish them to the peer, and SHOULD record the reason locally, since it is the only place the difference survives.
Enforcing the expected peer during certificate verification, rather than after the connection is up, is what keeps a dialer from disclosing its own identity to the wrong device: a TLS 1.3 client verifies the server’s credential before it sends its own. No post-handshake check can recover that.
Note. A completed dial does not mean the listener accepted you.TLS 1.3 completes the client on its own Finished, so a dialer’s connection resolves before the listener has examined the credential it presented. A listener’s refusal is observed as the connection closing, or as the first stream failing - never as the connection failing to establish. An implementation MUST NOT treat a connected dial as evidence of acceptance.
The identity key proven by this handshake is the peer’s only authenticated identifier, exactly as in §5. The device identifier and display name carried in the manifest are self-asserted and MUST NOT be used for any access-control decision.
13.2 Control-frame header and the two version numbers
Every message on the control stream is a 13-byte header followed by its payload.
offset size field
0 4 magic "ESHR"
4 2 protocol_version u16 the application protocol, §13.2.1
6 2 framing_revision u16 this framing, §13.2.1
8 1 kind u8 §13.5
9 4 payload_len u32 bytes of payload; excludes this header
13 n payloadThe header is on every frame, not once at the head of the stream. A transfer may open a second control stream (§13.3), and a per-frame header cannot be forgotten there.
A receiver MUST validate the header in this order, before reading kind: magic, then protocol_version, then framing_revision, then payload_len against the limit of §13.11. It MUST reject a frame whose magic differs; it MUST reject a version or revision that differs from the one it implements, on the first frame, before any payload is decoded. There is no negotiation of either number and no backward compatibility.
13.2.1 What each number governs
The two numbers are independent and have different owners. Both are on the wire precisely so that neither has to be bumped on the other’s account.
| Number | This document | Governs | Bumped when |
|---|---|---|---|
protocol_version | 9 | The application protocol: the offer structure of §13.6, the message set of §10 and every construction in §5 to §12. It is the same number the application-layer header of §9 carries. | Any of that changes. |
framing_revision | 1 | This framing alone: the control-frame header and kinds, the stream structure, the file-stream layout and the resume exchange. | Any of that changes. A change to the application protocol does not require it, and vice versa. |
A peer that cannot read the first frame it is given SHOULD answer with a VersionMismatch frame and then finish its side of the control stream, rather than closing the connection silently. The content of that answer is its own header: the four bytes at offsets 4 to 7 name the two versions the refusing peer speaks. Its recipient will reject it on those very bytes, which is the point - the refusal is legible to a packet capture and to a person reading a log, and it stops the sender from spending its reconnection budget on a peer that will refuse it identically every time.
13.3 Session resumption and 0-RTT
TLS 1.3 session resumption is permitted, and is what removes a round trip from the second and later transfers between a pair of devices. It carries an obligation that a naive implementation will get wrong: a resumed TLS 1.3 handshake runs no certificate verification on either side.The client restores the server’s credential from the stored session without rechecking it, and the server does not send a CertificateRequest at all. What makes the inherited identity trustworthy is the PSK binder - a MAC over the ClientHello keyed by the resumption secret of the original session, so a captured ticket alone resumes nothing.
Because the identity is inherited rather than rechecked, only a dialer that knows which device it means may resume: a dial that names no expected peer MUST NOT resume a session and MUST NOTstore one. This is not a performance choice. AnyShare devices have no names, so every dialer sends the same placeholder in SNI (§13.1), and a session store keyed the ordinary way - by server name - would hold every peer’s tickets in one bucket.
An implementation that resumes sessions MUST observe all four rules:
- Apply the identity policy of §13.1 on every connection, resumed or full, to the peer identity the completed connection reports - not only inside the verifier callbacks, which a resumed handshake never reaches.
- Never resume across a different expectation. A session established while expecting device A MUST NOT be continued by a connection expecting device B, or expecting nobody in particular.
- Scope stored tickets to the peer that issued them. Offering a ticket also spends it, so a ticket earned from one device MUST NOT be offered to another.
- Accept a ticket once. A listener MUST retire a resumption ticket on first use, as RFC 8446 §8.1 describes, and MUST NOT honour it on any other listening endpoint.
Where a sender dials several candidate addresses at once (§4.3, §14.3), at most one of those attempts MAY offer a stored ticket; the rest MUST dial without resuming, while still storing any ticket they are issued. Otherwise a single fan-out spends one ticket per dead address - offering a ticket spends it whether or not the handshake completes - and resumption silently stops engaging after a few sends.
Sessions are held in memory only; there is no ticket-encryption key, so a restarted listener issues no ticket that survives it, and a peer simply performs a full handshake. A listener SHOULD also reject a resumption whose reported ticket age differs from its own measurement by more than about a minute, the freshness check of RFC 8446 §8.3.
Note. AnyShare’s own deployment issues two tickets per handshake with an advertised lifetime of 24 hours, holds up to 32 peers’ sessions on the dialing side, and retires a ticket on first use at the listener. None of that is normative - the four rules above are - but an implementer sizing a store will want the order of magnitude.
13.3.1 Early data
A dialer resuming a session MAY place application data in its first flight. Exactly one frame is permitted there: the Manifest (§13.5). No other frame, and nothing on a file stream, MAY be sent as early data.
TLS 1.3 early data is protected by a key derived from the resumption secret alone. It carries no proof of liveness, so anyone who captured the packets can send them again (RFC 8446 §8). Three things answer that, and an implementation needs all three:
- Nothing replay-sensitive is in the flight. A manifest starts no transfer by itself; it asks for one.
- The listener MUST NOTsend application data before the client’s Finished. Early data MAY be decrypted on arrival but MUST NOT be acted upon until the handshake completes, at which point the dialer is authenticated. An attacker replaying a captured flight cannot produce a Finished and so never reaches the application at all.
- The receiver MUST deduplicate manifests. A manifest whose (proven peer identity key, transfer identifier) pair has already been seen MUST start no transfer and raise no prompt. The peer key MUSTbe the one the transport proved, never one read out of the message, or any device could suppress another’s transfers by guessing identifiers.
The deduplication window is bounded, and a bounded window forgets: an implementation MAY remember the most recent identifiers of the most recent peers rather than all of them, and an identifier that falls out of the window is accepted as new. Bounded memory and perfect deduplication cannot both hold, and an unbounded set of peer-supplied identifiers is a memory-exhaustion vector open to anyone who can complete a handshake.
13.4 Stream structure
Every stream is a QUIC bidirectional stream, and every stream is opened by the sender. The receiver only accepts. No unidirectional stream and no QUIC datagram is used.
sender receiver │ │ │ ── control stream ────────────────────────▶ │ first stream opened │ Manifest │ │ ◀─────────────────────────────────────── │ Accept | Reject │ │ │ ── file stream, index 0 ─────────────────▶ │ one per file, │ ── file stream, index 1 ─────────────────▶ │ at most 8 open at once, │ ── ... │ opened in index order │ │ │ Cancel, either direction, any time, on the control stream
Because the sender writes the manifest before opening any file stream, the receiver’s first accepted stream is the control stream. A sender MUST NOT open a file stream before receiving Accept, and MUST NOT open more than one stream for the same file index; a receiver MUST treat a second stream naming an index it has already claimed as a protocol error. A sender SHOULD keep at most eight file streams open at a time.
A sender using early data (§13.3.1) opens the control stream in its first flight. If the listener rejects the early data, that stream is reset by the transport; the sender then opens a new control stream after the handshake and re-sends the identical manifest bytes. A receiver therefore MUST be prepared for the control stream to be the second stream it accepts.
13.5 Control frames
| Kind | Name | From | Payload |
|---|---|---|---|
0x00 | Manifest | sender | Every file’s offer, at once. §13.6. The only kind with a payload, and the only kind permitted as early data. |
0x01 | Cancel | either | Empty. The transfer is over. |
0x02 | Accept | receiver | Empty. Open the file streams. |
0x03 | Reject | receiver | Empty. The transfer is declined. |
0x04 | VersionMismatch | either | Empty. “I cannot read what you sent me”; the answer is in its own header. §13.2. |
The kinds share one namespace across both directions, so a frame read in the wrong direction is a protocol error rather than a different message that happens to parse. A receiver MUST reject a kind it does not recognise rather than skipping the frame.
The exchange is: Manifest, then Accept or Reject. Either peer MAY send Cancel at any point, so both MUST keep reading the control stream for the duration of the transfer.
13.6 The manifest
The manifest payload is encoded as §2 describes. It is two fields:
Manifest
transfer_id UUID the sender's identifier for this transfer
files seq of Offer one per file, in the order the streams
of §13.7 will name them by indexAn Offer is the same structure the application-layer Offer message of §10 carries. Its fields, in wire order:
Offer file_name string display name, no path separators implied file_size u64 bytes of uncompressed content chunk_size u32 see §11.1; default 262144 total_chunks u64 ceil(file_size / chunk_size), minimum 1 sender_device_id UUID self-asserted; see §13.1 sender_name string self-asserted display name preview opt bytes JPEG thumbnail, ~64 KiB or less compression u32 0 = none, 1 = Zstandard (§13.8) relative_path opt string '/'-separated path within a batch manifest opt seq of BatchEntry send_nonce [16] 16 bytes, no length prefix BatchEntry relative_path string '/'-separated size u64 A receiver MUST reject a manifest in which: - files is empty - any chunk_size is 0 - compression is not 0 and chunk_size exceeds 4 MiB - total_chunks is not exactly max(1, ceil(file_size / chunk_size))
preview and manifest are present, if at all, only on the first offer, and describe the batch as a whole. relative_path, when present, is the path the receiver recreates the file at beneath its save directory; a receiver MUST discard empty, . and .. components rather than resolving them, and MUST NOT write outside that directory.
send_nonce is sixteen bytes minted once per send operation and stamped identically on every offer of that send - every file of a batch, and every transport the send is attempted over. It lets a receiver that sees one send arrive by two routes collapse them into one transfer, while still telling two concurrent sends of the same file by the same sender apart.
13.7 File streams
One bidirectional stream carries one file. Both directions are used, and the first three exchanges are strictly ordered: nothing on the wire labels them, so an implementation must read them in exactly this sequence.
sender → receiver receiver → sender
file_index u64 [ 8]
resume_offset u64 [ 8]
prefix_hash [32]
accepted_offset u64 [ 8]
── content, §13.8 ──
content_hash [32] confirmations u64 × n [ 8] each
── FIN ── ── FIN ──file_index indexes the manifest’s files. A receiver MUST reject an index the manifest does not contain.
Every u64 here is little-endian, and every offset counts uncompressed bytes of the file, never bytes on the wire. The content is everything between accepted_offsetand the trailer; the trailer is the last 32 bytes before the sender’s FIN. A stream that ends before the content and trailer are complete is a truncated transfer, not a completed one.
confirmations flow in the reverse direction concurrently with the content, and are described in §13.10.
13.8 Content and compression
There is no chunk AEAD on this transport, and this is deliberate.QUIC packet protection (RFC 9001 §5) already encrypts and authenticates every byte under keys derived from a handshake that proved both peers’ identity keys, so a second AEAD would encrypt already encrypted bytes under a key derived from the same devices. An implementation MUST NOT add one, and equally MUST NOT omit the AEAD of §11.2 on the application-layer transport, where nothing else provides it. There are no chunk records, no sequence numbers and no authentication tags on a file stream.
compression in the offer decides the shape of the content, and applies to the whole file:
compression = 0 (none)
the file's bytes from accepted_offset to end of file,
with no framing of any kind: no lengths, no records, no padding.
The reader knows the count: file_size - accepted_offset.
compression = 1 (Zstandard, RFC 8878)
a run of independent Zstandard frames, each preceded by its own
compressed length:
u32 compressed_len little-endian
compressed_len bytes one complete Zstandard frame
Frame k, counting from zero at this stream's accepted_offset,
decompresses to exactly the file bytes
[ accepted_offset + k*chunk_size ,
min(accepted_offset + (k+1)*chunk_size, file_size) )
so the last frame may be shorter and every frame boundary is a
known file offset. Neither the plaintext length nor the frame
count is transmitted; both sides derive them.Because the frames are anchored at accepted_offset rather than at byte zero, any resume offset is a frame boundary. A receiver MUST reject a frame whose announced compressed_len is zero, or larger than the Zstandard compression bound for the plaintext length it expects, and MUST reject a frame that decompresses to any length other than exactly that expected plaintext - longer or shorter. Both checks are required: the first bounds the allocation an announced length can compel, the second is what makes a decompression bomb impossible.
Note. A sender decides compression per file before sending, by compressing a sample of at most 64 KiB from the head of the file and choosing Zstandard only if the sample shrinks by at least a tenth. Files below 64 bytes, and files whose extension names an already-compressed format, are never compressed. None of this is on the wire - a receiver obeys the flag it is given.
13.9 Resumption by byte offset
Resumption is negotiated per file, on that file’s own stream, before any of its content flows. It is the same find-then-verify as §12 with a byte offset in place of a chunk index, and it costs one round trip per file - overlapped across the concurrent streams.
receiver → resume_offset u64 bytes it holds and proposes to skip;
0 means "nothing to resume"
prefix_hash [32] BLAKE3 of exactly those bytes;
ignored by the sender when the offset is 0
sender → accepted_offset u64 the offset it will start from:
the proposal, or 0 to declineA receiver with nothing to propose MUST still send the 40 bytes, all zero. A sender MUST always reply, so that the receiver knows whether to keep or truncate its partial file before opening it.
A sender MUST reject a resume_offset greater than file_size. Otherwise it MUST compute the BLAKE3 digest of its own first resume_offset bytes and MUST reply with zero unless that digest equals prefix_hash exactly. An offset equal to file_size is legal and skips the content entirely. A sender MUST NOT reply with any non-zero value other than the offset that was proposed, and a receiver MUST treat such a reply as a protocol error.
On a non-zero accepted_offset the receiver MUST truncate its partial file to exactly that length before appending, and MUSTseed the file’s running digest with those bytes so the trailer of §13.10 still covers the whole file. On zero it MUST discard the partial entirely.
A receiver matches a partial file to an offer by the sender’s device identifier, the offer’s relative_path (or file_name when there is none) and file_size - never by transfer_id, which is new on every attempt. That match is only the cheap find; the digest is the verify, and it is what detects a checkpoint referring to different content, a truncated write or an unflushed buffer, restarting cleanly instead of producing a corrupt file.
13.10 Integrity and checkpoints
The last 32 bytes a sender writes on a file stream, immediately before FIN, are the BLAKE3 digest of the complete file: the whole file including any prefix the resume skipped, and always of the uncompressed content. There is no Done frame on this transport and no digest for the batch as a whole; integrity is per file.
Both peers compute the digest incrementally from content that is streaming anyway, and neither re-reads what it stored. A receiver MUST compare the trailer against its own accumulated digest, and MUST discard the file on mismatch.
A confirmation is a bare u64little-endian byte offset in the reverse direction of the file’s stream - no header, no kind, no framing, and a reader MUSTtreat the reverse direction as a stream of 8-byte values that may arrive split across reads. Its value is the number of bytes of this file the receiver has durably written, counting any resumed prefix. It is the receiver’s checkpoint, and the offset it will propose in §13.9 if the transfer is interrupted.
A receiver SHOULDconfirm after each interval of durably written content - a file’s size divided by eight, clamped to between 64 KiB and 1 MiB, gives roughly eight checkpoints per file - and MUST send a final confirmation equal to file_size once the digest has verified, unless it has already sent that value. A confirmation follows the write, never the read.
13.11 Cancellation, limits and timeouts
Cancelling a file mid-stream is a RESET_STREAM with application error code 1, never a FIN. The distinction is the whole content of the message: a FIN part-way through a file is indistinguishable from a truncation the network caused, while a reset says a person or a peer decided. A peer that reads a reset on the receiving half of a file stream MUST treat the transfer as cancelled rather than failed. A reset observed while writing a confirmation means only that the peer has stopped reading, and MUST NOT be read as a cancellation.
Cancelling the transfer as a whole is a Cancel frame on the control stream, sent on a best-effort basis. A peer MUST NOT depend on receiving one: a reset stream or a closed connection ends a transfer just as surely.
| Limit | Value | Enforced by |
|---|---|---|
| Control-frame payload | 16 MiB | Both peers, on write and on read, before any buffer is allocated. |
| Concurrent file streams | 8 | The sender; a receiver need not accept more. |
| chunk_size, compressed offers | 4 MiB | The receiver, when validating the manifest. |
| Idle between reads once content is flowing | 60 seconds | Both peers. A half-open connection must not park a stream forever. |
| Wait for the final confirmation to be delivered | 5 seconds | The receiver, before returning. |
A peer MUST NOTimpose a deadline on a decision a person is making: the sender’s wait for Accept or Rejectis unbounded, as is either peer’s watch for a Cancel. Every other read is bounded.
There is no limit on the number of files beyond the 16 MiB manifest, and none on the length of a file name or relative path. An implementation MUST therefore treat both as untrusted input of unbounded length.
13.12 Connection parameters
AnyShare sets no QUIC transport parameter of its own except the payload sizing a relayed path requires (§14.6). The values below are what a peer will see; an implementation MAY choose others, but MUST be able to interoperate with these.
| Parameter | Value | Consequence for a peer |
|---|---|---|
| Idle timeout | 30 seconds | A connection with no traffic for that long is closed. Nothing sends a keep-alive; the transfer's own traffic is what keeps it open. |
| Concurrent bidirectional streams offered | 100 | Far above the nine (one control, eight files) this framing opens. |
| Concurrent unidirectional streams | 100, unused | This framing opens none. |
| QUIC DATAGRAM frames | Advertised, never sent | All content travels on streams. |
| Maximum UDP payload | 1472 bytes on a direct path; 1425 on a relayed one (§14.6) | A peer must respect the advertised value; a relayed endpoint's is deliberately lower. |
| Active migration | Permitted | A peer may change address mid-connection. |
Connection teardown is a CONNECTION_CLOSE with application error code 0 and an empty reason. The only other application error code this protocol defines is 1 on RESET_STREAM, which means cancellation and is described above. An identity rejection or a version refusal appears as a handshake failure or a closed connection, not as an application code.
14Candidate exchange and relaying
Two peers on one link reach each other at an address discovery already gave them (§4.3). Two peers separated by a NAT do not, and this section specifies how they find a path: each side offers a set of candidate addresses at which it might be reachable, the two sets are exchanged through the rendezvous service, and both sides then try the pairs.
None of it is trusted. A candidate is an unauthenticated claim by a peer about where to send packets, and the only thing that decides whether a path is real is a QUIC handshake completing over it against the expected identity key (§13.1).
A sender runs this exchange whenever no local discovery source gave it an address for a QUIC-capable peer (§4.3), so the candidates below, the address filtering of §14.3 and the relayed path of §14.5 describe traffic a deployed AnyShare device produces. Between native peers, a relayed transfer is QUIC carried over a bound TURN channel (§14.6); the indication encapsulation remains reachable, and §14.6 says when.
Relayed traffic to and from a browser is a separate matter: it is a WebRTC data channel (§4.2), which uses an allocation of the same kind but negotiates it through ICE rather than through the exchange specified here.
14.1 Candidates
A candidate is an address and a kind, and nothing else.
| Kind | Wire value | What it is |
|---|---|---|
| Host | host | An address the offering device's own socket is bound to. One per socket, not one per interface address. |
| Server-reflexive | server_reflexive | The public address a STUN server saw that same socket arrive from (§14.4). Omitted when it equals the host candidate. |
| Relayed | relayed | The address a TURN server will forward from on the offering device's behalf (§14.5). |
There is no priority, foundation, component or transport field. This is not ICE: QUIC’s own path validation decides which pair works, so there is nothing for a priority to order and no nomination to perform. Candidate order in the offered list is a hint and carries no normative weight.
14.2 The exchange
Candidates travel as JSON through the rendezvous service’s opaque device-to-device relay, the same channel that carries WebRTC signalling and the pairing exchange of §8. A client sends
{ "type": "signal",
"to": "<peer device UUID>",
"payload": { ... } }and the peer receives the identical object with to replaced by from. The service does not read, validate or alter the payload.
The two candidate payloads are:
{ "type": "quic_candidates_offer",
"candidates": [
{ "addr": "192.0.2.5:41234", "kind": "host" },
{ "addr": "203.0.113.9:9000", "kind": "server_reflexive" },
{ "addr": "198.51.100.7:50123", "kind": "relayed" }
] }
{ "type": "quic_candidates_answer",
"candidates": [ ... same shape ... ] }addr is the textual form of an address and port: a.b.c.d:port for IPv4, and a bracketed literal such as [2001:db8::5]:41234 for IPv6. kind is one of the three values in §14.1.
The peer that initiated the connection is the offerer: it sends quic_candidates_offer and waits for quic_candidates_answer. The other peer waits for the offer and answers it. The roles follow from who dialled; they are not negotiated.
A peer waiting on this exchange MUST ignore payloads carrying any other type - the channel is shared - and MUST treat the other candidate message arriving out of turn as an error rather than accepting it. The whole exchange SHOULD be abandoned after ten seconds. A payload whose candidates is missing or not an array is read as an empty list, and individual entries that do not parse are dropped rather than failing the message.
14.3 Filtering and selection
A received candidate list is an unauthenticated instruction to send packets somewhere. Before dialling anything, an implementation MUST discard every candidate whose address is a loopback address, the unspecified address, a multicast address, the IPv4 broadcast address, an IPv4 link-local address (169.254.0.0/16), an IPv6 link-local address (fe80::/10), or whose port is zero. An IPv4-mapped IPv6 address MUST be normalised to its IPv4 form before that judgement and thereafter. Private addresses are deliberately kept: they are exactly what a peer on the same network offers.
The surviving list MUST then be deduplicated and truncated - at most twenty remote candidates, and at most thirty-two local-to-remote pairs attempted. Pairs of differing address family are skipped. Where a device has several local sockets, pairs SHOULDbe ordered so that every socket gets its first target before any socket gets its second, so that a truncation trims each socket’s tail rather than starving the last socket entirely.
The remaining pairs are then dialled concurrently, as real QUIC handshakes, each carrying the expected peer identity. The first handshake to complete wins; every other attempt is closed. There is no check list and no nomination. A whole attempt SHOULD be abandoned after ten seconds. At most one attempt in a fan-out may offer a resumption ticket (§13.3), and no attempt in a fan-out sends early data.
14.4 Server-reflexive addresses
A server-reflexive candidate is learned with a STUN Binding request (RFC 5389) sent from the very socket the candidate will describe. The request is unauthenticated - 28 bytes, carrying only a FINGERPRINT attribute:
offset size field
0 2 message type 0x0001 Binding request
2 2 message length 0x0008 attributes only
4 4 magic cookie 0x2112A442
8 12 transaction id random
20 2 attribute type 0x8028 FINGERPRINT
22 2 attribute length 0x0004
24 4 CRC-32 of the message so far, XORed with 0x5354554EThe reply is a Binding success (0x0101). The reflexive address is taken from XOR-MAPPED-ADDRESS (0x0020) when present, falling back to the non-XOR MAPPED-ADDRESS (0x0001). A reply arriving from any address other than the server, or bearing a different transaction identifier, MUST be ignored. Failure is not an error: the candidate is simply absent.
Only the IPv4 address family is handled. A reply carrying an IPv6 mapped address yields no candidate, and a peer offering an IPv6 relayed candidate is not reachable through §14.5. Host candidates may be of either family.
Every message this protocol sends to a STUN or TURN server carries FINGERPRINT as its last attribute, and the length field is written to include it.
14.5 Relayed addresses
A relayed candidate is a TURN allocation (RFC 5766). The relay is not trusted: it forwards ciphertext for which it holds no key, and substituting a peer is defeated by the identity check of §13.1 (or §5.2 on the application-layer transport).
Credentials. The rendezvous service mints time-limited TURN credentials on request over its WebSocket, for a session that has proved possession of its identity key. They are the long-term credentials of RFC 5389 §10.2, in the ephemeral form that relay implementations have converged on:
username = "<unix expiry seconds>:<device UUID>" credential = base64( HMAC-SHA-1( shared secret, username ) ) lifetime = 1 hour
The shared secret is known only to the rendezvous service and the relay; a client never sees it and cannot mint its own credential. Note that the username is sent in the clear in every authenticated TURN request, so the device identifier and the expiry are visible to anyone watching the client-to-relay path. The long-term key derived from these is MD5(username : realm : credential), as RFC 5389 §15.4 specifies.
Allocation. Two round trips, as RFC 5766 §6 prescribes: an unauthenticated Allocate carrying only REQUESTED-TRANSPORT (UDP, value 17), the relay’s 401 carrying REALM and NONCE, then an Allocate carrying REQUESTED-TRANSPORT, LIFETIME (600 seconds), USERNAME, REALM, NONCE, MESSAGE-INTEGRITY and FINGERPRINT, in that order. The success response carries XOR-RELAYED-ADDRESS, which is the relayed candidate. A 438 Stale Nonce is retried once with the new nonce; no other error is retried.
The allocation is kept alive by a Refresh roughly every four minutes, or every half-lifetime, whichever is sooner; teardown is a Refresh with a LIFETIME of zero. The relayed address is IPv4: no address-family attribute is requested, and an IPv6 peer cannot be reached through an allocation.
Channels. For each peer the client attempts a ChannelBind (RFC 5766 §11) before sending anything, which is both the permission to exchange traffic with that peer and a two-byte handle for doing so. Channel numbers are assigned sequentially from 0x4000 upward within the range RFC 5766 permits (0x4000-0x7FFE), never reused within an allocation, and stable for a given peer across retries - rebinding a peer to a second number is a protocol error. A binding is refreshed on the same cadence as the allocation, by repeating the identical ChannelBind.
A relay that refuses ChannelBind is fallen back to CreatePermission, and that peer then uses indications for the rest of the session: a non-conformant relay costs bytes rather than failing. Implementers should note that RFC 8656 §12 narrows the channel range to 0x4000-0x4FFF; a client that follows RFC 5766 SHOULD be prepared for a relay that follows RFC 8656 to refuse a number above that.
14.6 What relayed traffic looks like
With a channel bound, every datagram in both directions is a bare ChannelData message on the client-to-relay UDP flow - four bytes of overhead, no STUN header, no transaction identifier, no per-packet authentication:
offset size field
0 2 channel number big-endian, 0x4000..0x7FFE
2 2 payload length big-endian, excludes any padding
4 n the datagram, verbatim
4+n 0-3 zero padding to a multiple of four
- present only over a stream carrier, never over UDPA receiver distinguishes ChannelData from STUN by the top two bits of the first byte: 00 is STUN, 01 is ChannelData. It MUST NOT distinguish them by trial parsing - a relayed QUIC packet can itself begin with bytes that look like a STUN header.
Without a channel, a datagram is a Send indication (0x0016) outbound and a Data indication (0x0017) inbound, carrying XOR-PEER-ADDRESS, DATA and FINGERPRINT. Indications are not authenticated by this protocol and cost up to 47 bytes rather than four. A peer whose source port changes under a permission scoped to its address falls back to indications silently, which is why the payload ceiling below is sized against the indication rather than the channel.
In between, an observer sees the control plane: roughly every four minutes an authenticated Refresh, one ChannelBind per bound peer and one CreatePermission per unbound one, each carrying USERNAME, REALM, NONCE, MESSAGE-INTEGRITY and FINGERPRINT. During any fifteen-second interval in which the client sent nothing, it sends a bare 28-byte STUN Binding indication to keep the NAT mapping alive; it is never answered.
Payload size. A relayed endpoint MUST bound the UDP payload it sends and the payload it permits a peer to send, because the encapsulation is added after QUIC has chosen a packet size. Sizing against a 1500-byte path and the worst-case indication gives
1500 - 28 (IPv4 + UDP) - 47 (worst-case Send indication) = 1425
so a relayed endpoint advertises a maximum UDP payload of 1425 bytes and sends at that size from the start. It MUST advertise this even when a channel is bound, because a Data indication can still arrive at any time. A relayed endpoint SHOULD disable path MTU discovery: the relay may fragment, so probe results are not evidence about the path, and one datagram per message means segmentation offload buys nothing.
15Device discovery
Discovery locates peers and is outside the secured session. It carries no content, and nothing it reports is trusted: the transport re-establishes identity independently, by §5.2 or by §13.1.
15.1 Mechanisms
| Mechanism | Carries | Names an endpoint |
|---|---|---|
DNS-SD over multicast DNS, service type _anyshare._tcp | Device identifier, display name, identity public key, transfer port, platform, transport capabilities | Yes - the SRV port |
| Bluetooth Low Energy, one readable characteristic | Device identifier, display name, identity public key, transport capabilities | No |
| Rendezvous service | Presence subject to the user's visibility setting, device identifier, display name, identity public key, listening addresses, transport capabilities | Yes - the addresses it reports |
The service type is _anyshare._tcp on both transports, and the single port a record names is the port number of both listeners: TCP for the byte stream, UDP for QUIC (§4.4). There is no separate _udp record.
15.2 Transport capabilities
A transport capability is an advertisement that the endpoint named by that same record accepts a given transport. The only one defined is quic, and it is encoded per mechanism:
| Mechanism | Encoding | Absent means |
|---|---|---|
| Multicast DNS | The TXT key quic with the one-character value 1. The pair is omitted rather than sent as 0, because the record is size-constrained. | Not capable |
| Bluetooth Low Energy | The JSON member "quic": "1" in the identity payload, omitted when not capable. | Not capable |
| Rendezvous service | The JSON member "quic" with a boolean value, always present. | Not capable |
An absent, empty or unrecognised value MUSTbe read as “not capable” and MUST NOT be an error: a peer running a build that predates the capability must still be discovered.
A record MUST NOT carry the capability unless the endpoint it names serves that transport. A record that names no endpoint - the Bluetooth one - makes the weaker claim that the device speaks the transport somewhere, which is why a reader that pools addresses from several records MUST treat the peer as capable only when everyrecord it currently holds for that peer says so. Any other rule lets one record’s claim vouch for another record’s address.
An implementation MAY ignore capabilities entirely, in which case it transfers as specified in §5 onwards; peers that omit the advertisement are transferred to that way in any case.
15.3 Bluetooth
The Bluetooth characteristic is readable without pairing, bonding or link-layer encryption, and implementations MUST NOT require any of them: the value is public information, and possession of the advertised identity is proven only by the transport handshake. Content is never transferred over Bluetooth.
16Observable metadata
A relay (§14.5) is not trusted, and forwards ciphertext it holds no key for. It cannot read, substitute or silently modify content: substitution is detected by §5.2 or §13.1, and modification by the authentication tag of §11.2 or by QUIC packet protection.
The protocol does not conceal that a transfer is occurring. An observer on the path - a relay, or any intermediary on a direct path - learns the following. What it learns differs between the two transports, and the difference matters:
| Observable | Application-layer | QUIC |
|---|---|---|
| That two endpoints are exchanging data, and approximately how much | Yes | Yes |
| Timing, duration and network addresses | Yes | Yes |
| Both peers' identity public keys | Yes - the key exchange of §5 is unencrypted | No - the credentials travel in TLS 1.3 Certificate messages, which are encrypted under handshake keys |
| File names, sizes and the batch manifest | Yes - control messages are unencrypted within the session (§10) | No |
| Number of files, and where each begins and ends | Yes, from the message framing | No - QUIC encrypts its own frame layer, so stream identifiers and boundaries are not visible. Only packet count, sizes and timing are. |
What is protected on both transports: file content; identity private keys, session keys and PINs; and any content transferred in a prior or subsequent session.
QUIC’s advantage here is narrower than the table alone suggests. Discovery publishes a device’s identity public key in the clear (§15.1), so an observer on the same link learns it whether or not the transfer conceals it, and an observer who already holds it can still recognise the device by its address. What QUIC withholds is the pairing: a passive observer elsewhere on the path cannot tell which two identities are transferring, nor what they are transferring.
A device that uses a relay additionally exposes its device identifier and its credential’s expiry to whoever can watch the client-to-relay path, because the TURN username carries both in the clear (§14.5).
17Security considerations
Both transports are designed to provide the following against an attacker who controls the network and any intermediary:
- Confidentiality and integrity of transferred content.
- Mutual authentication of both endpoints to long-term identity keys, re-established every session - by the signed transcript of §5 on the application-layer transport, and by TLS 1.3 to the same keys on QUIC.
- Forward secrecy: compromise of an identity key does not decrypt previously captured sessions.
- Downgrade resistance. On the application-layer transport the cipher negotiation is covered by the signed transcript; on QUIC there is nothing to downgrade to, because one TLS version, one certificate type, one signature scheme and one ALPN protocol are offered and no fallback exists.
- Resistance to replay, reflection and identity misbinding, through the transcript and role construction of §5, and on QUIC through TLS 1.3’s own transcript together with the rules of §13.3.
- Refusal to transfer over a transport the peer did not advertise. A QUIC selection never silently becomes an application-layer transfer (§4.1), so a failure on the stronger transport is visible rather than papered over.
It does not provide, and implementations MUST NOT be presented as providing:
- Anonymity or traffic-analysis resistance. See §16.
- Protection against replay by the authenticated peer itself on a 0-RTT flight. §13.3.1 bounds what such a replay can do - it can redeliver an offer, nothing more - and the deduplication window makes even that inert for as long as the window remembers it.
- Any guarantee from a candidate address. A candidate is an unauthenticated claim (§14), and an implementation that dials one without the filtering of §14.3 can be made to send packets at a third party.
- Protection against a compromised endpoint. An attacker with access to a device’s identity key is indistinguishable from that device.
- Authenticated first contact between parties with no prior relationship. Initial pairing depends on the PIN and on the user comparing the short authentication string, subject to §8.
- Protection against a user who authorises a transfer from an attacker.
Continuity of identity is the responsibility of the implementation: a peer’s identity key SHOULD be recorded on first acceptance, and a subsequent session presenting a different key for the same peer MUST NOT inherit the earlier trust.
18Version history
Two numbers move independently (§13.2.1). Neither is negotiated, and no version of either is interoperable with another; see §9.3 and §13.2.
18.1 Application protocol
| Version | Change |
|---|---|
2 | Authenticated key exchange with identity binding. |
3 | PIN folded into key derivation. |
4 | Optional per-chunk compression. |
5 | Multiple files per transfer, with a batch manifest. |
6 | Acknowledgement cadence changed from a chunk count to a byte interval. |
7 | Content digest removed from the offer; resumption became verified against a prefix digest. |
8 | Cipher suite negotiation, covered by the signed transcript. |
9 | PIN removed from the key schedule; the PIN-bound flag removed from key exchange. Proving the PIN moved to the pairing exchange of §8. |
18.2 QUIC framing
| Revision | Change |
|---|---|
1 | First numbered revision. Earlier development builds carried the same stream structure with no header on control frames at all, and are refused by the magic check of §13.2. |
18.3 This document
The QUIC transport arrived without a change to the application protocol, so version 9 is unchanged by it: no application-layer frame moved, and the framing carried no version at all until revision 1 gave it one. The revision of this page dated 24 August 2026 added §4, §13 and §14, and restated §1, §3, §15, §16 and §17 accordingly. An earlier revision of this page declared QUIC out of scope; that scoping is withdrawn, and this document again specifies everything two AnyShare devices say to each other.
The same revision then rewrote §4 for a change in transport selection, again without touching the wire: WebRTC data channels are no longer used between native peers at all. A QUIC-capable peer is reached over QUIC by one of the three routes of §4.3 - directly, punched, or through a relay - and a data channel is now the transport of exactly one kind of peer, a browser (§4.2), in both directions. §14 accordingly describes traffic a deployed device produces; the earlier note saying a sender did not yet select that path is withdrawn because it is no longer true, and no interoperability claim rests on it. An implementation of an earlier revision remains interoperable: nothing it puts on the wire has changed, only which transport a sender chooses to open.