Priority: P3 (Low) · Security audit finding · area: crypto / pairing UX · design discussion, not a defect
sas.FromBinding (go/internal/sas/sas.go, mirrored in web/src/pairing/sas.js) renders the Noise channel binding as a 64-bit safety number (xxxx-xxxx-xxxx-xxxx).
Why this is only "consider", not a bug
The SAS is defense-in-depth that only matters if the pairing token has already leaked. Without the token an attacker can't complete the NNpsk0 handshake at all, so there's no MITM and nothing to compare. The 64-bit comparison is the eyeball check that catches a MITM in the leaked-token case.
The concern
In a leaked-token MITM, the attacker runs two NNpsk0 handshakes (one with each side) and controls their own ephemeral on each leg. To stay invisible they need SAS(binding_client) == SAS(binding_agent). Because they can vary both legs, this is a birthday-style grind at ~2^32 work per side rather than a 2^64 second-preimage. They cannot precompute (both bindings depend on the honest parties' fresh ephemerals), so the grind must happen inside the interactive pairing window — which makes 2^32 impractical today. Still, 64 bits is on the low side versus, e.g., Signal's 60-decimal-digit safety number.
Options
- Document the 64-bit choice + the leaked-token-only threat in
SECURITY.md and close (cheapest; arguably sufficient).
- Widen to 80–96 bits (5–6 groups). Note: this changes the displayed format and the
testdata/ interop vectors (pair-interop.json safety_number), and both Go and JS must move in lockstep (the byte-identical invariant in CLAUDE.md).
No action taken in the audit PRs because (2) is a protocol/UX change worth a maintainer decision.
Filed from a security audit; see also the companion PRs labeled security.
Priority: P3 (Low) · Security audit finding · area: crypto / pairing UX · design discussion, not a defect
sas.FromBinding(go/internal/sas/sas.go, mirrored inweb/src/pairing/sas.js) renders the Noise channel binding as a 64-bit safety number (xxxx-xxxx-xxxx-xxxx).Why this is only "consider", not a bug
The SAS is defense-in-depth that only matters if the pairing token has already leaked. Without the token an attacker can't complete the
NNpsk0handshake at all, so there's no MITM and nothing to compare. The 64-bit comparison is the eyeball check that catches a MITM in the leaked-token case.The concern
In a leaked-token MITM, the attacker runs two
NNpsk0handshakes (one with each side) and controls their own ephemeral on each leg. To stay invisible they needSAS(binding_client) == SAS(binding_agent). Because they can vary both legs, this is a birthday-style grind at ~2^32 work per side rather than a 2^64 second-preimage. They cannot precompute (both bindings depend on the honest parties' fresh ephemerals), so the grind must happen inside the interactive pairing window — which makes 2^32 impractical today. Still, 64 bits is on the low side versus, e.g., Signal's 60-decimal-digit safety number.Options
SECURITY.mdand close (cheapest; arguably sufficient).testdata/interop vectors (pair-interop.jsonsafety_number), and both Go and JS must move in lockstep (the byte-identical invariant inCLAUDE.md).No action taken in the audit PRs because (2) is a protocol/UX change worth a maintainer decision.
Filed from a security audit; see also the companion PRs labeled
security.