diff --git a/apps/web/src/pages/RoomPage.tsx b/apps/web/src/pages/RoomPage.tsx index 15e580d..cca2ebf 100644 --- a/apps/web/src/pages/RoomPage.tsx +++ b/apps/web/src/pages/RoomPage.tsx @@ -612,17 +612,40 @@ function ResultPanel({ room, me, players, onLeave }: { room: RoomView; me: Publi 방 나가기 {room.lastSeed && ( -
- 공정성 확인 -

시작할 때 공개한 값: {room.seedHash ?? '-'}

-

이번 판의 난수 씨앗: {room.lastSeed}

-

씨앗의 SHA-256이 위 값과 같으면 서버가 게임 도중 난수를 바꾸지 않았다는 뜻이에요.

-
+ )} ); } +/** + * docs/04 §5: the seed's SHA-256 was published when the game started; the seed is revealed at the end. + * The browser recomputes the hash itself so players don't have to trust the server's word. + */ +function FairnessCheck({ seedHash, seed }: { seedHash: string | null; seed: string }) { + const [verdict, setVerdict] = useState(null); + const check = async () => { + const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(seed)); + const hex = [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, '0')).join(''); + setVerdict(hex === seedHash); + }; + return ( +
+ 공정성 확인 +

시작할 때 공개한 값(SHA-256): {seedHash ?? '-'}

+

끝나고 공개한 난수 씨앗: {seed}

+ + {verdict !== null && ( +

+ {verdict ? '✓ 일치해요. 게임 도중 카드 순서나 난수가 바뀌지 않았어요.' : '✗ 일치하지 않아요. 관리자에게 알려 주세요.'} +

+ )} +
+ ); +} + // ---------------------------------------------------------------- chat function ChatButton({ onOpen }: { onOpen: () => void }) { diff --git a/e2e/omok.e2e.ts b/e2e/omok.e2e.ts index ead116f..55bad1c 100644 --- a/e2e/omok.e2e.ts +++ b/e2e/omok.e2e.ts @@ -129,6 +129,10 @@ try { await a.getByText('🎉 이겼어요!').waitFor(); await b.getByText('아쉽게 졌어요').waitFor(); await a.waitForTimeout(500); // let the sheet animation finish + // Fairness check: the browser recomputes SHA-256(seed) and compares with the published hash. + await a.getByText('공정성 확인').click(); + await a.getByRole('button', { name: '내 브라우저에서 직접 확인하기' }).click(); + await a.getByText('✓ 일치해요').waitFor(); await a.screenshot({ path: join(SHOTS, '3-result-pc.png') }); await b.screenshot({ path: join(SHOTS, '4-result-phone.png') }); });