pub fn derive_sas_code(
our_secret: &StaticSecret,
their_public: &PublicKey,
tx_id: &[u8; 16],
our_mlkem_ek: Option<&[u8]>,
their_mlkem_ek: Option<&[u8]>,
) -> Result<SasCode>Expand description
Derive the 7-emoji + 3-group-decimal SAS code from the X25519
shared secret and the agreed-upon tx_id. Both peers compute this
independently and must end up with the same answer for OOB
comparison to succeed.
Matches the MSC 2241 SAS shape (not wire-compatible — see the module
doc): HKDF-SHA256 with tx_id as salt and the SAS info string as info,
expanded to 11 bytes. First 6 bytes → 7 6-bit chunks, rejection-sampled
into 0..49 → emoji indices. Next 5 bytes → 3 13-bit chunks (+ 1000) → 3
four-digit decimal groups.
our_mlkem_ek / their_mlkem_ek are huddle 2.0’s optional post-quantum
capability binding (see the module doc). The binding is gated on the
partner’s key: pass their_mlkem_ek = Some(ek) when we hold the peer’s
pinned ML-KEM encapsulation key, and our_mlkem_ek = Some(ek) for our own
(always available for a 2.0 identity). Both eks are then folded into the HKDF
info as domain-tag || SHA-256(sorted(our_ek, their_ek)), anchoring the
pair’s PQ capability into the verified SAS so a relay can’t silently
downgrade them to classical-only. Pass their_mlkem_ek = None for group
members, pre-1.3 partners, or the classical fallback; that path is
byte-for-byte identical to the 1.x derivation regardless of our_mlkem_ek.
Because the eks are sorted, both peers derive the same code without needing
to agree on an order.