Zero Trust Group Chat

Solving the Last Mile of Encrypted Communication with Threshold Cryptography and Air-Gapped Key Distribution
work-single-image

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:

  1. Perfect Forward Secrecy: Past messages should remain secure even if current keys are compromised
  2. Post-Compromise Security: Future messages should be protected even after a temporary breach
  3. 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:

  1. No Single Point of Failure: The complete key never exists after initialization
  2. Compromise Resilience: Up to t-1 compromised members cannot decrypt
  3. Forward Secrecy: Share refresh on membership changes prevents future compromise
  4. Post-Compromise Security: Refreshed shares invalidate compromised old shares
  5. 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.

Author
Houston Haynes
date
February 18, 2022
category
Systems

We want to hear from you!

Contact Us