Download Latest Version v6.4.0 -- Now it reads right to left source code.zip (31.5 MB)
Email in envelope

Get an email when there's a new version of SecureBit.chat - P2P Encrypted Messenger

Home / v5.9.0
Name Modified Size InfoDownloads / 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.
  • authProof is 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.

Source: README.md, updated 2026-08-07