@cofhe/mock-contracts 0.6.0 → 0.7.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.
- package/CHANGELOG.md +38 -0
- package/README.md +46 -30
- package/contracts/ABITest.sol +26 -13
- package/contracts/ACPShareRegistry.sol +145 -0
- package/contracts/ACPTimestampRevoker.sol +62 -0
- package/contracts/MockACL.sol +199 -20
- package/contracts/MockTaskManager.sol +90 -17
- package/contracts/MockThresholdNetwork.sol +19 -19
- package/contracts/Permissioned.sol +85 -124
- package/dist/index.d.mts +1023 -128
- package/dist/index.d.ts +1023 -128
- package/dist/index.js +868 -81
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +867 -82
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/ACPShareRegistry.ts +359 -0
- package/src/ACPTimestampRevoker.ts +134 -0
- package/src/MockACL.ts +207 -9
- package/src/MockTaskManager.ts +101 -50
- package/src/MockThresholdNetwork.ts +59 -14
- package/src/index.ts +10 -1
- package/src/typechain-types/ACPShareRegistry.ts +189 -0
- package/src/typechain-types/ACPTimestampRevoker.ts +157 -0
- package/src/typechain-types/MockACL.ts +151 -19
- package/src/typechain-types/MockTaskManager.ts +67 -33
- package/src/typechain-types/MockThresholdNetwork.ts +35 -29
- package/src/typechain-types/index.ts +2 -0
package/contracts/MockACL.sol
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
pragma solidity >=0.8.19 <0.9.0;
|
|
3
3
|
|
|
4
4
|
import { Strings } from '@openzeppelin/contracts/utils/Strings.sol';
|
|
5
|
-
import { MockPermissioned,
|
|
5
|
+
import { MockPermissioned, ACP, SCOPE_GLOBAL, SCOPE_CONTRACT, SCOPE_HANDLES } from './Permissioned.sol';
|
|
6
6
|
import { TASK_MANAGER_ADDRESS } from '@fhenixprotocol/cofhe-contracts/FHE.sol';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -27,6 +27,16 @@ contract MockACL is MockPermissioned {
|
|
|
27
27
|
/// @param sender Sender address.
|
|
28
28
|
error DirectAllowForbidden(address sender);
|
|
29
29
|
|
|
30
|
+
/// @notice Returned when no share is pending for `receiver` on `handle`.
|
|
31
|
+
/// @param handle Handle.
|
|
32
|
+
/// @param receiver Address the share would have been directed at.
|
|
33
|
+
error NotShared(uint256 handle, address receiver);
|
|
34
|
+
|
|
35
|
+
/// @notice Returned when a share is pending but was written by a different party.
|
|
36
|
+
/// @param expected Sharer the receiver named.
|
|
37
|
+
/// @param actual Sharer recorded in the slot.
|
|
38
|
+
error UnexpectedSharer(address expected, address actual);
|
|
39
|
+
|
|
30
40
|
/// @notice Emitted when a list of handles is allowed for decryption.
|
|
31
41
|
/// @param handlesList List of handles allowed for decryption.
|
|
32
42
|
event AllowedForDecryption(uint256[] handlesList);
|
|
@@ -37,17 +47,21 @@ contract MockACL is MockPermissioned {
|
|
|
37
47
|
/// @param contractAddress Contract address.
|
|
38
48
|
event NewDelegation(address indexed sender, address indexed delegatee, address indexed contractAddress);
|
|
39
49
|
|
|
50
|
+
/// @notice Emitted when the default revoker contract address is updated (zero = unset).
|
|
51
|
+
event DefaultRevokerContractUpdated(address oldAddress, address newAddress);
|
|
52
|
+
|
|
53
|
+
/// @notice Emitted when the share registry address is updated (zero = unset).
|
|
54
|
+
event ShareRegistryUpdated(address oldAddress, address newAddress);
|
|
55
|
+
|
|
40
56
|
/// @custom:storage-location erc7201:cofhe.storage.ACL
|
|
41
57
|
struct ACLStorage {
|
|
42
58
|
mapping(uint256 handle => bool isGlobal) globalHandles;
|
|
43
59
|
mapping(uint256 handle => mapping(address account => bool isAllowed)) persistedAllowedPairs;
|
|
44
60
|
mapping(uint256 => bool) allowedForDecryption;
|
|
45
61
|
mapping(address account => mapping(address delegatee => mapping(address contractAddress => bool isDelegate))) delegates;
|
|
46
|
-
/// @dev
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
/// In Hardhat automine mode (one tx per block) this faithfully replicates per-tx transience.
|
|
50
|
-
mapping(bytes32 => uint256) transientAllowanceBlocks;
|
|
62
|
+
/// @dev ACP infrastructure addresses served to SDKs (appended fields — do not reorder)
|
|
63
|
+
address defaultRevokerContract;
|
|
64
|
+
address shareRegistry;
|
|
51
65
|
}
|
|
52
66
|
|
|
53
67
|
/// @notice Name of the contract.
|
|
@@ -69,6 +83,11 @@ contract MockACL is MockPermissioned {
|
|
|
69
83
|
bytes32 private constant ACL_SLOT =
|
|
70
84
|
keccak256(abi.encode(uint256(keccak256('cofhe.storage.ACL')) - 1)) & ~bytes32(uint256(0xff));
|
|
71
85
|
|
|
86
|
+
/// @dev Domain separator for share slots. Transient allowance keys are
|
|
87
|
+
/// keccak256(abi.encodePacked(handle, account)) with no prefix, so share keys must be
|
|
88
|
+
/// separated to stop a (handle, receiver) pair aliasing between the two namespaces.
|
|
89
|
+
bytes32 private constant SHARE_DOMAIN = keccak256('cofhe.acl.share');
|
|
90
|
+
|
|
72
91
|
/// @custom:oz-upgrades-unsafe-allow constructor
|
|
73
92
|
constructor() MockPermissioned() {}
|
|
74
93
|
|
|
@@ -156,9 +175,92 @@ contract MockACL is MockPermissioned {
|
|
|
156
175
|
revert SenderNotAllowed(requester);
|
|
157
176
|
}
|
|
158
177
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
178
|
+
_allowTransient(handle, account);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @dev Derives the transient slot holding the sharer for a directed share.
|
|
183
|
+
* @param handle Handle.
|
|
184
|
+
* @param receiver Address the share is directed at.
|
|
185
|
+
*/
|
|
186
|
+
function _shareKey(uint256 handle, address receiver) private pure returns (bytes32) {
|
|
187
|
+
return keccak256(abi.encodePacked(SHARE_DOMAIN, handle, receiver));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* @dev tstore plus registration in the cleanup index at transient slot 0, so that
|
|
192
|
+
* cleanTransientStorage() clears the key. Every transient write goes through here,
|
|
193
|
+
* which is what keeps allowances and share slots cleared together.
|
|
194
|
+
* @param key Transient slot.
|
|
195
|
+
* @param value Value to store.
|
|
196
|
+
*/
|
|
197
|
+
function _tstoreTracked(bytes32 key, uint256 value) private {
|
|
198
|
+
assembly {
|
|
199
|
+
tstore(key, value)
|
|
200
|
+
let length := tload(0)
|
|
201
|
+
let lengthPlusOne := add(length, 1)
|
|
202
|
+
tstore(lengthPlusOne, key)
|
|
203
|
+
tstore(0, lengthPlusOne)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @dev Writes the transient allowance. Callers own the custody check.
|
|
209
|
+
* @param handle Handle.
|
|
210
|
+
* @param account Address being granted access.
|
|
211
|
+
*/
|
|
212
|
+
function _allowTransient(uint256 handle, address account) internal {
|
|
213
|
+
_tstoreTracked(keccak256(abi.encodePacked(handle, account)), 1);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* @notice Grants `receiver` transient access to `handle` and records `sharer` as the
|
|
218
|
+
* party that handed it over, for the duration of this transaction.
|
|
219
|
+
* @dev The caller must be the Task Manager contract.
|
|
220
|
+
* @dev Stricter than allowTransient: there is no TASK_MANAGER_ADDRESS bypass, since
|
|
221
|
+
* nothing shares on the Task Manager's own behalf.
|
|
222
|
+
* @param handle Handle.
|
|
223
|
+
* @param sharer Address handing the handle over.
|
|
224
|
+
* @param receiver Address the handle is being handed to.
|
|
225
|
+
*/
|
|
226
|
+
function shareCtHash(uint256 handle, address sharer, address receiver) public virtual {
|
|
227
|
+
if (msg.sender != TASK_MANAGER_ADDRESS_) {
|
|
228
|
+
revert DirectAllowForbidden(msg.sender);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (!isAllowed(handle, sharer)) {
|
|
232
|
+
revert SenderNotAllowed(sharer);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
_allowTransient(handle, receiver); // capability
|
|
236
|
+
_tstoreTracked(_shareKey(handle, receiver), uint256(uint160(sharer))); // provenance
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* @notice Consumes the share directed at `receiver` for `handle`.
|
|
241
|
+
* @dev The caller must be the Task Manager contract. The slot is cleared, so a
|
|
242
|
+
* share is claimable exactly once. On revert the clear is rolled back with
|
|
243
|
+
* the frame, leaving the share available to its intended receiver.
|
|
244
|
+
* @param handle Handle.
|
|
245
|
+
* @param expectedSharer Sharer the receiver requires. Must be the party that delivered the
|
|
246
|
+
* handle, not merely one the receiver trusts.
|
|
247
|
+
* @param receiver Address consuming the share.
|
|
248
|
+
*/
|
|
249
|
+
function receiveCtHash(uint256 handle, address expectedSharer, address receiver) public virtual {
|
|
250
|
+
if (msg.sender != TASK_MANAGER_ADDRESS_) {
|
|
251
|
+
revert DirectAllowForbidden(msg.sender);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
bytes32 shareKey = _shareKey(handle, receiver);
|
|
255
|
+
address sharer;
|
|
256
|
+
assembly {
|
|
257
|
+
sharer := tload(shareKey)
|
|
258
|
+
tstore(shareKey, 0)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (sharer == address(0)) revert NotShared(handle, receiver);
|
|
262
|
+
if (sharer != expectedSharer) revert UnexpectedSharer(expectedSharer, sharer);
|
|
263
|
+
if (!isAllowed(handle, sharer)) revert SenderNotAllowed(sharer);
|
|
162
264
|
}
|
|
163
265
|
|
|
164
266
|
/**
|
|
@@ -213,9 +315,12 @@ contract MockACL is MockPermissioned {
|
|
|
213
315
|
* @return isAllowedTransient Whether the account can access transiently the handle.
|
|
214
316
|
*/
|
|
215
317
|
function allowedTransient(uint256 handle, address account) public view virtual returns (bool) {
|
|
216
|
-
|
|
318
|
+
bool isAllowedTransient;
|
|
217
319
|
bytes32 key = keccak256(abi.encodePacked(handle, account));
|
|
218
|
-
|
|
320
|
+
assembly {
|
|
321
|
+
isAllowedTransient := tload(key)
|
|
322
|
+
}
|
|
323
|
+
return isAllowedTransient;
|
|
219
324
|
}
|
|
220
325
|
|
|
221
326
|
/**
|
|
@@ -269,13 +374,28 @@ contract MockACL is MockPermissioned {
|
|
|
269
374
|
}
|
|
270
375
|
|
|
271
376
|
/**
|
|
272
|
-
* @dev
|
|
273
|
-
*
|
|
377
|
+
* @dev This function removes the transient allowances, which could be useful for integration with
|
|
378
|
+
* Account Abstraction when bundling several UserOps calling the TaskManagerCoprocessor.
|
|
274
379
|
*/
|
|
275
380
|
function cleanTransientStorage() external virtual {
|
|
276
381
|
if (msg.sender != TASK_MANAGER_ADDRESS_) {
|
|
277
382
|
revert DirectAllowForbidden(msg.sender);
|
|
278
383
|
}
|
|
384
|
+
|
|
385
|
+
assembly {
|
|
386
|
+
let length := tload(0)
|
|
387
|
+
tstore(0, 0)
|
|
388
|
+
let lengthPlusOne := add(length, 1)
|
|
389
|
+
for {
|
|
390
|
+
let i := 1
|
|
391
|
+
} lt(i, lengthPlusOne) {
|
|
392
|
+
i := add(i, 1)
|
|
393
|
+
} {
|
|
394
|
+
let handle := tload(i)
|
|
395
|
+
tstore(i, 0)
|
|
396
|
+
tstore(handle, 0)
|
|
397
|
+
}
|
|
398
|
+
}
|
|
279
399
|
}
|
|
280
400
|
|
|
281
401
|
/**
|
|
@@ -307,14 +427,73 @@ contract MockACL is MockPermissioned {
|
|
|
307
427
|
}
|
|
308
428
|
}
|
|
309
429
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
430
|
+
|
|
431
|
+
// ---------------------------------------------------------------------------
|
|
432
|
+
// ACP (ACP V3) — scope-checked access
|
|
433
|
+
// ---------------------------------------------------------------------------
|
|
434
|
+
|
|
435
|
+
/// @notice Default revoker contract for newly created ACPs, served to SDKs (zero = unset).
|
|
436
|
+
function defaultRevokerContract() public view returns (address) {
|
|
437
|
+
return _getACLStorage().defaultRevokerContract;
|
|
315
438
|
}
|
|
316
439
|
|
|
317
|
-
|
|
318
|
-
|
|
440
|
+
/// @notice The ACPShareRegistry address, served to SDKs (zero = sharing unavailable).
|
|
441
|
+
function shareRegistry() public view returns (address) {
|
|
442
|
+
return _getACLStorage().shareRegistry;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/// @notice Sets the default revoker contract address (unrestricted in the mock;
|
|
446
|
+
/// the production setter is onlyOwner).
|
|
447
|
+
function setDefaultRevokerContract(address newAddress) external {
|
|
448
|
+
ACLStorage storage $ = _getACLStorage();
|
|
449
|
+
emit DefaultRevokerContractUpdated($.defaultRevokerContract, newAddress);
|
|
450
|
+
$.defaultRevokerContract = newAddress;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/// @notice Sets the share registry address (unrestricted in the mock;
|
|
454
|
+
/// the production setter is onlyOwner).
|
|
455
|
+
function setShareRegistry(address newAddress) external {
|
|
456
|
+
ACLStorage storage $ = _getACLStorage();
|
|
457
|
+
emit ShareRegistryUpdated($.shareRegistry, newAddress);
|
|
458
|
+
$.shareRegistry = newAddress;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/// @notice V3 (ACP) access check — the scope table.
|
|
462
|
+
///
|
|
463
|
+
/// | condition | result |
|
|
464
|
+
/// |------------------------------------------------------|--------|
|
|
465
|
+
/// | permission structure invalid (expired/sig/revoked) | REVERT |
|
|
466
|
+
/// | issuer does NOT have access to handle | false |
|
|
467
|
+
/// | permission.global | true |
|
|
468
|
+
/// | any permission.contracts allowed for handle | true |
|
|
469
|
+
/// | permission.handles contains handle | true |
|
|
470
|
+
/// | otherwise | false |
|
|
471
|
+
///
|
|
472
|
+
/// @dev Scopes narrow the issuer's existing access, never widen it.
|
|
473
|
+
/// Contract scope is an intersection over the EXISTING
|
|
474
|
+
/// `persistedAllowedPairs` (populated via FHE.allow/allowThis) —
|
|
475
|
+
/// no new data structures. Transient allowances deliberately do not
|
|
476
|
+
/// satisfy contract scope: only persisted grants count.
|
|
477
|
+
function isAllowedWithPermission(ACP memory acp, uint256 handle) public view withPermission(acp) returns (bool) {
|
|
478
|
+
// Scopes narrow the issuer's existing access, never widen it
|
|
479
|
+
if (!isAllowed(handle, acp.issuer)) return false;
|
|
480
|
+
|
|
481
|
+
if (acp.scope == SCOPE_GLOBAL) return true;
|
|
482
|
+
|
|
483
|
+
if (acp.scope == SCOPE_CONTRACT) {
|
|
484
|
+
for (uint256 i = 0; i < acp.contracts.length; i++) {
|
|
485
|
+
if (isAllowed(handle, acp.contracts[i])) return true;
|
|
486
|
+
}
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (acp.scope == SCOPE_HANDLES) {
|
|
491
|
+
for (uint256 i = 0; i < acp.handles.length; i++) {
|
|
492
|
+
if (acp.handles[i] == bytes32(handle)) return true;
|
|
493
|
+
}
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return false;
|
|
319
498
|
}
|
|
320
499
|
}
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
/* solhint-disable one-contract-per-file */
|
|
3
3
|
pragma solidity >=0.8.25 <0.9.0;
|
|
4
4
|
|
|
5
|
-
import { MockACL
|
|
5
|
+
import { MockACL } from './MockACL.sol';
|
|
6
|
+
import { ACP } from './Permissioned.sol';
|
|
6
7
|
// import {PlaintextsStorage} from "./PlaintextsStorage.sol";
|
|
7
8
|
import { Strings } from '@openzeppelin/contracts/utils/Strings.sol';
|
|
8
9
|
import { ECDSA } from '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';
|
|
9
10
|
import { MockCoFHE } from './MockCoFHE.sol';
|
|
10
|
-
import { ITaskManager, FunctionId, Utils,
|
|
11
|
+
import { ITaskManager, FunctionId, Utils, UnsignedEncryptedInput } from '@fhenixprotocol/cofhe-contracts/ICofhe.sol';
|
|
11
12
|
|
|
12
13
|
error DecryptionResultNotReady(uint256 ctHash);
|
|
13
14
|
// Input validation errors
|
|
@@ -556,26 +557,53 @@ contract MockTaskManager is ITaskManager, MockCoFHE {
|
|
|
556
557
|
}
|
|
557
558
|
}
|
|
558
559
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
560
|
+
/// @notice Verify a batch of encrypted inputs that share a single signature.
|
|
561
|
+
/// @dev The verifier signs the whole batch with one signature over
|
|
562
|
+
/// keccak256(h_0 || h_1 || ... || h_n), where each h_i is the same
|
|
563
|
+
/// per-input message hash defined by `inputMessageHash`:
|
|
564
|
+
/// keccak256(ctHash || utype || securityZone || sender || chainid || contractAddress).
|
|
565
|
+
/// `contractAddress` (the caller of this function) binds the batch to the specific
|
|
566
|
+
/// consuming contract, matching cofhe-contracts#77.
|
|
567
|
+
/// Inputs are processed in order; the returned hashes line up with `inputs`.
|
|
568
|
+
/// This is the sole verification path - the real FHE.sol library routes single
|
|
569
|
+
/// inputs through here too (as a one-element batch), so there's no separate
|
|
570
|
+
/// single-item `verifyInput` entry point to keep in sync.
|
|
571
|
+
/// @param inputs The encrypted inputs to verify (no per-input signature -
|
|
572
|
+
/// the batch is authenticated by the single `signature` argument).
|
|
573
|
+
/// @param sender The account the inputs are bound to.
|
|
574
|
+
/// @param signature The single ECDSA signature covering the whole batch.
|
|
575
|
+
/// @return appendedHashes The metadata-appended ct hashes, in input order.
|
|
576
|
+
function batchVerifyInputs(
|
|
577
|
+
UnsignedEncryptedInput[] memory inputs,
|
|
578
|
+
address sender,
|
|
579
|
+
bytes memory signature
|
|
580
|
+
) external returns (uint256[] memory) {
|
|
581
|
+
uint256 len = inputs.length;
|
|
562
582
|
// When signer is set to 0 address we skip this logic to be able to support debug use cases.
|
|
563
583
|
// In debug use cases we assume that the verifier is not necessarily running.
|
|
564
584
|
if (verifierSigner != address(0)) {
|
|
565
|
-
|
|
566
|
-
|
|
585
|
+
for (uint256 i = 0; i < len; i++) {
|
|
586
|
+
int32 securityZone = int32(uint32(inputs[i].securityZone));
|
|
587
|
+
if (!isValidSecurityZone(securityZone)) {
|
|
588
|
+
revert InvalidSecurityZone(securityZone, securityZoneMin, securityZoneMax);
|
|
589
|
+
}
|
|
567
590
|
}
|
|
568
591
|
|
|
569
|
-
address signer =
|
|
592
|
+
address signer = extractBatchSigner(inputs, sender, msg.sender, signature);
|
|
570
593
|
if (signer != verifierSigner) {
|
|
571
594
|
revert InvalidSigner(signer, verifierSigner);
|
|
572
595
|
}
|
|
573
596
|
}
|
|
574
597
|
|
|
575
|
-
uint256
|
|
598
|
+
uint256[] memory appendedHashes = new uint256[](len);
|
|
599
|
+
for (uint256 i = 0; i < len; i++) {
|
|
600
|
+
int32 securityZone = int32(uint32(inputs[i].securityZone));
|
|
601
|
+
uint256 appendedHash = TMCommon.appendMetadata(inputs[i].ctHash, securityZone, inputs[i].utype, false);
|
|
602
|
+
acl.allowTransient(appendedHash, msg.sender, address(this));
|
|
603
|
+
appendedHashes[i] = appendedHash;
|
|
604
|
+
}
|
|
576
605
|
|
|
577
|
-
|
|
578
|
-
return appendedHash;
|
|
606
|
+
return appendedHashes;
|
|
579
607
|
}
|
|
580
608
|
|
|
581
609
|
function allow(uint256 ctHash, address account) external {
|
|
@@ -623,11 +651,55 @@ contract MockTaskManager is ITaskManager, MockCoFHE {
|
|
|
623
651
|
return acl.isAllowed(ctHash, account);
|
|
624
652
|
}
|
|
625
653
|
|
|
626
|
-
function
|
|
627
|
-
|
|
628
|
-
|
|
654
|
+
function shareCtHash(uint256 ctHash, address receiver) external {
|
|
655
|
+
acl.shareCtHash(ctHash, msg.sender, receiver);
|
|
656
|
+
|
|
657
|
+
// NOTE: MOCK
|
|
658
|
+
MOCK_logAllow('FHE.share', ctHash, receiver);
|
|
659
|
+
}
|
|
629
660
|
|
|
630
|
-
|
|
661
|
+
function receiveCtHash(uint256 ctHash, address expectedSharer) external {
|
|
662
|
+
acl.receiveCtHash(ctHash, expectedSharer, msg.sender);
|
|
663
|
+
|
|
664
|
+
// NOTE: MOCK
|
|
665
|
+
MOCK_logAllow('FHE.receive', ctHash, expectedSharer);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/// @dev Per-input message hash used by batch verification:
|
|
669
|
+
/// keccak256(ctHash || utype || securityZone || sender || chainid || contractAddress).
|
|
670
|
+
/// `contractAddress` binds the input to the specific contract that consumes it
|
|
671
|
+
/// (the caller of `batchVerifyInputs`), matching cofhe-contracts#77 -
|
|
672
|
+
/// without this, a signed input observed on-chain could be replayed into any other
|
|
673
|
+
/// contract, which would then obtain an ACL allowance over the ciphertext.
|
|
674
|
+
function inputMessageHash(
|
|
675
|
+
uint256 ctHash,
|
|
676
|
+
uint8 utype,
|
|
677
|
+
uint8 securityZone,
|
|
678
|
+
address sender,
|
|
679
|
+
address contractAddress
|
|
680
|
+
) private view returns (bytes32) {
|
|
681
|
+
return keccak256(abi.encodePacked(ctHash, utype, securityZone, sender, block.chainid, contractAddress));
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/// @dev Recover the signer of a batch from the single signature over
|
|
685
|
+
/// keccak256(h_0 || h_1 || ... || h_n), where each h_i is `inputMessageHash`.
|
|
686
|
+
function extractBatchSigner(
|
|
687
|
+
UnsignedEncryptedInput[] memory inputs,
|
|
688
|
+
address sender,
|
|
689
|
+
address contractAddress,
|
|
690
|
+
bytes memory signature
|
|
691
|
+
) private view returns (address) {
|
|
692
|
+
bytes memory concatenatedHashes;
|
|
693
|
+
for (uint256 i = 0; i < inputs.length; i++) {
|
|
694
|
+
concatenatedHashes = abi.encodePacked(
|
|
695
|
+
concatenatedHashes,
|
|
696
|
+
inputMessageHash(inputs[i].ctHash, inputs[i].utype, inputs[i].securityZone, sender, contractAddress)
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
bytes32 batchHash = keccak256(concatenatedHashes);
|
|
701
|
+
|
|
702
|
+
address signer = ECDSA.recover(batchHash, signature);
|
|
631
703
|
|
|
632
704
|
if (signer == address(0)) {
|
|
633
705
|
revert InvalidSignature();
|
|
@@ -683,8 +755,9 @@ contract MockTaskManager is ITaskManager, MockCoFHE {
|
|
|
683
755
|
aggregator = _aggregatorAddress;
|
|
684
756
|
}
|
|
685
757
|
|
|
686
|
-
|
|
687
|
-
|
|
758
|
+
/// @notice ACP scope-checked access, forwarded to the ACL.
|
|
759
|
+
function isAllowedWithPermission(ACP memory acp, uint256 handle) public view returns (bool) {
|
|
760
|
+
return acl.isAllowedWithPermission(acp, handle);
|
|
688
761
|
}
|
|
689
762
|
|
|
690
763
|
// Stub implementations for new ITaskManager interface methods (inc PR #48)
|
|
@@ -9,7 +9,7 @@ pragma solidity >=0.8.19 <0.9.0;
|
|
|
9
9
|
|
|
10
10
|
import { MockACL } from './MockACL.sol';
|
|
11
11
|
import { MockTaskManager } from './MockTaskManager.sol';
|
|
12
|
-
import {
|
|
12
|
+
import { MockPermissioned, ACP } from './Permissioned.sol';
|
|
13
13
|
|
|
14
14
|
contract MockThresholdNetwork {
|
|
15
15
|
MockTaskManager public mockTaskManager;
|
|
@@ -35,10 +35,10 @@ contract MockThresholdNetwork {
|
|
|
35
35
|
function queryDecrypt(
|
|
36
36
|
uint256 ctHash,
|
|
37
37
|
uint256,
|
|
38
|
-
|
|
38
|
+
ACP memory acp
|
|
39
39
|
) public view returns (bool allowed, string memory error, uint256) {
|
|
40
40
|
bool isAllowed;
|
|
41
|
-
try mockAcl.isAllowedWithPermission(
|
|
41
|
+
try mockAcl.isAllowedWithPermission(acp, ctHash) returns (bool _isAllowed) {
|
|
42
42
|
isAllowed = _isAllowed;
|
|
43
43
|
} catch Error(string memory reason) {
|
|
44
44
|
// Handle string error messages
|
|
@@ -92,12 +92,12 @@ contract MockThresholdNetwork {
|
|
|
92
92
|
function querySealOutput(
|
|
93
93
|
uint256 ctHash,
|
|
94
94
|
uint256,
|
|
95
|
-
|
|
95
|
+
ACP memory acp
|
|
96
96
|
) public view returns (bool allowed, string memory error, bytes32) {
|
|
97
|
-
if (
|
|
97
|
+
if (acp.sealingKey == bytes32(0)) revert SealingKeyMissing();
|
|
98
98
|
|
|
99
99
|
bool isAllowed;
|
|
100
|
-
try mockAcl.isAllowedWithPermission(
|
|
100
|
+
try mockAcl.isAllowedWithPermission(acp, ctHash) returns (bool _isAllowed) {
|
|
101
101
|
isAllowed = _isAllowed;
|
|
102
102
|
} catch Error(string memory reason) {
|
|
103
103
|
// Handle string error messages
|
|
@@ -112,16 +112,16 @@ contract MockThresholdNetwork {
|
|
|
112
112
|
if (!isAllowed) return (false, 'NotAllowed', bytes32(0));
|
|
113
113
|
|
|
114
114
|
uint256 value = mockTaskManager.mockStorage(ctHash);
|
|
115
|
-
return (true, '', seal(value,
|
|
115
|
+
return (true, '', seal(value, acp.sealingKey));
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
// DECRYPT FOR TX
|
|
119
119
|
|
|
120
|
-
function
|
|
120
|
+
function _isAllowedWithACP(
|
|
121
121
|
uint256 ctHash,
|
|
122
|
-
|
|
122
|
+
ACP memory acp
|
|
123
123
|
) internal view returns (bool isAllowed, string memory error) {
|
|
124
|
-
try mockTaskManager.isAllowedWithPermission(
|
|
124
|
+
try mockTaskManager.isAllowedWithPermission(acp, ctHash) returns (bool _isAllowed) {
|
|
125
125
|
isAllowed = _isAllowed;
|
|
126
126
|
} catch Error(string memory reason) {
|
|
127
127
|
return (false, reason);
|
|
@@ -135,7 +135,7 @@ contract MockThresholdNetwork {
|
|
|
135
135
|
return (true, '');
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
function
|
|
138
|
+
function _isAllowedWithoutACP(uint256 ctHash) internal view returns (bool isAllowed, string memory error) {
|
|
139
139
|
try mockAcl.globalAllowed(ctHash) returns (bool _isAllowed) {
|
|
140
140
|
isAllowed = _isAllowed;
|
|
141
141
|
} catch Error(string memory reason) {
|
|
@@ -150,27 +150,27 @@ contract MockThresholdNetwork {
|
|
|
150
150
|
return (true, '');
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
/// @notice Decrypt a ciphertext for a transaction using
|
|
154
|
-
function
|
|
153
|
+
/// @notice Decrypt a ciphertext for a transaction using an ACP.
|
|
154
|
+
function decryptForTxWithACP(
|
|
155
155
|
uint256 ctHash,
|
|
156
|
-
|
|
156
|
+
ACP memory acp
|
|
157
157
|
) public view returns (bool allowed, string memory error, uint256 decryptedValue) {
|
|
158
|
-
if (
|
|
158
|
+
if (acp.issuer == address(0)) {
|
|
159
159
|
return (false, 'PermissionMissing', 0);
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
(bool isAllowed, string memory err) =
|
|
162
|
+
(bool isAllowed, string memory err) = _isAllowedWithACP(ctHash, acp);
|
|
163
163
|
if (!isAllowed) return (false, err, 0);
|
|
164
164
|
|
|
165
165
|
uint256 value = mockTaskManager.mockStorage(ctHash);
|
|
166
166
|
return (true, '', value);
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
/// @notice Decrypt a ciphertext for a transaction using global allowance (no
|
|
170
|
-
function
|
|
169
|
+
/// @notice Decrypt a ciphertext for a transaction using global allowance (no acp).
|
|
170
|
+
function decryptForTxWithoutACP(
|
|
171
171
|
uint256 ctHash
|
|
172
172
|
) public view returns (bool allowed, string memory error, uint256 decryptedValue) {
|
|
173
|
-
(bool isAllowed, string memory err) =
|
|
173
|
+
(bool isAllowed, string memory err) = _isAllowedWithoutACP(ctHash);
|
|
174
174
|
if (!isAllowed) return (false, err, 0);
|
|
175
175
|
|
|
176
176
|
uint256 value = mockTaskManager.mockStorage(ctHash);
|