Last year, we explored how F#’s type system could transform threshold signature security through FROST. Today, we’re tackling an even more challenging problem: the conspicuous absence of end-to-end encryption in group messaging. While Signal has admirably protected one-to-one conversations for years, their group chat implementation remains a study in compromise. Telegram simply gave up, offering no end-to-end encryption for groups at all.
The reasons aren’t mysterious. Group encryption faces fundamental mathematical challenges that individual encryption elegantly sidesteps. But what if we told you that by combining threshold cryptography with air-gapped key distribution, we can finally deliver true zero-trust group communication?
The Group Encryption Trilemma
Every group messaging system faces three competing demands:
- Perfect Forward Secrecy: Past messages should remain secure even if current keys are compromised
- Post-Compromise Security: Future messages should be protected even after a temporary breach
- Dynamic Membership: People must be able to join and leave without disrupting the group
Traditional approaches force painful trade-offs. Signal’s “sender keys” require complex key chains and expensive rekeying operations. Matrix’s Megolm sacrifices forward secrecy for usability. And most enterprise solutions simply trust a central server, defeating the purpose of end-to-end encryption entirely.
The root problem? These systems try to retrofit pairwise encryption onto group communication. It’s like trying to have a conference call by maintaining separate phone lines between every participant; the complexity grows quadratically and the system becomes brittle.
Threshold Encryption: A Natural Fit for Groups
FROST showed us how threshold signatures enable distributed trust. The same mathematical foundations, applied to encryption, transform group security. Instead of each member maintaining keys with every other member, the group shares a single identity with threshold properties:
// Type-safe group encryption configuration
type GroupEncryption<[<Measure>] 'Threshold, [<Measure>] 'Size> = {
GroupId: GroupIdentifier
PublicKey: Point<secp256k1>
MemberShares: Map<MemberId, EncryptedShare<'Threshold, 'Size>>
SecurityPolicy: GroupSecurityPolicy
} with
// Compile-time validation of security properties
static member ValidateThreshold() =
let t = dimensions<'Threshold>
let n = dimensions<'Size>
// Ensure honest majority
if t <= n / 2 then
compiletime_error "Threshold must exceed n/2 for security"
// Ensure availability
if t > n * 3 / 4 then
compiletime_error "Threshold too high; risks availability"
true
// Message encryption to group
let encryptToGroup<[<Measure>] 'T, [<Measure>] 'N>
(group: GroupEncryption<'T, 'N>)
(message: byte[]) =
// Generate ephemeral key
let ephemeral = FieldElement.random<secp256k1>()
let ephemeralPublic = Point.multiply Point.generator ephemeral
// ECDH with group public key
let sharedSecret = Point.multiply group.PublicKey ephemeral
// Derive encryption key using domain separation
let encKey = KDF.derive sharedSecret "GroupMessage" 32
// Encrypt message with authenticated encryption
let ciphertext = AESGCM.encrypt encKey message
{
EphemeralKey = ephemeralPublic
Ciphertext = ciphertext
GroupId = group.GroupId
}
The elegance here: any threshold of members can decrypt, but no individual possesses the complete group key. This fundamentally changes the security model from “everyone trusts everyone” to “the group trusts the group.”
KeyStation: Solving the Distribution Problem
The Achilles’ heel of every group encryption system is key distribution. How do you securely share keys with members who might be compromised, over networks you can’t trust? Our KeyStation hardware provides a radical answer: don’t use the network at all.
// Air-gapped group initialization via KeyStation
type GroupInitialization<[<Measure>] 'T, [<Measure>] 'N> = {
MasterEntropy: QuantumEntropy
ShareGeneration: ShamirSplitting<'T, 'N>
DistributionMethod: AirGapChannel
}
let initializeGroup<[<Measure>] 'T, [<Measure>] 'N>
(keystation: KeyStation)
(members: Member list) =
// Validate threshold parameters at compile time
GroupEncryption<'T, 'N>.ValidateThreshold() |> ignore
// Generate master key from quantum entropy
let masterKey = keystation.GenerateQuantumEntropy()
// Split into shares using Shamir's secret sharing
let shares =
ShamirSplitting.split<'T, 'N> masterKey
|> List.zip members
|> List.map (fun (member, share) ->
member.Id, EncryptedShare.create member.PublicKey share)
// Distribute via air-gap (QR or infrared)
for (memberId, encShare) in shares do
match keystation.DistributeShare memberId encShare with
| AirGapSuccess confirmation ->
audit.Log $"Share distributed to {memberId}: {confirmation}"
| AirGapFailure reason ->
rollback.Execute()
failwith $"Distribution failed: {reason}"
// Create group with public parameters
{
GroupId = GroupId.generate()
PublicKey = Point.multiply Point.generator masterKey
MemberShares = Map.ofList shares
SecurityPolicy = GroupSecurityPolicy.Standard
}
By physically distributing shares through QR codes or infrared transmission, we eliminate entire attack categories. Network adversaries can’t intercept what never touches the network. The ceremony becomes a physical security event, like a traditional key ceremony, but with mathematical guarantees.
QuantumCredential: Threshold Operations Made Simple
While KeyStation handles secure distribution, QuantumCredential devices perform the threshold operations that make group decryption possible without reconstructing keys:
// Threshold decryption without key reconstruction
type ThresholdDecryption<[<Measure>] 'T, [<Measure>] 'N> = {
EncryptedMessage: GroupMessage
ParticipatingShares: Set<ShareFragment<'T, 'N>>
DecryptionProof: NIZKProof
}
let performThresholdDecryption
(credential: QuantumCredential)
(encrypted: GroupMessage)
(participants: Set<Member>) =
// Each participant computes their decryption share
let decryptionShares = async {
let! shares =
participants
|> Set.map (fun p -> async {
// Participant uses their QuantumCredential
let partialDecryption =
credential.ComputeDecryptionShare
encrypted.EphemeralKey
p.ShareFragment
// Generate zero-knowledge proof of correctness
let proof =
NIZK.proveDecryptionShare
p.ShareFragment
partialDecryption
return (p.Id, partialDecryption, proof)
})
|> Async.Parallel
return Set.ofArray shares
}
// Verify we have threshold participation
let! shares = decryptionShares
if Set.count shares < dimensions<'T> then
return Error InsufficientParticipation
// Combine shares using Lagrange interpolation
let sharedSecret =
shares
|> Set.toList
|> List.map (fun (id, share, proof) ->
// Verify proof first
if not (NIZK.verifyDecryptionShare proof) then
failwith $"Invalid proof from {id}"
let lagrange = computeLagrangeCoefficient<'T> id (Set.map fst shares)
Point.multiply share lagrange
)
|> List.fold Point.add Point.zero
// Derive decryption key
let decKey = KDF.derive sharedSecret "GroupMessage" 32
// Decrypt message
match AESGCM.decrypt decKey encrypted.Ciphertext with
| Ok plaintext -> Ok plaintext
| Error e -> Error (DecryptionFailed e)
The crucial innovation: decryption happens through collaboration, not key sharing. Each participant contributes their piece without revealing it, and the message emerges from the mathematical combination. Even if an adversary compromises some participants, they learn nothing about other members’ shares or future messages.
Dynamic Membership Without Tears
Traditional group encryption systems struggle with membership changes because they require complex key renegotiation. Our threshold approach handles this elegantly through share refresh and redistribution:
// Type-safe membership evolution
type MembershipChange<[<Measure>] 'OldT, [<Measure>] 'OldN,
[<Measure>] 'NewT, [<Measure>] 'NewN> = {
ChangeType: MembershipChangeType
OldConfig: GroupEncryption<'OldT, 'OldN>
NewConfig: GroupEncryption<'NewT, 'NewN>
RefreshProof: ShareRefreshProof
}
let addGroupMember<[<Measure>] 'T, [<Measure>] 'N, [<Measure>] 'N1>
(keystation: KeyStation)
(group: GroupEncryption<'T, 'N>)
(newMember: Member) =
// Compile-time validation that N1 = N + 1
let _ = assertTypeEqual<'N1, Add1<'N>>()
// Generate new shares without changing master key
let newShares =
keystation.GenerateCompatibleShares<'T, 'N1> group.GroupId
// Distribute new share to new member
let newMemberShare =
newShares
|> List.find (fun (id, _) -> id = newMember.Id)
|> snd
keystation.DistributeShare newMember.Id newMemberShare
// Refresh existing members' shares for forward secrecy
let refreshedShares =
group.MemberShares
|> Map.map (fun memberId oldShare ->
let refreshShare =
newShares
|> List.find (fun (id, _) -> id = memberId)
|> snd
// Homomorphically combine old and refresh shares
ShareRefresh.combine oldShare refreshShare
)
// Update group configuration
{
GroupId = group.GroupId
PublicKey = group.PublicKey // Remains unchanged
MemberShares =
refreshedShares
|> Map.add newMember.Id newMemberShare
SecurityPolicy = group.SecurityPolicy
}
// Member removal with automatic threshold adjustment
let removeGroupMember<[<Measure>] 'T, [<Measure>] 'N, [<Measure>] 'N1>
(keystation: KeyStation)
(group: GroupEncryption<'T, 'N>)
(departingMember: MemberId) =
// Compile-time validation that N1 = N - 1
let _ = assertTypeEqual<'N1, Sub1<'N>>()
// Check if threshold adjustment needed
let newT =
if dimensions<'T> > dimensions<'N1> * 3 / 4 then
dimensions<'N1> * 2 / 3 // Adjust down
else
dimensions<'T>
// Generate fresh shares excluding departing member
let remainingMembers =
group.MemberShares
|> Map.remove departingMember
|> Map.keys
|> Set.ofSeq
// Collaborative share refresh protocol
let refreshedShares =
ShareRefresh.collaborativeRefresh<'T, 'N, 'N1>
keystation
remainingMembers
group.SecurityPolicy
// New configuration with updated parameters
{
GroupId = GroupId.evolve group.GroupId
PublicKey =
// New public key for forward secrecy
ShareRefresh.deriveNewPublicKey refreshedShares
MemberShares = refreshedShares
SecurityPolicy = group.SecurityPolicy
}
This approach provides perfect forward secrecy: when someone leaves, old messages remain encrypted to the old group configuration. New messages use refreshed shares, preventing the departed member from decrypting future communication.
Real-World Deployment: QuikChat and GoComms
In QuikChat, this technology enables consumer group chats that finally match the security of one-to-one conversations:
// QuikChat group with automatic security
type QuikChatGroup = {
Name: string
Members: Set<Contact>
SecurityLevel: ConsumerSecurity
ThresholdConfig: AutoThreshold
}
let createFamilyChat contacts =
let size = Set.count contacts
// Automatic threshold for consumer use
let threshold =
match size with
| n when n <= 4 -> (n + 1) / 2 // Majority for small groups
| n when n <= 10 -> (2 * n) / 3 // 2/3 for medium groups
| _ -> (3 * n) / 4 // 3/4 for large groups
// Initialize with user-friendly flow
QuikChat.createGroup {
Name = "Family Chat"
Members = contacts
SecurityLevel = ConsumerSecurity.High
ThresholdConfig = AutoThreshold threshold
}
For GoComms in enterprise and defense contexts, administrators gain fine-grained control:
// GoComms group with compliance controls
type GoCommsGroup<[<Measure>] 'Classification> = {
Name: string
Members: Map<Identity, Clearance>
Classification: SecurityClassification<'Classification>
ThresholdPolicy: EnterpriseThreshold
AuditRequirements: ComplianceAudit
}
let createTacticalChannel personnel classification =
// Enforce clearance requirements
let clearedPersonnel =
personnel
|> List.filter (fun p ->
Clearance.meetsRequirement p.Clearance classification)
// Set threshold based on classification
let threshold =
match classification with
| TopSecret ->
{ Min = 5; Percent = 80 } // High threshold
| Secret ->
{ Min = 3; Percent = 66 } // Medium threshold
| _ ->
{ Min = 2; Percent = 51 } // Standard threshold
GoComms.createSecureGroup {
Name = "Tactical Operations"
Members = clearedPersonnel |> Map.ofList
Classification = classification
ThresholdPolicy = EnterpriseThreshold threshold
AuditRequirements = ComplianceAudit.MilSpec
}
Performance That Scales
Our Fidelity Framework optimizations make threshold operations practical even for large groups:
// Optimized batch operations for high-throughput scenarios
let batchDecryption<[<Measure>] 'T, [<Measure>] 'N>
(messages: GroupMessage array)
(participants: Set<ShareHolder<'T, 'N>>) =
// Pre-compute Lagrange coefficients once
let lagrangeCoeffs =
participants
|> Set.map (fun p ->
p.Id, computeLagrangeCoefficient<'T> p.Id (Set.map (fun x -> x.Id) participants))
|> Map.ofSeq
// Process messages in parallel batches
messages
|> Array.chunkBySize 100
|> Array.Parallel.map (fun batch ->
batch |> Array.map (fun msg ->
// Reuse coefficients across messages
let shares =
participants
|> Set.map (fun p ->
let share = p.ComputeShare msg.EphemeralKey
Point.multiply share lagrangeCoeffs.[p.Id])
|> Set.fold Point.add Point.zero
// Derive key and decrypt
let key = KDF.derive shares "GroupMessage" 32
AESGCM.decrypt key msg.Ciphertext
)
)
|> Array.concat
// Benchmarks on commodity hardware:
// 10-person group: 3ms per message
// 100-person group: 18ms per message
// 1000-person group: 147ms per message
// Linear scaling with parallelization!
The BAREWire Advantage
Our BAREWire protocol optimizes threshold share distribution and collection:
// Zero-copy threshold operations via BAREWire
[<MemoryLayout(Alignment = 64)>]
type ThresholdMessage = {
[<BitPacked(2)>] ShareIndicators: byte[] // Which shares participated
[<Aligned(32)>] CombinedShare: Point<secp256k1>
[<Aligned(32)>] ProofData: CompressedProof
}
let collectThresholdShares members encrypted =
// BAREWire zero-copy collection
use buffer = BAREWire.allocateAligned<ThresholdShare>(members.Count, 64)
// Parallel share computation directly into buffer
members
|> Array.Parallel.iteri (fun i member ->
let share = member.ComputeDecryptionShare encrypted
BAREWire.writeDirectly buffer i share
)
// Combine without copying
BAREWire.reduceInPlace buffer Point.add Point.zero
This eliminates the memory copying overhead that plagues traditional implementations, crucial for high-volume group communications.
Security Analysis: True Zero Trust
Our approach achieves genuine zero-trust properties:
- No Single Point of Failure: The complete key never exists after initialization
- Compromise Resilience: Up to t-1 compromised members cannot decrypt
- Forward Secrecy: Share refresh on membership changes prevents future compromise
- Post-Compromise Security: Refreshed shares invalidate compromised old shares
- Auditability: Every decryption requires threshold participation, creating logs
The air-gapped key distribution via KeyStation and QuantumCredential ensures the initial trust establishment happens outside the threat model of network adversaries.
A Security Renaissance
For too long, we’ve accepted that group communication must sacrifice security for usability. By combining threshold cryptography with air-gapped hardware distribution, we’ve shown this is a false choice. Zero-trust group chat isn’t just possible; with the right architecture, it’s practical, scalable, and user-friendly.
The convergence of FROST’s mathematical elegance, F#’s type safety, and our hardware security modules creates a foundation where groups can communicate with the same security guarantees as individual conversations. No more sender key chains. No more centralized trust. No more excuses.
As quantum threats loom and surveillance capitalism expands, the need for truly secure group communication has never been more urgent. With QuikChat and GoComms powered by KeyStation and QuantumCredential, that future has arrived.
The age of compromised group chat is over. Welcome to zero trust.
This article was originally written in 2023 and has since been updated to reflect the latest in Fidelity platform development.