@inco/lightning 0.8.0-devnet-2 → 0.8.0-devnet-4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # Inco lite
1
+ # Inco Lightning
2
2
 
3
- ![coverage](./coverage.svg)
3
+ The core Inco Lightning smart contracts library for building confidential applications on EVM chains.
4
4
 
5
- <!-- todo #1035 upgrade deployment and upgrade documentation now outdated @silasdavis -->
5
+ ![coverage](./coverage.svg)
6
6
 
7
7
  ## Install dependencies
8
8
 
@@ -12,7 +12,7 @@ bun install
12
12
 
13
13
  ## Build
14
14
 
15
- Use [forge](https://book.getfoundry.sh/getting-started/installation) version 1.0 or higher
15
+ See [Foundry version requirements](../README.md#foundry-version-requirements) for version guidance.
16
16
 
17
17
  ```sh
18
18
  forge build
@@ -36,58 +36,32 @@ make coverage
36
36
 
37
37
  > This generates an lcov report, filters out `node_modules`, autogenerated contracts and other contracts that we don't want to include in the report (`Lib.XXXnet.sol`, etc.). Eventually, it `genhtml` the filtered lcov in `coverage` and a `lcov-badge` visible in this readme. You can check the latest coverage stats [here](./coverage/index.html).
38
38
 
39
- > Note: You need `lcov` for the `make coverage` to work
40
- > For MacOS - `brew install lcov`
41
-
42
- ## Deploy
43
-
44
- ### One-time key import
45
-
46
- 1. Import key into Foundry cast wallet using [`make import_sepolia_deployer`](./Makefile)
47
-
48
- ### Deploy new IncoLite contracts
49
-
50
- Targest for deploying the contracts are in the [Makefile](./Makefile). There are separate targtes for each chain and a target that deploys to all of them.
51
-
52
- The version of the contracts that will be deployed is defined by the current working directory and it will be deployed to an address that is deterministically calculated based on:
53
-
54
- - The contract name
55
- - The contract version
56
- - The deployer address
57
- - An optional deploy-time 'pepper'
58
-
59
- The contract name and version are constants in [IncoLightningConfig.sol](./src/version/IncoLightningConfig.sol) and the deployer address is the address of the wallet that is used to deploy the contracts. The optional deploy-time 'pepper' is a deployment-time string that can be chosen to alter the contract address for each deployment in the case where you what to differentiate a deployment from others.
60
-
61
- The point of the deterministic address is that holding the values above constant we can obtain a consistent canonical deployment address across all chains.
62
-
63
- #### Deployment process
64
-
65
- 1. Bump IncoLite version in [IncoLightningConfig.sol](./src/version/IncoLightningConfig.sol)
66
- 2. Run [`make deploy_multichain`](./Makefile) - this will update the manifest with the new release
67
- 3. Run [`make deploy_test_contracts_multichain`](./Makefile)
39
+ > **Note:** Coverage requires `lcov` (`brew install lcov` on macOS) and Foundry v1.3.6. See [Foundry version requirements](../README.md#foundry-version-requirements).
68
40
 
69
- ##### Providing a pepper
41
+ ## Directory Structure
70
42
 
71
- If you want to provide a pepper then use `make deploy_multichain INCO_LITE_PEPPER=<pepper>` where `pepper` is the string you want to use. This will be used to calculate the contract address and will be included in the manifest.
43
+ | Directory | Description |
44
+ | -------------- | -------------------------------------------------- |
45
+ | `src/` | Main contract source code |
46
+ | `src/libs/` | Release libraries that link to versioned executors |
47
+ | `src/version/` | Version configuration (`IncoLightningConfig.sol`) |
48
+ | `test/` | Unit tests |
49
+ | `script/` | Utility scripts |
72
50
 
73
- ## Defining a release without explicit deployment
51
+ ## Makefile Commands
74
52
 
75
- The deployment machinery is currently in a halfway house whereby we capture releases at the same time as performing smart contract deployments locally. In the future we expect the contract deployment (and later upgrade) processes to be run by CI or a by a covalidator initialisation container. At that point rather than the manifest being an artefact of record it will be a declaration of intent. At the moment it serves both purposes, but will be refactored to remove the deployment log capture element.
53
+ | Command | Description |
54
+ | --------------- | ------------------------------------ |
55
+ | `make build` | Build contracts with Foundry |
56
+ | `make test` | Run unit tests |
57
+ | `make coverage` | Generate coverage report (uses lcov) |
58
+ | `make lint` | Lint Solidity files |
76
59
 
77
- With a nod to the future we provide the `make release` target which performs a simulated deploy, updates the manifest with a dummy deployment, and declares a release.
78
-
79
- ## Deploy NPM modules
80
-
81
- 1. Bump contracts NPM version in [package.json](./package.json)
82
- 2. Bump JS NPM version in [package.json](../js/package.json)
83
- 3. Publish contracts `npm publish:github`
84
- 4. Build JS [`make build`](../js/Makefile) (depends on contract deployment being done first)
85
- 5. Publish js `npm publish:github`
60
+ ## Deploy
86
61
 
87
- ## Build covalidator docker images
62
+ For comprehensive deployment and upgrade instructions, see [`lightning-deployment/`](../lightning-deployment/README.md).
88
63
 
89
- 1. PR to main
90
- 2. Manually grab hash from dockerhub which is also `git describe --tags --always --dirty`
64
+ Contract version is defined in [`IncoLightningConfig.sol`](./src/version/IncoLightningConfig.sol).
91
65
 
92
66
  ## Manifest file
93
67
 
@@ -102,21 +76,3 @@ incoLightning_<major>_<minor>_<patch>__<salt>
102
76
  ```
103
77
 
104
78
  The salt is derived from the version and additional value called the `pepper`. The salt determines the on-chain address of the release, whence the pepper can be used to for releases of the same contract versions with separate state and addresses (e.g. for testing purposes).
105
-
106
- ## Deployment dumps
107
-
108
- In [`dumps`](./dumps) you can find a sequence of pairs of files named with the following format:
109
-
110
- ```
111
- <release_name>.dump.json
112
- ```
113
-
114
- Contains an [Anvil](https://book.getfoundry.sh/anvil/) state dump that can be loaded with `anvil --load-state <state-dump-file>`, which you see used in our local node [`docker-compose`](../../docker-compose.yaml) setup.
115
-
116
- ```
117
- <release_name>.env
118
- ```
119
-
120
- Is a dump parameters file, it contains a set of private and public keys, configuration for a covalidator, utility addresss, and the executor address as it exists in the particular state dump.
121
-
122
- Everything require to run a local covalidator (in docker or otherwise), to encrypt values and communicate with the executor contract of the dump, is included within this parameters file. The secrets are generated for each dump.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inco/lightning",
3
- "version": "0.8.0-devnet-2",
3
+ "version": "0.8.0-devnet-4",
4
4
  "repository": "https://github.com/Inco-fhevm/inco-monorepo",
5
5
  "files": [
6
6
  "src/",
@@ -4,11 +4,12 @@ pragma solidity ^0.8;
4
4
  import {IncoTest} from "../../../test/IncoTest.sol";
5
5
  import {SessionVerifier, Session} from "../../../periphery/SessionVerifier.sol";
6
6
  import {AllowanceVoucher, AllowanceProof} from "../AdvancedAccessControl.sol";
7
- import {euint256} from "../../../Types.sol";
7
+ import {euint256, SharerNotAllowedForHandle} from "../../../Types.sol";
8
8
  import {e, inco} from "../../../Lib.sol";
9
9
  import {AdvancedAccessControl} from "../AdvancedAccessControl.sol";
10
10
  import {ALLOWANCE_GRANTED_MAGIC_VALUE} from "../../../Types.sol";
11
11
  import {IIncoVerifier} from "../../../interfaces/IIncoVerifier.sol";
12
+ import {BaseAccessControlList} from "../BaseAccessControlList.sol";
12
13
 
13
14
  contract SomeContractWithConfidentialData {
14
15
 
@@ -192,4 +193,77 @@ contract TestAdvancedAccessControl is IncoTest {
192
193
  return getSignatureForDigest(incoVerifier.allowanceVoucherDigest(voucher), alicePrivKey);
193
194
  }
194
195
 
196
+ /// @notice Test SharerNotAllowedForHandle error when sharer is not allowed
197
+ function testIsAllowedWithProofSharerNotAllowed() public {
198
+ DoesNotVerifyAnything verifier = new DoesNotVerifyAnything();
199
+ AllowanceVoucher memory voucher = AllowanceVoucher({
200
+ sessionNonce: bytes32(0),
201
+ verifyingContract: address(verifier),
202
+ callFunction: verifier.someCheck.selector,
203
+ sharerArgData: ""
204
+ });
205
+ // Use bob as sharer, but bob is NOT allowed on the secret (only alice is)
206
+ AllowanceProof memory proof = AllowanceProof({
207
+ sharer: bob,
208
+ voucher: voucher,
209
+ voucherSignature: getSignatureForDigest(incoVerifier.allowanceVoucherDigest(voucher), bobPrivKey),
210
+ requesterArgData: ""
211
+ });
212
+ vm.expectRevert(abi.encodeWithSelector(SharerNotAllowedForHandle.selector, secretHandle, bob));
213
+ incoVerifier.isAllowedWithProof(secretHandle, carol, proof);
214
+ }
215
+
216
+ /// @notice Test InvalidVoucherSignature error when signature is invalid
217
+ function testIsAllowedWithProofInvalidSignature() public {
218
+ DoesNotVerifyAnything verifier = new DoesNotVerifyAnything();
219
+ AllowanceVoucher memory voucher = AllowanceVoucher({
220
+ sessionNonce: bytes32(0),
221
+ verifyingContract: address(verifier),
222
+ callFunction: verifier.someCheck.selector,
223
+ sharerArgData: ""
224
+ });
225
+ // Alice is the sharer (and is allowed), but we sign with Bob's key
226
+ bytes memory wrongSignature = getSignatureForDigest(incoVerifier.allowanceVoucherDigest(voucher), bobPrivKey);
227
+ AllowanceProof memory proof =
228
+ AllowanceProof({sharer: alice, voucher: voucher, voucherSignature: wrongSignature, requesterArgData: ""});
229
+ bytes32 voucherDigest = incoVerifier.allowanceVoucherDigest(voucher);
230
+ vm.expectRevert(
231
+ abi.encodeWithSelector(
232
+ AdvancedAccessControl.InvalidVoucherSignature.selector, alice, voucherDigest, wrongSignature
233
+ )
234
+ );
235
+ incoVerifier.isAllowedWithProof(secretHandle, bob, proof);
236
+ }
237
+
238
+ /// @notice Test claimHandle fails when proof verification fails (line 107)
239
+ function testClaimHandleProofVerificationFailed() public {
240
+ SomeVerifier verifier = new SomeVerifier();
241
+ // Create a voucher that will be valid signature-wise but the verifier will return false
242
+ // because we pass wrong requesterArgData (not 0xbeef)
243
+ AllowanceVoucher memory voucher = AllowanceVoucher({
244
+ sessionNonce: bytes32(0),
245
+ verifyingContract: address(verifier),
246
+ callFunction: verifier.someCheck.selector,
247
+ sharerArgData: abi.encode(SomeVerifier.SharerArg({handleShared: secretHandle, allowedAccount: bob}))
248
+ });
249
+ AllowanceProof memory proof = AllowanceProof({
250
+ sharer: alice, // alice IS allowed on the secret
251
+ voucher: voucher,
252
+ voucherSignature: getAliceSig(voucher),
253
+ requesterArgData: abi.encode(SomeVerifier.RequesterArg({mustBeBeef: bytes2(0x1234)})) // WRONG! Not 0xbeef
254
+ });
255
+
256
+ // isAllowedWithProof should return false (not revert), then claimHandle should revert
257
+ vm.prank(bob);
258
+ vm.expectRevert(
259
+ abi.encodeWithSelector(
260
+ BaseAccessControlList.ProofVerificationFailed.selector,
261
+ address(verifier),
262
+ verifier.someCheck.selector,
263
+ abi.encode(SomeVerifier.SharerArg({handleShared: secretHandle, allowedAccount: bob}))
264
+ )
265
+ );
266
+ inco.claimHandle(secretHandle, proof);
267
+ }
268
+
195
269
  }
@@ -4,6 +4,7 @@ pragma solidity ^0.8;
4
4
  import {BaseAccessControlList} from "../BaseAccessControlList.sol";
5
5
  import {VerifierAddressGetter} from "../../primitives/VerifierAddressGetter.sol";
6
6
  import {euint256, inco} from "../../../Lib.sol";
7
+ import {SenderNotAllowedForHandle} from "../../../Types.sol";
7
8
  import {IncoTest} from "../../../test/IncoTest.sol";
8
9
 
9
10
  contract TestBaseAccessControl is BaseAccessControlList, IncoTest {
@@ -25,4 +26,248 @@ contract TestBaseAccessControl is BaseAccessControlList, IncoTest {
25
26
  assert(inco.isAllowed(euint256.unwrap(secret), alice));
26
27
  }
27
28
 
29
+ // ============ allowTransient Tests ============
30
+
31
+ function testAllowTransient() public {
32
+ euint256 secret = inco.asEuint256(42);
33
+ bytes32 handle = euint256.unwrap(secret);
34
+
35
+ // Initially bob is not allowed
36
+ assertFalse(inco.isAllowed(handle, bob));
37
+
38
+ // Allow transient access from this contract (which is allowed)
39
+ inco.allowTransient(handle, bob);
40
+
41
+ // Bob should now have transient access
42
+ assertTrue(inco.allowedTransient(handle, bob));
43
+ assertTrue(inco.isAllowed(handle, bob));
44
+ }
45
+
46
+ function testAllowTransient_RevertsWhenSenderNotAllowed() public {
47
+ euint256 secret = inco.asEuint256(42);
48
+ bytes32 handle = euint256.unwrap(secret);
49
+
50
+ // Try to allow transient from an account that doesn't have access
51
+ vm.prank(bob);
52
+ vm.expectRevert(abi.encodeWithSelector(SenderNotAllowedForHandle.selector, handle, bob));
53
+ inco.allowTransient(handle, carol);
54
+ }
55
+
56
+ // ============ cleanTransientStorage Tests ============
57
+
58
+ function testCleanTransientStorage() public {
59
+ euint256 secret1 = inco.asEuint256(100);
60
+ euint256 secret2 = inco.asEuint256(200);
61
+ bytes32 handle1 = euint256.unwrap(secret1);
62
+ bytes32 handle2 = euint256.unwrap(secret2);
63
+
64
+ // Grant transient access
65
+ inco.allowTransient(handle1, bob);
66
+ inco.allowTransient(handle2, carol);
67
+
68
+ // Verify transient access is granted
69
+ assertTrue(inco.allowedTransient(handle1, bob));
70
+ assertTrue(inco.allowedTransient(handle2, carol));
71
+
72
+ // Clean transient storage
73
+ inco.cleanTransientStorage();
74
+
75
+ // Transient access should be revoked
76
+ assertFalse(inco.allowedTransient(handle1, bob));
77
+ assertFalse(inco.allowedTransient(handle2, carol));
78
+ }
79
+
80
+ // ============ allowedTransient Direct Call Tests ============
81
+
82
+ function testAllowedTransient_DirectCall() public {
83
+ euint256 secret = inco.asEuint256(42);
84
+ bytes32 handle = euint256.unwrap(secret);
85
+
86
+ // Direct call should return false initially
87
+ assertFalse(inco.allowedTransient(handle, bob));
88
+
89
+ // After allowing, direct call should return true
90
+ inco.allowTransient(handle, bob);
91
+ assertTrue(inco.allowedTransient(handle, bob));
92
+ }
93
+
94
+ // ============ isRevealed Direct Call Tests ============
95
+
96
+ function testIsRevealed_DirectCall() public {
97
+ euint256 secret = inco.asEuint256(42);
98
+ bytes32 handle = euint256.unwrap(secret);
99
+
100
+ // Direct call should return false initially
101
+ assertFalse(inco.isRevealed(handle));
102
+
103
+ // After reveal, direct call should return true
104
+ inco.reveal(handle);
105
+ assertTrue(inco.isRevealed(handle));
106
+ }
107
+
108
+ // ============ allow Revert Tests ============
109
+
110
+ function testAllow_RevertsWhenSenderNotAllowed() public {
111
+ euint256 secret = inco.asEuint256(42);
112
+ bytes32 handle = euint256.unwrap(secret);
113
+
114
+ // Try to allow from an account that doesn't have access
115
+ vm.prank(bob);
116
+ vm.expectRevert(abi.encodeWithSelector(SenderNotAllowedForHandle.selector, handle, bob));
117
+ inco.allow(handle, carol);
118
+ }
119
+
120
+ // ============ reveal Revert Tests ============
121
+
122
+ function testReveal_RevertsWhenSenderNotAllowed() public {
123
+ euint256 secret = inco.asEuint256(42);
124
+ bytes32 handle = euint256.unwrap(secret);
125
+
126
+ // Try to reveal from an account that doesn't have access
127
+ vm.prank(bob);
128
+ vm.expectRevert(abi.encodeWithSelector(SenderNotAllowedForHandle.selector, handle, bob));
129
+ inco.reveal(handle);
130
+ }
131
+
132
+ // ============ Fuzz Tests for isAllowed ============
133
+
134
+ /// @dev Fuzz test that isAllowed returns true when handle is revealed (regardless of account)
135
+ function testFuzzIsAllowedWhenRevealed(bytes32 randomSeed, address randomAccount) public {
136
+ vm.assume(randomAccount != address(0));
137
+
138
+ // Create a unique handle using the random seed
139
+ euint256 secret = inco.asEuint256(uint256(randomSeed));
140
+ bytes32 handle = euint256.unwrap(secret);
141
+
142
+ // Initially, random account should not have access (unless it's this contract)
143
+ if (randomAccount != address(this)) {
144
+ assertFalse(inco.isAllowed(handle, randomAccount));
145
+ }
146
+
147
+ // Reveal the handle
148
+ inco.reveal(handle);
149
+
150
+ // Now any account should have access
151
+ assertTrue(inco.isAllowed(handle, randomAccount));
152
+ assertTrue(inco.isRevealed(handle));
153
+ }
154
+
155
+ /// @dev Fuzz test that isAllowed returns true when persistAllowed is set
156
+ function testFuzzIsAllowedWhenPersisted(bytes32 randomSeed, address allowedAccount) public {
157
+ vm.assume(allowedAccount != address(0));
158
+ vm.assume(allowedAccount != address(this));
159
+
160
+ // Create a unique handle
161
+ euint256 secret = inco.asEuint256(uint256(randomSeed));
162
+ bytes32 handle = euint256.unwrap(secret);
163
+
164
+ // Initially not allowed
165
+ assertFalse(inco.isAllowed(handle, allowedAccount));
166
+
167
+ // Allow the account (from this contract which has access)
168
+ inco.allow(handle, allowedAccount);
169
+
170
+ // Now should be allowed
171
+ assertTrue(inco.isAllowed(handle, allowedAccount));
172
+ assertTrue(inco.persistAllowed(handle, allowedAccount));
173
+ }
174
+
175
+ /// @dev Fuzz test that isAllowed returns true when transient access is granted
176
+ function testFuzzIsAllowedWhenTransient(bytes32 randomSeed, address allowedAccount) public {
177
+ vm.assume(allowedAccount != address(0));
178
+ vm.assume(allowedAccount != address(this));
179
+
180
+ // Create a unique handle
181
+ euint256 secret = inco.asEuint256(uint256(randomSeed));
182
+ bytes32 handle = euint256.unwrap(secret);
183
+
184
+ // Initially not allowed
185
+ assertFalse(inco.isAllowed(handle, allowedAccount));
186
+
187
+ // Allow transient access
188
+ inco.allowTransient(handle, allowedAccount);
189
+
190
+ // Should be allowed via transient
191
+ assertTrue(inco.isAllowed(handle, allowedAccount));
192
+ assertTrue(inco.allowedTransient(handle, allowedAccount));
193
+ // But not persisted
194
+ assertFalse(inco.persistAllowed(handle, allowedAccount));
195
+ }
196
+
197
+ /// @dev Fuzz test the OR logic: isAllowed = transient OR persisted OR revealed
198
+ function testFuzzIsAllowedOrLogic(bytes32 randomSeed, uint8 accessMode) public {
199
+ // Create a unique handle
200
+ euint256 secret = inco.asEuint256(uint256(randomSeed));
201
+ bytes32 handle = euint256.unwrap(secret);
202
+
203
+ // accessMode determines which access type to grant:
204
+ // 0 = none, 1 = transient, 2 = persisted, 3 = revealed, 4+ = combinations
205
+ uint8 mode = accessMode % 8;
206
+
207
+ bool grantTransient = (mode & 1) != 0;
208
+ bool grantPersisted = (mode & 2) != 0;
209
+ bool grantRevealed = (mode & 4) != 0;
210
+
211
+ // Initially bob has no access
212
+ assertFalse(inco.isAllowed(handle, bob));
213
+
214
+ // Grant access based on mode
215
+ if (grantTransient) {
216
+ inco.allowTransient(handle, bob);
217
+ }
218
+ if (grantPersisted) {
219
+ inco.allow(handle, bob);
220
+ }
221
+ if (grantRevealed) {
222
+ inco.reveal(handle);
223
+ }
224
+
225
+ // isAllowed should be true if ANY access was granted
226
+ bool expectedAllowed = grantTransient || grantPersisted || grantRevealed;
227
+ assertEq(inco.isAllowed(handle, bob), expectedAllowed);
228
+ }
229
+
230
+ /// @dev Fuzz test that cleaning transient storage removes transient access
231
+ function testFuzzCleanTransientStorageRemovesAccess(bytes32 randomSeed) public {
232
+ // Create a unique handle
233
+ euint256 secret = inco.asEuint256(uint256(randomSeed));
234
+ bytes32 handle = euint256.unwrap(secret);
235
+
236
+ // Grant transient access
237
+ inco.allowTransient(handle, bob);
238
+ assertTrue(inco.allowedTransient(handle, bob));
239
+ assertTrue(inco.isAllowed(handle, bob));
240
+
241
+ // Clean transient storage
242
+ inco.cleanTransientStorage();
243
+
244
+ // Transient access should be revoked
245
+ assertFalse(inco.allowedTransient(handle, bob));
246
+ assertFalse(inco.isAllowed(handle, bob));
247
+ }
248
+
249
+ /// @dev Fuzz test multiple accounts with different access levels
250
+ function testFuzzMultipleAccountsAccessLevels(bytes32 randomSeed) public {
251
+ euint256 secret = inco.asEuint256(uint256(randomSeed));
252
+ bytes32 handle = euint256.unwrap(secret);
253
+
254
+ // Grant different access types to different accounts
255
+ inco.allowTransient(handle, bob);
256
+ inco.allow(handle, carol);
257
+ // dave gets no access
258
+
259
+ // Verify access levels
260
+ assertTrue(inco.isAllowed(handle, bob));
261
+ assertTrue(inco.allowedTransient(handle, bob));
262
+ assertFalse(inco.persistAllowed(handle, bob));
263
+
264
+ assertTrue(inco.isAllowed(handle, carol));
265
+ assertFalse(inco.allowedTransient(handle, carol));
266
+ assertTrue(inco.persistAllowed(handle, carol));
267
+
268
+ assertFalse(inco.isAllowed(handle, dave));
269
+ assertFalse(inco.allowedTransient(handle, dave));
270
+ assertFalse(inco.persistAllowed(handle, dave));
271
+ }
272
+
28
273
  }
@@ -17,52 +17,61 @@ error ExternalHandleDoesNotMatchComputedHandle(
17
17
  uint256 chainId,
18
18
  address aclAddress,
19
19
  address userAddress,
20
- address contractAddress
20
+ address contractAddress,
21
+ int32 version
21
22
  );
22
23
 
23
24
  abstract contract EncryptedInput is IEncryptedInput, BaseAccessControlList, HandleGeneration, Fee {
24
25
 
25
26
  event NewInput(
26
- bytes32 indexed result, address indexed contractAddress, address indexed user, bytes ciphertext, uint256 eventId
27
+ bytes32 indexed result,
28
+ address indexed contractAddress,
29
+ address indexed user,
30
+ int32 version,
31
+ bytes ciphertext,
32
+ uint256 eventId
27
33
  );
28
34
 
29
- function newEuint256(bytes memory input, address user) external payable returns (euint256 newValue) {
35
+ function newEuint256(bytes calldata input, address user) external payable returns (euint256 newValue) {
30
36
  return euint256.wrap(newInput(input, user, ETypes.Uint256));
31
37
  }
32
38
 
33
- function newEbool(bytes memory input, address user) external payable returns (ebool newValue) {
39
+ function newEbool(bytes calldata input, address user) external payable returns (ebool newValue) {
34
40
  return ebool.wrap(newInput(input, user, ETypes.Bool));
35
41
  }
36
42
 
37
- function newEaddress(bytes memory input, address user) external payable returns (eaddress newValue) {
43
+ function newEaddress(bytes calldata input, address user) external payable returns (eaddress newValue) {
38
44
  return eaddress.wrap(newInput(input, user, ETypes.AddressOrUint160OrBytes20));
39
45
  }
40
46
 
41
- function newInput(bytes memory ciphertext, address user, ETypes inputType)
47
+ function newInput(bytes calldata input, address user, ETypes inputType)
42
48
  internal
43
49
  paying
44
50
  returns (bytes32 newHandle)
45
51
  {
46
- newHandle = _newInput(ciphertext, user, inputType);
52
+ newHandle = _newInput(input, user, inputType);
47
53
  }
48
54
 
49
- function newInputNotPaying(bytes memory ciphertext, address user, ETypes inputType)
55
+ function newInputNotPaying(bytes calldata input, address user, ETypes inputType)
50
56
  internal
51
57
  returns (bytes32 newHandle)
52
58
  {
53
- newHandle = _newInput(ciphertext, user, inputType);
59
+ newHandle = _newInput(input, user, inputType);
54
60
  }
55
61
 
56
62
  /// @notice Creates a new input with a prepended handle as a checksum.
57
63
  /// @param input The input that contains the handle prepended to the ciphertext.
58
64
  /// @param user The user address associated with the input.
59
- function _newInput(bytes memory input, address user, ETypes inputType) private returns (bytes32 handle) {
65
+ function _newInput(bytes calldata input, address user, ETypes inputType) private returns (bytes32 handle) {
60
66
  // Since there is no sensible way to handle abi.decode errors (https://github.com/argotorg/solidity/issues/10381)
61
67
  // at least fail early on a conservative minimum length
62
- require(input.length >= 64, "Input too short, should be at least 64 bytes");
68
+ require(input.length >= 68, "Input too short, should be at least 68 bytes");
69
+ // Parse the version from the first 4 bytes.
70
+ bytes4 prefix = bytes4(input[:4]);
71
+ int32 version = int32(uint32(prefix));
63
72
  // Remove external handle prepended to input as a checksum
64
- (bytes32 externalHandle, bytes memory ciphertext) = abi.decode(input, (bytes32, bytes));
65
- handle = getInputHandle(ciphertext, user, msg.sender, inputType);
73
+ (bytes32 externalHandle, bytes memory ciphertext) = abi.decode(input[4:], (bytes32, bytes));
74
+ handle = getInputHandle(ciphertext, user, msg.sender, version, inputType);
66
75
  require(
67
76
  handle == externalHandle,
68
77
  ExternalHandleDoesNotMatchComputedHandle({
@@ -71,14 +80,22 @@ abstract contract EncryptedInput is IEncryptedInput, BaseAccessControlList, Hand
71
80
  chainId: block.chainid,
72
81
  aclAddress: address(this),
73
82
  userAddress: user,
74
- contractAddress: msg.sender
83
+ contractAddress: msg.sender,
84
+ version: version
75
85
  })
76
86
  );
77
87
  // We assume that providing the same handle (which via HADU implies same plaintext, same context, and same
78
88
  // instance of encryption)
79
89
  require(!isAllowed(handle, user), HandleAlreadyExists(handle));
80
90
  uint256 id = getNextEventId();
81
- emit NewInput({result: handle, contractAddress: msg.sender, user: user, ciphertext: ciphertext, eventId: id});
91
+ emit NewInput({
92
+ result: handle,
93
+ contractAddress: msg.sender,
94
+ user: user,
95
+ version: version,
96
+ ciphertext: ciphertext,
97
+ eventId: id
98
+ });
82
99
  setDigest(abi.encodePacked(handle, id));
83
100
  // We allow to user since this is harmless and it is convenient to use the allow mapping to track inputs.
84
101
  // NOTE: the allow must come after emitting the new input event, since allow emits its own event.
@@ -61,11 +61,6 @@ abstract contract EncryptedOperations is IEncryptedOperations, BaseAccessControl
61
61
  _;
62
62
  }
63
63
 
64
- modifier checkedHandle(euint256 handle) {
65
- checkInput(euint256.unwrap(handle), typeToBitMask(ETypes.Uint256));
66
- _;
67
- }
68
-
69
64
  function checkInput(bytes32 input, bytes32 requiredTypes) internal view {
70
65
  require(isAllowed(input, msg.sender), SenderNotAllowedForHandle(input, msg.sender));
71
66
  require(requiredTypes & typeToBitMask(typeOf(input)) != 0, UnexpectedType(typeOf(input), requiredTypes));
@@ -279,6 +274,7 @@ abstract contract EncryptedOperations is IEncryptedOperations, BaseAccessControl
279
274
  }
280
275
 
281
276
  function eCast(bytes32 ct, ETypes toType) external returns (bytes32 result) {
277
+ checkInput(ct, SUPPORTED_TYPES_MASK);
282
278
  require(isTypeSupported(toType), UnsupportedType(toType));
283
279
  result = createResultHandle(EOps.Cast, toType, abi.encodePacked(ct));
284
280
  allowTransientInternal(result, msg.sender);
@@ -7,24 +7,53 @@ import {BaseAccessControlList} from "./AccessControl/BaseAccessControlList.sol";
7
7
  import {HandleGeneration} from "./primitives/HandleGeneration.sol";
8
8
  import {ITrivialEncryption} from "./interfaces/ITrivialEncryption.sol";
9
9
 
10
+ /// @title TrivialEncryption
11
+ /// @notice Provides functions to create encrypted handles from plaintext values.
12
+ /// @dev Trivial encryption wraps plaintext values as encrypted handles. The plaintext is visible
13
+ /// on-chain (emitted in events), so this should only be used for values that are already public.
14
+ /// Common use cases include initializing encrypted state with known values or creating encrypted
15
+ /// constants. The resulting handles are granted transient access to the caller.
10
16
  abstract contract TrivialEncryption is ITrivialEncryption, EventCounter, BaseAccessControlList, HandleGeneration {
11
17
 
18
+ /// @notice Emitted when a trivial encryption is performed.
19
+ /// @param result The handle representing the encrypted value.
20
+ /// @param plainTextBytes The plaintext value that was encrypted (visible on-chain).
21
+ /// @param handleType The type of the encrypted value.
22
+ /// @param eventId The unique event ID for ordering and verification.
12
23
  event TrivialEncrypt(bytes32 indexed result, bytes32 plainTextBytes, ETypes handleType, uint256 eventId);
13
24
 
25
+ /// @notice Creates an encrypted uint256 handle from a plaintext value.
26
+ /// @dev The plaintext value is visible on-chain. Use this for public values only.
27
+ /// @param value The plaintext uint256 value to encrypt.
28
+ /// @return newEuint256 The encrypted handle representing the value.
14
29
  function asEuint256(uint256 value) external returns (euint256 newEuint256) {
15
30
  return euint256.wrap(newTrivialEncrypt(bytes32(value), ETypes.Uint256));
16
31
  }
17
32
 
33
+ /// @notice Creates an encrypted boolean handle from a plaintext value.
34
+ /// @dev The plaintext value is visible on-chain. Use this for public values only.
35
+ /// @param value The plaintext boolean value to encrypt.
36
+ /// @return newEbool The encrypted handle representing the value.
18
37
  function asEbool(bool value) external returns (ebool newEbool) {
19
38
  bytes32 castedValue = bytes32(uint256(value ? 1 : 0));
20
39
  return ebool.wrap(newTrivialEncrypt(castedValue, ETypes.Bool));
21
40
  }
22
41
 
42
+ /// @notice Creates an encrypted address handle from a plaintext value.
43
+ /// @dev The plaintext value is visible on-chain. Use this for public values only.
44
+ /// @param value The plaintext address value to encrypt.
45
+ /// @return newEaddress The encrypted handle representing the value.
23
46
  function asEaddress(address value) external returns (eaddress newEaddress) {
24
47
  bytes32 castedValue = bytes32(uint256(uint160(value)));
25
48
  return eaddress.wrap(newTrivialEncrypt(castedValue, ETypes.AddressOrUint160OrBytes20));
26
49
  }
27
50
 
51
+ /// @notice Internal function that performs the trivial encryption.
52
+ /// @dev Generates a deterministic handle, grants transient access to the caller,
53
+ /// emits the TrivialEncrypt event, and updates the digest for verification.
54
+ /// @param plainTextBytes The plaintext value as bytes32.
55
+ /// @param handleType The type of encrypted value being created.
56
+ /// @return newHandle The generated handle for the encrypted value.
28
57
  function newTrivialEncrypt(bytes32 plainTextBytes, ETypes handleType) internal returns (bytes32 newHandle) {
29
58
  newHandle = getTrivialEncryptHandle(plainTextBytes, handleType);
30
59
  allowTransientInternal(newHandle, msg.sender);
@@ -5,8 +5,8 @@ import {euint256, ebool, eaddress} from "../../Types.sol";
5
5
 
6
6
  interface IEncryptedInput {
7
7
 
8
- function newEuint256(bytes memory ciphertext, address user) external payable returns (euint256 newValue);
9
- function newEbool(bytes memory ciphertext, address user) external payable returns (ebool newValue);
10
- function newEaddress(bytes memory ciphertext, address user) external payable returns (eaddress newValue);
8
+ function newEuint256(bytes calldata ciphertext, address user) external payable returns (euint256 newValue);
9
+ function newEbool(bytes calldata ciphertext, address user) external payable returns (ebool newValue);
10
+ function newEaddress(bytes calldata ciphertext, address user) external payable returns (eaddress newValue);
11
11
 
12
12
  }