@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.
@@ -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 {ETypes, ebool, euint256, eaddress, typeToBitMask} from "../../Types.sol";
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 /* requesterArgData */
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
- euint256 c = a.xor(b);
142
+
143
+ euint256 c = a.shr(b);
143
144
  processAllOperations();
144
- assertEq(getUint256Value(c), 14);
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";