@inco/lightning 0.8.0-devnet-2 → 0.8.0-devnet-3
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 +23 -67
- package/package.json +1 -1
- package/src/lightning-parts/AccessControl/test/TestAdvancedAccessControl.t.sol +75 -1
- package/src/lightning-parts/AccessControl/test/TestBaseAccessControl.t.sol +245 -0
- package/src/lightning-parts/EncryptedOperations.sol +1 -5
- package/src/lightning-parts/TrivialEncryption.sol +29 -0
- package/src/lightning-parts/primitives/SignatureVerifier.sol +42 -11
- package/src/lightning-parts/primitives/test/SignatureVerifier.t.sol +454 -29
- package/src/lightning-parts/test/HandleMetadata.t.sol +335 -1
- package/src/periphery/SessionVerifier.sol +3 -1
- package/src/test/TEELifecycle/TEELifecycleMockTest.t.sol +231 -1
- package/src/test/TestEventCounter.t.sol +43 -0
- package/src/test/TestFakeInfra.t.sol +9 -2
- package/src/test/TestFeeWithdrawal.t.sol +0 -1
- package/src/test/TestIncoUtils.t.sol +50 -0
- package/src/test/TestLib.t.sol +794 -0
- package/src/test/TestUpgrade.t.sol +114 -0
- package/src/test/TestVersion.t.sol +1 -0
|
@@ -3,13 +3,25 @@ pragma solidity ^0.8;
|
|
|
3
3
|
|
|
4
4
|
import {TestUtils} from "../../shared/TestUtils.sol";
|
|
5
5
|
import {HandleMetadata} from "../primitives/HandleMetadata.sol";
|
|
6
|
+
import {HandleGeneration} from "../primitives/HandleGeneration.sol";
|
|
6
7
|
import {TrivialEncryption} from "../TrivialEncryption.sol";
|
|
7
8
|
import {EncryptedOperations} from "../EncryptedOperations.sol";
|
|
8
9
|
import {EncryptedInput} from "../EncryptedInput.sol";
|
|
9
10
|
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
ETypes,
|
|
13
|
+
ebool,
|
|
14
|
+
euint256,
|
|
15
|
+
eaddress,
|
|
16
|
+
typeToBitMask,
|
|
17
|
+
EOps,
|
|
18
|
+
isTypeSupported,
|
|
19
|
+
SenderNotAllowedForHandle
|
|
20
|
+
} from "../../Types.sol";
|
|
11
21
|
import {VerifierAddressGetter} from "../primitives/VerifierAddressGetter.sol";
|
|
12
22
|
import {FEE} from "../Fee.sol";
|
|
23
|
+
import {HandleAlreadyExists} from "../../Errors.sol";
|
|
24
|
+
import {ExternalHandleDoesNotMatchComputedHandle} from "../EncryptedInput.sol";
|
|
13
25
|
|
|
14
26
|
contract TestHandleMetadata is
|
|
15
27
|
EIP712,
|
|
@@ -97,4 +109,326 @@ contract TestHandleMetadata is
|
|
|
97
109
|
input = abi.encode(handle, ciphertext);
|
|
98
110
|
}
|
|
99
111
|
|
|
112
|
+
// ============ Tests for HandleGeneration functions ============
|
|
113
|
+
|
|
114
|
+
function testGetTrivialEncryptHandle() public view {
|
|
115
|
+
bytes32 plaintext = bytes32(uint256(42));
|
|
116
|
+
bytes32 handle = this.getTrivialEncryptHandle(plaintext, ETypes.Uint256);
|
|
117
|
+
// Verify handle has correct type embedded
|
|
118
|
+
assert(typeOf(handle) == ETypes.Uint256);
|
|
119
|
+
|
|
120
|
+
// Test with different types
|
|
121
|
+
bytes32 boolHandle = this.getTrivialEncryptHandle(bytes32(uint256(1)), ETypes.Bool);
|
|
122
|
+
assert(typeOf(boolHandle) == ETypes.Bool);
|
|
123
|
+
|
|
124
|
+
bytes32 addrHandle =
|
|
125
|
+
this.getTrivialEncryptHandle(bytes32(uint256(0xdeadbeef)), ETypes.AddressOrUint160OrBytes20);
|
|
126
|
+
assert(typeOf(addrHandle) == ETypes.AddressOrUint160OrBytes20);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function testGetOpResultHandle() public view {
|
|
130
|
+
bytes32 inputA = bytes32(uint256(1));
|
|
131
|
+
bytes32 inputB = bytes32(uint256(2));
|
|
132
|
+
bytes memory packedInputs = abi.encodePacked(inputA, inputB);
|
|
133
|
+
|
|
134
|
+
// Test Add operation
|
|
135
|
+
bytes32 addHandle = this.getOpResultHandle(EOps.Add, ETypes.Uint256, packedInputs);
|
|
136
|
+
assert(typeOf(addHandle) == ETypes.Uint256);
|
|
137
|
+
|
|
138
|
+
// Test comparison operation (returns bool)
|
|
139
|
+
bytes32 eqHandle = this.getOpResultHandle(EOps.Eq, ETypes.Bool, packedInputs);
|
|
140
|
+
assert(typeOf(eqHandle) == ETypes.Bool);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ============ Tests for EncryptedInput internal functions ============
|
|
144
|
+
|
|
145
|
+
/// @notice Expose the internal newInputNotPaying for testing
|
|
146
|
+
function exposedNewInputNotPaying(bytes memory ciphertext, address user, ETypes inputType)
|
|
147
|
+
public
|
|
148
|
+
returns (bytes32)
|
|
149
|
+
{
|
|
150
|
+
return newInputNotPaying(ciphertext, user, inputType);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function testNewInputNotPaying() public {
|
|
154
|
+
address self = address(this);
|
|
155
|
+
bytes32 ciphertextData = keccak256(abi.encodePacked("notpaying_ciphertext"));
|
|
156
|
+
// Create input with handle computed for this contract as msg.sender (since exposedNewInputNotPaying is public)
|
|
157
|
+
bytes memory input = getCiphertextInputForExposedCall(ciphertextData, self, ETypes.Uint256);
|
|
158
|
+
// Call the exposed version without paying
|
|
159
|
+
bytes32 handle = this.exposedNewInputNotPaying(input, self, ETypes.Uint256);
|
|
160
|
+
assert(typeOf(handle) == ETypes.Uint256);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/// @notice Helper to create input for exposed internal function calls where msg.sender == address(this)
|
|
164
|
+
function getCiphertextInputForExposedCall(bytes32 word, address user, ETypes inputType)
|
|
165
|
+
public
|
|
166
|
+
view
|
|
167
|
+
returns (bytes memory input)
|
|
168
|
+
{
|
|
169
|
+
bytes memory ciphertext = abi.encode(word);
|
|
170
|
+
// For external calls via this., msg.sender is address(this)
|
|
171
|
+
bytes32 handle = getInputHandle(ciphertext, address(this), user, address(this), inputType);
|
|
172
|
+
input = abi.encode(handle, ciphertext);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ============ Tests for EncryptedInput error branches ============
|
|
176
|
+
|
|
177
|
+
function testNewInputTooShort() public {
|
|
178
|
+
address self = address(this);
|
|
179
|
+
// Input less than 64 bytes should revert
|
|
180
|
+
bytes memory shortInput = hex"deadbeef";
|
|
181
|
+
vm.expectRevert("Input too short, should be at least 64 bytes");
|
|
182
|
+
this.exposedNewInputNotPaying(shortInput, self, ETypes.Uint256);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function testNewInputHandleMismatch() public {
|
|
186
|
+
address self = address(this);
|
|
187
|
+
bytes32 ciphertextData = keccak256(abi.encodePacked("mismatch_test"));
|
|
188
|
+
bytes memory ciphertext = abi.encode(ciphertextData);
|
|
189
|
+
// Create input with wrong handle (just a random bytes32)
|
|
190
|
+
bytes32 wrongHandle = bytes32(uint256(12345));
|
|
191
|
+
bytes memory badInput = abi.encode(wrongHandle, ciphertext);
|
|
192
|
+
|
|
193
|
+
// Compute the expected handle for the error message
|
|
194
|
+
bytes32 expectedHandle = getInputHandle(ciphertext, address(this), self, address(this), ETypes.Uint256);
|
|
195
|
+
|
|
196
|
+
vm.expectRevert(
|
|
197
|
+
abi.encodeWithSelector(
|
|
198
|
+
ExternalHandleDoesNotMatchComputedHandle.selector,
|
|
199
|
+
wrongHandle,
|
|
200
|
+
expectedHandle,
|
|
201
|
+
block.chainid,
|
|
202
|
+
address(this),
|
|
203
|
+
self,
|
|
204
|
+
address(this)
|
|
205
|
+
)
|
|
206
|
+
);
|
|
207
|
+
this.exposedNewInputNotPaying(badInput, self, ETypes.Uint256);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function testNewInputHandleAlreadyExists() public {
|
|
211
|
+
address self = address(this);
|
|
212
|
+
bytes32 ciphertextData = keccak256(abi.encodePacked("duplicate_test"));
|
|
213
|
+
bytes memory input = getCiphertextInputForExposedCall(ciphertextData, self, ETypes.Uint256);
|
|
214
|
+
|
|
215
|
+
// First call should succeed
|
|
216
|
+
bytes32 handle = this.exposedNewInputNotPaying(input, self, ETypes.Uint256);
|
|
217
|
+
|
|
218
|
+
// Second call with same input should revert with HandleAlreadyExists
|
|
219
|
+
vm.expectRevert(abi.encodeWithSelector(HandleAlreadyExists.selector, handle));
|
|
220
|
+
this.exposedNewInputNotPaying(input, self, ETypes.Uint256);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Tests for EncryptedOperations error branches
|
|
224
|
+
|
|
225
|
+
/// @notice Test eBitAnd with mismatched types (line 122)
|
|
226
|
+
function testEBitAndTypeMismatch() public {
|
|
227
|
+
euint256 a = this.asEuint256(42);
|
|
228
|
+
ebool b = this.asEbool(true);
|
|
229
|
+
// Error: UnexpectedType(lhsType, typeToBitMask(rhsType))
|
|
230
|
+
// With lhs=Uint256, rhs=Bool: UnexpectedType(Uint256, typeToBitMask(Bool))
|
|
231
|
+
vm.expectRevert(
|
|
232
|
+
abi.encodeWithSelector(
|
|
233
|
+
EncryptedOperations.UnexpectedType.selector, ETypes.Uint256, typeToBitMask(ETypes.Bool)
|
|
234
|
+
)
|
|
235
|
+
);
|
|
236
|
+
this.eBitAnd(euint256.unwrap(a), ebool.unwrap(b));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/// @notice Test eBitOr with mismatched types (line 134)
|
|
240
|
+
function testEBitOrTypeMismatch() public {
|
|
241
|
+
euint256 a = this.asEuint256(42);
|
|
242
|
+
ebool b = this.asEbool(true);
|
|
243
|
+
vm.expectRevert(
|
|
244
|
+
abi.encodeWithSelector(
|
|
245
|
+
EncryptedOperations.UnexpectedType.selector, ETypes.Uint256, typeToBitMask(ETypes.Bool)
|
|
246
|
+
)
|
|
247
|
+
);
|
|
248
|
+
this.eBitOr(euint256.unwrap(a), ebool.unwrap(b));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/// @notice Test eBitXor with mismatched types (line 146)
|
|
252
|
+
function testEBitXorTypeMismatch() public {
|
|
253
|
+
euint256 a = this.asEuint256(42);
|
|
254
|
+
ebool b = this.asEbool(true);
|
|
255
|
+
vm.expectRevert(
|
|
256
|
+
abi.encodeWithSelector(
|
|
257
|
+
EncryptedOperations.UnexpectedType.selector, ETypes.Uint256, typeToBitMask(ETypes.Bool)
|
|
258
|
+
)
|
|
259
|
+
);
|
|
260
|
+
this.eBitXor(euint256.unwrap(a), ebool.unwrap(b));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/// @notice Test eCast with unsupported target type (line 273)
|
|
264
|
+
function testECastUnsupportedType() public {
|
|
265
|
+
euint256 a = this.asEuint256(42);
|
|
266
|
+
ETypes unsupportedType = ETypes.Uint4UNSUPPORTED;
|
|
267
|
+
vm.expectRevert(abi.encodeWithSelector(EncryptedOperations.UnsupportedType.selector, unsupportedType));
|
|
268
|
+
this.eCast(euint256.unwrap(a), unsupportedType);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/// @notice Test eRand with unsupported type (line 282)
|
|
272
|
+
function testERandUnsupportedType() public {
|
|
273
|
+
ETypes unsupportedType = ETypes.Uint4UNSUPPORTED;
|
|
274
|
+
vm.expectRevert(abi.encodeWithSelector(EncryptedOperations.UnsupportedType.selector, unsupportedType));
|
|
275
|
+
this.eRand{value: FEE}(unsupportedType);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/// @notice Test eRandBounded with unsupported type (line 291)
|
|
279
|
+
function testERandBoundedUnsupportedType() public {
|
|
280
|
+
euint256 bound = this.asEuint256(100);
|
|
281
|
+
ETypes unsupportedType = ETypes.Uint4UNSUPPORTED;
|
|
282
|
+
vm.expectRevert(abi.encodeWithSelector(EncryptedOperations.UnsupportedType.selector, unsupportedType));
|
|
283
|
+
this.eRandBounded{value: FEE}(euint256.unwrap(bound), unsupportedType);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/// @notice Test eIfThenElse with unsupported ifTrue type (line 318)
|
|
287
|
+
function testEIfThenElseUnsupportedType() public {
|
|
288
|
+
ebool control = this.asEbool(true);
|
|
289
|
+
// Create a handle with unsupported type by manually crafting one
|
|
290
|
+
bytes32 unsupportedHandle = embedIndexTypeVersion(bytes32(uint256(1)), ETypes.Uint4UNSUPPORTED);
|
|
291
|
+
ebool ifFalse = this.asEbool(false);
|
|
292
|
+
vm.expectRevert(abi.encodeWithSelector(EncryptedOperations.UnsupportedType.selector, ETypes.Uint4UNSUPPORTED));
|
|
293
|
+
this.eIfThenElse(control, unsupportedHandle, ebool.unwrap(ifFalse));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/// @notice Test eIfThenElse with mismatched types between ifTrue and ifFalse (line 322)
|
|
297
|
+
function testEIfThenElseTypeMismatch() public {
|
|
298
|
+
ebool control = this.asEbool(true);
|
|
299
|
+
euint256 ifTrue = this.asEuint256(42);
|
|
300
|
+
ebool ifFalse = this.asEbool(false);
|
|
301
|
+
// ifTrue is Uint256, ifFalse is Bool - type mismatch should trigger checkInput failure
|
|
302
|
+
// Error is UnexpectedType(ifFalse type=Bool, required type mask for Uint256)
|
|
303
|
+
vm.expectRevert(
|
|
304
|
+
abi.encodeWithSelector(
|
|
305
|
+
EncryptedOperations.UnexpectedType.selector, ETypes.Bool, typeToBitMask(ETypes.Uint256)
|
|
306
|
+
)
|
|
307
|
+
);
|
|
308
|
+
this.eIfThenElse(control, euint256.unwrap(ifTrue), ebool.unwrap(ifFalse));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/// @notice Test eIfThenElse with ifTrue handle not allowed for sender
|
|
312
|
+
function testEIfThenElseIfTrueNotAllowed() public {
|
|
313
|
+
// Create handles from this contract (allowed)
|
|
314
|
+
ebool control = this.asEbool(true);
|
|
315
|
+
euint256 ifTrue = this.asEuint256(42);
|
|
316
|
+
ebool ifFalse = this.asEbool(false);
|
|
317
|
+
|
|
318
|
+
// Allow alice to access control and ifFalse, but NOT ifTrue
|
|
319
|
+
this.allow(ebool.unwrap(control), alice);
|
|
320
|
+
this.allow(ebool.unwrap(ifFalse), alice);
|
|
321
|
+
|
|
322
|
+
// Call eIfThenElse as alice - should fail on ifTrue check
|
|
323
|
+
vm.prank(alice);
|
|
324
|
+
vm.expectRevert(abi.encodeWithSelector(SenderNotAllowedForHandle.selector, euint256.unwrap(ifTrue), alice));
|
|
325
|
+
this.eIfThenElse(control, euint256.unwrap(ifTrue), ebool.unwrap(ifFalse));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ============ Fuzz Tests for HandleMetadata ============
|
|
329
|
+
|
|
330
|
+
/// @dev Fuzz test for type embedding round-trip: typeOf(embedTypeVersion(h, t)) == t
|
|
331
|
+
function testFuzzTypeEmbeddingRoundTrip(bytes32 prehandle, uint8 typeIndex) public pure {
|
|
332
|
+
// Constrain to supported types (0=Bool, 7=AddressOrUint160OrBytes20, 8=Uint256)
|
|
333
|
+
ETypes inputType;
|
|
334
|
+
uint8 typeSelector = typeIndex % 3;
|
|
335
|
+
if (typeSelector == 0) {
|
|
336
|
+
inputType = ETypes.Bool;
|
|
337
|
+
} else if (typeSelector == 1) {
|
|
338
|
+
inputType = ETypes.AddressOrUint160OrBytes20;
|
|
339
|
+
} else {
|
|
340
|
+
inputType = ETypes.Uint256;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
bytes32 embeddedHandle = embedTypeVersion(prehandle, inputType);
|
|
344
|
+
ETypes extractedType = typeOf(embeddedHandle);
|
|
345
|
+
|
|
346
|
+
assert(extractedType == inputType);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/// @dev Fuzz test for embedIndexTypeVersion round-trip
|
|
350
|
+
function testFuzzEmbedIndexTypeVersionRoundTrip(bytes32 prehandle, uint8 typeIndex) public pure {
|
|
351
|
+
// Constrain to supported types
|
|
352
|
+
ETypes inputType;
|
|
353
|
+
uint8 typeSelector = typeIndex % 3;
|
|
354
|
+
if (typeSelector == 0) {
|
|
355
|
+
inputType = ETypes.Bool;
|
|
356
|
+
} else if (typeSelector == 1) {
|
|
357
|
+
inputType = ETypes.AddressOrUint160OrBytes20;
|
|
358
|
+
} else {
|
|
359
|
+
inputType = ETypes.Uint256;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
bytes32 embeddedHandle = embedIndexTypeVersion(prehandle, inputType);
|
|
363
|
+
ETypes extractedType = typeOf(embeddedHandle);
|
|
364
|
+
|
|
365
|
+
assert(extractedType == inputType);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/// @dev Fuzz test that typeOf correctly extracts type from handles with valid embedded types
|
|
369
|
+
function testFuzzTypeOfExtraction(bytes32 prehandle, uint8 typeIndex) public pure {
|
|
370
|
+
// First embed a valid type, then verify extraction
|
|
371
|
+
ETypes inputType;
|
|
372
|
+
uint8 typeSelector = typeIndex % 3;
|
|
373
|
+
if (typeSelector == 0) {
|
|
374
|
+
inputType = ETypes.Bool;
|
|
375
|
+
} else if (typeSelector == 1) {
|
|
376
|
+
inputType = ETypes.AddressOrUint160OrBytes20;
|
|
377
|
+
} else {
|
|
378
|
+
inputType = ETypes.Uint256;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
bytes32 handle = embedTypeVersion(prehandle, inputType);
|
|
382
|
+
|
|
383
|
+
// Extract the type from the handle
|
|
384
|
+
ETypes extractedType = typeOf(handle);
|
|
385
|
+
|
|
386
|
+
// Manually compute what the type should be (bits 8-15 of the handle)
|
|
387
|
+
uint8 expectedTypeValue = uint8(uint256(handle) >> 8);
|
|
388
|
+
|
|
389
|
+
// Verify the extraction matches manual computation
|
|
390
|
+
assert(uint8(extractedType) == expectedTypeValue);
|
|
391
|
+
// Also verify it matches what we embedded
|
|
392
|
+
assert(extractedType == inputType);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/// @dev Fuzz test that embedding preserves upper bits of handle
|
|
396
|
+
function testFuzzEmbeddingPreservesUpperBits(bytes32 prehandle, uint8 typeIndex) public pure {
|
|
397
|
+
ETypes inputType;
|
|
398
|
+
uint8 typeSelector = typeIndex % 3;
|
|
399
|
+
if (typeSelector == 0) {
|
|
400
|
+
inputType = ETypes.Bool;
|
|
401
|
+
} else if (typeSelector == 1) {
|
|
402
|
+
inputType = ETypes.AddressOrUint160OrBytes20;
|
|
403
|
+
} else {
|
|
404
|
+
inputType = ETypes.Uint256;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
bytes32 embeddedHandle = embedTypeVersion(prehandle, inputType);
|
|
408
|
+
|
|
409
|
+
// Verify upper 30 bytes (240 bits) are preserved
|
|
410
|
+
// Mask out the lower 2 bytes (16 bits) for comparison
|
|
411
|
+
bytes32 upperMask = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000;
|
|
412
|
+
assert((prehandle & upperMask) == (embeddedHandle & upperMask));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/// @dev Fuzz test that embedIndexTypeVersion clears index byte correctly
|
|
416
|
+
function testFuzzEmbedIndexClearsCorrectly(bytes32 prehandle, uint8 typeIndex) public pure {
|
|
417
|
+
ETypes inputType;
|
|
418
|
+
uint8 typeSelector = typeIndex % 3;
|
|
419
|
+
if (typeSelector == 0) {
|
|
420
|
+
inputType = ETypes.Bool;
|
|
421
|
+
} else if (typeSelector == 1) {
|
|
422
|
+
inputType = ETypes.AddressOrUint160OrBytes20;
|
|
423
|
+
} else {
|
|
424
|
+
inputType = ETypes.Uint256;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
bytes32 embeddedHandle = embedIndexTypeVersion(prehandle, inputType);
|
|
428
|
+
|
|
429
|
+
// Extract index byte (bits 16-23) - should be HANDLE_INDEX (0)
|
|
430
|
+
uint8 indexByte = uint8(uint256(embeddedHandle) >> 16);
|
|
431
|
+
assert(indexByte == 0); // HANDLE_INDEX is 0
|
|
432
|
+
}
|
|
433
|
+
|
|
100
434
|
}
|
|
@@ -39,12 +39,14 @@ contract SessionVerifier is UUPSUpgradeable, OwnableUpgradeable, Version {
|
|
|
39
39
|
bytes32, /* handle */
|
|
40
40
|
address account,
|
|
41
41
|
bytes memory sharerArgData,
|
|
42
|
-
bytes memory
|
|
42
|
+
bytes memory requesterArgData
|
|
43
43
|
)
|
|
44
44
|
external
|
|
45
45
|
view
|
|
46
46
|
returns (bytes32)
|
|
47
47
|
{
|
|
48
|
+
// unused variable just here to bypass linter
|
|
49
|
+
(requesterArgData);
|
|
48
50
|
Session memory session = abi.decode(sharerArgData, (Session));
|
|
49
51
|
if (session.expiresAt >= block.timestamp && session.decrypter == account) {
|
|
50
52
|
return ALLOWANCE_GRANTED_MAGIC_VALUE;
|
|
@@ -2,9 +2,18 @@
|
|
|
2
2
|
pragma solidity ^0.8.0;
|
|
3
3
|
|
|
4
4
|
import {TEELifecycle} from "../../lightning-parts/TEELifecycle.sol";
|
|
5
|
-
import {BootstrapResult, AddNodeResult} from "../../lightning-parts/TEELifecycle.types.sol";
|
|
5
|
+
import {BootstrapResult, AddNodeResult, UpgradeResult} from "../../lightning-parts/TEELifecycle.types.sol";
|
|
6
6
|
import {MockRemoteAttestation} from "../FakeIncoInfra/MockRemoteAttestation.sol";
|
|
7
7
|
import {FakeQuoteVerifier} from "../FakeIncoInfra/FakeQuoteVerifier.sol";
|
|
8
|
+
import {IQuoteVerifier} from "../../interfaces/automata-interfaces/IQuoteVerifier.sol";
|
|
9
|
+
import {
|
|
10
|
+
TcbInfoJsonObj,
|
|
11
|
+
EnclaveIdentityJsonObj,
|
|
12
|
+
TDX_TEE,
|
|
13
|
+
HEADER_LENGTH,
|
|
14
|
+
MINIMUM_QUOTE_LENGTH
|
|
15
|
+
} from "../../interfaces/automata-interfaces/Types.sol";
|
|
16
|
+
import {SignatureVerifier} from "../../lightning-parts/primitives/SignatureVerifier.sol";
|
|
8
17
|
|
|
9
18
|
contract TEELifecycleMockTest is MockRemoteAttestation, TEELifecycle {
|
|
10
19
|
|
|
@@ -219,4 +228,225 @@ contract TEELifecycleMockTest is MockRemoteAttestation, TEELifecycle {
|
|
|
219
228
|
return getSignatureForDigest(addNodeResultDigest, privateKey);
|
|
220
229
|
}
|
|
221
230
|
|
|
231
|
+
// Helper function to sign the upgrade result
|
|
232
|
+
function signUpgradeResult(UpgradeResult memory upgradeResult, uint256 privateKey)
|
|
233
|
+
internal
|
|
234
|
+
view
|
|
235
|
+
returns (bytes memory)
|
|
236
|
+
{
|
|
237
|
+
bytes32 upgradeResultDigest = upgradeResultDigest(upgradeResult);
|
|
238
|
+
return getSignatureForDigest(upgradeResultDigest, privateKey);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ============ Getter Tests ============
|
|
242
|
+
|
|
243
|
+
function testQuoteVerifier() public view {
|
|
244
|
+
IQuoteVerifier qv = this.quoteVerifier();
|
|
245
|
+
assertEq(address(qv), address(getTeeLifecycleStorage().quoteVerifier));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function testNetworkPubkeyGetter() public {
|
|
249
|
+
// Before bootstrap, network pubkey should be empty
|
|
250
|
+
assertEq(this.networkPubkey().length, 0);
|
|
251
|
+
|
|
252
|
+
// Complete bootstrap
|
|
253
|
+
(BootstrapResult memory bootstrapResult,,, bytes memory quote, bytes memory signature, bytes32 mrAggregated) =
|
|
254
|
+
successfulBootstrapResult();
|
|
255
|
+
vm.startPrank(this.owner());
|
|
256
|
+
this.approveNewTeeVersion(mrAggregated);
|
|
257
|
+
this.verifyBootstrapResult(bootstrapResult, quote, signature);
|
|
258
|
+
vm.stopPrank();
|
|
259
|
+
|
|
260
|
+
// After bootstrap, network pubkey should be set
|
|
261
|
+
assertEq(this.networkPubkey(), testNetworkPubkey);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function testApprovedTeeVersions() public {
|
|
265
|
+
vm.startPrank(this.owner());
|
|
266
|
+
this.approveNewTeeVersion(testMrAggregated);
|
|
267
|
+
vm.stopPrank();
|
|
268
|
+
|
|
269
|
+
bytes32 version = this.approvedTeeVersions(0);
|
|
270
|
+
assertEq(version, testMrAggregated);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function testApprovedTeeVersions_IndexOutOfBounds() public {
|
|
274
|
+
vm.expectRevert(TEELifecycle.IndexOutOfBounds.selector);
|
|
275
|
+
this.approvedTeeVersions(0);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function testUpgradeResultDigest() public view {
|
|
279
|
+
UpgradeResult memory upgradeResult = UpgradeResult({networkPubkey: testNetworkPubkey});
|
|
280
|
+
bytes32 digest = this.upgradeResultDigest(upgradeResult);
|
|
281
|
+
// Digest should not be zero
|
|
282
|
+
assertTrue(digest != bytes32(0));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ============ verifyUpgradeResult Tests ============
|
|
286
|
+
|
|
287
|
+
function testVerifyUpgradeResult() public {
|
|
288
|
+
// First complete bootstrap
|
|
289
|
+
(
|
|
290
|
+
BootstrapResult memory bootstrapResult,
|
|
291
|
+
uint256 bootstrapPartyPrivkey,
|
|
292
|
+
address bootstrapPartyAddress,
|
|
293
|
+
bytes memory quote,
|
|
294
|
+
bytes memory signature,
|
|
295
|
+
bytes32 mrAggregated
|
|
296
|
+
) = successfulBootstrapResult();
|
|
297
|
+
vm.startPrank(this.owner());
|
|
298
|
+
this.approveNewTeeVersion(mrAggregated);
|
|
299
|
+
this.verifyBootstrapResult(bootstrapResult, quote, signature);
|
|
300
|
+
|
|
301
|
+
// Now test upgrade - use the same signer (they're upgrading their TDX)
|
|
302
|
+
UpgradeResult memory upgradeResult = UpgradeResult({networkPubkey: testNetworkPubkey});
|
|
303
|
+
bytes memory upgradeSignature = signUpgradeResult(upgradeResult, bootstrapPartyPrivkey);
|
|
304
|
+
bytes memory upgradeQuote = createQuote(testMrtd, bootstrapPartyAddress);
|
|
305
|
+
|
|
306
|
+
// verifyUpgradeResult calls _verifyResultForEoa which has onlyOwner
|
|
307
|
+
this.verifyUpgradeResult(mrAggregated, upgradeResult, upgradeQuote, upgradeSignature);
|
|
308
|
+
vm.stopPrank();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function testVerifyUpgradeResult_BootstrapNotComplete() public {
|
|
312
|
+
UpgradeResult memory upgradeResult = UpgradeResult({networkPubkey: testNetworkPubkey});
|
|
313
|
+
vm.expectRevert(TEELifecycle.BootstrapNotComplete.selector);
|
|
314
|
+
this.verifyUpgradeResult(testMrAggregated, upgradeResult, hex"", hex"");
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function testVerifyUpgradeResult_InvalidNetworkPubkey() public {
|
|
318
|
+
// First complete bootstrap
|
|
319
|
+
(BootstrapResult memory bootstrapResult,,, bytes memory quote, bytes memory signature, bytes32 mrAggregated) =
|
|
320
|
+
successfulBootstrapResult();
|
|
321
|
+
vm.startPrank(this.owner());
|
|
322
|
+
this.approveNewTeeVersion(mrAggregated);
|
|
323
|
+
this.verifyBootstrapResult(bootstrapResult, quote, signature);
|
|
324
|
+
vm.stopPrank();
|
|
325
|
+
|
|
326
|
+
// Try upgrade with wrong network pubkey
|
|
327
|
+
UpgradeResult memory upgradeResult = UpgradeResult({networkPubkey: hex"deadbeef"});
|
|
328
|
+
vm.expectRevert(TEELifecycle.InvalidNetworkPubkey.selector);
|
|
329
|
+
this.verifyUpgradeResult(mrAggregated, upgradeResult, quote, signature);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function testVerifyUpgradeResult_TEEVersionNotFound() public {
|
|
333
|
+
// First complete bootstrap
|
|
334
|
+
(BootstrapResult memory bootstrapResult,,, bytes memory quote, bytes memory signature, bytes32 mrAggregated) =
|
|
335
|
+
successfulBootstrapResult();
|
|
336
|
+
vm.startPrank(this.owner());
|
|
337
|
+
this.approveNewTeeVersion(mrAggregated);
|
|
338
|
+
this.verifyBootstrapResult(bootstrapResult, quote, signature);
|
|
339
|
+
vm.stopPrank();
|
|
340
|
+
|
|
341
|
+
// Try upgrade with unapproved MR_AGGREGATED
|
|
342
|
+
bytes32 unapprovedMr = bytes32(uint256(1234));
|
|
343
|
+
UpgradeResult memory upgradeResult = UpgradeResult({networkPubkey: testNetworkPubkey});
|
|
344
|
+
vm.expectRevert(TEELifecycle.TEEVersionNotFound.selector);
|
|
345
|
+
this.verifyUpgradeResult(unapprovedMr, upgradeResult, quote, signature);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ============ Tests for digest functions ============
|
|
349
|
+
|
|
350
|
+
function testAddNodeResultDigest() public view {
|
|
351
|
+
AddNodeResult memory addNodeResult = AddNodeResult({networkPubkey: testNetworkPubkey});
|
|
352
|
+
bytes32 digest = this.addNodeResultDigest(addNodeResult);
|
|
353
|
+
// Verify digest is non-zero and deterministic
|
|
354
|
+
assertTrue(digest != bytes32(0), "Digest should not be zero");
|
|
355
|
+
// Call again to verify determinism
|
|
356
|
+
bytes32 digest2 = this.addNodeResultDigest(addNodeResult);
|
|
357
|
+
assertEq(digest, digest2, "Digest should be deterministic");
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ============ Tests for uploadCollateral validation ============
|
|
361
|
+
|
|
362
|
+
function testUploadCollateral_RevertsWhenEmptyTcbInfo() public {
|
|
363
|
+
TcbInfoJsonObj memory emptyTcbInfo = TcbInfoJsonObj({tcbInfoStr: "", signature: ""});
|
|
364
|
+
EnclaveIdentityJsonObj memory validIdentity = EnclaveIdentityJsonObj({identityStr: "valid", signature: ""});
|
|
365
|
+
|
|
366
|
+
vm.prank(this.owner());
|
|
367
|
+
vm.expectRevert(TEELifecycle.EmptyTcbInfo.selector);
|
|
368
|
+
this.uploadCollateral(emptyTcbInfo, validIdentity);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function testUploadCollateral_RevertsWhenEmptyIdentity() public {
|
|
372
|
+
TcbInfoJsonObj memory validTcbInfo = TcbInfoJsonObj({tcbInfoStr: "valid", signature: ""});
|
|
373
|
+
EnclaveIdentityJsonObj memory emptyIdentity = EnclaveIdentityJsonObj({identityStr: "", signature: ""});
|
|
374
|
+
|
|
375
|
+
vm.prank(this.owner());
|
|
376
|
+
vm.expectRevert(TEELifecycle.EmptyIdentity.selector);
|
|
377
|
+
this.uploadCollateral(validTcbInfo, emptyIdentity);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ============ Tests for verifyUpgradeResult SignerNotFound ============
|
|
381
|
+
|
|
382
|
+
function testVerifyUpgradeResult_RevertsWhenNotASigner() public {
|
|
383
|
+
// First complete bootstrap
|
|
384
|
+
(BootstrapResult memory bootstrapResult,,, bytes memory quote, bytes memory signature, bytes32 mrAggregated) =
|
|
385
|
+
successfulBootstrapResult();
|
|
386
|
+
vm.startPrank(this.owner());
|
|
387
|
+
this.approveNewTeeVersion(mrAggregated);
|
|
388
|
+
this.verifyBootstrapResult(bootstrapResult, quote, signature);
|
|
389
|
+
|
|
390
|
+
// Create a new EOA that is NOT a signer
|
|
391
|
+
(uint256 nonSignerPrivkey, address nonSignerAddress) = getLabeledKeyPair("nonSigner");
|
|
392
|
+
|
|
393
|
+
// Create upgrade result and quote for the non-signer
|
|
394
|
+
UpgradeResult memory upgradeResult = UpgradeResult({networkPubkey: testNetworkPubkey});
|
|
395
|
+
bytes memory upgradeSignature = signUpgradeResult(upgradeResult, nonSignerPrivkey);
|
|
396
|
+
bytes memory upgradeQuote = createQuote(testMrtd, nonSignerAddress);
|
|
397
|
+
|
|
398
|
+
// Should revert because nonSignerAddress is not a registered signer
|
|
399
|
+
vm.expectRevert(abi.encodeWithSelector(SignatureVerifier.SignerNotFound.selector, nonSignerAddress));
|
|
400
|
+
this.verifyUpgradeResult(mrAggregated, upgradeResult, upgradeQuote, upgradeSignature);
|
|
401
|
+
vm.stopPrank();
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// ============ Tests for _verifyAndAttestOnChain error paths ============
|
|
405
|
+
|
|
406
|
+
function testVerifyBootstrapResult_RevertsWhenQuoteTooShort() public {
|
|
407
|
+
BootstrapResult memory bootstrapResult = BootstrapResult({networkPubkey: testNetworkPubkey});
|
|
408
|
+
bytes memory shortQuote = hex"0102030405"; // Much shorter than HEADER_LENGTH (48 bytes)
|
|
409
|
+
bytes memory signature = signBootstrapResult(bootstrapResult, teePrivKey);
|
|
410
|
+
|
|
411
|
+
vm.startPrank(this.owner());
|
|
412
|
+
this.approveNewTeeVersion(testMrAggregated);
|
|
413
|
+
vm.expectRevert("Could not parse quote header");
|
|
414
|
+
this.verifyBootstrapResult(bootstrapResult, shortQuote, signature);
|
|
415
|
+
vm.stopPrank();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function testVerifyBootstrapResult_RevertsWhenUnsupportedQuoteVersion() public {
|
|
419
|
+
BootstrapResult memory bootstrapResult = BootstrapResult({networkPubkey: testNetworkPubkey});
|
|
420
|
+
|
|
421
|
+
// Create a quote with wrong version (version 3 instead of 4)
|
|
422
|
+
// Version is in first 2 bytes as little-endian
|
|
423
|
+
bytes memory wrongVersionQuote = createQuoteWithVersion(testMrtd, teeEOA, 3);
|
|
424
|
+
bytes memory signature = signBootstrapResult(bootstrapResult, teePrivKey);
|
|
425
|
+
|
|
426
|
+
vm.startPrank(this.owner());
|
|
427
|
+
this.approveNewTeeVersion(testMrAggregated);
|
|
428
|
+
vm.expectRevert("Unsupported quote version");
|
|
429
|
+
this.verifyBootstrapResult(bootstrapResult, wrongVersionQuote, signature);
|
|
430
|
+
vm.stopPrank();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Helper function to create a quote with a specific version
|
|
434
|
+
function createQuoteWithVersion(bytes memory mrtd, address signer, uint16 version)
|
|
435
|
+
internal
|
|
436
|
+
pure
|
|
437
|
+
returns (bytes memory quote)
|
|
438
|
+
{
|
|
439
|
+
require(mrtd.length == 48, "MRTD should be 48 bytes");
|
|
440
|
+
// Version as little-endian 2 bytes, then 2 bytes padding
|
|
441
|
+
bytes4 versionBytes = bytes4(uint32(version)); // little-endian
|
|
442
|
+
bytes4 tdxTeeType = TDX_TEE;
|
|
443
|
+
bytes memory prefix = new bytes(HEADER_LENGTH + 136 - 8);
|
|
444
|
+
bytes memory middle = new bytes(520 - 184);
|
|
445
|
+
bytes memory reportDataSuffix = new bytes(44);
|
|
446
|
+
bytes memory suffix = new bytes(MINIMUM_QUOTE_LENGTH - HEADER_LENGTH - 584);
|
|
447
|
+
quote = abi.encodePacked(
|
|
448
|
+
versionBytes, tdxTeeType, prefix, mrtd, middle, abi.encodePacked(signer), reportDataSuffix, suffix
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
222
452
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// SPDX-License-Identifier: No License
|
|
2
|
+
pragma solidity ^0.8;
|
|
3
|
+
|
|
4
|
+
import {IncoTest} from "./IncoTest.sol";
|
|
5
|
+
import {inco} from "../Lib.sol";
|
|
6
|
+
import {EventCounter} from "../lightning-parts/primitives/EventCounter.sol";
|
|
7
|
+
|
|
8
|
+
/// @dev Test harness to expose internal getNewEventId() for coverage
|
|
9
|
+
contract EventCounterHarness is EventCounter {
|
|
10
|
+
|
|
11
|
+
function exposed_getNewEventId() external returns (uint256) {
|
|
12
|
+
return getNewEventId();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
contract TestEventCounter is IncoTest {
|
|
18
|
+
|
|
19
|
+
/// @dev Tests the deprecated getEventCounter() function for coverage.
|
|
20
|
+
/// getEventCounter() is deprecated in favor of getNextEventId().
|
|
21
|
+
function testGetEventCounter_Deprecated() public view {
|
|
22
|
+
// Both functions should return the same value
|
|
23
|
+
uint256 counter = inco.getEventCounter();
|
|
24
|
+
uint256 nextId = inco.getNextEventId();
|
|
25
|
+
assertEq(counter, nextId, "getEventCounter should equal getNextEventId");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// @dev Tests getNewEventId() which increments the counter and returns the new ID.
|
|
29
|
+
/// This function is used by lightning-preview's EList operations.
|
|
30
|
+
function testGetNewEventId() public {
|
|
31
|
+
EventCounterHarness harness = new EventCounterHarness();
|
|
32
|
+
|
|
33
|
+
uint256 firstId = harness.exposed_getNewEventId();
|
|
34
|
+
assertEq(firstId, 0, "First event ID should be 0");
|
|
35
|
+
|
|
36
|
+
uint256 secondId = harness.exposed_getNewEventId();
|
|
37
|
+
assertEq(secondId, 1, "Second event ID should be 1");
|
|
38
|
+
|
|
39
|
+
uint256 thirdId = harness.exposed_getNewEventId();
|
|
40
|
+
assertEq(thirdId, 2, "Third event ID should be 2");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
}
|
|
@@ -139,9 +139,10 @@ contract TestFakeInfra is IncoTest {
|
|
|
139
139
|
function testEShr() public {
|
|
140
140
|
euint256 a = e.asEuint256(10);
|
|
141
141
|
euint256 b = e.asEuint256(4);
|
|
142
|
-
|
|
142
|
+
|
|
143
|
+
euint256 c = a.shr(b);
|
|
143
144
|
processAllOperations();
|
|
144
|
-
assertEq(getUint256Value(c),
|
|
145
|
+
assertEq(getUint256Value(c), 0);
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
function testERotl() public {
|
|
@@ -546,6 +547,12 @@ contract TestFakeInfra is IncoTest {
|
|
|
546
547
|
a.add(euint256.wrap(randomHandle));
|
|
547
548
|
}
|
|
548
549
|
|
|
550
|
+
function testECastAllowed() public {
|
|
551
|
+
bytes32 invalidHandle = keccak256("invalid handle");
|
|
552
|
+
vm.expectRevert(abi.encodeWithSelector(SenderNotAllowedForHandle.selector, invalidHandle, address(this)));
|
|
553
|
+
euint256.wrap(invalidHandle).asEbool();
|
|
554
|
+
}
|
|
555
|
+
|
|
549
556
|
function testCreateQuote() public view {
|
|
550
557
|
bytes memory mrtd =
|
|
551
558
|
hex"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
|