11// SPDX-License-Identifier: BSD-3-Clause-Clear
22pragma solidity ^ 0.8.24 ;
3- import { gatewayConfigAddress, kmsGenerationAddress } from "../addresses/GatewayAddresses.sol " ;
3+ import {
4+ gatewayConfigAddress,
5+ kmsGenerationAddress,
6+ coprocessorContextsAddress
7+ } from "../addresses/GatewayAddresses.sol " ;
48import { Strings } from "@openzeppelin/contracts/utils/Strings.sol " ;
59import { ICiphertextCommits } from "./interfaces/ICiphertextCommits.sol " ;
610import { IGatewayConfig } from "./interfaces/IGatewayConfig.sol " ;
711import { IKMSGeneration } from "./interfaces/IKMSGeneration.sol " ;
12+ import { ICoprocessorContexts } from "./interfaces/ICoprocessorContexts.sol " ;
813import { UUPSUpgradeableEmptyProxy } from "./shared/UUPSUpgradeableEmptyProxy.sol " ;
914import { GatewayConfigChecks } from "./shared/GatewayConfigChecks.sol " ;
1015import { GatewayOwnable } from "./shared/GatewayOwnable.sol " ;
1116import { CiphertextMaterial, SnsCiphertextMaterial } from "./shared/Structs.sol " ;
17+ import { ContextStatus } from "./shared/Enums.sol " ;
18+ import { ContextChecks } from "./shared/ContextChecks.sol " ;
1219
1320/**
1421 * @title CiphertextCommits smart contract
1522 * @notice See {ICiphertextCommits}.
1623 */
17- contract CiphertextCommits is ICiphertextCommits , UUPSUpgradeableEmptyProxy , GatewayOwnable , GatewayConfigChecks {
24+ contract CiphertextCommits is
25+ ICiphertextCommits ,
26+ UUPSUpgradeableEmptyProxy ,
27+ GatewayOwnable ,
28+ GatewayConfigChecks ,
29+ ContextChecks
30+ {
1831 /**
1932 * @notice The address of the GatewayConfig contract, used for fetching information about coprocessors.
2033 */
@@ -25,14 +38,17 @@ contract CiphertextCommits is ICiphertextCommits, UUPSUpgradeableEmptyProxy, Gat
2538 */
2639 IKMSGeneration private constant KMS_GENERATION = IKMSGeneration (kmsGenerationAddress);
2740
41+ /// @notice The address of the CoprocessorContexts contract, used for fetching information about coprocessors.
42+ ICoprocessorContexts private constant COPROCESSOR_CONTEXTS = ICoprocessorContexts (coprocessorContextsAddress);
43+
2844 /**
2945 * @dev The following constants are used for versioning the contract. They are made private
3046 * in order to force derived contracts to consider a different version. Note that
3147 * they can still define their own private constants with the same name.
3248 */
3349 string private constant CONTRACT_NAME = "CiphertextCommits " ;
3450 uint256 private constant MAJOR_VERSION = 0 ;
35- uint256 private constant MINOR_VERSION = 1 ;
51+ uint256 private constant MINOR_VERSION = 2 ;
3652 uint256 private constant PATCH_VERSION = 0 ;
3753
3854 /**
@@ -41,7 +57,7 @@ contract CiphertextCommits is ICiphertextCommits, UUPSUpgradeableEmptyProxy, Gat
4157 * This constant does not represent the number of time a specific contract have been upgraded,
4258 * as a contract deployed from version VX will have a REINITIALIZER_VERSION > 2.
4359 */
44- uint64 private constant REINITIALIZER_VERSION = 2 ;
60+ uint64 private constant REINITIALIZER_VERSION = 3 ;
4561
4662 /**
4763 * @notice The contract's variable storage struct (@dev see ERC-7201)
@@ -75,6 +91,11 @@ contract CiphertextCommits is ICiphertextCommits, UUPSUpgradeableEmptyProxy, Gat
7591 alreadyAddedCoprocessorTxSenders;
7692 /// @notice The coprocessor transaction senders involved in a consensus for a ciphertext material addition.
7793 mapping (bytes32 addCiphertextHash = > address [] coprocessorTxSenderAddresses ) coprocessorTxSenderAddresses;
94+ // ----------------------------------------------------------------------------------------------
95+ // Coprocessor context state variables:
96+ // ----------------------------------------------------------------------------------------------
97+ /// @notice The coprocessor context ID associated to the add ciphertext
98+ mapping (bytes32 addCiphertextHash = > uint256 contextId ) addCiphertextContextId;
7899 }
79100
80101 /**
@@ -99,34 +120,54 @@ contract CiphertextCommits is ICiphertextCommits, UUPSUpgradeableEmptyProxy, Gat
99120
100121 /**
101122 * @notice Re-initializes the contract from V1.
102- * @dev Define a `reinitializeVX` function once the contract needs to be upgraded.
103123 */
104124 /// @custom:oz-upgrades-unsafe-allow missing-initializer-call
105125 /// @custom:oz-upgrades-validate-as-initializer
106- // function reinitializeV2() public virtual reinitializer(REINITIALIZER_VERSION) {}
126+ function reinitializeV2 () public virtual reinitializer (REINITIALIZER_VERSION) {}
107127
108- /**
109- * @notice See {ICiphertextCommits-addCiphertextMaterial}.
110- */
128+ /// @notice See {ICiphertextCommits-addCiphertextMaterial}.
111129 function addCiphertextMaterial (
112130 bytes32 ctHandle ,
113131 uint256 keyId ,
114132 bytes32 ciphertextDigest ,
115133 bytes32 snsCiphertextDigest
116- ) external virtual onlyCoprocessorTxSender onlyHandleFromRegisteredHostChain (ctHandle) {
134+ ) external virtual onlyHandleFromRegisteredHostChain (ctHandle) refreshCoprocessorContextStatuses {
135+ // The addCiphertextHash is the hash of all received input arguments which means that multiple
136+ // Coprocessors can only have a consensus on a ciphertext material with the same information.
137+ // This hash is used to differentiate different calls to the function, in particular when
138+ // tracking the consensus on the received ciphertext material.
139+ // Note that chainId is not included in the hash because it is already contained in the ctHandle.
140+ bytes32 addCiphertextHash = keccak256 (abi.encode (ctHandle, keyId, ciphertextDigest, snsCiphertextDigest));
141+
117142 CiphertextCommitsStorage storage $ = _getCiphertextCommitsStorage ();
118143
144+ // Get the context ID from the input verification context ID mapping
145+ // This ID may be 0 (invalid) if this is the first addCiphertextMaterial call for this
146+ // addCiphertextHash (see right below)
147+ uint256 contextId = $.addCiphertextContextId[addCiphertextHash];
148+
149+ // If the context ID is null, get the active coprocessor context's ID and associate it to
150+ // this ciphertext material addition
151+ if (contextId == 0 ) {
152+ contextId = COPROCESSOR_CONTEXTS.getActiveCoprocessorContextId ();
153+ $.addCiphertextContextId[addCiphertextHash] = contextId;
154+
155+ // Else, that means a coprocessor already started to add the ciphertext material
156+ // and we need to check that the context is active or suspended
157+ // If it is not, that means the context is no longer valid for this operation and we revert
158+ } else if (! COPROCESSOR_CONTEXTS.isCoprocessorContextActiveOrSuspended (contextId)) {
159+ ContextStatus contextStatus = COPROCESSOR_CONTEXTS.getCoprocessorContextStatus (contextId);
160+ revert InvalidCoprocessorContextAddCiphertext (ctHandle, contextId, contextStatus);
161+ }
162+
163+ // Only accept coprocessor transaction senders from the same context
164+ _checkIsCoprocessorTxSenderFromContext (contextId, msg .sender );
165+
119166 // Check if the coprocessor transaction sender has already added the ciphertext handle.
120167 if ($.alreadyAddedCoprocessorTxSenders[ctHandle][msg .sender ]) {
121168 revert CoprocessorAlreadyAdded (ctHandle, msg .sender );
122169 }
123170
124- // The addCiphertextHash is the hash of all received input arguments which means that multiple
125- // Coprocessors can only have a consensus on a ciphertext material with the same information.
126- // This hash is used to differentiate different calls to the function, in particular when
127- // tracking the consensus on the received ciphertext material.
128- // Note that chainId is not included in the hash because it is already contained in the ctHandle.
129- bytes32 addCiphertextHash = keccak256 (abi.encode (ctHandle, keyId, ciphertextDigest, snsCiphertextDigest));
130171 $.addCiphertextHashCounters[addCiphertextHash]++ ;
131172
132173 // It is ok to only the handle can be considered here as a handle should only be added once
@@ -143,9 +184,10 @@ contract CiphertextCommits is ICiphertextCommits, UUPSUpgradeableEmptyProxy, Gat
143184
144185 // Send the event if and only if the consensus is reached in the current response call.
145186 // This means a "late" response will not be reverted, just ignored and no event will be emitted
187+ // Besides, consensus only considers the coprocessors of the same context
146188 if (
147189 ! $.isCiphertextMaterialAdded[ctHandle] &&
148- _isConsensusReached ($.addCiphertextHashCounters[addCiphertextHash])
190+ _isConsensusReached (contextId, $.addCiphertextHashCounters[addCiphertextHash])
149191 ) {
150192 $.ciphertextDigests[ctHandle] = ciphertextDigest;
151193 $.snsCiphertextDigests[ctHandle] = snsCiphertextDigest;
@@ -159,12 +201,7 @@ contract CiphertextCommits is ICiphertextCommits, UUPSUpgradeableEmptyProxy, Gat
159201 // by only knowing the handle, since a consensus can only happen once per handle
160202 $.ctHandleConsensusHash[ctHandle] = addCiphertextHash;
161203
162- emit AddCiphertextMaterial (
163- ctHandle,
164- ciphertextDigest,
165- snsCiphertextDigest,
166- $.coprocessorTxSenderAddresses[addCiphertextHash]
167- );
204+ emit AddCiphertextMaterial (ctHandle, ciphertextDigest, snsCiphertextDigest, contextId);
168205 }
169206 }
170207
@@ -191,16 +228,10 @@ contract CiphertextCommits is ICiphertextCommits, UUPSUpgradeableEmptyProxy, Gat
191228 revert CiphertextMaterialNotFound (ctHandles[i]);
192229 }
193230
194- // Get the unique hash associated to the handle and use it to get the list of coprocessor
195- // transaction sender address that were involved in the consensus
196- bytes32 addCiphertextHash = $.ctHandleConsensusHash[ctHandles[i]];
197- address [] memory coprocessorTxSenderAddresses = $.coprocessorTxSenderAddresses[addCiphertextHash];
198-
199231 ctMaterials[i] = CiphertextMaterial (
200232 ctHandles[i],
201233 $.keyIds[ctHandles[i]],
202- $.ciphertextDigests[ctHandles[i]],
203- coprocessorTxSenderAddresses
234+ $.ciphertextDigests[ctHandles[i]]
204235 );
205236 }
206237
@@ -222,29 +253,21 @@ contract CiphertextCommits is ICiphertextCommits, UUPSUpgradeableEmptyProxy, Gat
222253 revert CiphertextMaterialNotFound (ctHandles[i]);
223254 }
224255
225- // Get the unique hash associated to the handle and use it to get the list of coprocessor
226- // transaction sender address that were involved in the consensus
227- bytes32 addCiphertextHash = $.ctHandleConsensusHash[ctHandles[i]];
228- address [] memory coprocessorTxSenderAddresses = $.coprocessorTxSenderAddresses[addCiphertextHash];
229-
230256 snsCtMaterials[i] = SnsCiphertextMaterial (
231257 ctHandles[i],
232258 $.keyIds[ctHandles[i]],
233- $.snsCiphertextDigests[ctHandles[i]],
234- coprocessorTxSenderAddresses
259+ $.snsCiphertextDigests[ctHandles[i]]
235260 );
236261 }
237262
238263 return snsCtMaterials;
239264 }
240265
241266 /**
242- * @notice See {ICiphertextCommits-getAddCiphertextMaterialConsensusTxSenders }.
267+ * @notice See {ICiphertextCommits-getConsensusTxSenders }.
243268 * The list remains empty until the consensus is reached.
244269 */
245- function getAddCiphertextMaterialConsensusTxSenders (
246- bytes32 ctHandle
247- ) external view virtual returns (address [] memory ) {
270+ function getConsensusTxSenders (bytes32 ctHandle ) external view virtual returns (address [] memory ) {
248271 CiphertextCommitsStorage storage $ = _getCiphertextCommitsStorage ();
249272
250273 // Get the unique hash associated to the handle in order to retrieve the list of transaction
@@ -255,6 +278,54 @@ contract CiphertextCommits is ICiphertextCommits, UUPSUpgradeableEmptyProxy, Gat
255278 return $.coprocessorTxSenderAddresses[addCiphertextHash];
256279 }
257280
281+ /**
282+ * @notice See {ICiphertextCommits-getConsensusStorageUrls}.
283+ */
284+ function getConsensusStorageUrls (bytes32 [] calldata ctHandles ) external view virtual returns (string [][] memory ) {
285+ CiphertextCommitsStorage storage $ = _getCiphertextCommitsStorage ();
286+ string [][] memory consensusStorageUrls = new string [][](ctHandles.length );
287+
288+ for (uint256 i = 0 ; i < ctHandles.length ; i++ ) {
289+ // Check that the consensus has been reached
290+ if (! isCiphertextMaterialAdded (ctHandles[i])) {
291+ revert CiphertextMaterialNotFound (ctHandles[i]);
292+ }
293+
294+ // Get the unique hash associated to the handle in order to retrieve the list of transaction
295+ // sender address that participated in the consensus
296+ // This digest is null (0x0) for version V1.
297+ bytes32 addCiphertextHash = $.ctHandleConsensusHash[ctHandles[i]];
298+
299+ // Get the transaction sender addresses and the context ID associated to the consensus
300+ // If the consensus has been reached but the hash is 0x0, it means that the handle has been
301+ // added in V1: the handle was used to retrieve the list of transaction sender addresses
302+ // instead of the hash, under the first context (`contextId=1`).
303+ // We therefore consider this in order to be backward compatible.
304+ // DEPRECATED: to remove in next state reset
305+ // See https://github.com/zama-ai/fhevm-internal/issues/471
306+ address [] memory coprocessorTxSenderAddresses;
307+ uint256 contextId;
308+ if (addCiphertextHash == bytes32 (0 )) {
309+ coprocessorTxSenderAddresses = $.coprocessorTxSenderAddresses[ctHandles[i]];
310+ contextId = 1 ;
311+ } else {
312+ coprocessorTxSenderAddresses = $.coprocessorTxSenderAddresses[addCiphertextHash];
313+ contextId = $.addCiphertextContextId[addCiphertextHash];
314+ }
315+
316+ // Get the list of storage URLs associated to the transaction sender addresses
317+ string [] memory coprocessorStorageUrls = new string [](coprocessorTxSenderAddresses.length );
318+ for (uint256 j = 0 ; j < coprocessorTxSenderAddresses.length ; j++ ) {
319+ coprocessorStorageUrls[j] = COPROCESSOR_CONTEXTS
320+ .getCoprocessorFromContext (contextId, coprocessorTxSenderAddresses[j])
321+ .storageUrl;
322+ }
323+ consensusStorageUrls[i] = coprocessorStorageUrls;
324+ }
325+
326+ return consensusStorageUrls;
327+ }
328+
258329 /**
259330 * @notice See {ICiphertextCommits-getVersion}.
260331 */
@@ -280,12 +351,13 @@ contract CiphertextCommits is ICiphertextCommits, UUPSUpgradeableEmptyProxy, Gat
280351 function _authorizeUpgrade (address _newImplementation ) internal virtual override onlyGatewayOwner {}
281352
282353 /**
283- * @notice Checks if the consensus is reached among the Coprocessors.
354+ * @notice Checks if the consensus is reached among the coprocessors from the same context.
355+ * @param contextId The coprocessor context ID
284356 * @param coprocessorCounter The number of coprocessors that agreed
285357 * @return Whether the consensus is reached
286358 */
287- function _isConsensusReached (uint256 coprocessorCounter ) internal view virtual returns (bool ) {
288- uint256 consensusThreshold = GATEWAY_CONFIG. getCoprocessorMajorityThreshold ( );
359+ function _isConsensusReached (uint256 contextId , uint256 coprocessorCounter ) internal view virtual returns (bool ) {
360+ uint256 consensusThreshold = COPROCESSOR_CONTEXTS. getCoprocessorMajorityThresholdFromContext (contextId );
289361 return coprocessorCounter >= consensusThreshold;
290362 }
291363
0 commit comments