@inco/lightning 0.8.0-devnet-7 → 0.8.0-devnet-8
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/package.json +1 -1
- package/src/IncoLightning.sol +13 -0
- package/src/interfaces/IIncoLightning.sol +4 -0
- package/src/lightning-parts/EncryptedInput.sol +60 -5
- package/src/lightning-parts/TEELifecycle.sol +36 -28
- package/src/lightning-parts/interfaces/IEncryptedInput.sol +6 -0
- package/src/lightning-parts/primitives/HandleGeneration.sol +2 -2
- package/src/lightning-parts/primitives/test/SignatureVerifier.t.sol +1 -1
- package/src/lightning-parts/test/HandleMetadata.t.sol +59 -9
- package/src/test/FakeIncoInfra/FakeIncoInfraBase.sol +3 -3
- package/src/test/FakeIncoInfra/MockRemoteAttestation.sol +2 -1
- package/src/test/TEELifecycle/TEELifecycleMockTest.t.sol +81 -56
- package/src/test/TestDeploy.t.sol +28 -0
package/package.json
CHANGED
package/src/IncoLightning.sol
CHANGED
|
@@ -54,6 +54,7 @@ contract IncoLightning is
|
|
|
54
54
|
/// @param owner The address that will own this contract and can authorize upgrades
|
|
55
55
|
function initialize(address owner) public initializer {
|
|
56
56
|
__Ownable_init(owner);
|
|
57
|
+
_setAcceptedVersion(2, true);
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
/// @notice Withdraws accumulated protocol fees to the owner
|
|
@@ -63,6 +64,18 @@ contract IncoLightning is
|
|
|
63
64
|
_withdrawFeesTo(owner());
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
/// @notice Adds a version to the accepted input versions whitelist.
|
|
68
|
+
/// @param version The version number to accept (must be non-negative).
|
|
69
|
+
function addAcceptedVersion(uint16 version) external onlyOwner {
|
|
70
|
+
_setAcceptedVersion(version, true);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// @notice Removes a version from the accepted input versions whitelist.
|
|
74
|
+
/// @param version The version number to remove.
|
|
75
|
+
function removeAcceptedVersion(uint16 version) external onlyOwner {
|
|
76
|
+
_setAcceptedVersion(version, false);
|
|
77
|
+
}
|
|
78
|
+
|
|
66
79
|
/// @notice Required for CreateX deterministic deployment
|
|
67
80
|
/// @dev Empty fallback allows the contract to be deployed via CreateX's create2 mechanism
|
|
68
81
|
fallback() external {}
|
|
@@ -24,15 +24,67 @@ error ExternalHandleDoesNotMatchComputedHandle(
|
|
|
24
24
|
address aclAddress,
|
|
25
25
|
address userAddress,
|
|
26
26
|
address contractAddress,
|
|
27
|
-
|
|
27
|
+
uint16 version
|
|
28
28
|
);
|
|
29
29
|
|
|
30
|
+
/// @title EncryptedInputStorage
|
|
31
|
+
/// @notice Storage for EncryptedInput contract using ERC-7201 namespaced storage pattern
|
|
32
|
+
/// @dev Stores a whitelist of accepted versions for encrypted inputs.
|
|
33
|
+
abstract contract EncryptedInputStorage {
|
|
34
|
+
|
|
35
|
+
/// @notice Storage for version whitelist, using ERC-7201 namespaced storage.
|
|
36
|
+
struct StorageForEncryptedInput {
|
|
37
|
+
/// @notice Mapping of accepted version numbers for encrypted inputs.
|
|
38
|
+
/// @dev Only versions in this mapping are accepted for new inputs.
|
|
39
|
+
mapping(uint16 => bool) acceptedVersions;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// @notice Storage slot location using keccak256 of a unique namespace string
|
|
43
|
+
bytes32 private constant ENCRYPTED_INPUT_STORAGE_LOCATION = keccak256("inco.storage.EncryptedInput");
|
|
44
|
+
|
|
45
|
+
/// @notice Retrieves the storage struct from its dedicated slot
|
|
46
|
+
/// @dev Uses assembly to directly access the storage slot for gas efficiency
|
|
47
|
+
/// @return $ Reference to the StorageForEncryptedInput struct
|
|
48
|
+
function _getEncryptedInputStorage() internal pure returns (StorageForEncryptedInput storage $) {
|
|
49
|
+
bytes32 loc = ENCRYPTED_INPUT_STORAGE_LOCATION;
|
|
50
|
+
assembly {
|
|
51
|
+
$.slot := loc
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
}
|
|
56
|
+
|
|
30
57
|
/// @title EncryptedInput
|
|
31
58
|
/// @notice Handles the submission of client-encrypted values to create on-chain encrypted handles.
|
|
32
59
|
/// @dev Users encrypt values client-side and submit them with a prepended handle as a checksum.
|
|
33
60
|
/// The contract verifies the handle matches the on-chain computation to ensure context consistency.
|
|
34
61
|
/// This is a paid operation - users must send ETH to cover processing costs.
|
|
35
|
-
abstract contract EncryptedInput is
|
|
62
|
+
abstract contract EncryptedInput is
|
|
63
|
+
IEncryptedInput,
|
|
64
|
+
EncryptedInputStorage,
|
|
65
|
+
BaseAccessControlList,
|
|
66
|
+
HandleGeneration,
|
|
67
|
+
Fee
|
|
68
|
+
{
|
|
69
|
+
|
|
70
|
+
/// @notice Sets whether a version is accepted.
|
|
71
|
+
/// @param version The version number (must be non-negative).
|
|
72
|
+
/// @param accepted Whether the version should be accepted.
|
|
73
|
+
function _setAcceptedVersion(uint16 version, bool accepted) internal {
|
|
74
|
+
_getEncryptedInputStorage().acceptedVersions[version] = accepted;
|
|
75
|
+
if (accepted) {
|
|
76
|
+
emit VersionAccepted(version);
|
|
77
|
+
} else {
|
|
78
|
+
emit VersionRemoved(version);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// @notice Checks whether a version is accepted.
|
|
83
|
+
/// @param version The version number to check.
|
|
84
|
+
/// @return True if the version is in the whitelist.
|
|
85
|
+
function isAcceptedVersion(uint16 version) public view returns (bool) {
|
|
86
|
+
return _getEncryptedInputStorage().acceptedVersions[version];
|
|
87
|
+
}
|
|
36
88
|
|
|
37
89
|
/// @notice Emitted when a new encrypted input is submitted.
|
|
38
90
|
/// @param result The generated handle for the encrypted value.
|
|
@@ -44,7 +96,7 @@ abstract contract EncryptedInput is IEncryptedInput, BaseAccessControlList, Hand
|
|
|
44
96
|
bytes32 indexed result,
|
|
45
97
|
address indexed contractAddress,
|
|
46
98
|
address indexed user,
|
|
47
|
-
|
|
99
|
+
uint16 version,
|
|
48
100
|
bytes ciphertext,
|
|
49
101
|
uint256 eventId
|
|
50
102
|
);
|
|
@@ -111,10 +163,13 @@ abstract contract EncryptedInput is IEncryptedInput, BaseAccessControlList, Hand
|
|
|
111
163
|
function _newInput(bytes calldata input, address user, ETypes inputType) private returns (bytes32 handle) {
|
|
112
164
|
// Since there is no sensible way to handle abi.decode errors (https://github.com/argotorg/solidity/issues/10381)
|
|
113
165
|
// at least fail early on a conservative minimum length
|
|
114
|
-
require(input.length >= 68,
|
|
166
|
+
require(input.length >= 68, InputLengthTooShort(input.length));
|
|
115
167
|
// Parse the version from the first 4 bytes.
|
|
116
168
|
bytes4 prefix = bytes4(input[:4]);
|
|
117
|
-
|
|
169
|
+
uint16 version = uint16(uint32(prefix));
|
|
170
|
+
|
|
171
|
+
// Reject versions not in the whitelist
|
|
172
|
+
require(isAcceptedVersion(version), InvalidInputVersion(version));
|
|
118
173
|
// Remove external handle prepended to input as a checksum
|
|
119
174
|
(bytes32 externalHandle, bytes memory ciphertext) = abi.decode(input[4:], (bytes32, bytes));
|
|
120
175
|
handle = getInputHandle(ciphertext, user, msg.sender, version, inputType);
|
|
@@ -23,8 +23,6 @@ contract TEELifecycleStorage {
|
|
|
23
23
|
|
|
24
24
|
struct StorageForTeeLifecycle {
|
|
25
25
|
IQuoteVerifier quoteVerifier;
|
|
26
|
-
BootstrapResult verifiedBootstrapResult;
|
|
27
|
-
bool bootstrapComplete;
|
|
28
26
|
// @notice The list of approved TEE versions, each item is the MR_AGGREGATED 32 bytes
|
|
29
27
|
// @dev The index of the TEE version is the version number
|
|
30
28
|
bytes32[] approvedTeeVersions;
|
|
@@ -70,8 +68,6 @@ abstract contract TEELifecycle is
|
|
|
70
68
|
// Events
|
|
71
69
|
// @notice A new MR_AGGREGATED has been approved
|
|
72
70
|
event NewTEEVersionApproved(uint256 indexed version, bytes32 indexed mrAggregated);
|
|
73
|
-
// @notice A previously approved TEE version has been removed
|
|
74
|
-
event TEEVersionRemoved(bytes32 indexed mrAggregated);
|
|
75
71
|
event NewCovalidatorAdded(address covalidatorAddress, bytes quote);
|
|
76
72
|
event BootstrapStageComplete(address indexed newEoaSigner, BootstrapResult bootstrapResult);
|
|
77
73
|
// @notice Emitted to prove that an EOA has upgraded their TDX to a new
|
|
@@ -213,28 +209,6 @@ abstract contract TEELifecycle is
|
|
|
213
209
|
emit NewTEEVersionApproved($.approvedTeeVersions.length - 1, newMrAggregated);
|
|
214
210
|
}
|
|
215
211
|
|
|
216
|
-
/**
|
|
217
|
-
* @notice Removes a previously approved TEE version from the contract state
|
|
218
|
-
* @param mrAggregated - The MR_AGGREGATED bytes of the TEE version to remove
|
|
219
|
-
*/
|
|
220
|
-
function removeApprovedTeeVersion(bytes32 mrAggregated) public onlyOwner {
|
|
221
|
-
StorageForTeeLifecycle storage $ = getTeeLifecycleStorage();
|
|
222
|
-
bool found = false;
|
|
223
|
-
for (uint256 i = 0; i < $.approvedTeeVersions.length; i++) {
|
|
224
|
-
if ($.approvedTeeVersions[i] == mrAggregated) {
|
|
225
|
-
// Shift all elements after index i to the left to preserve insertion order
|
|
226
|
-
for (uint256 j = i; j < $.approvedTeeVersions.length - 1; j++) {
|
|
227
|
-
$.approvedTeeVersions[j] = $.approvedTeeVersions[j + 1];
|
|
228
|
-
}
|
|
229
|
-
$.approvedTeeVersions.pop();
|
|
230
|
-
found = true;
|
|
231
|
-
break;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
require(found, TEEVersionNotFound());
|
|
235
|
-
emit TEEVersionRemoved(mrAggregated);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
212
|
function _isApprovedTeeVersion(bytes32 newMrAggregated) internal view returns (bool) {
|
|
239
213
|
StorageForTeeLifecycle storage $ = getTeeLifecycleStorage();
|
|
240
214
|
for (uint256 i = 0; i < $.approvedTeeVersions.length; i++) {
|
|
@@ -283,6 +257,40 @@ abstract contract TEELifecycle is
|
|
|
283
257
|
return getTeeLifecycleStorage().networkPubkey.length > 0;
|
|
284
258
|
}
|
|
285
259
|
|
|
260
|
+
/**
|
|
261
|
+
* @notice Resets the contract state to the initial state.
|
|
262
|
+
* @dev This function performs a destructive operation and should be used carefully. In particular:
|
|
263
|
+
* 1) This function will remove all signers and reset the network pubkey and approved TEE versions, requiring a full bootstrap to restore normal operation.
|
|
264
|
+
* 2) The contract effectively enters a maintenance mode where any operations that depend on signers or the network key will fail until re-bootstrap is completed.
|
|
265
|
+
* 3) This function is intended to be called only as part of a coordinated key rotation or recovery process, with off-chain components updated accordingly.
|
|
266
|
+
* @dev This function is only callable by the owner.
|
|
267
|
+
*/
|
|
268
|
+
function reset() public onlyOwner {
|
|
269
|
+
getTeeLifecycleStorage().networkPubkey = bytes("");
|
|
270
|
+
getTeeLifecycleStorage().approvedTeeVersions = new bytes32[](0);
|
|
271
|
+
|
|
272
|
+
// Directly access SignatureVerifier storage to reset signers and threshold
|
|
273
|
+
// We bypass the normal functions because:
|
|
274
|
+
// 1. removeSigner is external and has threshold validation
|
|
275
|
+
// 2. setThreshold requires newThreshold > 0
|
|
276
|
+
// We emit RemovedSignatureVerifier and ThresholdChanged so off-chain
|
|
277
|
+
// systems tracking signer changes via events stay in sync.
|
|
278
|
+
StorageForSigVerifier storage sigStorage = getSigVerifierStorage();
|
|
279
|
+
uint256 oldThreshold = sigStorage.threshold;
|
|
280
|
+
if (oldThreshold > 0) {
|
|
281
|
+
emit ThresholdChanged(oldThreshold, 0);
|
|
282
|
+
}
|
|
283
|
+
sigStorage.threshold = 0;
|
|
284
|
+
// In theory, this could revert if there are many signers. But in
|
|
285
|
+
// practice, this should not be a problem.
|
|
286
|
+
while (sigStorage.signers.length > 0) {
|
|
287
|
+
address signer = sigStorage.signers[sigStorage.signers.length - 1];
|
|
288
|
+
sigStorage.isSigner[signer] = false;
|
|
289
|
+
sigStorage.signers.pop();
|
|
290
|
+
emit RemovedSignatureVerifier(signer);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
286
294
|
/**
|
|
287
295
|
* @notice From https://github.com/automata-network/automata-dcap-attestation/blob/evm-v1.0.0/evm/contracts/AttestationEntrypointBase.sol#L103
|
|
288
296
|
* @notice full on-chain verification for an attestation
|
|
@@ -381,7 +389,7 @@ abstract contract TEELifecycle is
|
|
|
381
389
|
|
|
382
390
|
/**
|
|
383
391
|
* @notice Computes the MR_AGGREGATED from the TD10 report body. It is defined as:
|
|
384
|
-
* KECCAK256(
|
|
392
|
+
* as KECCAK256(abi.encode(MRTD, RTMR0, RTMR1, RTMR2))
|
|
385
393
|
* @param mrTd - The MR_TD bytes
|
|
386
394
|
* @param rtMr0 - The RTMR0 bytes
|
|
387
395
|
* @param rtMr1 - The RTMR1 bytes
|
|
@@ -393,7 +401,7 @@ abstract contract TEELifecycle is
|
|
|
393
401
|
pure
|
|
394
402
|
returns (bytes32)
|
|
395
403
|
{
|
|
396
|
-
bytes memory message = abi.
|
|
404
|
+
bytes memory message = abi.encode(mrTd, rtMr0, rtMr1, rtMr2);
|
|
397
405
|
return keccak256(message);
|
|
398
406
|
}
|
|
399
407
|
|
|
@@ -5,6 +5,12 @@ import {euint256, ebool, eaddress} from "../../Types.sol";
|
|
|
5
5
|
|
|
6
6
|
interface IEncryptedInput {
|
|
7
7
|
|
|
8
|
+
error InvalidInputVersion(uint16 version);
|
|
9
|
+
error InputLengthTooShort(uint256 length);
|
|
10
|
+
|
|
11
|
+
event VersionAccepted(uint16 version);
|
|
12
|
+
event VersionRemoved(uint16 version);
|
|
13
|
+
|
|
8
14
|
function newEuint256(bytes calldata ciphertext, address user) external payable returns (euint256 newValue);
|
|
9
15
|
function newEbool(bytes calldata ciphertext, address user) external payable returns (ebool newValue);
|
|
10
16
|
function newEaddress(bytes calldata ciphertext, address user) external payable returns (eaddress newValue);
|
|
@@ -41,7 +41,7 @@ contract HandleGeneration is IHandleGeneration, HandleMetadata {
|
|
|
41
41
|
bytes memory ciphertext,
|
|
42
42
|
address user,
|
|
43
43
|
address contractAddress,
|
|
44
|
-
|
|
44
|
+
uint16 version,
|
|
45
45
|
ETypes inputType
|
|
46
46
|
) internal view returns (bytes32 generatedHandle) {
|
|
47
47
|
return getInputHandle(ciphertext, address(this), user, contractAddress, version, inputType);
|
|
@@ -61,7 +61,7 @@ contract HandleGeneration is IHandleGeneration, HandleMetadata {
|
|
|
61
61
|
address executorAddress,
|
|
62
62
|
address user,
|
|
63
63
|
address contractAddress,
|
|
64
|
-
|
|
64
|
+
uint16 version,
|
|
65
65
|
ETypes inputType
|
|
66
66
|
) internal view returns (bytes32 generatedHandle) {
|
|
67
67
|
// Here we ensure that our hashing scheme is binary-compatible between IncoLightning and IncoFhevm, this helps
|
|
@@ -17,7 +17,7 @@ contract TestSignatureVerifier is TestUtils, SignatureVerifier {
|
|
|
17
17
|
// Helper function to create sorted signatures
|
|
18
18
|
// Takes a digest and an array of private keys, generates signatures,
|
|
19
19
|
// and returns them sorted by signer address in ascending order
|
|
20
|
-
function getSortedSignatures(bytes32 digest, uint256[] memory privKeys) internal returns (bytes[] memory) {
|
|
20
|
+
function getSortedSignatures(bytes32 digest, uint256[] memory privKeys) internal pure returns (bytes[] memory) {
|
|
21
21
|
bytes[] memory signatures = new bytes[](privKeys.length);
|
|
22
22
|
address[] memory signers = new address[](privKeys.length);
|
|
23
23
|
|
|
@@ -22,6 +22,7 @@ import {VerifierAddressGetter} from "../primitives/VerifierAddressGetter.sol";
|
|
|
22
22
|
import {FEE} from "../Fee.sol";
|
|
23
23
|
import {HandleAlreadyExists} from "../../Errors.sol";
|
|
24
24
|
import {ExternalHandleDoesNotMatchComputedHandle} from "../EncryptedInput.sol";
|
|
25
|
+
import {IEncryptedInput} from "../interfaces/IEncryptedInput.sol";
|
|
25
26
|
|
|
26
27
|
contract TestHandleMetadata is
|
|
27
28
|
EIP712,
|
|
@@ -32,7 +33,9 @@ contract TestHandleMetadata is
|
|
|
32
33
|
EncryptedInput
|
|
33
34
|
{
|
|
34
35
|
|
|
35
|
-
constructor() EIP712("", "") VerifierAddressGetter(address(0)) {
|
|
36
|
+
constructor() EIP712("", "") VerifierAddressGetter(address(0)) {
|
|
37
|
+
_setAcceptedVersion(2, true);
|
|
38
|
+
}
|
|
36
39
|
|
|
37
40
|
function testTypeAssignment() public pure {
|
|
38
41
|
bytes32 someHandle = bytes32(keccak256("someHandle"));
|
|
@@ -105,16 +108,16 @@ contract TestHandleMetadata is
|
|
|
105
108
|
{
|
|
106
109
|
// We need a single word here to get correct encoding
|
|
107
110
|
bytes memory ciphertext = abi.encode(word);
|
|
111
|
+
uint16 version = 2; // version - X-Wing
|
|
108
112
|
bytes32 handle = getInputHandle(
|
|
109
113
|
ciphertext,
|
|
110
114
|
address(this),
|
|
111
115
|
user,
|
|
112
116
|
contractAddress,
|
|
113
|
-
|
|
114
|
-
/* unspecified */
|
|
117
|
+
version, // version - X-Wing
|
|
115
118
|
inputType
|
|
116
119
|
);
|
|
117
|
-
input = abi.encodePacked(
|
|
120
|
+
input = abi.encodePacked(uint32(version), abi.encode(handle, ciphertext));
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
// ============ Tests for HandleGeneration functions ============
|
|
@@ -155,6 +158,53 @@ contract TestHandleMetadata is
|
|
|
155
158
|
return newInputNotPaying(input, user, inputType);
|
|
156
159
|
}
|
|
157
160
|
|
|
161
|
+
function testNewInputUnwhitelistedVersion() public {
|
|
162
|
+
address self = address(this);
|
|
163
|
+
bytes32 ciphertextData = keccak256(abi.encodePacked("unwhitelisted_version"));
|
|
164
|
+
bytes memory ciphertext = abi.encode(ciphertextData);
|
|
165
|
+
uint16 unwhitelistedVersion = 0;
|
|
166
|
+
bytes32 handle =
|
|
167
|
+
getInputHandle(ciphertext, address(this), self, address(this), unwhitelistedVersion, ETypes.Uint256);
|
|
168
|
+
bytes memory badInput = abi.encodePacked(uint32(unwhitelistedVersion), abi.encode(handle, ciphertext));
|
|
169
|
+
|
|
170
|
+
vm.expectRevert(abi.encodeWithSelector(IEncryptedInput.InvalidInputVersion.selector, unwhitelistedVersion));
|
|
171
|
+
this.exposedNewInputNotPaying(badInput, self, ETypes.Uint256);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function testNewInputVersionAddedThenRemoved() public {
|
|
175
|
+
address self = address(this);
|
|
176
|
+
bytes32 ciphertextData = keccak256(abi.encodePacked("version_toggle"));
|
|
177
|
+
bytes memory ciphertext = abi.encode(ciphertextData);
|
|
178
|
+
uint16 version = 3;
|
|
179
|
+
|
|
180
|
+
bytes32 handle = getInputHandle(ciphertext, address(this), self, address(this), version, ETypes.Uint256);
|
|
181
|
+
bytes memory input = abi.encodePacked(uint32(version), abi.encode(handle, ciphertext));
|
|
182
|
+
|
|
183
|
+
// Version 3 is not accepted yet
|
|
184
|
+
vm.expectRevert(abi.encodeWithSelector(IEncryptedInput.InvalidInputVersion.selector, version));
|
|
185
|
+
this.exposedNewInputNotPaying(input, self, ETypes.Uint256);
|
|
186
|
+
|
|
187
|
+
// Whitelist version 3
|
|
188
|
+
_setAcceptedVersion(3, true);
|
|
189
|
+
|
|
190
|
+
// Now it should succeed
|
|
191
|
+
bytes32 resultHandle = this.exposedNewInputNotPaying(input, self, ETypes.Uint256);
|
|
192
|
+
assert(typeOf(resultHandle) == ETypes.Uint256);
|
|
193
|
+
|
|
194
|
+
// Remove version 3
|
|
195
|
+
_setAcceptedVersion(3, false);
|
|
196
|
+
|
|
197
|
+
// Use different ciphertext to avoid HandleAlreadyExists
|
|
198
|
+
bytes32 ciphertextData2 = keccak256(abi.encodePacked("version_toggle_2"));
|
|
199
|
+
bytes memory ciphertext2 = abi.encode(ciphertextData2);
|
|
200
|
+
bytes32 handle2 = getInputHandle(ciphertext2, address(this), self, address(this), version, ETypes.Uint256);
|
|
201
|
+
bytes memory input2 = abi.encodePacked(uint32(version), abi.encode(handle2, ciphertext2));
|
|
202
|
+
|
|
203
|
+
// Should revert again
|
|
204
|
+
vm.expectRevert(abi.encodeWithSelector(IEncryptedInput.InvalidInputVersion.selector, version));
|
|
205
|
+
this.exposedNewInputNotPaying(input2, self, ETypes.Uint256);
|
|
206
|
+
}
|
|
207
|
+
|
|
158
208
|
function testNewInputNotPaying() public {
|
|
159
209
|
address self = address(this);
|
|
160
210
|
bytes32 ciphertextData = keccak256(abi.encodePacked("notpaying_ciphertext"));
|
|
@@ -172,10 +222,10 @@ contract TestHandleMetadata is
|
|
|
172
222
|
returns (bytes memory input)
|
|
173
223
|
{
|
|
174
224
|
bytes memory ciphertext = abi.encode(word);
|
|
175
|
-
|
|
225
|
+
uint16 version = 2; // version - X-Wing
|
|
176
226
|
// For external calls via this., msg.sender is address(this)
|
|
177
227
|
bytes32 handle = getInputHandle(ciphertext, address(this), user, address(this), version, inputType);
|
|
178
|
-
input = abi.encodePacked(version, abi.encode(handle, ciphertext));
|
|
228
|
+
input = abi.encodePacked(uint32(version), abi.encode(handle, ciphertext));
|
|
179
229
|
}
|
|
180
230
|
|
|
181
231
|
// ============ Tests for EncryptedInput error branches ============
|
|
@@ -184,7 +234,7 @@ contract TestHandleMetadata is
|
|
|
184
234
|
address self = address(this);
|
|
185
235
|
// Input less than 64 bytes should revert
|
|
186
236
|
bytes memory shortInput = hex"deadbeef";
|
|
187
|
-
vm.expectRevert(
|
|
237
|
+
vm.expectRevert(abi.encodeWithSelector(IEncryptedInput.InputLengthTooShort.selector, shortInput.length));
|
|
188
238
|
this.exposedNewInputNotPaying(shortInput, self, ETypes.Uint256);
|
|
189
239
|
}
|
|
190
240
|
|
|
@@ -194,8 +244,8 @@ contract TestHandleMetadata is
|
|
|
194
244
|
bytes memory ciphertext = abi.encode(ciphertextData);
|
|
195
245
|
// Create input with wrong handle (just a random bytes32)
|
|
196
246
|
bytes32 wrongHandle = bytes32(uint256(12345));
|
|
197
|
-
|
|
198
|
-
bytes memory badInput = abi.encodePacked(version, abi.encode(wrongHandle, ciphertext));
|
|
247
|
+
uint16 version = 2; // version - X-Wing
|
|
248
|
+
bytes memory badInput = abi.encodePacked(uint32(version), abi.encode(wrongHandle, ciphertext));
|
|
199
249
|
|
|
200
250
|
// Compute the expected handle for the error message
|
|
201
251
|
bytes32 expectedHandle = getInputHandle(ciphertext, address(this), self, address(this), version, ETypes.Uint256);
|
|
@@ -19,16 +19,16 @@ contract FakeIncoInfraBase is TestUtils, KVStore, HandleGeneration {
|
|
|
19
19
|
{
|
|
20
20
|
// We need a single word here to get correct encoding
|
|
21
21
|
bytes memory ciphertext = abi.encode(word);
|
|
22
|
+
uint16 version = 2; // version - X-Wing
|
|
22
23
|
bytes32 handle = getInputHandle(
|
|
23
24
|
ciphertext,
|
|
24
25
|
address(inco),
|
|
25
26
|
user,
|
|
26
27
|
contractAddress,
|
|
27
|
-
|
|
28
|
-
/* unspecified */
|
|
28
|
+
version, // version - X-Wing
|
|
29
29
|
inputType
|
|
30
30
|
);
|
|
31
|
-
input = abi.encodePacked(
|
|
31
|
+
input = abi.encodePacked(uint32(version), abi.encode(handle, ciphertext));
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
function fakePrepareEuint256Ciphertext(uint256 value, address userAddress, address contractAddress)
|
|
@@ -60,7 +60,8 @@ contract MockRemoteAttestation is TestUtils {
|
|
|
60
60
|
hex"010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101";
|
|
61
61
|
// See DEFAULT_MR_AGGREGATED in attestation/src/remote_attestation.rs to
|
|
62
62
|
// see the calculation of the default value.
|
|
63
|
-
|
|
63
|
+
// Note: This uses abi.encode (not encodePacked) to avoid hash collision vulnerabilities.
|
|
64
|
+
mrAggregated = hex"3d48a1faa8620d86ae037f4fd6746987733d085314b3cd5d5d074ade8bab6ebd";
|
|
64
65
|
bootstrapResult = BootstrapResult({networkPubkey: networkPubkey});
|
|
65
66
|
|
|
66
67
|
quote = createQuote(mrtd, signer);
|
|
@@ -24,7 +24,8 @@ contract TEELifecycleMockTest is MockRemoteAttestation, TEELifecycle {
|
|
|
24
24
|
hex"010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101";
|
|
25
25
|
// See DEFAULT_MR_AGGREGATED in attestation/src/remote_attestation.rs to
|
|
26
26
|
// see the calculation of the default value.
|
|
27
|
-
|
|
27
|
+
// Note: This uses abi.encode (not encodePacked) to avoid hash collision vulnerabilities.
|
|
28
|
+
bytes32 testMrAggregated = hex"3d48a1faa8620d86ae037f4fd6746987733d085314b3cd5d5d074ade8bab6ebd";
|
|
28
29
|
|
|
29
30
|
function setUp() public {
|
|
30
31
|
getTeeLifecycleStorage().quoteVerifier = new FakeQuoteVerifier();
|
|
@@ -133,61 +134,6 @@ contract TEELifecycleMockTest is MockRemoteAttestation, TEELifecycle {
|
|
|
133
134
|
vm.stopPrank();
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
function testRemoveApprovedTeeVersionPreservesOrder() public {
|
|
137
|
-
bytes32 mrAggregated1 = hex"1111111111111111111111111111111111111111111111111111111111111111";
|
|
138
|
-
bytes32 mrAggregated2 = hex"2222222222222222222222222222222222222222222222222222222222222222";
|
|
139
|
-
bytes32 mrAggregated3 = hex"3333333333333333333333333333333333333333333333333333333333333333";
|
|
140
|
-
|
|
141
|
-
vm.startPrank(this.owner());
|
|
142
|
-
|
|
143
|
-
// Add three versions
|
|
144
|
-
this.approveNewTeeVersion(mrAggregated1);
|
|
145
|
-
this.approveNewTeeVersion(mrAggregated2);
|
|
146
|
-
this.approveNewTeeVersion(mrAggregated3);
|
|
147
|
-
|
|
148
|
-
// Verify all exist in order
|
|
149
|
-
assertEq(this.approvedTeeVersions(0), mrAggregated1);
|
|
150
|
-
assertEq(this.approvedTeeVersions(1), mrAggregated2);
|
|
151
|
-
assertEq(this.approvedTeeVersions(2), mrAggregated3);
|
|
152
|
-
|
|
153
|
-
// Remove the middle one (mrAggregated2)
|
|
154
|
-
this.removeApprovedTeeVersion(mrAggregated2);
|
|
155
|
-
|
|
156
|
-
// Verify insertion order is preserved: mrAggregated1 stays at 0, mrAggregated3 shifts to 1
|
|
157
|
-
assertEq(this.approvedTeeVersions(0), mrAggregated1);
|
|
158
|
-
assertEq(this.approvedTeeVersions(1), mrAggregated3);
|
|
159
|
-
|
|
160
|
-
// Verify index 2 is now out of bounds
|
|
161
|
-
vm.expectRevert(TEELifecycle.IndexOutOfBounds.selector);
|
|
162
|
-
this.approvedTeeVersions(2);
|
|
163
|
-
|
|
164
|
-
vm.stopPrank();
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function testRemoveApprovedTeeVersionNotFound() public {
|
|
168
|
-
bytes32 nonExistentMrAggregated = hex"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
|
169
|
-
|
|
170
|
-
vm.startPrank(this.owner());
|
|
171
|
-
vm.expectRevert(TEELifecycle.TEEVersionNotFound.selector);
|
|
172
|
-
this.removeApprovedTeeVersion(nonExistentMrAggregated);
|
|
173
|
-
vm.stopPrank();
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function testRemoveApprovedTeeVersionOnlyOwner() public {
|
|
177
|
-
bytes32 mrAggregated = hex"1111111111111111111111111111111111111111111111111111111111111111";
|
|
178
|
-
|
|
179
|
-
vm.startPrank(this.owner());
|
|
180
|
-
this.approveNewTeeVersion(mrAggregated);
|
|
181
|
-
vm.stopPrank();
|
|
182
|
-
|
|
183
|
-
// Try to remove as non-owner
|
|
184
|
-
address nonOwner = address(0x1234);
|
|
185
|
-
vm.startPrank(nonOwner);
|
|
186
|
-
vm.expectRevert();
|
|
187
|
-
this.removeApprovedTeeVersion(mrAggregated);
|
|
188
|
-
vm.stopPrank();
|
|
189
|
-
}
|
|
190
|
-
|
|
191
137
|
// Helper function to create a successful bootstrap result
|
|
192
138
|
function successfulBootstrapResult()
|
|
193
139
|
internal
|
|
@@ -449,4 +395,83 @@ contract TEELifecycleMockTest is MockRemoteAttestation, TEELifecycle {
|
|
|
449
395
|
);
|
|
450
396
|
}
|
|
451
397
|
|
|
398
|
+
// ============ Tests for reset() ============
|
|
399
|
+
|
|
400
|
+
function testReset_OnlyOwner() public {
|
|
401
|
+
address nonOwner = address(0x1234);
|
|
402
|
+
vm.startPrank(nonOwner);
|
|
403
|
+
vm.expectRevert();
|
|
404
|
+
this.reset();
|
|
405
|
+
vm.stopPrank();
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function testReset_WithMultipleSigners() public {
|
|
409
|
+
// Complete bootstrap with first signer
|
|
410
|
+
(
|
|
411
|
+
BootstrapResult memory bootstrapResult,,
|
|
412
|
+
address bootstrapPartyAddress,
|
|
413
|
+
bytes memory quote,
|
|
414
|
+
bytes memory signature,
|
|
415
|
+
bytes32 mrAggregated
|
|
416
|
+
) = successfulBootstrapResult();
|
|
417
|
+
|
|
418
|
+
vm.startPrank(this.owner());
|
|
419
|
+
this.approveNewTeeVersion(mrAggregated);
|
|
420
|
+
this.verifyBootstrapResult(bootstrapResult, quote, signature);
|
|
421
|
+
|
|
422
|
+
// Add a second node
|
|
423
|
+
(uint256 newNodePrivkey, address newNodeAddress) = getLabeledKeyPair("newNode");
|
|
424
|
+
AddNodeResult memory addNodeResult = AddNodeResult({networkPubkey: testNetworkPubkey});
|
|
425
|
+
bytes memory addNodeSignature = signAddNodeResult(addNodeResult, newNodePrivkey);
|
|
426
|
+
bytes memory addNodeQuote = createQuote(testMrtd, newNodeAddress);
|
|
427
|
+
this.verifyAddNodeResult(mrAggregated, addNodeResult, addNodeQuote, addNodeSignature);
|
|
428
|
+
|
|
429
|
+
// Verify state before reset
|
|
430
|
+
assertTrue(this.isBootstrapComplete(), "Bootstrap should be complete before reset");
|
|
431
|
+
assertEq(this.networkPubkey(), testNetworkPubkey, "Network pubkey should be set before reset");
|
|
432
|
+
assertEq(this.approvedTeeVersions(0), mrAggregated, "Approved TEE version should exist before reset");
|
|
433
|
+
assertEq(this.getSignersCount(), 2, "Should have 2 signers before reset");
|
|
434
|
+
assertTrue(this.isSigner(bootstrapPartyAddress), "First signer should exist");
|
|
435
|
+
assertTrue(this.isSigner(newNodeAddress), "Second signer should exist");
|
|
436
|
+
|
|
437
|
+
// Call reset
|
|
438
|
+
this.reset();
|
|
439
|
+
|
|
440
|
+
// Verify all state has been cleared
|
|
441
|
+
assertFalse(this.isBootstrapComplete(), "Bootstrap should not be complete after reset");
|
|
442
|
+
assertEq(this.networkPubkey().length, 0, "Network pubkey should be empty after reset");
|
|
443
|
+
assertEq(this.getSignersCount(), 0, "Should have 0 signers after reset");
|
|
444
|
+
assertFalse(this.isSigner(bootstrapPartyAddress), "First signer should be removed");
|
|
445
|
+
assertFalse(this.isSigner(newNodeAddress), "Second signer should be removed");
|
|
446
|
+
assertEq(this.getThreshold(), 0, "Threshold should be 0 after reset");
|
|
447
|
+
|
|
448
|
+
// Verify approved TEE versions array is empty
|
|
449
|
+
vm.expectRevert(TEELifecycle.IndexOutOfBounds.selector);
|
|
450
|
+
this.approvedTeeVersions(0);
|
|
451
|
+
|
|
452
|
+
vm.stopPrank();
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function testReset_AllowsNewBootstrap() public {
|
|
456
|
+
// Complete bootstrap
|
|
457
|
+
(BootstrapResult memory bootstrapResult,,, bytes memory quote, bytes memory signature, bytes32 mrAggregated) =
|
|
458
|
+
successfulBootstrapResult();
|
|
459
|
+
|
|
460
|
+
vm.startPrank(this.owner());
|
|
461
|
+
this.approveNewTeeVersion(mrAggregated);
|
|
462
|
+
this.verifyBootstrapResult(bootstrapResult, quote, signature);
|
|
463
|
+
assertTrue(this.isBootstrapComplete(), "Bootstrap should be complete");
|
|
464
|
+
|
|
465
|
+
// Reset the contract
|
|
466
|
+
this.reset();
|
|
467
|
+
assertFalse(this.isBootstrapComplete(), "Bootstrap should not be complete after reset");
|
|
468
|
+
|
|
469
|
+
// Should be able to bootstrap again
|
|
470
|
+
this.approveNewTeeVersion(mrAggregated);
|
|
471
|
+
this.verifyBootstrapResult(bootstrapResult, quote, signature);
|
|
472
|
+
assertTrue(this.isBootstrapComplete(), "Should be able to bootstrap again after reset");
|
|
473
|
+
|
|
474
|
+
vm.stopPrank();
|
|
475
|
+
}
|
|
476
|
+
|
|
452
477
|
}
|
|
@@ -5,6 +5,7 @@ import {Test} from "forge-std/Test.sol";
|
|
|
5
5
|
import {TrivialEncryption} from "../lightning-parts/TrivialEncryption.sol";
|
|
6
6
|
import {ETypes} from "../Types.sol";
|
|
7
7
|
import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
|
|
8
|
+
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
|
8
9
|
import {inco} from "../Lib.sol";
|
|
9
10
|
import {IncoTest} from "./IncoTest.sol";
|
|
10
11
|
|
|
@@ -26,6 +27,7 @@ contract TestDeploy is Test, IncoTest {
|
|
|
26
27
|
vm.expectEmit(false, false, true, false, address(inco));
|
|
27
28
|
emit TrivialEncryption.TrivialEncrypt(bytes32(uint256(1)), bytes32(uint256(1)), ETypes.Bool, 0);
|
|
28
29
|
inco.asEbool(true);
|
|
30
|
+
assertTrue(inco.isAcceptedVersion(2));
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
function testUpgrade() public {
|
|
@@ -35,4 +37,30 @@ contract TestDeploy is Test, IncoTest {
|
|
|
35
37
|
assertEq(ReturnTwo(address(inco)).getTwo(), 2);
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
function testAddAcceptedVersion() public {
|
|
41
|
+
assertFalse(inco.isAcceptedVersion(42));
|
|
42
|
+
vm.prank(owner);
|
|
43
|
+
inco.addAcceptedVersion(42);
|
|
44
|
+
assertTrue(inco.isAcceptedVersion(42));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function testAddAcceptedVersionNotOwner() public {
|
|
48
|
+
vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice));
|
|
49
|
+
vm.prank(alice);
|
|
50
|
+
inco.addAcceptedVersion(42);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function testRemoveAcceptedVersion() public {
|
|
54
|
+
assertFalse(inco.isAcceptedVersion(42));
|
|
55
|
+
vm.prank(owner);
|
|
56
|
+
inco.removeAcceptedVersion(42); // removing a non-existent version should be no-op
|
|
57
|
+
assertFalse(inco.isAcceptedVersion(42));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function testRemoveAcceptedVersionNotOwner() public {
|
|
61
|
+
vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, alice));
|
|
62
|
+
vm.prank(alice);
|
|
63
|
+
inco.removeAcceptedVersion(42);
|
|
64
|
+
}
|
|
65
|
+
|
|
38
66
|
}
|