@cofhe/foundry-plugin 0.6.1 → 0.7.1
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 +19 -15
- package/contracts/CofheClient.sol +257 -155
- package/contracts/CofheTest.sol +14 -0
- package/contracts/MockZkVerifierSigner.sol +27 -21
- package/package.json +3 -3
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
83
|
-
|
|
|
84
|
-
| `connect(pkey)`
|
|
85
|
-
| `account()`
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
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 {
|
|
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
|
|
20
|
-
struct
|
|
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
|
|
25
|
-
address
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/// @notice Creates an encrypted
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
|
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.
|
|
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
|
|
189
|
-
function
|
|
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
|
-
|
|
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.
|
|
287
|
+
(bool allowed, string memory error, uint256 decryptedValue) = mockThresholdNetwork.decryptForTxWithACP(
|
|
196
288
|
ct,
|
|
197
|
-
|
|
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
|
|
210
|
-
function decryptForView(bytes32 ctHash,
|
|
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
|
-
|
|
308
|
+
acp
|
|
217
309
|
);
|
|
218
310
|
require(allowed, string.concat('CofheClient: decryptForView failed: ', error));
|
|
219
311
|
|
|
220
|
-
return mockThresholdNetwork.unseal(sealedOutput,
|
|
312
|
+
return mockThresholdNetwork.unseal(sealedOutput, acp.sealingKey);
|
|
221
313
|
}
|
|
222
314
|
|
|
223
315
|
// =====================
|
|
224
|
-
//
|
|
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(
|
|
255
|
-
bytes32 permissionHash =
|
|
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
|
-
|
|
258
|
-
return
|
|
350
|
+
acp.issuerSignature = _signPermission(structHash, pkey);
|
|
351
|
+
return acp;
|
|
259
352
|
}
|
|
260
353
|
|
|
261
|
-
function _signIssuerShared(
|
|
262
|
-
bytes32 permissionHash =
|
|
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
|
-
|
|
265
|
-
return
|
|
357
|
+
acp.issuerSignature = _signPermission(structHash, pkey);
|
|
358
|
+
return acp;
|
|
266
359
|
}
|
|
267
360
|
|
|
268
|
-
function _signRecipient(
|
|
269
|
-
bytes32 permissionHash =
|
|
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
|
-
|
|
272
|
-
return
|
|
364
|
+
acp.recipientSignature = _signPermission(structHash, pkey);
|
|
365
|
+
return acp;
|
|
273
366
|
}
|
|
274
367
|
|
|
275
|
-
/// @notice Returns a blank
|
|
276
|
-
function
|
|
277
|
-
|
|
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
|
-
|
|
282
|
-
|
|
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-
|
|
295
|
-
function
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/// @notice Creates the issuer side of a shared
|
|
303
|
-
function
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/// @notice Exports a shared
|
|
311
|
-
function
|
|
312
|
-
exported =
|
|
313
|
-
issuer:
|
|
314
|
-
expiration:
|
|
315
|
-
recipient:
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
|
323
|
-
function
|
|
324
|
-
|
|
325
|
-
) public view onlyConnected returns (
|
|
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
|
-
|
|
427
|
+
acp = ACP({
|
|
329
428
|
issuer: data.issuer,
|
|
330
429
|
expiration: data.expiration,
|
|
331
430
|
recipient: data.recipient,
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
440
|
+
acp = _signRecipient(acp, _pkey);
|
|
339
441
|
}
|
|
340
442
|
}
|
package/contracts/CofheTest.sol
CHANGED
|
@@ -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 {
|
|
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 `
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
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.
|
|
3
|
+
"version": "0.7.1",
|
|
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.
|
|
27
|
+
"@fhenixprotocol/cofhe-contracts": "0.2.0",
|
|
28
28
|
"@openzeppelin/contracts": "5.4.0",
|
|
29
|
-
"@cofhe/mock-contracts": "0.
|
|
29
|
+
"@cofhe/mock-contracts": "0.7.1"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"forge-std": "github:foundry-rs/forge-std#c0f966cb111f621bfb283b2a388bc7be18953bba",
|