@peeramid-labs/sdk 3.7.3 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/cli/abis/MAODistribution.js +10 -0
  2. package/cli/abis/MAODistribution.js.map +1 -1
  3. package/cli/abis/RankToken.js +84 -0
  4. package/cli/abis/RankToken.js.map +1 -1
  5. package/cli/abis/superinterface.js +9 -3
  6. package/cli/abis/superinterface.js.map +1 -1
  7. package/cli/cli/commands/blockchain/index.js +9 -0
  8. package/cli/cli/commands/blockchain/index.js.map +1 -0
  9. package/cli/cli/commands/blockchain/mine.js +45 -0
  10. package/cli/cli/commands/blockchain/mine.js.map +1 -0
  11. package/cli/cli/commands/distributions/add.js +57 -31
  12. package/cli/cli/commands/distributions/add.js.map +1 -1
  13. package/cli/cli/commands/distributions/index.js +3 -1
  14. package/cli/cli/commands/distributions/index.js.map +1 -1
  15. package/cli/cli/commands/distributions/remove.js +67 -0
  16. package/cli/cli/commands/distributions/remove.js.map +1 -0
  17. package/cli/cli/commands/fellowship/create.js +97 -54
  18. package/cli/cli/commands/fellowship/create.js.map +1 -1
  19. package/cli/cli/commands/fellowship/game/cancel.js +45 -0
  20. package/cli/cli/commands/fellowship/game/cancel.js.map +1 -0
  21. package/cli/cli/commands/fellowship/game/create.js +91 -0
  22. package/cli/cli/commands/fellowship/game/create.js.map +1 -0
  23. package/cli/cli/commands/fellowship/game/end-turn.js +43 -0
  24. package/cli/cli/commands/fellowship/game/end-turn.js.map +1 -0
  25. package/cli/cli/commands/fellowship/game/index.js +31 -0
  26. package/cli/cli/commands/fellowship/game/index.js.map +1 -0
  27. package/cli/cli/commands/fellowship/game/join.js +131 -0
  28. package/cli/cli/commands/fellowship/game/join.js.map +1 -0
  29. package/cli/cli/commands/fellowship/{games.js → game/list.js} +6 -6
  30. package/cli/cli/commands/fellowship/game/list.js.map +1 -0
  31. package/cli/cli/commands/fellowship/game/propose.js +178 -0
  32. package/cli/cli/commands/fellowship/game/propose.js.map +1 -0
  33. package/cli/cli/commands/fellowship/game/start.js +117 -0
  34. package/cli/cli/commands/fellowship/game/start.js.map +1 -0
  35. package/cli/cli/commands/fellowship/game/vote.js +114 -0
  36. package/cli/cli/commands/fellowship/game/vote.js.map +1 -0
  37. package/cli/cli/commands/fellowship/index.js +4 -2
  38. package/cli/cli/commands/fellowship/index.js.map +1 -1
  39. package/cli/cli/commands/fellowship/params.js +49 -0
  40. package/cli/cli/commands/fellowship/params.js.map +1 -0
  41. package/cli/cli/commands/getPk.js +48 -0
  42. package/cli/cli/commands/getPk.js.map +1 -0
  43. package/cli/cli/commands/playbook.js +92 -0
  44. package/cli/cli/commands/playbook.js.map +1 -0
  45. package/cli/cli/getPk.js +62 -0
  46. package/cli/cli/getPk.js.map +1 -0
  47. package/cli/cli/helpers.js +64 -0
  48. package/cli/cli/helpers.js.map +1 -0
  49. package/cli/cli/index.js +6 -0
  50. package/cli/cli/index.js.map +1 -1
  51. package/cli/cli/utils.js +64 -0
  52. package/cli/cli/utils.js.map +1 -0
  53. package/cli/rankify/GameMaster.js +1066 -0
  54. package/cli/rankify/GameMaster.js.map +1 -0
  55. package/cli/rankify/InstanceBase.js +61 -36
  56. package/cli/rankify/InstanceBase.js.map +1 -1
  57. package/cli/rankify/MAODistributor.js +28 -0
  58. package/cli/rankify/MAODistributor.js.map +1 -1
  59. package/cli/rankify/Player.js +355 -0
  60. package/cli/rankify/Player.js.map +1 -0
  61. package/cli/utils/ApiError.js +11 -6
  62. package/cli/utils/ApiError.js.map +1 -1
  63. package/cli/utils/blockchain.js +62 -0
  64. package/cli/utils/blockchain.js.map +1 -0
  65. package/docs/classes/GameMaster.md +23 -63
  66. package/docs/classes/InstanceBase.md +23 -29
  67. package/docs/classes/InstancePlayer.md +27 -33
  68. package/docs/classes/MAODistributorClient.md +18 -1
  69. package/docs/docs/classes/GameMaster.md +23 -63
  70. package/docs/docs/classes/InstanceBase.md +23 -29
  71. package/docs/docs/classes/InstancePlayer.md +27 -33
  72. package/docs/docs/classes/MAODistributorClient.md +18 -1
  73. package/docs/docs/index.md +4 -4
  74. package/docs/index.md +4 -4
  75. package/lib.commonjs/abis/MAODistribution.d.ts +8 -0
  76. package/lib.commonjs/abis/MAODistribution.d.ts.map +1 -1
  77. package/lib.commonjs/abis/MAODistribution.js +10 -0
  78. package/lib.commonjs/abis/MAODistribution.js.map +1 -1
  79. package/lib.commonjs/abis/RankToken.d.ts +65 -0
  80. package/lib.commonjs/abis/RankToken.d.ts.map +1 -1
  81. package/lib.commonjs/abis/RankToken.js +84 -0
  82. package/lib.commonjs/abis/RankToken.js.map +1 -1
  83. package/lib.commonjs/abis/index.d.ts +74 -1
  84. package/lib.commonjs/abis/index.d.ts.map +1 -1
  85. package/lib.commonjs/abis/superinterface.d.ts +1 -1
  86. package/lib.commonjs/abis/superinterface.d.ts.map +1 -1
  87. package/lib.commonjs/abis/superinterface.js +9 -3
  88. package/lib.commonjs/abis/superinterface.js.map +1 -1
  89. package/lib.commonjs/multipass/MultipassBase.d.ts.map +1 -1
  90. package/lib.commonjs/multipass/Registrar.d.ts.map +1 -1
  91. package/lib.commonjs/rankify/GameMaster.d.ts +27 -34
  92. package/lib.commonjs/rankify/GameMaster.d.ts.map +1 -1
  93. package/lib.commonjs/rankify/GameMaster.js +218 -217
  94. package/lib.commonjs/rankify/GameMaster.js.map +1 -1
  95. package/lib.commonjs/rankify/InstanceBase.d.ts +65 -51
  96. package/lib.commonjs/rankify/InstanceBase.d.ts.map +1 -1
  97. package/lib.commonjs/rankify/InstanceBase.js +61 -36
  98. package/lib.commonjs/rankify/InstanceBase.js.map +1 -1
  99. package/lib.commonjs/rankify/MAODistributor.d.ts +1036 -0
  100. package/lib.commonjs/rankify/MAODistributor.d.ts.map +1 -1
  101. package/lib.commonjs/rankify/MAODistributor.js +28 -0
  102. package/lib.commonjs/rankify/MAODistributor.js.map +1 -1
  103. package/lib.commonjs/rankify/Player.d.ts.map +1 -1
  104. package/lib.commonjs/rankify/RankToken.d.ts.map +1 -1
  105. package/lib.commonjs/utils/ApiError.d.ts.map +1 -1
  106. package/lib.commonjs/utils/ApiError.js +11 -6
  107. package/lib.commonjs/utils/ApiError.js.map +1 -1
  108. package/lib.commonjs/utils/artifacts.d.ts.map +1 -1
  109. package/lib.commonjs/utils/blockchain.d.ts +32 -0
  110. package/lib.commonjs/utils/blockchain.d.ts.map +1 -0
  111. package/lib.commonjs/utils/blockchain.js +62 -0
  112. package/lib.commonjs/utils/blockchain.js.map +1 -0
  113. package/lib.commonjs/utils/index.d.ts.map +1 -1
  114. package/lib.commonjs/utils/permutations.d.ts.map +1 -1
  115. package/lib.esm/abis/MAODistribution.d.ts +8 -0
  116. package/lib.esm/abis/MAODistribution.d.ts.map +1 -1
  117. package/lib.esm/abis/MAODistribution.js +10 -0
  118. package/lib.esm/abis/MAODistribution.js.map +1 -1
  119. package/lib.esm/abis/RankToken.d.ts +65 -0
  120. package/lib.esm/abis/RankToken.d.ts.map +1 -1
  121. package/lib.esm/abis/RankToken.js +84 -0
  122. package/lib.esm/abis/RankToken.js.map +1 -1
  123. package/lib.esm/abis/index.d.ts +74 -1
  124. package/lib.esm/abis/index.d.ts.map +1 -1
  125. package/lib.esm/abis/superinterface.d.ts +1 -1
  126. package/lib.esm/abis/superinterface.d.ts.map +1 -1
  127. package/lib.esm/abis/superinterface.js +9 -3
  128. package/lib.esm/abis/superinterface.js.map +1 -1
  129. package/lib.esm/multipass/MultipassBase.d.ts.map +1 -1
  130. package/lib.esm/multipass/Registrar.d.ts.map +1 -1
  131. package/lib.esm/rankify/GameMaster.d.ts +27 -34
  132. package/lib.esm/rankify/GameMaster.d.ts.map +1 -1
  133. package/lib.esm/rankify/GameMaster.js +219 -218
  134. package/lib.esm/rankify/GameMaster.js.map +1 -1
  135. package/lib.esm/rankify/InstanceBase.d.ts +65 -51
  136. package/lib.esm/rankify/InstanceBase.d.ts.map +1 -1
  137. package/lib.esm/rankify/InstanceBase.js +61 -36
  138. package/lib.esm/rankify/InstanceBase.js.map +1 -1
  139. package/lib.esm/rankify/MAODistributor.d.ts +1036 -0
  140. package/lib.esm/rankify/MAODistributor.d.ts.map +1 -1
  141. package/lib.esm/rankify/MAODistributor.js +28 -0
  142. package/lib.esm/rankify/MAODistributor.js.map +1 -1
  143. package/lib.esm/rankify/Player.d.ts.map +1 -1
  144. package/lib.esm/rankify/RankToken.d.ts.map +1 -1
  145. package/lib.esm/utils/ApiError.d.ts.map +1 -1
  146. package/lib.esm/utils/ApiError.js +11 -6
  147. package/lib.esm/utils/ApiError.js.map +1 -1
  148. package/lib.esm/utils/artifacts.d.ts.map +1 -1
  149. package/lib.esm/utils/blockchain.d.ts +32 -0
  150. package/lib.esm/utils/blockchain.d.ts.map +1 -0
  151. package/lib.esm/utils/blockchain.js +58 -0
  152. package/lib.esm/utils/blockchain.js.map +1 -0
  153. package/lib.esm/utils/index.d.ts.map +1 -1
  154. package/lib.esm/utils/permutations.d.ts.map +1 -1
  155. package/package.json +8 -8
  156. package/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15.groth16.vkey.json +1 -0
  157. package/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15.groth16.zkey +0 -0
  158. package/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15.r1cs +0 -0
  159. package/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15.sym +19202 -0
  160. package/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15_artifacts.json +84 -0
  161. package/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15_js/ProposalsIntegrity15.wasm +0 -0
  162. package/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15_js/generate_witness.js +21 -0
  163. package/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15_js/witness_calculator.js +384 -0
  164. package/zk_artifacts/types/core/ProposalsIntegrity15.ts +163 -0
  165. package/zk_artifacts/types/core/index.ts +6 -0
  166. package/zk_artifacts/types/hardhat.d.ts +14 -0
  167. package/zk_artifacts/types/helpers.ts +44 -0
  168. package/zk_artifacts/types/index.ts +8 -0
  169. package/zk_artifacts/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15.groth16.vkey.json +1 -0
  170. package/zk_artifacts/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15.groth16.zkey +0 -0
  171. package/zk_artifacts/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15.r1cs +0 -0
  172. package/zk_artifacts/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15.sym +19202 -0
  173. package/zk_artifacts/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15_artifacts.json +84 -0
  174. package/zk_artifacts/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15_js/ProposalsIntegrity15.wasm +0 -0
  175. package/zk_artifacts/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15_js/generate_witness.js +21 -0
  176. package/zk_artifacts/zk_artifacts/circuits/proposals_integrity_15.circom/ProposalsIntegrity15_js/witness_calculator.js +384 -0
  177. package/zk_artifacts/zk_artifacts/types/core/ProposalsIntegrity15.ts +163 -0
  178. package/zk_artifacts/zk_artifacts/types/core/index.ts +6 -0
  179. package/zk_artifacts/zk_artifacts/types/hardhat.d.ts +14 -0
  180. package/zk_artifacts/zk_artifacts/types/helpers.ts +44 -0
  181. package/zk_artifacts/zk_artifacts/types/index.ts +8 -0
  182. package/cli/cli/commands/fellowship/games.js.map +0 -1
