Skip to content

Commit

Permalink
adding wordle with errors
Browse files Browse the repository at this point in the history
  • Loading branch information
poppyseedDev committed Dec 19, 2024
1 parent 9000025 commit 7e30ca2
Show file tree
Hide file tree
Showing 7 changed files with 19,605 additions and 0 deletions.
307 changes: 307 additions & 0 deletions hardhat/contracts/fheWordle/FHEWordle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear

pragma solidity ^0.8.24;

import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "fhevm/lib/TFHE.sol";
import "fhevm/gateway/GatewayCaller.sol";
import "fhevm/config/ZamaFHEVMConfig.sol";
import "fhevm/config/ZamaGatewayConfig.sol";
/**
* @title FHEordle
* @notice This contract implements a fully homomorphic encryption (FHE) version of the classic word game, similar to "Wordle".
* It allows players to submit encrypted guesses and receive feedback on whether their guess is correct.
* The contract is integrated with a secure gateway that handles decryption requests to ensure confidentiality.
* @dev This contract leverages the TFHE library for encryption operations and the MerkleProof library for verifying word sets.
* The game state and logic are managed using various public and private variables.
*/
contract FHEWordle is SepoliaZamaFHEVMConfig, SepoliaZamaGatewayConfig, GatewayCaller, Ownable2Step, Initializable {
// /// Constants
// bytes32 public constant root = 0x918fd5f641d6c8bb0c5e07a42f975969c2575250dc3fb743346d1a3c11728bdd;
// bytes32 public constant rootAllowed = 0xd3e7a12d252dcf5de57a406f0bd646217ec1f340bad869182e5b2bfadd086993;
uint16 public constant wordSetSz = 5757;

// /// Initialization variables
address public playerAddr;
address public relayerAddr;
uint16 public testFlag;

/// Secret Word Variables
euint16 private word1Id;
euint8[5] private word1Letters;
euint32 private word1LettersMask;
uint32 public word1;

/// Player Guess variables
uint8 public nGuesses;
uint32[5] public guessHist;

/// Game state variables
bool public wordSubmitted;
bool public gameStarted;
bool public playerWon;
bool public proofChecked;

// Storage values for callbacks
uint8 public l0;
uint8 public l1;
uint8 public l2;
uint8 public l3;
uint8 public l4;
bytes32[] public storedProof;

uint16 public decryptedWordId;
uint8 public decryptedEqMask;
uint32 public decryptedLetterMask;
//events
event WordSubmitted(address indexed player, uint32 word);
event GuessDecryptionRequested(uint8 guessN, uint256 timestamp);
event PlayerWon(address indexed player);
event GuessDecrypted(uint8 decryptedEqMask, uint32 decryptedLetterMask);
event WinDecryptionRequested(uint8 guessN, uint256 timestamp);
event WordRevealRequested(address indexed player, uint256 timestamp);

constructor() Ownable(msg.sender) {}

function initialize(address _playerAddr, address _relayerAddr, uint16 _testFlag) external initializer {
relayerAddr = _relayerAddr;
playerAddr = _playerAddr;
testFlag = _testFlag;
if (testFlag > 0) {
word1Id = TFHE.asEuint16(testFlag);
} else {
word1Id = TFHE.rem(TFHE.randEuint16(), wordSetSz);
}
TFHE.allowThis(word1Id);
word1LettersMask = TFHE.asEuint32(0);
TFHE.allowThis(word1LettersMask);
for (uint8 i = 0; i < 5; i++) {
guessHist[i] = 0;
}
nGuesses = 0;
wordSubmitted = false;
gameStarted = false;
playerWon = false;
proofChecked = false;
word1 = 0;
}

// function getWord1Id(
// bytes32 publicKey,
// bytes calldata signature
// ) public view virtual onlySignedPublicKey(publicKey, signature) onlyRelayer returns (bytes memory) {
// return TFHE.reencrypt(word1Id, publicKey);
// }

function getWord1Id() public view virtual onlyRelayer returns (euint16) {
return (word1Id);
}

// function submitWord1(einput el0, einput el1, einput el2, einput el3, einput el4, bytes calldata inputProof) public {
// euint8 _l0 = TFHE.asEuint8(el0, inputProof);
// euint8 _l1 = TFHE.asEuint8(el1, inputProof);
// euint8 _l2 = TFHE.asEuint8(el2, inputProof);
// euint8 _l3 = TFHE.asEuint8(el3, inputProof);
// euint8 _l4 = TFHE.asEuint8(el4, inputProof);

// // Call the overloaded submitWord1 with euint8 values
// submitWord1(_l0, _l1, _l2, _l3, _l4);
// }

// function submitWord1(euint8 _l0, euint8 _l1, euint8 _l2, euint8 _l3, euint8 _l4) public onlyRelayer {
// require(!wordSubmitted, "word submitted");
// word1Letters[0] = _l0;
// word1Letters[1] = _l1;
// word1Letters[2] = _l2;
// word1Letters[3] = _l3;
// word1Letters[4] = _l4;

// word1LettersMask = TFHE.or(
// TFHE.shl(TFHE.asEuint32(1), word1Letters[0]),
// TFHE.or(
// TFHE.shl(TFHE.asEuint32(1), word1Letters[1]),
// TFHE.or(
// TFHE.shl(TFHE.asEuint32(1), word1Letters[2]),
// TFHE.or(TFHE.shl(TFHE.asEuint32(1), word1Letters[3]), TFHE.shl(TFHE.asEuint32(1), word1Letters[4]))
// )
// )
// );
// wordSubmitted = true;
// gameStarted = true;
// }

// function guessWord1(uint32 word, bytes32[] calldata proof) public onlyPlayer {
// require(gameStarted, "game not started");
// require(nGuesses < 5, "cannot exceed five guesses!");

// uint8 zeroIndex = 0;
// bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(zeroIndex, word))));
// require(MerkleProof.verify(proof, rootAllowed, leaf), "Invalid word");
// guessHist[nGuesses] = word;
// nGuesses += 1;
// }

// function getEqMask(uint8 guessN) internal returns (euint8) {
// uint32 word = guessHist[guessN];
// uint8 _l0 = uint8((word) % 26);
// uint8 _l1 = uint8((word / 26) % 26);
// uint8 _l2 = uint8((word / 26 / 26) % 26);
// uint8 _l3 = uint8((word / 26 / 26 / 26) % 26);
// uint8 _l4 = uint8((word / 26 / 26 / 26 / 26) % 26);
// euint8 g0 = TFHE.asEuint8(TFHE.eq(word1Letters[0], _l0));
// euint8 g1 = TFHE.asEuint8(TFHE.eq(word1Letters[1], _l1));
// euint8 g2 = TFHE.asEuint8(TFHE.eq(word1Letters[2], _l2));
// euint8 g3 = TFHE.asEuint8(TFHE.eq(word1Letters[3], _l3));
// euint8 g4 = TFHE.asEuint8(TFHE.eq(word1Letters[4], _l4));
// euint8 eqMask = TFHE.or(
// TFHE.shl(g0, 0),
// TFHE.or(TFHE.shl(g1, 1), TFHE.or(TFHE.shl(g2, 2), TFHE.or(TFHE.shl(g3, 3), TFHE.shl(g4, 4))))
// );
// return eqMask;
// }

// function getLetterMaskGuess(uint8 guessN) internal returns (euint32) {
// uint32 word = guessHist[guessN];
// uint32 _l0 = (word) % 26;
// uint32 _l1 = (word / 26) % 26;
// uint32 _l2 = (word / 26 / 26) % 26;
// uint32 _l3 = (word / 26 / 26 / 26) % 26;
// uint32 _l4 = (word / 26 / 26 / 26 / 26) % 26;
// uint32 base = 1;
// uint32 letterMask = (base << _l0) | (base << _l1) | (base << _l2) | (base << _l3) | (base << _l4);
// return TFHE.and(word1LettersMask, TFHE.asEuint32(letterMask));
// }

// function getGuess(uint8 guessN) public onlyPlayer {
// require(guessN < nGuesses, "cannot exceed nGuesses");

// // Get the encrypted values
// euint8 eqMask = getEqMask(guessN);
// euint32 letterMaskGuess = getLetterMaskGuess(guessN);

// // Prepare an array of ciphertexts to decrypt
// uint256[] memory cts = new uint256[](2);
// cts[0] = Gateway.toUint256(eqMask);
// cts[1] = Gateway.toUint256(letterMaskGuess);

// // Emit an event for easier tracking of decryption requests
// emit GuessDecryptionRequested(guessN, block.timestamp);

// // Request decryption via the gateway
// Gateway.requestDecryption(cts, this.callbackGuess.selector, 0, block.timestamp + 100, false);
// }

// function callbackGuess(
// uint256 /*requestID*/,
// uint8 _decryptedEqMask,
// uint32 _decryptedLetterMask
// ) public onlyGateway returns (uint8, uint32) {
// decryptedEqMask = _decryptedEqMask;
// decryptedLetterMask = _decryptedLetterMask;
// emit GuessDecrypted(decryptedEqMask, decryptedLetterMask);
// return (decryptedEqMask, decryptedLetterMask);
// }

// function claimWin(uint8 guessN) public onlyPlayer {
// euint8 fullMask = TFHE.asEuint8(31);
// ebool is_equal = TFHE.eq(fullMask, getEqMask(guessN));
// // Request decryption via the Gateway
// uint256[] memory cts = new uint256[](1);
// cts[0] = Gateway.toUint256(is_equal);
// emit WinDecryptionRequested(guessN, block.timestamp);

// Gateway.requestDecryption(cts, this.callbackClaimWin.selector, 0, block.timestamp + 100, false);
// }

// function callbackClaimWin(uint256 /*requestID*/, bool decryptedComparison) public onlyGateway {
// // Handle the decrypted comparison result
// if (decryptedComparison) {
// playerWon = true;
// }
// }

// function revealWord() public onlyPlayer {
// // Prepare the ciphertext array for the five letters
// uint256[] memory cts = new uint256[](5);

// for (uint8 i = 0; i < 5; i++) {
// cts[i] = Gateway.toUint256(word1Letters[i]);
// }

// emit WordRevealRequested(msg.sender, block.timestamp);
// // Request decryption of the letters
// Gateway.requestDecryption(cts, this.callbackRevealWord.selector, 0, block.timestamp + 100, false);
// }

// function callbackRevealWord(
// uint256 /*requestID*/,
// uint8 _l0,
// uint8 _l1,
// uint8 _l2,
// uint8 _l3,
// uint8 _l4
// ) public onlyPlayer returns (uint8, uint8, uint8, uint8, uint8) {
// l0 = _l0;
// l1 = _l1;
// l2 = _l2;
// l3 = _l3;
// l4 = _l4;
// // Handle the decrypted word letters here (e.g., emit events or store values)
// return (l0, l1, l2, l3, l4); // Optionally emit an event
// }

// function revealWordAndStore() public onlyPlayer {
// require(l0 != 0 || l1 != 0 || l2 != 0 || l3 != 0 || l4 != 0, "Word not revealed yet");

// word1 =
// uint32(l0) +
// uint32(l1) *
// 26 +
// uint32(l2) *
// 26 *
// 26 +
// uint32(l3) *
// 26 *
// 26 *
// 26 +
// uint32(l4) *
// 26 *
// 26 *
// 26 *
// 26;
// }

// function checkProof(bytes32[] calldata proof) public onlyRelayer {
// assert(nGuesses == 5 || playerWon);
// // Store the proof for use in the callback
// storedProof = proof;

// // Prepare the ciphertext for word1Id
// uint256[] memory cts = new uint256[](1);
// cts[0] = Gateway.toUint256(word1Id);

// // Request decryption of word1Id
// Gateway.requestDecryption(cts, this.callbackCheckProof.selector, 0, block.timestamp + 100, false);
// }

// function callbackCheckProof(uint256 /*requestID*/, uint16 _decryptedWordId) public onlyGateway {
// decryptedWordId = _decryptedWordId;
// // Handle the decrypted wordId and check the proof
// bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(decryptedWordId, word1))));
// if (MerkleProof.verify(storedProof, root, leaf)) {
// proofChecked = true;
// }
// }

modifier onlyRelayer() {
require(msg.sender == relayerAddr);
_;
}

modifier onlyPlayer() {
require(msg.sender == playerAddr);
_;
}
}
88 changes: 88 additions & 0 deletions hardhat/contracts/fheWordle/FHEWordleFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear

