-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9000025
commit 7e30ca2
Showing
7 changed files
with
19,605 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
_; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
// } | ||
// } | ||
} |
Oops, something went wrong.