@@ -0,0 +1,1066 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.GameMaster = void 0;
7
+ const viem_1 = require("viem");
8
+ const abis_1 = require("../abis");
9
+ const InstanceBase_1 = __importDefault(require("./InstanceBase"));
10
+ const types_1 = require("../types");
11
+ const utils_1 = require("../utils");
12
+ const accounts_1 = require("viem/accounts");
13
+ const log_1 = require("../utils/log");
14
+ const circomlibjs_1 = require("circomlibjs");
15
+ const aes_1 = __importDefault(require("crypto-js/aes"));
16
+ const crypto_js_1 = __importDefault(require("crypto-js"));
17
+ const zkit_1 = require("@solarity/zkit");
18
+ const path_1 = __importDefault(require("path"));
19
+ const permutations_1 = require("../utils/permutations");
20
+ /**
21
+ * GameMaster class for managing game state and cryptographic operations in Rankify
22
+ * Extends InstanceBase to provide game master specific functionality
23
+ * @public
24
+ */
25
+ class GameMaster {
26
+ /**
27
+ * Creates a new GameMaster instance
28
+
29
+ * @param walletClient - Viem wallet client for transactions
30
+ * @param publicClient - Viem public client for reading state
31
+ * @param chainId - Chain ID of the network
32
+ */
33
+ constructor({ walletClient, chainId, publicClient, }) {
34
+ this.maxSlotSizeForProofs = 15;
35
+ /**
36
+ * Decrypts a proposal for a specific game turn
37
+ * @param proposal - The encrypted proposal
38
+ * @param turn - The turn number
39
+ * @param instanceAddress - The address of the instance
40
+ * @param gameId - The ID of the game
41
+ * @param proposer - The address of the proposer
42
+ * @returns The decrypted proposal
43
+ */
44
+ this.decryptProposal = async ({ proposal, turn, instanceAddress, gameId, proposer, instance, }) => {
45
+ const _instance = instance ?? new InstanceBase_1.default({ instanceAddress, publicClient: this.publicClient, chainId: this.chainId });
46
+ const proposerPubKey = await _instance.getPlayerPubKey({
47
+ instanceAddress,
48
+ gameId,
49
+ player: proposer,
50
+ });
51
+ const sharedKey = _instance.sharedSigner({
52
+ publicKey: proposerPubKey,
53
+ privateKey: await this.gameKey({ gameId, contractAddress: instanceAddress }),
54
+ gameId,
55
+ turn,
56
+ contractAddress: instanceAddress,
57
+ chainId: this.chainId,
58
+ });
59
+ (0, log_1.logger)(`Decrypting proposal ${proposal} with shared key (hashed value: ${(0, viem_1.keccak256)(sharedKey)})`);
60
+ const decryptedProposal = aes_1.default.decrypt(proposal, sharedKey).toString(crypto_js_1.default.enc.Utf8);
61
+ (0, log_1.logger)(`Decrypted proposal ${decryptedProposal}`);
62
+ return decryptedProposal;
63
+ };
64
+ /**
65
+ * Decrypts proposals for a specific game turn
66
+ * @param gameId - ID of the game
67
+ * @param turn - Turn number
68
+ * @param proposer - Optional proposer address to filter proposals
69
+ * @returns Array of decrypted proposals with proposer addresses
70
+ */
71
+ this.decryptProposals = async ({ instanceAddress, gameId, turn, players, padToMaxSize = false, permute = false, }) => {
72
+ (0, log_1.logger)(`Getting proposals for instance ${instanceAddress}, game ${gameId}, turn ${turn.toString()}`);
73
+ const ProposalSubmittedEvents = await this.publicClient.getContractEvents({
74
+ abi: abis_1.RankifyDiamondInstanceAbi,
75
+ address: instanceAddress,
76
+ eventName: "ProposalSubmitted",
77
+ args: { gameId: gameId, turn: turn },
78
+ fromBlock: 0n,
79
+ });
80
+ (0, log_1.logger)(`Found ${ProposalSubmittedEvents.length} proposals`);
81
+ const instance = new InstanceBase_1.default({ instanceAddress, publicClient: this.publicClient, chainId: this.chainId });
82
+ (0, log_1.logger)(`Decrypting ${ProposalSubmittedEvents.length} proposals`);
83
+ const proposalsForPlayers = await Promise.all((players)?.map(async (player) => {
84
+ const log = ProposalSubmittedEvents.find((log) => log.args.proposer === player);
85
+ if (!log) {
86
+ return {
87
+ proposer: player,
88
+ proposal: "",
89
+ };
90
+ }
91
+ else {
92
+ (0, log_1.logger)(`Decrypting proposal ${log.args.proposer}`);
93
+ if (!log.args.proposer)
94
+ throw new Error("No proposer");
95
+ if (!log.args.encryptedProposal)
96
+ throw new Error("No proposalEncryptedByGM");
97
+ return {
98
+ proposer: log.args.proposer,
99
+ proposal: await this.decryptProposal({
100
+ proposal: log.args.encryptedProposal,
101
+ turn: turn,
102
+ instanceAddress: instanceAddress,
103
+ gameId: gameId,
104
+ proposer: log.args.proposer,
105
+ instance,
106
+ }),
107
+ };
108
+ }
109
+ }));
110
+ if (permute) {
111
+ const proposalsPermuted = await this.permuteArray({ array: proposalsForPlayers, gameId, turn, verifierAddress: instanceAddress });
112
+ return padToMaxSize ? this.padProposalsArrayWithZeroAddress(proposalsPermuted) : proposalsPermuted;
113
+ }
114
+ return padToMaxSize ? this.padProposalsArrayWithZeroAddress(proposalsForPlayers) : proposalsForPlayers;
115
+ };
116
+ /**
117
+ * Generates a deterministic permutation for a specific game turn
118
+ * @param gameId - ID of the game
119
+ * @param turn - Turn number
120
+ * @param size - Size of the permutation
121
+ * @param verifierAddress - Address of the verifier
122
+ * @returns The generated permutation, secret, and commitment
123
+ */
124
+ this.getPermutation = async ({ gameId, turn, size, verifierAddress, }) => {
125
+ const turnSalt = await this.getTurnSalt({ gameId, turn, verifierAddress });
126
+ // Create deterministic seed from game parameters and GM's signature
127
+ // Use the seed to generate permutation
128
+ const permutation = Array.from({ length: this.maxSlotSizeForProofs }, (_, i) => i);
129
+ // Fisher-Yates shuffle with deterministic randomness
130
+ for (let i = size - 1; i >= 0; i--) {
131
+ // Generate deterministic random number for this position
132
+ const randHash = (0, viem_1.keccak256)((0, viem_1.encodePacked)(["uint256", "uint256"], [turnSalt, BigInt(i)]));
133
+ const rand = BigInt(randHash);
134
+ const j = Number(rand % BigInt(i + 1));
135
+ // Swap elements
136
+ [permutation[i], permutation[j]] = [permutation[j], permutation[i]];
137
+ }
138
+ // Ensure inactive slots map to themselves
139
+ for (let i = size; i < this.maxSlotSizeForProofs; i++) {
140
+ permutation[i] = i;
141
+ }
142
+ return { permutation, turnSalt };
143
+ };
144
+ /**
145
+ * Generates a deterministic permutation for a specific game turn
146
+ * @param gameId - ID of the game
147
+ * @param turn - Turn number
148
+ * @param size - Size of the permutation
149
+ * @param verifierAddress - Address of the verifier
150
+ * @returns The generated permutation, secret, and commitment
151
+ */
152
+ this.generateDeterministicPermutation = async ({ gameId, turn, size = 15, verifierAddress, }) => {
153
+ // This is kept secret to generate witness
154
+ // Create deterministic seed from game parameters and GM's signature
155
+ const { permutation, turnSalt } = await this.getPermutation({ gameId, turn, size, verifierAddress });
156
+ // Generate commitment
157
+ const poseidon = await (0, circomlibjs_1.buildPoseidon)();
158
+ const PoseidonFirst = BigInt(poseidon.F.toObject(poseidon([permutation[0], permutation[1], permutation[2], permutation[3], permutation[4]])));
159
+ const PoseidonSecond = BigInt(poseidon.F.toObject(poseidon([PoseidonFirst, permutation[5], permutation[6], permutation[7], permutation[8], permutation[9]])));
160
+ const PoseidonThird = BigInt(poseidon.F.toObject(poseidon([PoseidonSecond, permutation[10], permutation[11], permutation[12], permutation[13], permutation[14]])));
161
+ const commitment = BigInt(poseidon.F.toObject(poseidon([PoseidonThird, turnSalt])));
162
+ return {
163
+ permutation,
164
+ turnSalt,
165
+ commitment,
166
+ };
167
+ };
168
+ /**
169
+ * Permutes an array based on a deterministic permutation
170
+ * @param array - Array to permute
171
+ * @param gameId - ID of the game
172
+ * @param turn - Turn number
173
+ * @param verifierAddress - Address of the verifier
174
+ * @returns The permuted array
175
+ */
176
+ this.permuteArray = async ({ array, gameId, turn, verifierAddress, }) => {
177
+ const { permutation } = await this.getPermutation({ gameId, turn, size: array.length, verifierAddress });
178
+ return (0, permutations_1.permuteArray)({ array, permutation });
179
+ };
180
+ /**
181
+ * Reverses a permutation of an array
182
+ * @param permutedArray - Array to reverse
183
+ * @param gameId - ID of the game
184
+ * @param turn - Turn number
185
+ * @param verifierAddress - Address of the verifier
186
+ * @returns The original array
187
+ */
188
+ this.reversePermutation = async ({ permutedArray, gameId, turn, verifierAddress, }) => {
189
+ const { permutation } = await this.getPermutation({ gameId, turn, size: permutedArray.length, verifierAddress });
190
+ return (0, permutations_1.reversePermutation)({ array: permutedArray, permutation });
191
+ };
192
+ /**
193
+ * Generates a salt for a specific game turn
194
+ * @param gameId - ID of the game
195
+ * @param turn - Turn number
196
+ * @param verifierAddress - Address of the verifier
197
+ * @returns Generated salt as Hex
198
+ */
199
+ this.getTurnSalt = async ({ gameId, turn, verifierAddress, }) => {
200
+ const gameKey = await this.gameKey({ gameId, contractAddress: verifierAddress });
201
+ const instance = new InstanceBase_1.default({
202
+ instanceAddress: verifierAddress,
203
+ publicClient: this.publicClient,
204
+ chainId: this.chainId,
205
+ });
206
+ const seed = instance.pkdf({
207
+ privateKey: gameKey,
208
+ turn,
209
+ gameId,
210
+ contractAddress: verifierAddress,
211
+ chainId: this.chainId,
212
+ scope: "turnSalt",
213
+ });
214
+ return BigInt(seed);
215
+ };
216
+ /**
217
+ * Generates a salt for a specific player in a game turn
218
+ * @param gameId - ID of the game
219
+ * @param turn - Turn number
220
+ * @param player - Address of the player
221
+ * @param verifierAddress - Address of the verifier
222
+ * @param size - Size of the permutation
223
+ * @returns Generated salt as Hex
224
+ */
225
+ this.getTurnPlayersSalt = async ({ gameId, turn, player, verifierAddress, size, }) => {
226
+ (0, log_1.logger)(`Generating vote salt for player ${player} in game ${gameId}, turn ${turn}`);
227
+ const result = await this.generateDeterministicPermutation({
228
+ gameId,
229
+ turn: turn - 1n,
230
+ verifierAddress,
231
+ size,
232
+ }).then((perm) => {
233
+ return (0, viem_1.keccak256)((0, viem_1.encodePacked)(["address", "uint256"], [player, perm.turnSalt]));
234
+ });
235
+ (0, log_1.logger)(`Generated vote salt for player ${player}`);
236
+ return result;
237
+ };
238
+ /**
239
+ * Finds the index of a player's ongoing proposal
240
+ * @param gameId - ID of the game
241
+ * @param player - Address of the player
242
+ * @returns Index of the player's proposal, -1 if not found
243
+ */
244
+ this.findPlayerOngoingProposalIndex = async ({ instanceAddress, gameId, player, turn, }) => {
245
+ if (!turn) {
246
+ turn = await this.currentTurn({ instanceAddress, gameId });
247
+ }
248
+ const players = await this.getPlayers({ instanceAddress, gameId });
249
+ const decryptedProposalsPermuted = await this.decryptProposals({ instanceAddress, gameId, turn: turn - 1n, players: [...players], permute: true });
250
+ return decryptedProposalsPermuted.findIndex((p) => p?.proposer === player);
251
+ };
252
+ this.validateJoinGame = async (props) => {
253
+ const { gameId, participant, instanceAddress } = props;
254
+ try {
255
+ const gameState = await this.getGameState({ gameId, instanceAddress });
256
+ if (gameState.gamePhase !== types_1.gameStatusEnum.open) {
257
+ return { result: false, errorMessage: "Game is not open for registration" };
258
+ }
259
+ if (gameState.players.length === Number(gameState.maxPlayerCnt)) {
260
+ return { result: false, errorMessage: "Game is already full" };
261
+ }
262
+ if (gameState.players.indexOf(participant) !== -1) {
263
+ return { result: false, errorMessage: "Player already registered" };
264
+ }
265
+ return { result: true, errorMessage: "" };
266
+ }
267
+ catch (e) {
268
+ throw await (0, utils_1.handleRPCError)(e);
269
+ }
270
+ };
271
+ /**
272
+ * Signs a joining game event
273
+ * @param gameId - ID of the game
274
+ * @param participant - Address of the participant
275
+ * @param instanceAddress - Address of the game instance
276
+ * @returns Signature and gmCommitment
277
+ */
278
+ this.signJoiningGame = async (props, timeToJoin = 60 * 10) => {
279
+ if (!this.walletClient.account)
280
+ throw new Error("No account");
281
+ (0, log_1.logger)(`Signing joining game..`);
282
+ const { gameId, participant, instanceAddress, participantPubKeyHash } = props;
283
+ const { result: isValid, errorMessage } = await this.validateJoinGame({ gameId, participant, instanceAddress });
284
+ if (!isValid) {
285
+ throw new Error(errorMessage);
286
+ }
287
+ const baseInstance = new InstanceBase_1.default({ instanceAddress, publicClient: this.publicClient, chainId: this.chainId });
288
+ const eip712 = await baseInstance.getEIP712Domain();
289
+ (0, log_1.logger)({
290
+ gameId: props.gameId,
291
+ participant: props.participant,
292
+ instanceAddress: props.instanceAddress,
293
+ chainId: this.chainId,
294
+ name: eip712.name,
295
+ version: eip712.version,
296
+ gameMaster: this.walletClient.account?.address,
297
+ participantPubKeyHash,
298
+ }, 2);
299
+ const deadline = BigInt(Math.floor(Date.now() / 1000) + timeToJoin);
300
+ //ToDo This is placeholder for now, we will need it later in staking
301
+ const gmCommitment = (0, viem_1.stringToHex)("0x123131231311", { size: 32 });
302
+ console.log({
303
+ name: eip712.name,
304
+ version: eip712.version,
305
+ chainId: this.chainId,
306
+ verifyingContract: instanceAddress,
307
+ }, {
308
+ participant,
309
+ gameId,
310
+ gmCommitment,
311
+ deadline,
312
+ participantPubKeyHash,
313
+ });
314
+ const signature = await this.walletClient.signTypedData({
315
+ domain: {
316
+ name: eip712.name,
317
+ version: eip712.version,
318
+ chainId: this.chainId,
319
+ verifyingContract: instanceAddress,
320
+ },
321
+ types: {
322
+ AttestJoiningGame: [
323
+ { type: "address", name: "participant" },
324
+ { type: "uint256", name: "gameId" },
325
+ { type: "bytes32", name: "gmCommitment" },
326
+ { type: "uint256", name: "deadline" },
327
+ { type: "bytes32", name: "participantPubKeyHash" },
328
+ ],
329
+ },
330
+ message: {
331
+ participant,
332
+ gameId,
333
+ gmCommitment,
334
+ deadline,
335
+ participantPubKeyHash,
336
+ },
337
+ primaryType: "AttestJoiningGame",
338
+ account: this.walletClient.account,
339
+ });
340
+ return { signature, gmCommitment, deadline };
341
+ };
342
+ /**
343
+ * Submits a vote for proposals
344
+ * @param gameId - ID of the game
345
+ * @param vote - Array of vote values
346
+ * @param voter - Address of the voter
347
+ * @returns Transaction hash
348
+ */
349
+ this.submitVote = async ({ instanceAddress, gameId, vote, voter, voterSignature, ballotHash, ballotId, }) => {
350
+ const players = await this.getPlayers({ instanceAddress, gameId });
351
+ const turn = await this.currentTurn({ instanceAddress, gameId });
352
+ const validationResult = await this.validateVote({ gameId, turn, voter, vote, instanceAddress, players: [...players] });
353
+ if (!validationResult.result) {
354
+ throw new Error('Vote validation failed: ' + validationResult.reason);
355
+ }
356
+ try {
357
+ const { request } = await this.publicClient.simulateContract({
358
+ account: this.walletClient.account,
359
+ address: instanceAddress,
360
+ abi: abis_1.RankifyDiamondInstanceAbi,
361
+ functionName: "submitVote",
362
+ args: [gameId, ballotId, voter, viem_1.zeroHash, voterSignature, ballotHash],
363
+ });
364
+ return this.walletClient.writeContract(request);
365
+ }
366
+ catch (e) {
367
+ throw await (0, utils_1.handleRPCError)(e);
368
+ }
369
+ };
370
+ /**
371
+ * Gets the current turn progress in percent value
372
+ * @param instanceAddress - Address of the instance
373
+ * @param gameState - Current game state
374
+ * @param gameId - ID of the game
375
+ * @returns Current turn progress
376
+ */
377
+ this.getTurnProgress = async ({ instanceAddress, gameState, gameId }) => {
378
+ const prevTurnProposals = await this.decryptProposals({ instanceAddress, gameId, turn: gameState.currentTurn - 1n, players: [...gameState.players] });
379
+ const proposalCountInPrevTurn = prevTurnProposals.filter(p => p.proposal !== "").length;
380
+ const proposalsMadeInCurrentTurn = await this.decryptProposals({ instanceAddress, gameId, turn: gameState.currentTurn, players: [...gameState.players] });
381
+ const proposalCountInCurrentTurn = proposalsMadeInCurrentTurn.filter(p => p.proposal !== "").length;
382
+ if (proposalCountInPrevTurn === 0) {
383
+ return (proposalCountInCurrentTurn / gameState.players.length) * 100;
384
+ }
385
+ else {
386
+ const votesMadeInCurrentTurn = await this.decryptTurnVotes({ instanceAddress, gameId, turn: gameState.currentTurn, players: [...gameState.players] });
387
+ const votesCount = votesMadeInCurrentTurn.filter(v => this.hasVoted({ vote: v })).length;
388
+ return ((votesCount + proposalCountInCurrentTurn) / (gameState.players.length * 2)) * 100;
389
+ }
390
+ };
391
+ this.validateVote = async ({ gameId, turn, voter, vote, instanceAddress, players }) => {
392
+ const decryptedProposals = await this.decryptProposals({ instanceAddress, gameId, turn: turn - 1n, players, permute: true });
393
+ //Invalid vote length
394
+ if (vote.length !== decryptedProposals.length) {
395
+ return { result: false, reason: "Invalid vote length" };
396
+ }
397
+ // Check if points used are correct (Quadratic voting system)
398
+ let pointsUsed = 0n;
399
+ for (let i = 0; i < vote.length; i++) {
400
+ if (vote[i] > 0n) {
401
+ pointsUsed += 1n ** vote[i];
402
+ }
403
+ }
404
+ const gameState = await this.getGameState({ gameId, instanceAddress });
405
+ if (pointsUsed > gameState.voteCredits) {
406
+ return { result: false, reason: "Too many points used" };
407
+ }
408
+ if (pointsUsed < gameState.voteCredits) {
409
+ return { result: false, reason: "Not all points used" };
410
+ }
411
+ // Check if voter voted for a non-proposed player or their own proposal
412
+ for (let i = 0; i < vote.length; i++) {
413
+ if (vote[i] === 0n)
414
+ continue;
415
+ if (decryptedProposals[i].proposal === "" || decryptedProposals[i].proposer === viem_1.zeroAddress) {
416
+ return { result: false, reason: "Vote for non existing proposal" };
417
+ }
418
+ if (decryptedProposals[i].proposer === voter) {
419
+ return { result: false, reason: "Voter cannot vote for their own proposal" };
420
+ }
421
+ }
422
+ // Valid vote
423
+ return { result: true, reason: "" };
424
+ };
425
+ this.padProposalsArrayWithZeroAddress = (proposals) => {
426
+ if (proposals.length < this.maxSlotSizeForProofs) {
427
+ for (let i = proposals.length; i < this.maxSlotSizeForProofs; i++) {
428
+ proposals.push({
429
+ proposer: viem_1.zeroAddress,
430
+ proposal: "",
431
+ });
432
+ }
433
+ }
434
+ return proposals;
435
+ };
436
+ /**
437
+ * Types for proposal submission
438
+ */
439
+ this.proposalTypes = {
440
+ SubmitProposal: [
441
+ { type: "uint256", name: "gameId" },
442
+ { type: "address", name: "proposer" },
443
+ { type: "string", name: "encryptedProposal" },
444
+ { type: "uint256", name: "commitment" },
445
+ ],
446
+ };
447
+ this.signProposal = async ({ verifierAddress, proposer, gameId, encryptedProposal, commitment, eip712, }) => {
448
+ // Generate typed data hash matching Solidity's keccak256(abi.encode(...))
449
+ if (!this.walletClient.account)
450
+ throw new Error("No account");
451
+ return this.walletClient.signTypedData({
452
+ domain: {
453
+ name: eip712.name,
454
+ version: eip712.version,
455
+ chainId: this.chainId,
456
+ verifyingContract: verifierAddress,
457
+ },
458
+ types: this.proposalTypes,
459
+ message: {
460
+ gameId,
461
+ proposer,
462
+ encryptedProposal,
463
+ commitment,
464
+ },
465
+ primaryType: "SubmitProposal",
466
+ account: this.walletClient.account,
467
+ });
468
+ };
469
+ this.proposalValues = async ({ instanceAddress, gameId, proposal, proposer, turn, }) => {
470
+ const instance = new InstanceBase_1.default({ instanceAddress, publicClient: this.publicClient, chainId: this.chainId });
471
+ const proposerPubKey = await instance.getPlayerPubKey({
472
+ instanceAddress,
473
+ gameId,
474
+ player: proposer,
475
+ });
476
+ const sharedKey = instance.sharedSigner({
477
+ publicKey: proposerPubKey,
478
+ privateKey: await this.gameKey({ gameId, contractAddress: instanceAddress }),
479
+ gameId,
480
+ turn,
481
+ contractAddress: instanceAddress,
482
+ chainId: this.chainId,
483
+ });
484
+ // const poseidon = await buildPoseidon();
485
+ const proposalValue = BigInt((0, viem_1.keccak256)((0, viem_1.encodePacked)(["string"], [proposal])));
486
+ const randomnessValue = BigInt((0, viem_1.keccak256)((0, viem_1.encodePacked)(["string"], [sharedKey])));
487
+ // Calculate commitment using poseidon
488
+ return {
489
+ proposalValue,
490
+ randomnessValue,
491
+ proposal,
492
+ };
493
+ };
494
+ /**
495
+ * Encrypts a proposal
496
+ * @param proposal - Proposal to encrypt
497
+ * @param turn - Turn number
498
+ * @param instanceAddress - Address of the game instance
499
+ * @param gameId - ID of the game
500
+ * @param proposerPubKey - Public key of the proposer
501
+ * @returns Encrypted proposal and shared key
502
+ */
503
+ this.encryptProposal = async ({ proposal, turn, instanceAddress, gameId, proposerPubKey, }) => {
504
+ const instance = new InstanceBase_1.default({ instanceAddress, publicClient: this.publicClient, chainId: this.chainId });
505
+ const sharedKey = instance.sharedSigner({
506
+ publicKey: proposerPubKey,
507
+ privateKey: await this.gameKey({ gameId, contractAddress: instanceAddress }),
508
+ gameId,
509
+ turn,
510
+ contractAddress: instanceAddress,
511
+ chainId: this.chainId,
512
+ });
513
+ (0, log_1.logger)(`Encrypting proposal ${proposal} with shared key (hashed value: ${(0, viem_1.keccak256)(sharedKey)})`);
514
+ const encryptedProposal = aes_1.default.encrypt(proposal, sharedKey).toString();
515
+ (0, log_1.logger)(`Encrypted proposal ${encryptedProposal}`);
516
+ return { encryptedProposal, sharedKey };
517
+ };
518
+ /**
519
+ * Attests a proposal
520
+ * @param instanceAddress - Address of the game instance
521
+ * @param gameId - ID of the game
522
+ * @param proposal - Proposal to attest
523
+ * @param proposerPubKey - Public key of the proposer
524
+ * @param turn - Turn number
525
+ * @returns The attested proposal
526
+ */
527
+ this.attestProposal = async ({ instanceAddress, gameId, proposal, proposerPubKey, turn, }) => {
528
+ const proposerAddress = (0, accounts_1.publicKeyToAddress)(proposerPubKey);
529
+ (0, log_1.logger)(`Creating proposal secrets for player ${proposerAddress} in game ${gameId}`);
530
+ const poseidon = await (0, circomlibjs_1.buildPoseidon)();
531
+ const instance = new InstanceBase_1.default({ instanceAddress, publicClient: this.publicClient, chainId: this.chainId });
532
+ const { encryptedProposal, sharedKey } = await this.encryptProposal({
533
+ proposal,
534
+ turn,
535
+ instanceAddress,
536
+ gameId,
537
+ proposerPubKey,
538
+ });
539
+ const proposalValue = BigInt((0, viem_1.keccak256)((0, viem_1.encodePacked)(["string"], [proposal])));
540
+ const randomnessValue = BigInt((0, viem_1.keccak256)((0, viem_1.encodePacked)(["string"], [sharedKey])));
541
+ // Calculate commitment using poseidon
542
+ const hash = poseidon([proposalValue, randomnessValue]);
543
+ const poseidonCommitment = BigInt(poseidon.F.toObject(hash));
544
+ const eip712 = await instance.getEIP712Domain();
545
+ const signature = await this.signProposal({
546
+ verifierAddress: instanceAddress,
547
+ proposer: proposerAddress,
548
+ gameId,
549
+ encryptedProposal,
550
+ commitment: poseidonCommitment,
551
+ eip712,
552
+ });
553
+ const params = {
554
+ gameId,
555
+ encryptedProposal,
556
+ commitment: poseidonCommitment,
557
+ proposer: proposerAddress,
558
+ gmSignature: signature,
559
+ };
560
+ (0, log_1.logger)(`Generated proposal secrets with commitment ${poseidonCommitment}`);
561
+ return {
562
+ submissionParams: params,
563
+ proposal,
564
+ proposerAddress,
565
+ proposalValue,
566
+ randomnessValue,
567
+ };
568
+ };
569
+ /**
570
+ * Submits a proposal to the game
571
+ * @param gameId - ID of the game
572
+ * @param commitmentHash - Hash of the proposal commitment
573
+ * @param encryptedProposal - Encrypted proposal data
574
+ * @param proposer - Address of the proposer
575
+ * @returns Transaction hash
576
+ */
577
+ this.submitProposal = async ({ instanceAddress, submissionParams, proposerSignature, }) => {
578
+ const txParams = [
579
+ {
580
+ ...submissionParams,
581
+ proposerSignature,
582
+ },
583
+ ];
584
+ try {
585
+ const { request } = await this.publicClient.simulateContract({
586
+ account: this.walletClient.account,
587
+ address: instanceAddress,
588
+ abi: abis_1.RankifyDiamondInstanceAbi,
589
+ functionName: "submitProposal",
590
+ args: txParams,
591
+ });
592
+ return this.walletClient.writeContract(request);
593
+ }
594
+ catch (e) {
595
+ throw await (0, utils_1.handleRPCError)(e);
596
+ }
597
+ };
598
+ /**
599
+ * Decrypts votes for a specific game turn
600
+ * @param gameId - ID of the game
601
+ * @param turn - Turn number
602
+ * @returns Array of decrypted votes with player addresses
603
+ */
604
+ this.decryptTurnVotes = async ({ instanceAddress, gameId, turn, players = [], }) => {
605
+ (0, log_1.logger)(`Decrypting votes for game ${BigInt(gameId)} turn ${turn} at address ${instanceAddress} at ${await this.publicClient.getBlockNumber()} block`);
606
+ const VoteSubmittedEvents = await this.publicClient.getContractEvents({
607
+ address: instanceAddress,
608
+ abi: abis_1.RankifyDiamondInstanceAbi,
609
+ eventName: "VoteSubmitted",
610
+ fromBlock: 0n,
611
+ args: { turn, gameId },
612
+ });
613
+ (0, log_1.logger)(`Found ${VoteSubmittedEvents.length} events`);
614
+ if (VoteSubmittedEvents.length === 0)
615
+ return [];
616
+ //Decrypting votes from events
617
+ const votes = [];
618
+ console.log("Events:", VoteSubmittedEvents);
619
+ for (const event of VoteSubmittedEvents) {
620
+ console.log("Event:", event);
621
+ if (!event.args.player)
622
+ throw new Error("No player in event");
623
+ if (!event.args.sealedBallotId)
624
+ throw new Error("No sealedBallotId in event");
625
+ const turnKey = await this.calculateSharedTurnKey({
626
+ instanceAddress,
627
+ gameId,
628
+ turn,
629
+ player: event.args.player,
630
+ });
631
+ const decryptedVotes = await this.decryptVote(event.args.sealedBallotId, turnKey);
632
+ votes.push({
633
+ player: event.args.player,
634
+ votes: decryptedVotes,
635
+ });
636
+ }
637
+ const votesForEachPlayer = await Promise.all(players.map(async (player) => {
638
+ const vote = votes.find((v) => v.player === player);
639
+ if (!vote?.votes)
640
+ return players.map(() => 0n);
641
+ return vote.votes;
642
+ }));
643
+ return votesForEachPlayer;
644
+ };
645
+ /**
646
+ * Checks if the current turn can be ended
647
+ * @param gameId - ID of the game
648
+ * @returns Boolean indicating if turn can be ended
649
+ */
650
+ this.canEndTurn = async ({ instanceAddress, gameId }) => {
651
+ const canEndTurn = await this.publicClient.readContract({
652
+ address: instanceAddress,
653
+ abi: abis_1.RankifyDiamondInstanceAbi,
654
+ functionName: "canEndTurn",
655
+ args: [gameId],
656
+ });
657
+ if (!canEndTurn)
658
+ return false;
659
+ //Extra check to not allow to end turn if current phase timeout is not passed and progress is less than 100% (probably must be fixed in contracts!)
660
+ // TODO: if fixed in contracts, remove this check
661
+ const gameState = await this.getGameState({ instanceAddress, gameId });
662
+ const lastBlock = await this.publicClient.getBlock({ blockNumber: BigInt(await this.publicClient.getBlockNumber()) });
663
+ if (gameState.currentPhaseTimeoutAt > lastBlock.timestamp) {
664
+ const turnProgress = await this.getTurnProgress({ instanceAddress, gameState, gameId });
665
+ if (turnProgress <= 100)
666
+ return false;
667
+ }
668
+ return true;
669
+ };
670
+ /**
671
+ * Gets the current turn number
672
+ * @param gameId - ID of the game
673
+ * @returns Current turn number
674
+ */
675
+ this.currentTurn = async ({ instanceAddress, gameId }) => {
676
+ try {
677
+ return this.publicClient.readContract({
678
+ address: instanceAddress,
679
+ abi: abis_1.RankifyDiamondInstanceAbi,
680
+ functionName: "getTurn",
681
+ args: [gameId],
682
+ });
683
+ }
684
+ catch (e) {
685
+ throw await (0, utils_1.handleRPCError)(e);
686
+ }
687
+ };
688
+ /**
689
+ * Gets the list of players in the game
690
+ * @param gameId - ID of the game
691
+ * @returns Array of player addresses
692
+ */
693
+ this.getPlayers = async ({ instanceAddress, gameId }) => {
694
+ try {
695
+ return this.publicClient.readContract({
696
+ address: instanceAddress,
697
+ abi: abis_1.RankifyDiamondInstanceAbi,
698
+ functionName: "getPlayers",
699
+ args: [gameId],
700
+ });
701
+ }
702
+ catch (e) {
703
+ throw await (0, utils_1.handleRPCError)(e);
704
+ }
705
+ };
706
+ /**
707
+ * Ends the current turn and processes votes
708
+ * @param gameId - ID of the game
709
+ * @returns Transaction hash
710
+ */
711
+ this.endTurn = async ({ instanceAddress, gameId }) => {
712
+ (0, log_1.logger)(`Ending turn for game ${gameId}`, 2);
713
+ try {
714
+ if (!(await this.canEndTurn({ instanceAddress, gameId }))) {
715
+ throw new Error("Cannot end turn");
716
+ }
717
+ const turn = await this.currentTurn({ instanceAddress, gameId });
718
+ const players = await this.getPlayers({ instanceAddress, gameId });
719
+ (0, log_1.logger)(`Current turn: ${turn}, Players count: ${players.length}`, 2);
720
+ const newPaddedDecryptedProposals = await this.decryptProposals({ instanceAddress, gameId, turn, players: [...players], padToMaxSize: true });
721
+ (0, log_1.logger)(`newPaddedDecryptedProposals:`);
722
+ (0, log_1.logger)(newPaddedDecryptedProposals);
723
+ const votesDecrypted = await this.decryptTurnVotes({ instanceAddress, gameId, turn, players: [...players] });
724
+ (0, log_1.logger)(`votesDecrypted:`);
725
+ (0, log_1.logger)(votesDecrypted);
726
+ const tableData = players.map((player, idx) => ({
727
+ player,
728
+ voted: this.hasVoted({ vote: votesDecrypted[idx] }),
729
+ proposal: newPaddedDecryptedProposals[idx]?.proposal.substring(0, 50) || "not-proposed",
730
+ }));
731
+ console.table(tableData);
732
+ const attested = await this.getProposalsIntegrity({
733
+ gameId,
734
+ turn,
735
+ verifierAddress: instanceAddress,
736
+ size: players.length,
737
+ proposals: newPaddedDecryptedProposals,
738
+ });
739
+ const { request } = await this.publicClient.simulateContract({
740
+ abi: abis_1.RankifyDiamondInstanceAbi,
741
+ account: this.walletClient.account,
742
+ address: instanceAddress,
743
+ functionName: "endTurn",
744
+ args: [gameId, votesDecrypted, attested.newProposals, attested.prevTurnPermutation, attested.prevTurnSalt],
745
+ });
746
+ return this.walletClient.writeContract(request);
747
+ }
748
+ catch (e) {
749
+ throw await (0, utils_1.handleRPCError)(e);
750
+ }
751
+ };
752
+ this.gameKey = async ({ gameId, contractAddress }) => {
753
+ (0, log_1.logger)(`Signing game key for game ${gameId} at address ${contractAddress}`);
754
+ const message = (0, viem_1.encodePacked)(["uint256", "address", "string"], [gameId, contractAddress, "gameKey"]);
755
+ (0, log_1.logger)(`Signing message: ${message}`, 2);
756
+ if (!this.walletClient.account)
757
+ throw new Error("No account");
758
+ const gameKey = await this.walletClient
759
+ .signMessage({
760
+ message,
761
+ account: this.walletClient.account,
762
+ })
763
+ .then((sig) => (0, viem_1.keccak256)(sig));
764
+ (0, log_1.logger)(`Game key: ${gameKey}`, 2);
765
+ return gameKey;
766
+ };
767
+ this.calculateSharedTurnKey = async ({ instanceAddress, gameId, turn, player, }) => {
768
+ (0, log_1.logger)(`Calculating shared turn key for player ${player} in game ${gameId} at address ${instanceAddress}`);
769
+ const instance = new InstanceBase_1.default({ instanceAddress, publicClient: this.publicClient, chainId: this.chainId });
770
+ const playerPubKey = await instance.getPlayerPubKey({ instanceAddress, gameId, player });
771
+ (0, log_1.logger)(`Player public key: ${playerPubKey}`, 2);
772
+ console.log("Player public key:", playerPubKey);
773
+ console.log("Game key:", await this.gameKey({ gameId, contractAddress: instanceAddress }));
774
+ return instance.sharedSigner({
775
+ publicKey: playerPubKey,
776
+ privateKey: await this.gameKey({ gameId, contractAddress: instanceAddress }),
777
+ gameId,
778
+ turn,
779
+ contractAddress: instanceAddress,
780
+ chainId: this.chainId,
781
+ });
782
+ };
783
+ this.decryptVote = async (vote, privateKey) => {
784
+ const decrypted = aes_1.default.decrypt(vote, privateKey).toString(crypto_js_1.default.enc.Utf8);
785
+ if (!decrypted) {
786
+ throw new Error("Failed to decrypt vote");
787
+ }
788
+ try {
789
+ const parsed = JSON.parse(decrypted);
790
+ (0, log_1.logger)(`Decrypted vote:`, 2);
791
+ (0, log_1.logger)(parsed, 2);
792
+ return parsed.map((v) => BigInt(v));
793
+ // eslint-disable-next-line
794
+ }
795
+ catch (e) {
796
+ throw new Error("Unexpected token");
797
+ }
798
+ };
799
+ /**
800
+ * Creates and signs a vote for testing purposes
801
+ * @param params - Parameters including voter, game info, and vote configuration
802
+ * @returns A complete mock vote with signatures
803
+ */
804
+ this.attestVote = async ({ voter, gameId, turn, vote, verifierAddress, }) => {
805
+ (0, log_1.logger)(`Attesting vote for player ${voter} in game ${gameId}, turn ${turn}`);
806
+ const players = await this.getPlayers({ instanceAddress: verifierAddress, gameId });
807
+ const validationResult = await this.validateVote({ gameId, turn, voter, vote, instanceAddress: verifierAddress, players: [...players] });
808
+ if (!validationResult.result) {
809
+ throw new Error('Vote validation failed: ' + validationResult.reason);
810
+ }
811
+ const gameSize = players.length;
812
+ const instance = new InstanceBase_1.default({
813
+ instanceAddress: verifierAddress,
814
+ publicClient: this.publicClient,
815
+ chainId: this.chainId,
816
+ });
817
+ const eip712 = await instance.getEIP712Domain();
818
+ const playerSalt = await this.getTurnPlayersSalt({
819
+ gameId,
820
+ turn,
821
+ player: voter,
822
+ verifierAddress,
823
+ size: gameSize,
824
+ });
825
+ const ballot = {
826
+ vote: vote,
827
+ salt: playerSalt,
828
+ };
829
+ const ballotHash = (0, viem_1.keccak256)((0, viem_1.encodePacked)(["uint256[]", "bytes32"], [vote, playerSalt]));
830
+ const turnKey = await this.calculateSharedTurnKey({
831
+ instanceAddress: verifierAddress,
832
+ gameId,
833
+ turn,
834
+ player: voter,
835
+ });
836
+ const ballotId = aes_1.default.encrypt(JSON.stringify(ballot.vote.map((v) => v.toString())), turnKey).toString();
837
+ const gmSignature = await this.signVote({
838
+ verifierAddress,
839
+ voter,
840
+ gameId,
841
+ sealedBallotId: ballotId,
842
+ ballotHash,
843
+ name: eip712.name,
844
+ version: eip712.version,
845
+ });
846
+ (0, log_1.logger)(`Vote attested for player ${voter} by ${this.walletClient.account?.address}`);
847
+ (0, log_1.logger)({
848
+ gameId,
849
+ turn,
850
+ vote,
851
+ verifierAddress,
852
+ gameSize,
853
+ name: eip712.name,
854
+ version: eip712.version,
855
+ chainId: this.chainId,
856
+ }, 2);
857
+ return { vote, ballotHash, ballot, ballotId, gmSignature };
858
+ };
859
+ /**
860
+ * Signs a vote
861
+ * @param params - Parameters including voter, game info, and vote configuration
862
+ * @returns The signed vote
863
+ */
864
+ this.signVote = async (params) => {
865
+ const { voter, gameId, verifierAddress, sealedBallotId, ballotHash, name, version } = params;
866
+ (0, log_1.logger)(`Signing vote for player ${voter} in game ${gameId}`);
867
+ const types = {
868
+ SubmitVote: [
869
+ { name: "gameId", type: "uint256" },
870
+ { name: "voter", type: "address" },
871
+ { name: "sealedBallotId", type: "string" },
872
+ { name: "ballotHash", type: "bytes32" },
873
+ ],
874
+ };
875
+ if (!this.walletClient.account)
876
+ throw new Error("No account");
877
+ const signature = await this.walletClient.signTypedData({
878
+ domain: {
879
+ name,
880
+ version,
881
+ chainId: this.chainId,
882
+ verifyingContract: verifierAddress,
883
+ },
884
+ types,
885
+ primaryType: "SubmitVote",
886
+ message: {
887
+ gameId,
888
+ voter,
889
+ sealedBallotId,
890
+ ballotHash,
891
+ },
892
+ account: this.walletClient.account,
893
+ });
894
+ (0, log_1.logger)(`Vote signed for player ${voter}`);
895
+ return signature;
896
+ };
897
+ /**
898
+ * Generates integrity data for the end of a game turn
899
+ * @param params - Parameters including game info, turn, and proposal data
900
+ * @returns Integrity data including permutation, secret, and proof
901
+ */
902
+ this.generateEndTurnIntegrity = async ({ gameId, turn, verifierAddress, size = 15, proposals, }) => {
903
+ let _proposals = [...proposals];
904
+ const { permutation: prevTurnPermutation, turnSalt: prevTurnSalt } = await this.generateDeterministicPermutation({
905
+ gameId,
906
+ turn: turn - 1n,
907
+ verifierAddress,
908
+ size,
909
+ });
910
+ const values = await Promise.all(_proposals.map((p) => p.proposal === ""
911
+ ? {
912
+ proposalValue: 0n,
913
+ randomnessValue: 0n,
914
+ proposer: p.proposer,
915
+ }
916
+ : this.proposalValues({
917
+ instanceAddress: verifierAddress,
918
+ gameId,
919
+ proposal: p.proposal,
920
+ turn,
921
+ proposer: p.proposer,
922
+ })));
923
+ (0, log_1.logger)("proposals with added empty proposals:", 3);
924
+ (0, log_1.logger)(_proposals, 3);
925
+ const inputs = await this.createInputs({
926
+ numActive: size,
927
+ proposals: values.map((v) => v.proposalValue),
928
+ commitmentRandomnesses: values.map((v) => v.randomnessValue),
929
+ gameId,
930
+ turn,
931
+ verifierAddress,
932
+ });
933
+ (0, log_1.logger)("inputs:", 3);
934
+ (0, log_1.logger)(inputs, 3);
935
+ // Apply permutation to proposals array
936
+ console.log("permutation used on new proposals:", inputs.permutation);
937
+ console.log("revealed permutation for prevTurn proposals:", prevTurnPermutation);
938
+ const permutedProposals = (0, permutations_1.permuteArray)({ array: _proposals, permutation: inputs.permutation });
939
+ console.log("permutedProposals:", permutedProposals);
940
+ const config = {
941
+ circuitName: "ProposalsIntegrity15",
942
+ circuitArtifactsPath: path_1.default.join(__dirname, "../../zk_artifacts/circuits/proposals_integrity_15.circom/"),
943
+ verifierDirPath: path_1.default.join(__dirname, "../../zk_artifacts/verifiers"),
944
+ };
945
+ const implementer = new zkit_1.Groth16Implementer();
946
+ const circuit = new zkit_1.CircuitZKit(config, implementer);
947
+ const proof = await circuit.generateProof(inputs);
948
+ (0, log_1.logger)("proof:", 3);
949
+ (0, log_1.logger)(proof, 3);
950
+ const callData = await circuit.generateCalldata(proof);
951
+ (0, log_1.logger)("callData:", 3);
952
+ (0, log_1.logger)(callData, 3);
953
+ if (!proof) {
954
+ throw new Error("Proof not found");
955
+ }
956
+ // const callData = await circuit.generateCalldata(proof);
957
+ const a = callData[0].map((a) => BigInt(a));
958
+ const b = callData[1].map((b) => b.map((b) => BigInt(b)));
959
+ const c = callData[2].map((c) => BigInt(c));
960
+ return {
961
+ commitment: inputs.permutationCommitment,
962
+ prevTurnSalt,
963
+ prevTurnPermutation,
964
+ permutedProposals: permutedProposals.map((proposal) => proposal.proposal),
965
+ a,
966
+ b,
967
+ c,
968
+ };
969
+ };
970
+ /**
971
+ * Creates inputs for the proposal integrity circuit
972
+ * @param params - Parameters including number of active proposals, proposals, commitment random numbers, game ID, turn, and verifier address
973
+ * @returns The inputs for the proposal integrity circuit
974
+ */
975
+ this.createInputs = async ({ numActive, proposals, commitmentRandomnesses, gameId, turn, verifierAddress, }) => {
976
+ const poseidon = await (0, circomlibjs_1.buildPoseidon)();
977
+ // Initialize arrays with zeros
978
+ const commitments = Array(this.maxSlotSizeForProofs).fill(0n);
979
+ const randomnesses = Array(this.maxSlotSizeForProofs).fill(0n);
980
+ const permutedProposals = Array(this.maxSlotSizeForProofs).fill(0n);
981
+ // Generate deterministic permutation
982
+ const { permutation, turnSalt: secret, commitment } = await this.generateDeterministicPermutation({
983
+ gameId,
984
+ turn,
985
+ verifierAddress,
986
+ size: numActive,
987
+ });
988
+ // Fill arrays with values
989
+ for (let i = 0; i < this.maxSlotSizeForProofs; i++) {
990
+ if (i < numActive) {
991
+ // Active slots
992
+ const proposal = proposals[i];
993
+ const randomness = commitmentRandomnesses[i];
994
+ const hash = poseidon([proposal, randomness]);
995
+ commitments[i] = BigInt(poseidon.F.toObject(hash));
996
+ randomnesses[i] = randomness;
997
+ // Store proposal in permuted position
998
+ permutedProposals[permutation[i]] = proposal;
999
+ }
1000
+ else {
1001
+ (0, log_1.logger)(`Inactive slot ${i}`, 3);
1002
+ // Inactive slots
1003
+ const hash = poseidon([0n, 0n]);
1004
+ commitments[i] = BigInt(poseidon.F.toObject(hash));
1005
+ randomnesses[i] = 0n;
1006
+ // permutedProposals already 0n
1007
+ }
1008
+ }
1009
+ return {
1010
+ numActive: BigInt(numActive),
1011
+ commitments,
1012
+ permutedProposals,
1013
+ permutationCommitment: commitment,
1014
+ permutation: permutation.map((p) => BigInt(p)),
1015
+ randomnesses,
1016
+ permutationRandomness: secret,
1017
+ };
1018
+ };
1019
+ this.chainId = chainId;
1020
+ this.publicClient = publicClient;
1021
+ this.walletClient = walletClient;
1022
+ }
1023
+ hasVoted({ vote }) {
1024
+ return vote?.reduce((a, b) => a + b, 0n) !== 0n;
1025
+ }
1026
+ ;
1027
+ getGameState({ gameId, instanceAddress }) {
1028
+ const baseInstance = new InstanceBase_1.default({
1029
+ instanceAddress,
1030
+ publicClient: this.publicClient,
1031
+ chainId: this.chainId,
1032
+ });
1033
+ return baseInstance.getGameStateDetails(gameId);
1034
+ }
1035
+ /**
1036
+ * Gets proposal integrity data for testing
1037
+ * @param params - Parameters including game info and proposal data
1038
+ * @returns Proposal integrity information including permutations and proofs
1039
+ */
1040
+ async getProposalsIntegrity({ size, gameId, turn, proposals, verifierAddress, }) {
1041
+ (0, log_1.logger)(`Generating proposals integrity for game ${gameId}, turn ${turn} with ${size} players.`);
1042
+ const { commitment, prevTurnSalt, prevTurnPermutation, permutedProposals, a, b, c } = await this.generateEndTurnIntegrity({
1043
+ gameId,
1044
+ turn,
1045
+ verifierAddress,
1046
+ size,
1047
+ proposals,
1048
+ });
1049
+ (0, log_1.logger)(`Generated proposals integrity with commitment ${commitment}`);
1050
+ return {
1051
+ newProposals: {
1052
+ a,
1053
+ b,
1054
+ c,
1055
+ proposals: permutedProposals,
1056
+ permutationCommitment: commitment,
1057
+ },
1058
+ prevTurnPermutation: prevTurnPermutation.map((p) => BigInt(p)),
1059
+ proposalsNotPermuted: proposals.map((proposal) => proposal.proposal),
1060
+ prevTurnSalt,
1061
+ };
1062
+ }
1063
+ }
1064
+ exports.GameMaster = GameMaster;
1065
+ exports.default = GameMaster;
1066
+ //# sourceMappingURL=GameMaster.js.map