const verifyTimestamps = (commitment, selection, reveal) => { return ( commitment.gameTimestamp < selection.selectionTimestamp && selection.selectionTimestamp < reveal.gameTimestamp ) }; const verifyGameNonce = (selection, reveal) => { return ( commitment.gameNonce === selection.gameNonce && selection.gameNonce === reveal.gameNonce ) } const verifyGameNonceSignature = (bitcore, verifyMessageSignatureRsv, ECPair, Buffer, commitment, selection) => { if (!commitment.gameNonce === selection.gameNonce) return const gameNonceHash = bitcore.crypto.Hash.sha256(Buffer.from(commitment.gameNonce)).toString('hex') const message = new bitcore.Message(gameNonceHash); var hash = message.magicHash(); // Using Bitcore for message hashing and verification try { var signature = bitcore.crypto.Signature.fromCompact( Buffer.from(selection.signedGameNonce, "base64") ); // recover the public key var ecdsa = new bitcore.crypto.ECDSA(); ecdsa.hashbuf = hash; ecdsa.sig = signature; const pubkeyInSig = ecdsa.toPublicKey(); const pubkeyInSigString = new bitcore.PublicKey( Object.assign({}, pubkeyInSig.toObject(), { compressed: true }) ).toString(); if (pubkeyInSigString != selection.userPublicKey) { return false; } return bitcore.crypto.ECDSA.verify(hash, signature, pubkeyInSig); } catch (e) { const publicKeyBuffer = Buffer.from(selection.userPublicKey, 'hex'); const keyPair = ECPair.fromPublicKey(publicKeyBuffer); const signatureBuffer = Buffer.from(selection.signedGameNonce, "base64"); if (signatureBuffer.length == 65) { const rawSignature = new Uint8Array(signatureBuffer).slice(1); const rawSignatureBuffer = Buffer.from(rawSignature); const isVerified = keyPair.verify(hash, rawSignatureBuffer); return isVerified } else if (signatureBuffer.length == 97) { const verified = verifyMessageSignatureRsv({ message: gameNonceHash, publicKey: selection.userPublicKey, signature: selection.signedGameNonce }); return verified } } } const verifyCommitmentIntegrity = (bitcore, Buffer, commitment, reveal) => { const reconstuctedCommitment = bitcore.crypto.Hash.sha256(Buffer.from(reveal.vrn + reveal.gameNonce + reveal.secretNonce)) return (reconstuctedCommitment.toString('hex') === commitment.commitment) } const verifyOutcomeHashValue = (bitcore, Buffer, selection, reveal) => { const choiceString = selection.choice ? "heads" : "tails" // True is heads, False is Tails const userData = bitcore.crypto.Hash.sha256(Buffer.from(choiceString + selection.gameNonce + selection.amount)) const outcomeHashBuffer = Buffer.from(reveal.vrn + userData.toString('hex')); const outcomeHash = bitcore.crypto.Hash.sha256(outcomeHashBuffer) return Buffer.from(reveal.outcomeHash).toString('hex') === outcomeHash.toString('hex') } const verifyGameOutcome = (reveal) => { const outcome = parseInt(reveal.outcomeHash[reveal.outcomeHash.length - 1], 16) % 2 === 0 ? "heads" : "tails" return outcome === reveal.outcomeString } const checks = { 'Timestamp Verification': verifyTimestamps(commitment, selection, reveal), 'Game Nonce Verification': verifyGameNonce(selection, reveal), 'Game Nonce Signature Verification': verifyGameNonceSignature(bitcore, verifyMessageSignatureRsv, ECPair, Buffer, commitment, selection), 'Commitment Integrity Verification': verifyCommitmentIntegrity(bitcore, Buffer, commitment, reveal), 'Outcome Hash Verification': verifyOutcomeHashValue(bitcore, Buffer, selection, reveal), 'Game Outcome Verification': verifyGameOutcome(reveal) }; let allChecksPass = true; for (const [checkName, result] of Object.entries(checks)) { if (result) { console.log(`✅ - ${checkName}`); } else { console.log(`❌ - ${checkName}`); allChecksPass = false; } } if (allChecksPass) { console.log(`Congratulations, you have carried out a direct stateless verification of a trustlessly random coin flip, which you ${reveal.didWin ? '✨won✨' : 'lost'}`); } else { console.log(`This is mathematically impossible, contact a member of the team for help on how to verify.`); } return allChecksPass