Every P2P transfer tool still has a server. Here is why.
Two browsers cannot find each other on their own. Before a WebRTC connection can open, each side has to hand the other a description of itself — the SDP: which network addresses it can be reached at, the fingerprint of the certificate it will encrypt with, and which data channels it wants. That exchange is called signaling, and WebRTC deliberately leaves it undefined. Every browser-based transfer tool solves it the same way: a small server that both browsers connect to, which forwards the two descriptions. Snapdrop, ShareDrop, PairDrop, transfer.zip, ToffeeShare — all of them run one. The file bytes go peer to peer; the introduction does not.
Most also run a second server, a TURN relay, for the pairs whose networks refuse a direct connection. That one does carry the file, encrypted. So "peer to peer" usually means two servers: one that always sees who is talking to whom, and one that sometimes sees the bytes.
Pairchute runs neither. The rest of this page is how, and where the seams show.
The invite is the signaling channel
When you click Create invite, your browser generates its whole session description up front — non-trickle, meaning it waits for ICE address gathering to finish (capped at eight seconds) rather than sending addresses one at a time — and packs it into a single text blob: PC1. followed by the JSON, deflate-compressed and base64url-encoded. That blob rides in the URL fragment of the invite link. You paste the link into whatever channel you already have with the other person: a chat, an email, a note. They click it, their browser generates the matching answer the same way, and they send that one blob back. Two messages, one each way, and the DataChannel opens.
This works because signaling does not need to be fast or automatic; it needs to be complete. A copy-paste channel operated by two humans is a perfectly good signaling channel — slow, but the SDP is only a few kilobytes and it only happens once per session. The fragment matters too: browsers never send the part of a URL after # to the web server, so pairchute.com never receives an invite even though every invite link points at it. The site serves a static page; the page does the rest.
The invite is signed with the sending device’s non-extractable P-256 key, and it carries that public key inside it. Because every invite is first contact, that signature proves the blob arrived unmodified, not who wrote it. Identity is handled after the connection opens, below.
No TURN relay, on purpose, and what that costs
The only third party in the whole system is one STUN lookup — a UDP request to a public server that answers with the address your network presents to the internet. It sees an IP address and nothing else. With that, most pairs of home and office networks can hole-punch a direct UDP path.
Some cannot. When both sides sit behind symmetric NATs — the kind that allocates a fresh external port per destination — hole-punching fails, and every other tool falls back to its TURN relay. Pairchute has no relay to fall back to. It reports the failure and stops. The honest number is roughly one pair in ten across the public internet; on a shared LAN it is effectively zero, because local addresses connect before any of this is needed.
That is the price of the design, and it is paid openly rather than hidden behind a relay that would make "no server sees your file" untrue for exactly the sessions where it would matter most. A short disconnection is not the same as a refused connection: if the path drops and comes back within twenty seconds, the transfer carries on.
Why one connection tops out at a few megabytes per second
A WebRTC DataChannel runs SCTP over DTLS over UDP, and the SCTP is implemented in userspace inside the browser. Chromium’s stack uses a 1191-byte MTU — roughly 1150 bytes of payload per packet, against 1460 for TCP with hardware offload — and classic loss-based congestion control in the RFC 4960 style: additive increase, halve on loss. No CUBIC, no BBR, no pacing beyond a four-packet burst limit, and nothing tunable from JavaScript.
The Mathis relation gives the steady-state throughput of that kind of controller: bandwidth ≈ (MSS ÷ RTT) × 1.22 ÷ √loss. At 1150 bytes and a 30 ms round trip, MSS ÷ RTT is about 38 KB/s, so a single channel delivering 2 MB/s is running at a packet-loss rate near 0.05 % — background noise on any consumer path, and the bottleneck queue between a fast uplink and a slower downlink manufactures more every time the window overshoots. TCP on the same path shrugs; browser SCTP sawtooths at a fraction of the link rate. This is the measured reality behind every "WebRTC file transfer is slow" thread.
Multiple DataChannels on one connection do not help: they share one SCTP association and therefore one congestion window. Unreliable channels with a hand-rolled repair layer do not help either, because they sit inside the same window. The one lever that does help is more associations.
Eight lanes, negotiated in-band
Pairchute opens up to eight RTCPeerConnections and stripes the file across them. Each is its own SCTP association with its own congestion window, so eight of them approach eight times the single-channel rate until the physical link saturates and they begin to share it fairly. It is the same trick download managers used against lossy links in the nineties.
The extra seven cost the user nothing, because they are not signaled by copy-paste. The first connection, once open, is itself a signaling channel: the offers and answers for the additional lanes travel over it as small JSON messages, with trickle ICE, and each lane joins the pool of workers as soon as it comes up. A lane that fails to open is simply not used; with one lane the system degrades to plain single-channel behaviour.
Ordering across lanes is gone, so every binary message carries an eight-byte header — a transfer epoch and a chunk index — and a chunk’s position in the file is just the index times the chunk size. A single shared queue of chunk indexes feeds all the lanes; a lane pulls the next index whenever its own send buffer drains, so faster lanes naturally take more work with no rate arithmetic at all. Over gigabit Ethernet on a LAN this lands near wire speed, about 80 MB/s; across the internet the ceiling is the slower side’s uplink, which no number of lanes lifts.
The disk path: each byte written exactly once
Chunks are 262 136 bytes — Chromium’s 256 KiB maximum SCTP message minus the header — and 256 of them make a 64 MiB block. On the receiving side each chunk is hashed with SHA-256 as it arrives and written straight to its offset in the destination file through the File System Access API. There is no in-memory assembly, no temporary file, no final copy: one writable stream, positional writes, one close. Memory stays flat no matter how large the file is, which is what "no size limit" means here in practice — the limit is the receiver’s drive.
Chunk digests chain into block hashes, and the block hashes into a single root, so the receiver can prove the whole file arrived intact rather than trusting the transport. If a chunk is missing or fails its hash, the receiver asks for exactly that chunk again over the control channel; the file is not restarted.
Browsers without the File System Access API — Firefox, Safari, Brave until its flag is enabled — still receive, but into memory, so they are capped at 512 MB. Sending works everywhere, because reading a file in slices is a decade-old browser feature.
Encryption, and the six-digit code that makes it mean something
The transport is DTLS, which is TLS over UDP, with a fresh certificate generated by each browser for each session. The data channel is therefore end-to-end encrypted by construction, and there is no server that could have been given a key. That much every WebRTC tool can claim.
What the encryption does not do on its own is tell you who is on the other end. If someone intercepted your invite and replaced the answer with their own, the connection would be perfectly encrypted — to them. So once the channel opens, both screens derive a six-digit code from the two device identities and both sessions’ DTLS certificate fingerprints, sorted so each side computes the same value. An attacker in the middle necessarily has different fingerprints on each leg, so the two screens show different codes. Compare them over any channel — the same chat, a phone call, out loud — and a match rules out a man in the middle. The code is displayed, not enforced: whether to compare it is your call for that transfer.
What leaves your machine, in full
Over HTTP: this page and its stylesheet, script and font, all served from pairchute.com itself. Then nothing — no analytics beacon, no cookie, no third-party script, and the Content-Security-Policy on every page would block one if it appeared. You can watch this in the Network panel of any browser during a transfer of any size.
Over UDP: one STUN lookup to learn your public address, then DTLS-encrypted SCTP packets straight to the other machine, on up to eight ports. Visible in chrome://webrtc-internals or about:webrtc, where the selected candidate pair shows the two endpoint addresses and no relay.
Stored on your device between sessions: one keypair, non-extractable, in IndexedDB. No transfer history, no peer list, no partial files.
What the design refuses to do
No store-and-forward: both people are present or the transfer does not happen, because there is nowhere for a file to wait. No persisted resume across sessions: an earlier version checkpointed progress to disk and, on a 130 GB receive, ended up copying more than twice the file’s size; the single-pass design replaced it, so a dropped connection discards the partial file and that file starts over. No relay: covered above. No accounts, no links that work for twenty people, no expiring download pages.
Each of those is a feature some other tool does better, and for those jobs that tool is the right choice. This one exists for the case where the point is that no server is involved — and where "no server" should be checkable rather than promised.
Send a file now
Scroll up and hit Create invite — you get a link to send the other person. They click it, send one reply back, and the direct encrypted pipe opens. Nothing is uploaded; nothing is stored.
Related
See also: sending files without uploading · private, encrypted file transfer — and the Pairchute home page.