Skip to content

Commit 55bca51

Browse files
committed
add frontend
1 parent 54cd4d5 commit 55bca51

File tree

9 files changed

+528
-38
lines changed

9 files changed

+528
-38
lines changed

contracts/FHEordle.sol

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
// SPDX-License-Identifier: BSD-3-Clause-Clear
2+
3+
pragma solidity 0.8.19;
4+
5+
import "fhevm/abstracts/EIP712WithModifier.sol";
6+
import "fhevm/lib/TFHE.sol";
7+
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
8+
9+
contract FHEordle is EIP712WithModifier {
10+
11+
12+
address public playerAddr;
13+
address public relayerAddr;
14+
15+
euint16 public word1Id;
16+
euint16[5] public word1Letters;
17+
euint32 public word1LettersMask;
18+
uint32 public word1;
19+
20+
uint8 public nGuesses;
21+
euint8[5] public eqMaskGuess;
22+
euint32[5] public letterMaskGuess;
23+
24+
bytes32[] private proof;
25+
bytes32 private root;
26+
27+
bool public wordSubmitted;
28+
bool public gameStarted;
29+
bool public playerWon;
30+
bool public proofChecked;
31+
32+
constructor(
33+
address _playerAddr,
34+
address _relayerAddr,
35+
uint16 _word1Id,
36+
bytes32 _root,
37+
uint16 wordSetSz
38+
) EIP712WithModifier("Authorization token", "1") {
39+
relayerAddr = _relayerAddr;
40+
playerAddr = _playerAddr;
41+
if (_word1Id > 0) {
42+
word1Id = TFHE.asEuint16(_word1Id);
43+
}
44+
else {
45+
word1Id = TFHE.rem(TFHE.randEuint16(), wordSetSz);
46+
}
47+
for (uint8 i = 0; i < 5; i++) {
48+
eqMaskGuess[i] = TFHE.asEuint8(0);
49+
letterMaskGuess[i] = TFHE.asEuint32(0);
50+
}
51+
nGuesses = 0;
52+
wordSubmitted = false;
53+
gameStarted = false;
54+
playerWon = false;
55+
proofChecked = false;
56+
root = _root;
57+
word1 = 0;
58+
}
59+
60+
function getWord1Id(
61+
bytes32 publicKey,
62+
bytes calldata signature
63+
) public view onlySignedPublicKey(publicKey, signature) onlyRelayer returns (bytes memory) {
64+
return TFHE.reencrypt(word1Id, publicKey);
65+
}
66+
67+
function submitWord1(
68+
bytes calldata el0,
69+
bytes calldata el1,
70+
bytes calldata el2,
71+
bytes calldata el3,
72+
bytes calldata el4,
73+
bytes calldata eMask
74+
) public {
75+
submitWord1(
76+
TFHE.asEuint16(el0),
77+
TFHE.asEuint16(el1),
78+
TFHE.asEuint16(el2),
79+
TFHE.asEuint16(el3),
80+
TFHE.asEuint16(el4),
81+
TFHE.asEuint32(eMask)
82+
);
83+
}
84+
85+
function submitWord1(
86+
euint16 l0, euint16 l1, euint16 l2, euint16 l3, euint16 l4, euint32 mask
87+
) public onlyRelayer {
88+
require(!wordSubmitted, "word submitted");
89+
TFHE.optReq(
90+
TFHE.eq(
91+
TFHE.or(
92+
TFHE.shl(1, TFHE.asEuint32(l0)),
93+
TFHE.or(
94+
TFHE.shl(1, TFHE.asEuint32(l1)),
95+
TFHE.or(
96+
TFHE.shl(1, TFHE.asEuint32(l2)),
97+
TFHE.or(
98+
TFHE.shl(1, TFHE.asEuint32(l3)),
99+
TFHE.shl(1, TFHE.asEuint32(l4))
100+
)
101+
)
102+
)
103+
),
104+
mask
105+
)
106+
);
107+
word1Letters[0] = l0;
108+
word1Letters[1] = l1;
109+
word1Letters[2] = l2;
110+
word1Letters[3] = l3;
111+
word1Letters[4] = l4;
112+
word1LettersMask = mask;
113+
wordSubmitted = true;
114+
}
115+
116+
function submitProof(bytes32[] calldata _proof) public onlyRelayer {
117+
require(wordSubmitted, "word not submitted");
118+
require(!gameStarted, "game started");
119+
proof = _proof;
120+
gameStarted = true;
121+
}
122+
123+
function guessWord1(
124+
bytes calldata el0,
125+
bytes calldata el1,
126+
bytes calldata el2,
127+
bytes calldata el3,
128+
bytes calldata el4,
129+
bytes calldata eMask
130+
) public {
131+
guessWord1(
132+
TFHE.asEuint16(el0),
133+
TFHE.asEuint16(el1),
134+
TFHE.asEuint16(el2),
135+
TFHE.asEuint16(el3),
136+
TFHE.asEuint16(el4),
137+
TFHE.asEuint32(eMask)
138+
);
139+
}
140+
141+
function guessWord1(
142+
euint16 l0, euint16 l1, euint16 l2, euint16 l3, euint16 l4, euint32 letterMask
143+
) public {
144+
require(gameStarted, "game not started");
145+
require(nGuesses < 5, "cannot exceed five guesses!");
146+
euint8 g0 = TFHE.asEuint8(TFHE.eq(word1Letters[0], l0));
147+
euint8 g1 = TFHE.asEuint8(TFHE.eq(word1Letters[1], l1));
148+
euint8 g2 = TFHE.asEuint8(TFHE.eq(word1Letters[2], l2));
149+
euint8 g3 = TFHE.asEuint8(TFHE.eq(word1Letters[3], l3));
150+
euint8 g4 = TFHE.asEuint8(TFHE.eq(word1Letters[4], l4));
151+
euint8 eqMask =
152+
TFHE.or(
153+
TFHE.shl(g0, 0),
154+
TFHE.or(
155+
TFHE.shl(g1, 1),
156+
TFHE.or(
157+
TFHE.shl(g2, 2),
158+
TFHE.or(
159+
TFHE.shl(g3, 3),
160+
TFHE.shl(g4, 4)
161+
)
162+
)
163+
)
164+
);
165+
eqMaskGuess[nGuesses] = eqMask;
166+
letterMaskGuess[nGuesses] = TFHE.and(word1LettersMask, letterMask);
167+
168+
nGuesses += 1;
169+
}
170+
171+
function getGuess(uint8 guessN,
172+
bytes32 publicKey,
173+
bytes calldata signature
174+
)
175+
public view onlySignedPublicKey(publicKey, signature) onlyPlayer
176+
returns (bytes memory, bytes memory) {
177+
require(guessN < nGuesses, "canno exceed nGuesses");
178+
return (
179+
TFHE.reencrypt(eqMaskGuess[guessN], publicKey),
180+
TFHE.reencrypt(letterMaskGuess[guessN], publicKey)
181+
);
182+
}
183+
184+
function claimWin(uint8 guessN) public onlyPlayer {
185+
euint8 fullMask = TFHE.asEuint8(31);
186+
bool compare = TFHE.decrypt(TFHE.eq(fullMask, eqMaskGuess[guessN]));
187+
if (compare) {
188+
playerWon = true;
189+
}
190+
}
191+
192+
function revealWord() public view onlyPlayer returns (uint16, uint16, uint16, uint16, uint16) {
193+
assert(nGuesses == 5 || playerWon);
194+
uint16 l0 = TFHE.decrypt(word1Letters[0]);
195+
uint16 l1 = TFHE.decrypt(word1Letters[1]);
196+
uint16 l2 = TFHE.decrypt(word1Letters[2]);
197+
uint16 l3 = TFHE.decrypt(word1Letters[3]);
198+
uint16 l4 = TFHE.decrypt(word1Letters[4]);
199+
return (l0, l1, l2, l3, l4);
200+
}
201+
202+
function revealWordAndStore() public onlyPlayer {
203+
uint16 l0;
204+
uint16 l1;
205+
uint16 l2;
206+
uint16 l3;
207+
uint16 l4;
208+
(l0, l1, l2, l3, l4) = revealWord();
209+
word1 = uint32(l0) + uint32(l1) * 26 + uint32(l2)*26*26 + uint32(l3)*26*26*26 + uint32(l4)*26*26*26*26;
210+
}
211+
212+
function checkProof() public onlyPlayer {
213+
assert(nGuesses == 5 || playerWon);
214+
uint16 wordId = TFHE.decrypt(word1Id);
215+
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(wordId, word1))));
216+
if (MerkleProof.verify(proof, root, leaf)) {
217+
proofChecked = true;
218+
}
219+
}
220+
221+
modifier onlyRelayer() {
222+
require(msg.sender == relayerAddr);
223+
_;
224+
}
225+
226+
modifier onlyPlayer() {
227+
require(msg.sender == playerAddr);
228+
_;
229+
}
230+
231+
}

