| Name | Modified | Size | Downloads / Week |
|---|---|---|---|
| Parent folder | |||
| README.md | 2026-08-07 | 6.2 kB | |
| v5.9.0 -- A smaller connection exchange source code.tar.gz | 2026-08-07 | 29.9 MB | |
| v5.9.0 -- A smaller connection exchange source code.zip | 2026-08-07 | 30.0 MB | |
| Totals: 3 Items | 59.8 MB | 0 | |
Connecting two people used to mean handing over the entire cryptographic state of the session. This release cuts the out-of-band exchange down to the minimum that is actually needed to open a connection, and moves everything else onto the connection itself.
The invitation went from ~2,300 characters across four animated QR frames to 124 bytes in a single frame. Measured on the live site, not on fixtures.
What the exchange was
The old SB1:bin: payload was CBOR + zlib + base64url of the whole offer package:
the full SDP, both public keys in SPKI DER, both signatures, a 64-byte session
salt, a 48-byte challenge with nonce, four timestamps, five copies of the version
string, and several fingerprints and identifiers that the receiver could have
derived for itself. On a real Chrome offer that is 2,780 bytes of CBOR — 2,255
characters after compression and base64. QR version 40 tops out at 2,331 bytes, so
with a TURN server configured the answer did not fit in a QR code at all, and
the app fell back to slicing the payload into four frames and animating them.
What it is now
The invitation carries only what brings up DTLS:
| ICE credentials | ufrag + pwd, verbatim |
| DTLS fingerprint | SHA-256 of the certificate, 32 raw bytes |
| Candidates | 1 byte of type + address + port each; foundation and priority re-derived per RFC 8445 |
| Expiry | 3 bytes, minute granularity |
| Commitment | 16 bytes, SHA-256 of the key material |
That is 98–149 bytes depending on browser and network path — QR version 6 to 8, one frame. The SDP is rebuilt from a template by a strict serializer rather than transmitted; only the fields above vary between connections.
Key material — ECDH key, ECDSA identity key — is sent as the first frame on the data channel once it opens.
Why this is not just a smaller QR code
The reduction is what made several things possible that were not before:
- The commitment is checked before the key blob is parsed. Substituted key material is rejected by the code path, not by two humans comparing digits. Previously the safety code was the only thing standing between you and a key substitution.
- The safety code now covers a transcript of both invitations byte for byte plus both key blobs, length-prefixed. It used to cover only the two DTLS fingerprints. Nothing exchanged anywhere in the handshake can now be altered without changing the digits.
- The HKDF salt is derived from that transcript instead of being transmitted, which binds every session key to both fingerprints and every ICE candidate, and removes a value either side could previously choose alone.
authProofis replaced by one ECDSA signature over the transcript. The old construction echoed a challenge and nonce back across seven fields; the signature proves the same possession and binds the whole handshake at once.- Less material is exposed before anyone is authenticated. Keys no longer sit in a blob that gets pasted between apps, photographed, or left in a clipboard.
- One frame is scanned in person. A four-frame animated code pushes people toward pasting the invitation through a chat app — out of the only channel the security model actually assumes.
Format and decoder
SB2: + base64url, or raw bytes in QR byte mode. The version byte is first and a
mismatch is an error, never an attempt to parse a different shape. The decoder
treats its input as hostile: fixed offsets and explicit lengths, deny-by-default
on every reserved value and on unknown TLV extension types, trailing bytes
rejected, ICE credentials alphabet-checked so a CR/LF cannot reach the SDP
serializer, and a payload ceiling applied before any structure is walked.
Compression is deliberately gone. On this payload DEFLATE adds 2–11 bytes, and removing it removes the decompression-bomb surface with it.
Candidate pruning keeps coverage before count: one candidate survives per (address family, candidate type, transport) before any surplus is admitted, so an IPv6-only or UDP-blocked path cannot be pruned away by a v4-first sort.
Compatibility and rollback
SB1: invitations are still read, and the animated multi-frame QR path stays for
them. Formats are separated by first byte (0x02 vs ASCII S) and text prefix,
not by heuristics.
A client older than 5.9.0 cannot read an SBQ2 invitation. From 5.9.0 on, an
unrecognised SB<n>: family reports "This invitation was created by a newer
version of SecureBit. Please update the app to connect." Versions 5.8.1 and
earlier predate that check and show a JSON parse error. Both ends must be on
5.9.0+.
Rollback is one value: EnhancedSecureWebRTCManager.SBQ2_SEND_ENABLED = false and
redeploy. It governs only what is emitted — reception of both formats is
unconditional — so the two ends never have to be rolled back together. Inside an
established session the format is latched and cannot be downgraded at all;
every failure closes the connection instead.
Verification
Real browsers, real network, no stubs:
- 12/12 full cycles across {Chrome, Firefox}² and three network profiles (STUN, STUN+TURN, relay-only) — through to matching safety codes, an active Double Ratchet, a 64-byte transcript-derived salt, and a decrypted message on the far side.
- 48/48 connections across the same matrix at the transport layer.
- Live production: offer 170 characters / 1 QR frame, answer 152 characters / 1 frame, both peers showing the same safety code.
Two defects were found this way and are fixed here. The rebuilt SDP omitted
raddr/rport on srflx and relay candidates — mandatory per RFC 8839 §5.1,
tolerated by Chrome, and cause for Firefox to drop the candidate: relay-only
connections to Firefox failed 0/8 where the browser's own SDP succeeded 8/8. And
_initializeRatchet silently fell back to static keys on this path, because it
looks for a dr capability field that SBQ2 has no reason to carry.
Full wire format, decoder rules and the measurements behind every number in this
note: doc/DESCRIPTOR-SBQ2.md.