Если в группу входит 3 человек то скрипт не может дешифровать сообщение. проблема вероятно в групповом ключе не верным<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Group Chat with End-to-End Encryption</title>
</head>
<body>
    <h1>Group Chat with End-to-End Encryption</h1>
    <div>
        <input type="text" id="roomName" placeholder="Enter Room Name">
        <button id="joinBtn">Join Room</button>
    </div>
    <div id="chat" style="display:none;">
        <div id="messages" style="height: 300px; overflow-y: scroll; border: 1px solid #ccc; padding: 5px;"></div>
        <input type="text" id="messageInput" placeholder="Enter message">
        <button id="sendBtn">Send</button>
    </div>

    <script>
        // Cryptography functions
        let keyPair;
        let sharedGroupKey;
        let peerPublicKeys = {};
        let isGroupKeyReady = false;
        let groupKeyVersion = 0;
        let bufferedMessages = [];
        let expectedPeers = new Set();
        let localId = null;
        let ws;

        async function generateKeyPair() {
            return window.crypto.subtle.generateKey({
                name: "ECDH",
                namedCurve: "P-256"
            }, true, ["deriveKey", "deriveBits"]);
        }

        async function exportPublicKey(key) {
            const exported = await window.crypto.subtle.exportKey("raw", key);
            return btoa(String.fromCharCode(...new Uint8Array(exported)));
        }

        async function importPublicKey(keyData) {
            const binaryDer = Uint8Array.from(atob(keyData), c => c.charCodeAt(0));
            return window.crypto.subtle.importKey(
                "raw",
                binaryDer,
                { name: "ECDH", namedCurve: "P-256" },
                true,
                []
            );
        }

        async function deriveSharedSecret(privateKey, publicKey) {
            return window.crypto.subtle.deriveBits(
                {
                    name: "ECDH",
                    public: publicKey
                },
                privateKey,
                256    // 256 bits
            );
        }

        async function deriveGroupKey() {
            isGroupKeyReady = false; // Disable message sending during key derivation

            // Wait until we have public keys for all expected peers
            while (Object.keys(peerPublicKeys).length < expectedPeers.size) {
                await new Promise(resolve => setTimeout(resolve, 100));
            }

            // Sort peer IDs to ensure consistent ordering
            let peerIds = Array.from(expectedPeers).sort();

            // Compute groupKeyVersion as a hash of the sorted peer IDs
            const encoder = new TextEncoder();
            const peerIdsString = peerIds.join(',');
            const peerIdsBuffer = encoder.encode(peerIdsString);
            const versionHashBuffer = await window.crypto.subtle.digest('SHA-256', peerIdsBuffer);
            const versionArray = new Uint8Array(versionHashBuffer);
            // Convert the hash to a hex string
            groupKeyVersion = Array.from(versionArray).map(b => b.toString(16).padStart(2, '0')).join('');

            console.log(`Group Key Version: ${groupKeyVersion}`);

            // Now derive shared secrets with all peers and combine them
            let sharedSecrets = [];
            for (let peerId of peerIds) {
                if (peerId === localId) continue; // Skip self
                let publicKey = peerPublicKeys[peerId];
                let sharedSecret = await deriveSharedSecret(keyPair.privateKey, publicKey);
                sharedSecrets.push(new Uint8Array(sharedSecret));
            }

            // Concatenate shared secrets
            let totalLength = sharedSecrets.reduce((acc, curr) => acc + curr.length, 0);
            let concatenatedSecrets = new Uint8Array(totalLength);
            let offset = 0;
            for (let secret of sharedSecrets) {
                concatenatedSecrets.set(secret, offset);
                offset += secret.length;
            }

            // Hash the concatenated secrets to derive the group key
            const hashBuffer = await window.crypto.subtle.digest('SHA-256', concatenatedSecrets);
            sharedGroupKey = await window.crypto.subtle.importKey(
                'raw',
                hashBuffer,
                { name: 'AES-GCM' },
                true,
                ['encrypt', 'decrypt']
            );

            // Optionally, log the group key hash for debugging
            const keyBuffer = await window.crypto.subtle.exportKey('raw', sharedGroupKey);
            const keyHashBuffer = await window.crypto.subtle.digest('SHA-256', keyBuffer);
            const keyHashArray = new Uint8Array(keyHashBuffer);
            const keyHashHex = Array.from(keyHashArray).map(b => b.toString(16).padStart(2, '0')).join('');
            console.log(`Derived group key hash: ${keyHashHex}`);

            isGroupKeyReady = true; // Re-enable message sending

            // Process any buffered messages
            processBufferedMessages();
        }

        async function encryptMessage(message) {
            const encoder = new TextEncoder();
            const encodedMessage = encoder.encode(message);
            const iv = window.crypto.getRandomValues(new Uint8Array(12));
            const ciphertext = await window.crypto.subtle.encrypt(
                { name: 'AES-GCM', iv: iv },
                sharedGroupKey,
                encodedMessage
            );
            return {
                iv: btoa(String.fromCharCode(...iv)),
                ciphertext: btoa(String.fromCharCode(...new Uint8Array(ciphertext))),
                version: groupKeyVersion // Include deterministic version
            };
        }

        async function decryptMessage(encryptedMessage) {
            try {
                const { iv: ivBase64, ciphertext: ciphertextBase64 } = encryptedMessage;
                const iv = Uint8Array.from(atob(ivBase64), c => c.charCodeAt(0));
                const ciphertext = Uint8Array.from(atob(ciphertextBase64), c => c.charCodeAt(0));
                const decrypted = await window.crypto.subtle.decrypt(
                    { name: 'AES-GCM', iv: iv },
                    sharedGroupKey,
                    ciphertext
                );
                const decoder = new TextDecoder();
                return decoder.decode(decrypted);
            } catch (error) {
                console.error("Decryption failed:", error);
                throw error; // Re-throw after logging
            }
        }

        // WebRTC and UI code
        let peerConnections = {};
        let dataChannels = {};

        document.getElementById('joinBtn').addEventListener('click', async () => {
            let room = document.getElementById('roomName').value.trim();
            if (room === "") {
                alert("Please enter a room name.");
                return;
            }
            await joinRoom(room);
            document.getElementById('chat').style.display = 'block';
        });

        document.getElementById('sendBtn').addEventListener('click', async () => {
            if (!isGroupKeyReady) {
                alert("Group key is not ready yet. Please wait.");
                return;
            }
            let message = document.getElementById('messageInput').value;

            if (message.trim() === "") return;
            const encryptedMessage = await encryptMessage(message);
            sendMessageToPeers(JSON.stringify(encryptedMessage));
            addMessageToChat("Me", message);
            document.getElementById('messageInput').value = '';
        });

        async function joinRoom(roomName) {
            keyPair = await generateKeyPair();
            const publicKeyString = await exportPublicKey(keyPair.publicKey);

            ws = new WebSocket(`wss://p2pchat.fa-n.ru/ws/${roomName}`);
            ws.onopen = () => {
                // Send own public key to server
                ws.send(JSON.stringify({
                    type: 'public-key',
                    key: publicKeyString
                }));
            };
            ws.onmessage = async (event) => {
                let data = JSON.parse(event.data);
                if (data.type === 'init') {
                    localId = data.id;
                    let peers = data.peers;
                    // Import public keys of existing peers
                    peerPublicKeys = {};
                    expectedPeers = new Set([...Object.keys(peers), localId]);
                    for (let peerId in peers) {
                        let publicKey = await importPublicKey(peers[peerId]);
                        peerPublicKeys[peerId] = publicKey;
                    }
                    // Derive group key
                    await deriveGroupKey();
                    // Create peer connections
                    for (let peerId of Object.keys(peers)) {
                        if (peerId !== localId) {
                            createPeerConnection(peerId); // No longer pass isInitiator
                        }
                    }
                } else if (data.type === 'new-peer') {
                    let peerId = data.id;
                    let publicKeyString = data.public_key;
                    expectedPeers.add(peerId);
                    let publicKey = await importPublicKey(publicKeyString);
                    peerPublicKeys[peerId] = publicKey;
                    // Derive new group key
                    await deriveGroupKey();
                    // Then create peer connection
                    createPeerConnection(peerId);
                } else if (data.type === 'signal') {
                    let from = data.from;
                    let signalData = data.data;
                    await handleSignal(signalData, from);
                } else if (data.type === 'peer-left') {
                    let peerId = data.id;
                    closeConnection(peerId);
                    delete peerPublicKeys[peerId];
                    expectedPeers.delete(peerId);
                    // Derive new group key
                    await deriveGroupKey();
                }

            };
            ws.onclose = () => {
                console.log('WebSocket connection closed.');
            };
            ws.onerror = (err) => {
                console.error('WebSocket error:', err);
            };
        }

        async function handleSignal(signalData, from) {
            let pc = peerConnections[from];
            if (!pc) {
                pc = createPeerConnection(from);
            }
            if (signalData.sdp) {
                await pc.setRemoteDescription(new RTCSessionDescription(signalData.sdp));
                if (pc.remoteDescription.type === 'offer') {
                    let answer = await pc.createAnswer();
                    await pc.setLocalDescription(answer);
                    sendSignal({
                        sdp: pc.localDescription
                    }, from);
                }
            } else if (signalData.candidate) {
                await pc.addIceCandidate(new RTCIceCandidate(signalData.candidate));
            }
        }

        function createPeerConnection(peerId) {
            const isInitiator = localId > peerId; // Initiator if localId is greater
            let pc = new RTCPeerConnection({
                iceServers: [{
                    urls: 'stun:stun.l.google.com:19302'
                }]
            });

            pc.onicecandidate = (event) => {
                if (event.candidate) {
                    sendSignal({
                        candidate: event.candidate
                    }, peerId);
                }
            };

            pc.ondatachannel = (event) => {
                let channel = event.channel;
                setupDataChannel(channel, peerId);
            };

            if (isInitiator) {
                let channel = pc.createDataChannel('chat');
                setupDataChannel(channel, peerId);
                pc.createOffer().then(offer => {
                    pc.setLocalDescription(offer);
                    sendSignal({
                        sdp: offer
                    }, peerId);
                });
            }

            peerConnections[peerId] = pc;
            return pc;
        }

        function setupDataChannel(channel, peerId) {
            channel.onopen = () => {
                console.log('Data channel opened with', peerId);
            };
            channel.onmessage = async (event) => {
                let encryptedMessage = JSON.parse(event.data);

                if (encryptedMessage.version !== groupKeyVersion) {
                    console.warn("Received message with outdated group key version.");
                    // Optionally buffer or discard the message
                    return;
                }

                let processMessage = async () => {
                    if (!isGroupKeyReady) {
                        // Buffer the message until the group key is ready
                        bufferedMessages.push({ encryptedMessage, peerId });
                        return;
                    }
                    try {
                        const message = await decryptMessage(encryptedMessage);
                        addMessageToChat(`Peer ${peerId.substring(0, 8)}`, message);
                    } catch (error) {
                        console.error("Decryption failed:", error);
                    }
                };

                processMessage();
            };
            dataChannels[peerId] = channel;
        }

        function sendSignal(data, toId) {
            ws.send(JSON.stringify({
                type: 'signal',
                data: data,
                to: toId
            }));
        }

        function sendMessageToPeers(message) {
            for (let peerId in dataChannels) {
                let channel = dataChannels[peerId];
                if (channel.readyState === 'open') {
                    channel.send(message);
                }
            }
        }

        function addMessageToChat(sender, message) {
            let messagesDiv = document.getElementById('messages');
            let messageElem = document.createElement('div');
            messageElem.textContent = `${sender}: ${message}`;
            messagesDiv.appendChild(messageElem);
            messagesDiv.scrollTop = messagesDiv.scrollHeight;
        }

        function closeConnection(peerId) {
            console.log(`Peer ${peerId} has left.`);
            if (dataChannels[peerId]) {
                dataChannels[peerId].close();
                delete dataChannels[peerId];
            }
            if (peerConnections[peerId]) {
                peerConnections[peerId].close();
                delete peerConnections[peerId];
            }
        }

        function processBufferedMessages() {
            bufferedMessages.forEach(async ({ encryptedMessage, peerId }) => {
                try {
                    const message = await decryptMessage(encryptedMessage);
                    addMessageToChat(`Peer ${peerId.substring(0, 8)}`, message);
                } catch (error) {
                    console.error("Decryption failed for buffered message:", error);
                }
            });
            bufferedMessages = [];
        }

    </script>
</body>
</html>