contracts/FHEordleFactory.sol

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// SPDX-License-Identifier: BSD-3-Clause-Clear
2+
3+
pragma solidity 0.8.19;
4+
5+
import "fhevm/abstracts/EIP712WithModifier.sol";
6+
import "./FHEordle.sol";
7+
import "fhevm/lib/TFHE.sol";
8+
9+
10+
contract FHEordleFactory is EIP712WithModifier {
11+
12+
address public creator;
13+
14+
mapping(address => address) public userLastContract;
15+
16+
constructor() EIP712WithModifier("Authorization token", "1") {
17+
creator = msg.sender;
18+
}
19+
20+
function createGame(address _relayerAddr) public {
21+
if (userLastContract[msg.sender] != address(0)) {
22+
FHEordle game = FHEordle(userLastContract[msg.sender]);
23+
require(
24+
game.playerWon() || (game.nGuesses() == 5),
25+
"Previous game has not ended"
26+
);
27+
}
28+
userLastContract[msg.sender] = address(
29+
new FHEordle(
30+
msg.sender,
31+
_relayerAddr,
32+
0,
33+
0xd34274ed281b4de16823ef28eff9d2164ca3dc2ba8d38e1861bbf74597760d3e,
34+
3
35+
)
36+
);
37+
}
38+
39+
function createTest(address _relayerAddr) public {
40+
require(userLastContract[msg.sender] == address(0), "kek");
41+
userLastContract[msg.sender] = address(
42+
new FHEordle(
43+
msg.sender,
44+
_relayerAddr,
45+
3,
46+
0xd34274ed281b4de16823ef28eff9d2164ca3dc2ba8d38e1861bbf74597760d3e,
47+
3
48+
)
49+
);
50+
}
51+
52+
function gameNotStarted() public view returns (bool){
53+
if (userLastContract[msg.sender] != address(0)) {
54+
FHEordle game = FHEordle(userLastContract[msg.sender]);
55+
return game.playerWon() || (game.nGuesses() == 5);
56+
}
57+
return true;
58+
}
59+
60+
61+
62+
}