pragma solidity ^0.8.20;

import "./FHEWordle.sol";
import "@openzeppelin/contracts/proxy/Clones.sol";
import "fhevm/lib/TFHE.sol";
import "fhevm/gateway/GatewayCaller.sol";
import "fhevm/config/ZamaFHEVMConfig.sol";
import "fhevm/config/ZamaGatewayConfig.sol";
/**
* @title FHEWordleFactory
* @notice This contract is a factory for deploying new instances of the FHEWordle game using the minimal proxy pattern (Clones).
* It manages multiple instances of the game for different users, tracks game results, and allows minting of rewards
* based on the game outcomes.
* @dev This contract uses OpenZeppelin's Clones library for creating deterministic contract instances and relies on the
* FHEWordle game logic deployed at a predefined implementation address.
*/
contract FHEWordleFactory is SepoliaZamaFHEVMConfig, SepoliaZamaGatewayConfig, GatewayCaller, Ownable2Step {
address public creator;

mapping(address => address) public userLastContract;

mapping(address => uint32) public gamesWon;
mapping(address => bool) public claimedWin;
address private immutable implementation;

/**
* @notice Constructor to set the implementation address for game instances.
* @param _implementation The address of the deployed FHEWordle contract used as a template for Clones.
*/
constructor(address _implementation) Ownable(msg.sender) {
creator = msg.sender;
implementation = _implementation;
}

/**
* @notice Creates a new game instance for a user using the specified relayer address and a unique salt.
* @dev Uses OpenZeppelin's Clones library to deploy a minimal proxy contract. The salt ensures unique deployments.
* @param _relayerAddr The address of the relayer used for the game.
* @param salt A unique salt used to determine the deployment address.
*/
function createGame(address _relayerAddr, bytes32 salt) public {
address cloneAdd = Clones.cloneDeterministic(implementation, salt);
FHEWordle(cloneAdd).initialize(msg.sender, _relayerAddr, 0);
userLastContract[msg.sender] = cloneAdd;
}

/**
* @notice Creates a test game instance with a specific word ID for testing purposes.
* @dev Ensures that a user can only create a single test instance.
* @param _relayerAddr The address of the relayer used for the game.
* @param id The word ID to use for testing.
* @param salt A unique salt used to determine the deployment address.
*/
function createTest(address _relayerAddr, uint16 id, bytes32 salt) public {
require(userLastContract[msg.sender] == address(0), "kek");
address cloneAdd = Clones.cloneDeterministic(implementation, salt);
FHEWordle(cloneAdd).initialize(msg.sender, _relayerAddr, id);
userLastContract[msg.sender] = cloneAdd;
}

// /**
// * @notice Checks if the user's last game has been completed.
// * @return True if the game has not started or if the player has either won or used up all guesses.
// */
// function gameNotStarted() public view returns (bool) {
// if (userLastContract[msg.sender] != address(0)) {
// FHEWordle game = FHEWordle(userLastContract[msg.sender]);
// return game.playerWon() || (game.nGuesses() == 5);
// }
// return true;
// }

// /**
// * @notice Allows a user to mint rewards if they have won a game and the proof has been verified.
// * @dev The user must have a completed game with proof checked and should not have already claimed the reward.
// */
// function mint() public {
// if (userLastContract[msg.sender] != address(0)) {
// address contractAddr = userLastContract[msg.sender];
// FHEWordle game = FHEWordle(contractAddr);
// require(game.playerWon() && game.proofChecked() && !claimedWin[contractAddr], "has to win and check proof");
// claimedWin[contractAddr] = true;
// gamesWon[msg.sender] += 1;
// }
// }
}
Loading

0 comments on commit 7e30ca2

Please sign in to comment.