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
+ }
0 commit comments