hardhat.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import "./tasks/accounts";
99
import "./tasks/deployERC20";
1010
import "./tasks/getEthereumAddress";
1111
import "./tasks/mint";
12+
import "./tasks/deployFheordleFactory";
1213

1314
const dotenvConfigPath: string = process.env.DOTENV_CONFIG_PATH || "./.env";
1415
dotenvConfig({ path: resolve(__dirname, dotenvConfigPath) });
@@ -69,7 +70,7 @@ function getChainConfig(chain: keyof typeof chainIds): NetworkUserConfig {
6970
}
7071

7172
const config: HardhatUserConfig = {
72-
defaultNetwork: "local",
73+
defaultNetwork: "zama",
7374
namedAccounts: {
7475
deployer: 0,
7576
},

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"task:getEthereumAddress": "hardhat task:getEthereumAddress",
8080
"task:mint": "hardhat task:mint",
8181
"task:deployERC20": "hardhat task:deployERC20",
82+
"task:deployFheordleFactory": "hardhat task:deployFheordleFactory",
8283
"task:accounts": "hardhat task:accounts",
8384
"fhevm:start": "docker run -i -p 8545:8545 --rm --name fhevm ghcr.io/zama-ai/evmos-dev-node:v0.1.10",
8485
"fhevm:stop": "docker rm -f fhevm",
@@ -90,6 +91,7 @@
9091
"fhevm:faucet:dave": "docker exec -i fhevm faucet $(npx hardhat task:getEthereumAddressDave)"
9192
},
9293
"dependencies": {
93-
"@openzeppelin/contracts": "^4.9.2"
94+
"@openzeppelin/contracts": "^4.9.2",
95+
"@openzeppelin/merkle-tree": "^1.0.5"
9496
}
9597
}

0 commit comments

Comments
 (0)