class CoinFlipProtocol { constructor(mesh) { this.mesh = mesh; this.state = 'IDLE'; this.secret = null; this.commits = new Map(); this.reveals = new Map(); this.result = null; this.totalPeers = 0; this.onstatechange = null; this.onprogress = null; this.oncomplete = null; this.onerror = null; } async start() { if (this.state !== 'IDLE') return; this.state = 'STARTING'; this.totalPeers = this.mesh.getCount() + 1; this.secret = await generateSecret(); const myCommit = await commit(this.secret); this.commits.set(this.mesh.myPeerId, myCommit); if (this.onprogress) this.onprogress('commit', 1, this.totalPeers); if (this.onstatechange) this.onstatechange('COMMITTING'); this.mesh.broadcast({ type: 'protocol', phase: 'commit', data: bufToBase64(myCommit) }); this.state = 'WAITING_FOR_COMMITS'; if (this.onstatechange) this.onstatechange('WAITING_FOR_COMMITS'); this._checkCommits(); } handleMessage(peerId, msg) { if (msg.type !== 'protocol') return; if (msg.phase === 'commit' && (this.state === 'WAITING_FOR_COMMITS' || this.state === 'COMMITTING' || this.state === 'STARTING')) { if (!this.commits.has(peerId)) { this.commits.set(peerId, base64ToBuf(msg.data)); if (this.onprogress) this.onprogress('commit', this.commits.size, this.totalPeers); this._checkCommits(); } } if (msg.phase === 'reveal' && (this.state === 'WAITING_FOR_REVEALS' || this.state === 'REVEALING')) { if (!this.reveals.has(peerId)) { this.reveals.set(peerId, base64ToBuf(msg.data)); if (this.onprogress) this.onprogress('reveal', this.reveals.size, this.totalPeers); this._checkReveals(); } } } _checkCommits() { if (this.state !== 'WAITING_FOR_COMMITS') return; if (this.commits.size >= this.totalPeers) { this._allCommitsReceived(); } } async _allCommitsReceived() { this.state = 'REVEALING'; if (this.onstatechange) this.onstatechange('REVEALING'); this.mesh.broadcast({ type: 'protocol', phase: 'reveal', data: bufToBase64(this.secret) }); this.reveals.set(this.mesh.myPeerId, this.secret); if (this.onprogress) this.onprogress('reveal', 1, this.totalPeers); this.state = 'WAITING_FOR_REVEALS'; if (this.onstatechange) this.onstatechange('WAITING_FOR_REVEALS'); this._checkReveals(); } _checkReveals() { if (this.state !== 'WAITING_FOR_REVEALS') return; if (this.reveals.size >= this.totalPeers) { this._allRevealsReceived(); } } async _allRevealsReceived() { this.state = 'VERIFYING'; if (this.onstatechange) this.onstatechange('VERIFYING'); for (const [peerId, secret] of this.reveals) { if (peerId === this.mesh.myPeerId) continue; const commitment = this.commits.get(peerId); if (!commitment) { if (this.onerror) this.onerror(`Fehler: Kein Commit von ${peerId}`); return; } const expected = await commit(secret); if (!arraysEqual(expected, commitment)) { if (this.onerror) this.onerror(`Betrug erkannt! ${peerId} hat gefälscht.`); return; } } this.state = 'COMPLETE'; this.result = combine(Array.from(this.reveals.values())); if (this.onstatechange) this.onstatechange('COMPLETE'); if (this.oncomplete) this.oncomplete(this.result); } reset() { this.state = 'IDLE'; this.secret = null; this.commits.clear(); this.reveals.clear(); this.result = null; this.totalPeers = 0; } }