@cofhe/foundry-plugin 0.6.1 → 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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @cofhe/foundry-plugin
2
2
 
3
- Foundry test utilities for [CoFHE](https://github.com/FhenixProtocol/cofhesdk). Provides `CofheTest` (base test contract with mock deployment) and `CofheClient` (SDK-like client for encrypting inputs, decrypting outputs, and managing permits).
3
+ Foundry test utilities for [CoFHE](https://github.com/FhenixProtocol/cofhesdk). Provides `CofheTest` (base test contract with mock deployment) and `CofheClient` (SDK-like client for encrypting inputs, decrypting outputs, and managing acps).
4
4
 
5
5
  ## Installation
6
6
 
@@ -53,10 +53,13 @@ contract MyTest is CofheTest {
53
53
  }
54
54
 
55
55
  function testEncryptAndStore() public {
56
- InEuint32 memory enc = client.createInEuint32(42);
56
+ // The last argument binds the input to the contract that will consume it.
57
+ (externalEuint32 hash, bytes memory signature) = client.createExternalEuint32(42, address(target));
58
+ externalEuint32[] memory hashes = new externalEuint32[](1);
59
+ hashes[0] = hash;
57
60
 
58
61
  vm.prank(client.account());
59
- target.store(enc);
62
+ target.storeBatch(hashes, signature);
60
63
 
61
64
  expectPlaintext(target.eValue(), uint32(42));
62
65
  }
@@ -79,15 +82,16 @@ contract MyTest is CofheTest {
79
82
 
80
83
  ### `CofheClient`
81
84
 
82
- | Function | Description |
83
- | --------------------------------------------- | -------------------------------------------------------------------------------- |
84
- | `connect(pkey)` | Sets the active account from a private key |
85
- | `account()` | Returns the connected account address |
86
- | `createInEbool/8/16/32/64/128/address(value)` | Creates a signed encrypted input |
87
- | `decryptForTx_withoutPermit(ctHash)` | Decrypts a globally-allowed ciphertext; returns `(ctHash, plaintext, signature)` |
88
- | `decryptForTx_withPermit(ctHash, permit)` | Decrypts with a permission; returns `(ctHash, plaintext, signature)` |
89
- | `decryptForView(ctHash, sealingKey, permit)` | Seals and unseals for off-chain reading |
90
- | `permit_createSelf()` | Creates a self-permit for the connected account |
91
- | `permit_createShared(recipient)` | Creates the issuer half of a shared permit |
92
- | `permit_exportShared(permit)` | Strips recipient fields for safe transmission |
93
- | `permit_importShared(export)` | Completes a shared permit as the recipient |
85
+ | Function | Description |
86
+ | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
87
+ | `connect(pkey)` | Sets the active account from a private key |
88
+ | `account()` | Returns the connected account address |
89
+ | `createExternalEbool/8/16/32/64/128/address(value, consumingContract)` | Creates a batch-signed encrypted input bound to `consumingContract`; returns `(external*Hash, bytes signature)` |
90
+ | `createEuint32sBatch(values, consumingContract)` | Creates a batch of encrypted uint32s sharing one signature |
91
+ | `decryptForTx_withoutACP(ctHash)` | Decrypts a globally-allowed ciphertext; returns `(ctHash, plaintext, signature)` |
92
+ | `decryptForTx_withACP(ctHash, acp)` | Decrypts with a permission; returns `(ctHash, plaintext, signature)` |
93
+ | `decryptForView(ctHash, sealingKey, acp)` | Seals and unseals for off-chain reading |
94
+ | `acp_createSelf()` | Creates a self-acp for the connected account |
95
+ | `acp_createShared(recipient)` | Creates the issuer half of a shared acp |
96
+ | `acp_exportShared(acp)` | Strips recipient fields for safe transmission |
97
+ | `acp_importShared(export)` | Completes a shared acp as the recipient |
@@ -10,19 +10,23 @@ import { MockZkVerifier } from '@cofhe/mock-contracts/contracts/MockZkVerifier.s
10
10
  import { MockZkVerifierSigner } from './MockZkVerifierSigner.sol';
11
11
  import { MockThresholdNetwork } from '@cofhe/mock-contracts/contracts/MockThresholdNetwork.sol';
12
12
  import { MockThresholdNetworkSigner } from './MockThresholdNetworkSigner.sol';
13
- import { Permission, PermissionUtils } from '@cofhe/mock-contracts/contracts/Permissioned.sol';
13
+ import { ACP, ACPUtils, SCOPE_GLOBAL } from '@cofhe/mock-contracts/contracts/Permissioned.sol';
14
14
  import {
15
15
  ZK_VERIFIER_SIGNER_ADDRESS,
16
16
  DECRYPT_RESULT_SIGNER_ADDRESS
17
17
  } from '@cofhe/mock-contracts/contracts/MockCoFHE.sol';
18
18
 
19
- /// @notice Portable representation of the issuer's half of a shared permit, safe to transmit as cleartext.
20
- struct SharedPermitExport {
19
+ /// @notice Portable representation of the issuer's half of a shared acp, safe to transmit as cleartext.
20
+ struct SharedACPExport {
21
21
  address issuer;
22
22
  uint64 expiration;
23
23
  address recipient;
24
- uint256 validatorId;
25
- address validatorContract;
24
+ uint256 revokerData;
25
+ address revokerContract;
26
+ // (scope) part of the issuer signature — the recipient needs them to reconstruct it
27
+ uint8 scope;
28
+ address[] contracts;
29
+ bytes32[] handles;
26
30
  bytes issuerSignature;
27
31
  }
28
32
 
@@ -74,98 +78,186 @@ contract CofheClient is Test {
74
78
  // =====================
75
79
  // ENCRYPT
76
80
  // =====================
77
-
78
- /// @notice Creates a signed encrypted input for the connected account (security zone 0).
79
- function createEncryptedInput(
81
+ // Every encryption helper takes the consuming contract explicitly as its last parameter - i.e.
82
+ // the contract that will call `FHE.asEuint*`/`FHE.asEuint*s` with the resulting hashes. The
83
+ // verifier binds this address into the signed digest (cofhe-contracts#77), so a batch signed
84
+ // for one contract cannot be replayed into another. It is a required argument rather than
85
+ // client-level state so that a single test can encrypt for several contracts without a
86
+ // stateful setter to keep track of.
87
+
88
+ /// @notice Computes, stores, and signs a single-item encrypted input via the canonical batch
89
+ /// path (createEncryptedInputsBatch, with a batch of size 1) - the root of all
90
+ /// encryption helpers on this client, single-item or not.
91
+ function _createSingleEncryptedInput(
80
92
  uint8 utype,
81
- uint256 value
82
- ) internal onlyConnected returns (EncryptedInput memory input) {
83
- input = mockZkVerifier.zkVerify(value, utype, _account, 0, block.chainid);
84
- input = mockZkVerifierSigner.zkVerifySign(input, _account);
85
- }
86
-
87
- /// @notice Creates an encrypted boolean input.
88
- function createInEbool(bool value) public returns (InEbool memory) {
89
- return abi.decode(abi.encode(createEncryptedInput(Utils.EBOOL_TFHE, value ? 1 : 0)), (InEbool));
90
- }
91
-
92
- /// @notice Creates an encrypted uint8 input.
93
- function createInEuint8(uint8 value) public returns (InEuint8 memory) {
94
- return abi.decode(abi.encode(createEncryptedInput(Utils.EUINT8_TFHE, value)), (InEuint8));
95
- }
96
-
97
- /// @notice Creates an encrypted uint16 input.
98
- function createInEuint16(uint16 value) public returns (InEuint16 memory) {
99
- return abi.decode(abi.encode(createEncryptedInput(Utils.EUINT16_TFHE, value)), (InEuint16));
100
- }
101
-
102
- /// @notice Creates an encrypted uint32 input.
103
- function createInEuint32(uint32 value) public returns (InEuint32 memory) {
104
- return abi.decode(abi.encode(createEncryptedInput(Utils.EUINT32_TFHE, value)), (InEuint32));
105
- }
106
-
107
- /// @notice Creates an encrypted uint64 input.
108
- function createInEuint64(uint64 value) public returns (InEuint64 memory) {
109
- return abi.decode(abi.encode(createEncryptedInput(Utils.EUINT64_TFHE, value)), (InEuint64));
110
- }
111
-
112
- /// @notice Creates an encrypted uint128 input.
113
- function createInEuint128(uint128 value) public returns (InEuint128 memory) {
114
- return abi.decode(abi.encode(createEncryptedInput(Utils.EUINT128_TFHE, value)), (InEuint128));
115
- }
116
-
117
- /// @notice Creates an encrypted address input.
118
- function createInEaddress(address value) public returns (InEaddress memory) {
119
- return abi.decode(abi.encode(createEncryptedInput(Utils.EADDRESS_TFHE, uint256(uint160(value)))), (InEaddress));
120
- }
121
-
122
- /// @notice Creates an encrypted boolean input as hash plus proof.
123
- function createInEbool_asHashPlusProof(bool value) public returns (externalEbool hash, bytes memory proof) {
124
- InEbool memory input = createInEbool(value);
125
- hash = externalEbool.wrap(bytes32(input.ctHash));
126
- proof = input.signature;
127
- }
128
-
129
- /// @notice Creates an encrypted uint8 input as hash plus proof.
130
- function createInEuint8_asHashPlusProof(uint8 value) public returns (externalEuint8 hash, bytes memory proof) {
131
- InEuint8 memory input = createInEuint8(value);
132
- hash = externalEuint8.wrap(bytes32(input.ctHash));
133
- proof = input.signature;
134
- }
135
-
136
- /// @notice Creates an encrypted uint16 input as hash plus proof.
137
- function createInEuint16_asHashPlusProof(uint16 value) public returns (externalEuint16 hash, bytes memory proof) {
138
- InEuint16 memory input = createInEuint16(value);
139
- hash = externalEuint16.wrap(bytes32(input.ctHash));
140
- proof = input.signature;
141
- }
142
-
143
- /// @notice Creates an encrypted uint32 input as hash plus proof.
144
- function createInEuint32_asHashPlusProof(uint32 value) public returns (externalEuint32 hash, bytes memory proof) {
145
- InEuint32 memory input = createInEuint32(value);
146
- hash = externalEuint32.wrap(bytes32(input.ctHash));
147
- proof = input.signature;
148
- }
149
-
150
- /// @notice Creates an encrypted uint64 input as hash plus proof.
151
- function createInEuint64_asHashPlusProof(uint64 value) public returns (externalEuint64 hash, bytes memory proof) {
152
- InEuint64 memory input = createInEuint64(value);
153
- hash = externalEuint64.wrap(bytes32(input.ctHash));
154
- proof = input.signature;
93
+ uint256 value,
94
+ address consumingContract
95
+ ) internal onlyConnected returns (uint256 ctHash, bytes memory signature) {
96
+ uint8[] memory utypes = new uint8[](1);
97
+ uint256[] memory values = new uint256[](1);
98
+ utypes[0] = utype;
99
+ values[0] = value;
100
+
101
+ (UnsignedEncryptedInput[] memory inputs, bytes memory sig) = createEncryptedInputsBatch(
102
+ utypes,
103
+ values,
104
+ consumingContract
105
+ );
106
+ ctHash = inputs[0].ctHash;
107
+ signature = sig;
108
+ }
109
+
110
+ /// @notice Creates an encrypted boolean input as hash plus proof, bound to `consumingContract`.
111
+ function createExternalEbool(
112
+ bool value,
113
+ address consumingContract
114
+ ) public returns (externalEbool hash, bytes memory proof) {
115
+ (uint256 ctHash, bytes memory signature) = _createSingleEncryptedInput(
116
+ Utils.EBOOL_TFHE,
117
+ value ? 1 : 0,
118
+ consumingContract
119
+ );
120
+ hash = externalEbool.wrap(bytes32(ctHash));
121
+ proof = signature;
122
+ }
123
+
124
+ /// @notice Creates an encrypted uint8 input as hash plus proof, bound to `consumingContract`.
125
+ function createExternalEuint8(
126
+ uint8 value,
127
+ address consumingContract
128
+ ) public returns (externalEuint8 hash, bytes memory proof) {
129
+ (uint256 ctHash, bytes memory signature) = _createSingleEncryptedInput(
130
+ Utils.EUINT8_TFHE,
131
+ value,
132
+ consumingContract
133
+ );
134
+ hash = externalEuint8.wrap(bytes32(ctHash));
135
+ proof = signature;
136
+ }
137
+
138
+ /// @notice Creates an encrypted uint16 input as hash plus proof, bound to `consumingContract`.
139
+ function createExternalEuint16(
140
+ uint16 value,
141
+ address consumingContract
142
+ ) public returns (externalEuint16 hash, bytes memory proof) {
143
+ (uint256 ctHash, bytes memory signature) = _createSingleEncryptedInput(
144
+ Utils.EUINT16_TFHE,
145
+ value,
146
+ consumingContract
147
+ );
148
+ hash = externalEuint16.wrap(bytes32(ctHash));
149
+ proof = signature;
150
+ }
151
+
152
+ /// @notice Creates an encrypted uint32 input as hash plus proof, bound to `consumingContract`.
153
+ function createExternalEuint32(
154
+ uint32 value,
155
+ address consumingContract
156
+ ) public returns (externalEuint32 hash, bytes memory proof) {
157
+ (uint256 ctHash, bytes memory signature) = _createSingleEncryptedInput(
158
+ Utils.EUINT32_TFHE,
159
+ value,
160
+ consumingContract
161
+ );
162
+ hash = externalEuint32.wrap(bytes32(ctHash));
163
+ proof = signature;
164
+ }
165
+
166
+ /// @notice Creates an encrypted uint64 input as hash plus proof, bound to `consumingContract`.
167
+ function createExternalEuint64(
168
+ uint64 value,
169
+ address consumingContract
170
+ ) public returns (externalEuint64 hash, bytes memory proof) {
171
+ (uint256 ctHash, bytes memory signature) = _createSingleEncryptedInput(
172
+ Utils.EUINT64_TFHE,
173
+ value,
174
+ consumingContract
175
+ );
176
+ hash = externalEuint64.wrap(bytes32(ctHash));
177
+ proof = signature;
178
+ }
179
+
180
+ /// @notice Creates an encrypted uint128 input as hash plus proof, bound to `consumingContract`.
181
+ function createExternalEuint128(
182
+ uint128 value,
183
+ address consumingContract
184
+ ) public returns (externalEuint128 hash, bytes memory proof) {
185
+ (uint256 ctHash, bytes memory signature) = _createSingleEncryptedInput(
186
+ Utils.EUINT128_TFHE,
187
+ value,
188
+ consumingContract
189
+ );
190
+ hash = externalEuint128.wrap(bytes32(ctHash));
191
+ proof = signature;
192
+ }
193
+
194
+ /// @notice Creates an encrypted address input as hash plus proof, bound to `consumingContract`.
195
+ function createExternalEaddress(
196
+ address value,
197
+ address consumingContract
198
+ ) public returns (externalEaddress hash, bytes memory proof) {
199
+ (uint256 ctHash, bytes memory signature) = _createSingleEncryptedInput(
200
+ Utils.EADDRESS_TFHE,
201
+ uint256(uint160(value)),
202
+ consumingContract
203
+ );
204
+ hash = externalEaddress.wrap(bytes32(ctHash));
205
+ proof = signature;
155
206
  }
156
207
 
157
- /// @notice Creates an encrypted uint128 input as hash plus proof.
158
- function createInEuint128_asHashPlusProof(uint128 value) public returns (externalEuint128 hash, bytes memory proof) {
159
- InEuint128 memory input = createInEuint128(value);
160
- hash = externalEuint128.wrap(bytes32(input.ctHash));
161
- proof = input.signature;
162
- }
208
+ // =====================
209
+ // ENCRYPT (BATCH)
210
+ // =====================
211
+ // The canonical batch verification path - a whole batch of inputs authenticated by a single
212
+ // signature over keccak256(h_0 || ... || h_n) (see MockZkVerifierSigner.zkVerifyBatchSign /
213
+ // MockTaskManager.batchVerifyInputs). This is the root of every encryption helper on this
214
+ // client - the single-item createExternalEuintX helpers above are thin wrappers over a batch of
215
+ // size 1, not a separate signing scheme.
216
+
217
+ /// @notice Creates a batch of encrypted inputs (mixed utypes allowed) sharing one signature,
218
+ /// all bound to `consumingContract`.
219
+ function createEncryptedInputsBatch(
220
+ uint8[] memory utypes,
221
+ uint256[] memory values,
222
+ address consumingContract
223
+ ) internal onlyConnected returns (UnsignedEncryptedInput[] memory inputs, bytes memory signature) {
224
+ require(utypes.length == values.length, 'CofheClient: length mismatch');
225
+ require(consumingContract != address(0), 'CofheClient: consuming contract must not be the zero address');
226
+
227
+ inputs = new UnsignedEncryptedInput[](utypes.length);
228
+ for (uint256 i = 0; i < utypes.length; i++) {
229
+ uint256 ctHash = mockZkVerifier.zkVerifyCalcCtHash(values[i], utypes[i], _account, 0, block.chainid);
230
+ mockZkVerifier.insertCtHash(ctHash, values[i]);
231
+ inputs[i] = UnsignedEncryptedInput({ ctHash: ctHash, securityZone: 0, utype: utypes[i] });
232
+ }
233
+
234
+ signature = mockZkVerifierSigner.zkVerifyBatchSign(inputs, _account, consumingContract);
235
+ }
236
+
237
+ /// @notice Creates a batch of encrypted uint32 inputs sharing one signature, all bound to
238
+ /// `consumingContract`.
239
+ function createEuint32sBatch(
240
+ uint32[] memory values,
241
+ address consumingContract
242
+ ) public returns (externalEuint32[] memory hashes, bytes memory signature) {
243
+ uint8[] memory utypes = new uint8[](values.length);
244
+ uint256[] memory rawValues = new uint256[](values.length);
245
+ for (uint256 i = 0; i < values.length; i++) {
246
+ utypes[i] = Utils.EUINT32_TFHE;
247
+ rawValues[i] = values[i];
248
+ }
249
+
250
+ (UnsignedEncryptedInput[] memory inputs, bytes memory sig) = createEncryptedInputsBatch(
251
+ utypes,
252
+ rawValues,
253
+ consumingContract
254
+ );
163
255
 
164
- /// @notice Creates an encrypted address input as hash plus proof.
165
- function createInEaddress_asHashPlusProof(address value) public returns (externalEaddress hash, bytes memory proof) {
166
- InEaddress memory input = createInEaddress(value);
167
- hash = externalEaddress.wrap(bytes32(input.ctHash));
168
- proof = input.signature;
256
+ hashes = new externalEuint32[](inputs.length);
257
+ for (uint256 i = 0; i < inputs.length; i++) {
258
+ hashes[i] = externalEuint32.wrap(bytes32(inputs[i].ctHash));
259
+ }
260
+ signature = sig;
169
261
  }
170
262
 
171
263
  // =====================
@@ -173,28 +265,28 @@ contract CofheClient is Test {
173
265
  // =====================
174
266
 
175
267
  /// @notice Decrypts a globally-allowed ciphertext and returns the plaintext with a publishable signature.
176
- function decryptForTx_withoutPermit(
268
+ function decryptForTx_withoutACP(
177
269
  bytes32 ctHash
178
270
  ) public view onlyConnected returns (bytes32, uint256, bytes memory) {
179
271
  uint256 ct = uint256(ctHash);
180
272
 
181
- (bool allowed, string memory error, uint256 decryptedValue) = mockThresholdNetwork.decryptForTxWithoutPermit(ct);
273
+ (bool allowed, string memory error, uint256 decryptedValue) = mockThresholdNetwork.decryptForTxWithoutACP(ct);
182
274
  require(allowed, string.concat('CofheClient: decryptForTx failed: ', error));
183
275
 
184
276
  bytes memory signature = mockThresholdNetworkSigner.signDecryptResult(ct, decryptedValue);
185
277
  return (ctHash, decryptedValue, signature);
186
278
  }
187
279
 
188
- /// @notice Decrypts a ciphertext using a permit and returns the plaintext with a publishable signature.
189
- function decryptForTx_withPermit(
280
+ /// @notice Decrypts a ciphertext using an ACP and returns the plaintext with a publishable signature.
281
+ function decryptForTx_withACP(
190
282
  bytes32 ctHash,
191
- Permission memory permission
283
+ ACP memory acp
192
284
  ) public view onlyConnected returns (bytes32, uint256, bytes memory) {
193
285
  uint256 ct = uint256(ctHash);
194
286
 
195
- (bool allowed, string memory error, uint256 decryptedValue) = mockThresholdNetwork.decryptForTxWithPermit(
287
+ (bool allowed, string memory error, uint256 decryptedValue) = mockThresholdNetwork.decryptForTxWithACP(
196
288
  ct,
197
- permission
289
+ acp
198
290
  );
199
291
  require(allowed, string.concat('CofheClient: decryptForTx failed: ', error));
200
292
 
@@ -206,22 +298,22 @@ contract CofheClient is Test {
206
298
  // DECRYPT FOR VIEW
207
299
  // =====================
208
300
 
209
- /// @notice Decrypts a ciphertext for off-chain reading by sealing/unsealing with the permit's sealing key.
210
- function decryptForView(bytes32 ctHash, Permission memory permission) public view onlyConnected returns (uint256) {
301
+ /// @notice Decrypts a ciphertext for off-chain reading by sealing/unsealing with the acp's sealing key.
302
+ function decryptForView(bytes32 ctHash, ACP memory acp) public view onlyConnected returns (uint256) {
211
303
  uint256 ct = uint256(ctHash);
212
304
 
213
305
  (bool allowed, string memory error, bytes32 sealedOutput) = mockThresholdNetwork.querySealOutput(
214
306
  ct,
215
307
  block.chainid,
216
- permission
308
+ acp
217
309
  );
218
310
  require(allowed, string.concat('CofheClient: decryptForView failed: ', error));
219
311
 
220
- return mockThresholdNetwork.unseal(sealedOutput, permission.sealingKey);
312
+ return mockThresholdNetwork.unseal(sealedOutput, acp.sealingKey);
221
313
  }
222
314
 
223
315
  // =====================
224
- // PERMITS
316
+ // ACPS
225
317
  // =====================
226
318
 
227
319
  bytes32 private constant PERMISSION_TYPE_HASH =
@@ -233,6 +325,7 @@ contract CofheClient is Test {
233
325
  uint256 chainId;
234
326
  address verifyingContract;
235
327
 
328
+ // ACP (V3): the signing domain lives on the ACL (name "ACL", version "2")
236
329
  (, name, version, chainId, verifyingContract, , ) = mockAcl.eip712Domain();
237
330
 
238
331
  return
@@ -251,35 +344,38 @@ contract CofheClient is Test {
251
344
  return abi.encodePacked(r, s, v);
252
345
  }
253
346
 
254
- function _signIssuerSelf(Permission memory permission, uint256 pkey) internal view returns (Permission memory) {
255
- bytes32 permissionHash = PermissionUtils.issuerSelfHash(permission);
347
+ function _signIssuerSelf(ACP memory acp, uint256 pkey) internal view returns (ACP memory) {
348
+ bytes32 permissionHash = ACPUtils.issuerSelfHash(acp);
256
349
  bytes32 structHash = permissionHashTypedDataV4(permissionHash);
257
- permission.issuerSignature = _signPermission(structHash, pkey);
258
- return permission;
350
+ acp.issuerSignature = _signPermission(structHash, pkey);
351
+ return acp;
259
352
  }
260
353
 
261
- function _signIssuerShared(Permission memory permission, uint256 pkey) internal view returns (Permission memory) {
262
- bytes32 permissionHash = PermissionUtils.issuerSharedHash(permission);
354
+ function _signIssuerShared(ACP memory acp, uint256 pkey) internal view returns (ACP memory) {
355
+ bytes32 permissionHash = ACPUtils.issuerSharedHash(acp);
263
356
  bytes32 structHash = permissionHashTypedDataV4(permissionHash);
264
- permission.issuerSignature = _signPermission(structHash, pkey);
265
- return permission;
357
+ acp.issuerSignature = _signPermission(structHash, pkey);
358
+ return acp;
266
359
  }
267
360
 
268
- function _signRecipient(Permission memory permission, uint256 pkey) internal view returns (Permission memory) {
269
- bytes32 permissionHash = PermissionUtils.recipientHash(permission);
361
+ function _signRecipient(ACP memory acp, uint256 pkey) internal view returns (ACP memory) {
362
+ bytes32 permissionHash = ACPUtils.recipientHash(acp);
270
363
  bytes32 structHash = permissionHashTypedDataV4(permissionHash);
271
- permission.recipientSignature = _signPermission(structHash, pkey);
272
- return permission;
364
+ acp.recipientSignature = _signPermission(structHash, pkey);
365
+ return acp;
273
366
  }
274
367
 
275
- /// @notice Returns a blank Permission with default field values.
276
- function createBasePermission() public pure returns (Permission memory permission) {
277
- permission = Permission({
368
+ /// @notice Returns a blank ACP with default field values.
369
+ function createBaseACP() public pure returns (ACP memory acp) {
370
+ acp = ACP({
278
371
  issuer: address(0),
279
372
  expiration: 1000000000000,
280
373
  recipient: address(0),
281
- validatorId: 0,
282
- validatorContract: address(0),
374
+ revokerData: 0,
375
+ revokerContract: address(0),
376
+ scope: SCOPE_GLOBAL,
377
+ contracts: new address[](0),
378
+ handles: new bytes32[](0),
283
379
  sealingKey: bytes32(0),
284
380
  issuerSignature: new bytes(0),
285
381
  recipientSignature: new bytes(0)
@@ -291,50 +387,56 @@ contract CofheClient is Test {
291
387
  return keccak256(abi.encodePacked(seed));
292
388
  }
293
389
 
294
- /// @notice Creates a self-permit for the connected account, signed with the stored private key.
295
- function permit_createSelf() public view onlyConnected returns (Permission memory permission) {
296
- permission = createBasePermission();
297
- permission.issuer = _account;
298
- permission.sealingKey = createSealingKey(uint256(uint160(_account)));
299
- permission = _signIssuerSelf(permission, _pkey);
300
- }
301
-
302
- /// @notice Creates the issuer side of a shared permit. The result has no sealingKey (added by recipient on import).
303
- function permit_createShared(address recipient) public view onlyConnected returns (Permission memory permission) {
304
- permission = createBasePermission();
305
- permission.issuer = _account;
306
- permission.recipient = recipient;
307
- permission = _signIssuerShared(permission, _pkey);
308
- }
309
-
310
- /// @notice Exports a shared permit, stripping sensitive/recipient-specific fields.
311
- function permit_exportShared(Permission memory permission) public pure returns (SharedPermitExport memory exported) {
312
- exported = SharedPermitExport({
313
- issuer: permission.issuer,
314
- expiration: permission.expiration,
315
- recipient: permission.recipient,
316
- validatorId: permission.validatorId,
317
- validatorContract: permission.validatorContract,
318
- issuerSignature: permission.issuerSignature
390
+ /// @notice Creates a self-acp for the connected account, signed with the stored private key.
391
+ function ACP_createSelf() public view onlyConnected returns (ACP memory acp) {
392
+ acp = createBaseACP();
393
+ acp.issuer = _account;
394
+ acp.sealingKey = createSealingKey(uint256(uint160(_account)));
395
+ acp = _signIssuerSelf(acp, _pkey);
396
+ }
397
+
398
+ /// @notice Creates the issuer side of a shared acp. The result has no sealingKey (added by recipient on import).
399
+ function ACP_createShared(address recipient) public view onlyConnected returns (ACP memory acp) {
400
+ acp = createBaseACP();
401
+ acp.issuer = _account;
402
+ acp.recipient = recipient;
403
+ acp = _signIssuerShared(acp, _pkey);
404
+ }
405
+
406
+ /// @notice Exports a shared acp, stripping sensitive/recipient-specific fields.
407
+ function ACP_exportShared(ACP memory acp) public pure returns (SharedACPExport memory exported) {
408
+ exported = SharedACPExport({
409
+ issuer: acp.issuer,
410
+ expiration: acp.expiration,
411
+ recipient: acp.recipient,
412
+ revokerData: acp.revokerData,
413
+ revokerContract: acp.revokerContract,
414
+ scope: acp.scope,
415
+ contracts: acp.contracts,
416
+ handles: acp.handles,
417
+ issuerSignature: acp.issuerSignature
319
418
  });
320
419
  }
321
420
 
322
- /// @notice Imports a shared permit export, adds the recipient's sealing key and signature.
323
- function permit_importShared(
324
- SharedPermitExport memory data
325
- ) public view onlyConnected returns (Permission memory permission) {
421
+ /// @notice Imports a shared acp export, adds the recipient's sealing key and signature.
422
+ function ACP_importShared(
423
+ SharedACPExport memory data
424
+ ) public view onlyConnected returns (ACP memory acp) {
326
425
  require(data.recipient == _account, 'CofheClient: recipient mismatch');
327
426
 
328
- permission = Permission({
427
+ acp = ACP({
329
428
  issuer: data.issuer,
330
429
  expiration: data.expiration,
331
430
  recipient: data.recipient,
332
- validatorId: data.validatorId,
333
- validatorContract: data.validatorContract,
431
+ revokerData: data.revokerData,
432
+ revokerContract: data.revokerContract,
433
+ scope: data.scope,
434
+ contracts: data.contracts,
435
+ handles: data.handles,
334
436
  sealingKey: createSealingKey(uint256(uint160(_account))),
335
437
  issuerSignature: data.issuerSignature,
336
438
  recipientSignature: new bytes(0)
337
439
  });
338
- permission = _signRecipient(permission, _pkey);
440
+ acp = _signRecipient(acp, _pkey);
339
441
  }
340
442
  }
@@ -5,6 +5,8 @@ import { Test } from 'forge-std/Test.sol';
5
5
  import '@fhenixprotocol/cofhe-contracts/FHE.sol';
6
6
  import { MockTaskManager } from '@cofhe/mock-contracts/contracts/MockTaskManager.sol';
7
7
  import { MockACL } from '@cofhe/mock-contracts/contracts/MockACL.sol';
8
+ import { ACPTimestampRevoker } from '@cofhe/mock-contracts/contracts/ACPTimestampRevoker.sol';
9
+ import { ACPShareRegistry } from '@cofhe/mock-contracts/contracts/ACPShareRegistry.sol';
8
10
  import { MockZkVerifier } from '@cofhe/mock-contracts/contracts/MockZkVerifier.sol';
9
11
  import { MockZkVerifierSigner } from './MockZkVerifierSigner.sol';
10
12
  import { MockThresholdNetwork } from '@cofhe/mock-contracts/contracts/MockThresholdNetwork.sol';
@@ -21,6 +23,8 @@ import {
21
23
  abstract contract CofheTest is Test {
22
24
  MockTaskManager public mockTaskManager;
23
25
  MockACL public mockAcl;
26
+ ACPTimestampRevoker public acpRevoker;
27
+ ACPShareRegistry public acpShareRegistry;
24
28
  MockZkVerifier public mockZkVerifier;
25
29
  MockZkVerifierSigner public mockZkVerifierSigner;
26
30
  MockThresholdNetwork public mockThresholdNetwork;
@@ -49,6 +53,16 @@ abstract contract CofheTest is Test {
49
53
  mockAcl = new MockACL();
50
54
  vm.label(address(mockAcl), 'MockACL');
51
55
 
56
+ // ACP (ACP V3): default revoker (verification inherited by the ACL)
57
+ acpRevoker = new ACPTimestampRevoker();
58
+ vm.label(address(acpRevoker), 'ACPTimestampRevoker');
59
+ mockAcl.setDefaultRevokerContract(address(acpRevoker));
60
+
61
+ // ACP: on-chain hand-off for sharing ACPs
62
+ acpShareRegistry = new ACPShareRegistry();
63
+ vm.label(address(acpShareRegistry), 'ACPShareRegistry');
64
+ mockAcl.setShareRegistry(address(acpShareRegistry));
65
+
52
66
  // 3. Link Task Manager <-> ACL, configure signers
53
67
  vm.startPrank(TM_ADMIN);
54
68
  mockTaskManager.setACLContract(address(mockAcl));
@@ -3,38 +3,44 @@
3
3
 
4
4
  pragma solidity >=0.8.19 <0.9.0;
5
5
 
6
- import { console } from 'forge-std/console.sol';
7
6
  import { Test } from 'forge-std/Test.sol';
8
- import { MessageHashUtils } from '@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol';
9
- import { EncryptedInput } from '@fhenixprotocol/cofhe-contracts/ICofhe.sol';
7
+ import { UnsignedEncryptedInput } from '@fhenixprotocol/cofhe-contracts/ICofhe.sol';
10
8
  import { ZK_VERIFIER_SIGNER_PRIVATE_KEY } from '@cofhe/mock-contracts/contracts/MockCoFHE.sol';
11
9
 
12
10
  /**
13
11
  * @dev Generates valid signatures for encrypted inputs.
14
12
  * Uses vm.sign to generate the signatures (only available in foundry tests)
15
- * Should not need to be interacted with directly, is part of the `createEncryptedInput` function set
13
+ * Should not need to be interacted with directly, is part of the `CofheClient.createExternal*` function set
16
14
  */
17
15
  contract MockZkVerifierSigner is Test {
18
- function zkVerifySignPacked(
19
- EncryptedInput[] memory inputs,
20
- address sender
21
- ) public view returns (EncryptedInput[] memory) {
22
- EncryptedInput[] memory signedInputs = new EncryptedInput[](inputs.length);
16
+ /// @notice The canonical batch signer: one signature over a whole batch of inputs, matching
17
+ /// `MockTaskManager.batchVerifyInputs`/`extractBatchSigner`'s digest -
18
+ /// keccak256(h_0 || h_1 || ... || h_n), where each h_i is the per-input message hash
19
+ /// (`ctHash || utype || securityZone || sender || chainid || contractAddress`).
20
+ /// `contractAddress` binds the batch to the specific contract that will consume it
21
+ /// (matching cofhe-contracts#77's contract-binding fix) - the caller must know in
22
+ /// advance which contract will call `FHE.asEuint*s` with the resulting hashes.
23
+ function zkVerifyBatchSign(
24
+ UnsignedEncryptedInput[] memory inputs,
25
+ address sender,
26
+ address contractAddress
27
+ ) public view returns (bytes memory signature) {
28
+ bytes memory concatenatedHashes;
23
29
  for (uint256 i = 0; i < inputs.length; i++) {
24
- signedInputs[i] = zkVerifySign(inputs[i], sender);
30
+ bytes memory combined = abi.encodePacked(
31
+ inputs[i].ctHash,
32
+ inputs[i].utype,
33
+ inputs[i].securityZone,
34
+ sender,
35
+ block.chainid,
36
+ contractAddress
37
+ );
38
+ concatenatedHashes = abi.encodePacked(concatenatedHashes, keccak256(combined));
25
39
  }
26
- return signedInputs;
27
- }
28
-
29
- function zkVerifySign(EncryptedInput memory input, address sender) public view returns (EncryptedInput memory) {
30
- bytes memory combined = abi.encodePacked(input.ctHash, input.utype, input.securityZone, sender, block.chainid);
31
-
32
- bytes32 expectedHash = keccak256(combined);
33
40
 
34
- (uint8 v, bytes32 r, bytes32 s) = vm.sign(ZK_VERIFIER_SIGNER_PRIVATE_KEY, expectedHash);
35
- bytes memory signature = abi.encodePacked(r, s, v); // note the order here is different from line above.
41
+ bytes32 batchHash = keccak256(concatenatedHashes);
36
42
 
37
- input.signature = signature;
38
- return input;
43
+ (uint8 v, bytes32 r, bytes32 s) = vm.sign(ZK_VERIFIER_SIGNER_PRIVATE_KEY, batchHash);
44
+ signature = abi.encodePacked(r, s, v);
39
45
  }
40
46
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cofhe/foundry-plugin",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Foundry plugin for CoFHE — Write and test FHE enabled contracts",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -24,9 +24,9 @@
24
24
  "fhenix"
25
25
  ],
26
26
  "dependencies": {
27
- "@fhenixprotocol/cofhe-contracts": "0.1.4",
27
+ "@fhenixprotocol/cofhe-contracts": "0.2.0-beta.3",
28
28
  "@openzeppelin/contracts": "5.4.0",
29
- "@cofhe/mock-contracts": "0.6.1"
29
+ "@cofhe/mock-contracts": "0.7.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "forge-std": "github:foundry-rs/forge-std#c0f966cb111f621bfb283b2a388bc7be18953bba",