Skip to main content

Import

import {euint256, ebool, e, inco, elist, ETypes} from "@inco/lightning/src/Lib.sol";

Important: Fee Payments and Access Control

Fee Payments: Most elist operations require paying fees. The fee scales with the number of elements and element type. Use inco.getEListFee(nOfElements, elType) to calculate the required amount. For example, when calling e.newEList(inputs, listType, user), ensure msg.value >= inco.getEListFee(uint16(inputs.length), listType). Other operations like set(), append() and shuffle() also require fee payment proportional to the list length produced. See Fees for the full fee table and cost accounting formula.
Access Control: After creating or modifying an elist, you must explicitly grant access permissions to allow addresses to decrypt the list contents. Use the e.allow() convenience helper:
e.allow(myList, address(this));
e.allow(myList, msg.sender);

Creating new empty EList

newEList(type) creates a new empty list and returns a new elist handle. Type must be specified ahead of time and can not be changed. Here is an example of how to create a new EList holding euint256 values:
elist myList = e.newEList(ETypes.Uint256);
// myList = E([])
Note: Each elist handle is IMMUTABLE, so a handle will forever point to a particular list of values. Any operation on a list will return a new handle pointing to a new list, leaving the original list unchanged.
Arguments:
  • ETypes listType - Type of each element in the list. This can not be changed
Returns:
  • Elist - a new elist handle

Creating new EList from existing handles

newEList(handles, type) creates a new list from existing list of handles and returns a new elist handle. Type must be specified ahead of time and can not be changed. Handles type must match the type of the list container, otherwise it will revert. Here is an example of how to create a new EList from existing euint256 handles:
function createListFromHandles() public payable returns (elist) {
    bytes32[] memory handles = new bytes32[](5);
    for (uint256 i = 0; i < 5; i++) {
        handles[i] = euint256.unwrap(e.asEuint256(i + 1));
    }
    require(msg.value >= inco.getEListFee(uint16(handles.length), ETypes.Uint256), "Fee not paid");
    elist myList = e.newEList(handles, ETypes.Uint256);
    // myList = E([1, 2, 3, 4, 5])
    return myList;
}
Arguments:
  • bytes32[] handles - An array of handles to create a new list from
  • ETypes listType - Type of each element in the list. This can not be changed and must match the type of each handle in the array.
Returns:
  • Elist - A new elist handle

Length

length(list) returns the length of the list in plaintext. It’s a pure function that doesn’t require any gas to call.
uint16 len = e.length(myList);
// len == 5
Note: It is IMPORTANT to keep in mind that the length of the elist is ALWAYS PUBLIC and encoded inside the returning handle itself. It’s an intentional design choice to make contract behavior more predictable at a cost of leaking the length of the list, because it can (for the most part) always be predicted by looking at history of on-chain operations.
Arguments:
  • elist list - list to read length from
Returns:
  • uint16 len - length of the list in plaintext

ListTypeOf

listTypeOf(list) returns the type of elements contained in the list. It’s a view function that doesn’t require any gas to call.
function listTypeOf() public view returns (ETypes) {
    return e.listTypeOf(list);
}
// Returns ETypes.Uint256 or ETypes.Bool
Arguments:
  • elist list - list to read type from
Returns:
  • ETypes - the type of elements in the list (e.g., ETypes.Uint256, ETypes.Bool)

Creating new EList from user inputs

Sometimes it’s desired to create an elist from user inputs directly, like from a javascript dApp. newEList(inputs, type, user) can take an array of user encrypted ciphertexts and returns a new elist handle. Expected type must be specified ahead of time and can not be changed. Handles type must match the type of the list container, otherwise it will revert. Here is a complete example of how to create a new EList from user encrypted inputs:
function newEList(bytes[] memory inputs, ETypes listType, address user)
    public
    payable
    returns (elist)
{
    require(msg.value >= inco.getEListFee(uint16(inputs.length), listType), "Fee not paid");
    elist list = e.newEList(inputs, listType, user);
    e.allow(list, address(this));
    e.allow(list, msg.sender);
    return list;
}
Arguments:
  • bytes[] ciphertexts - An array of encrypted user inputs to create a new list from
  • ETypes listType - Expected type of each element in the list. This can not be changed and must match the type of each handle in the array.
  • address user - Address of the owner of the inputs, used to decrypt ciphertexts.
Returns:
  • elist - A new elist handle

Append

append(list, value) appends an euint256 or ebool element type at the end of an array, returning a new modified list handle. Example usage:
function listAppend(bytes memory ctValue) public payable returns (elist) {
    require(msg.value >= inco.getEListFee(1, ETypes.Uint256), "Fee not paid");
    euint256 handle = e.newEuint256(ctValue, msg.sender);
    e.allow(handle, address(this));
    e.allow(handle, msg.sender);
    list = e.append(list, handle);
    e.allow(list, address(this));
    e.allow(list, msg.sender);
    return list;
}
// [].append(5) == [5]
Arguments:
  • elist list - An elist handle to append to
  • euint256/ebool value - Element value to be appended to the list. Must match the elist type.
Returns:
  • elist - A new elist handle

Insert

insert(list, i, value) inserts a hidden element at a desired hidden position, returns a new modified list. Index can be both plaintext or encrypted.
Note however that if index is out of range, it works similarly to append() and appends element at the end of the list.
Example usage:
elist myList = e.newEList(ETypes.Uint256);
elist myNewList = e.append(l, e.asEuint256(10));
euint256 el = e.asEuint256(5);

elist insertedList = e.insert(myNewList, uint256(0), el);
// [10].insert(0, 5) == [5, 10]
Arguments:
  • elist A - An elist handle to insert to
  • euint256/uint256 i - Index position to insert at, can be both encrypted or plaintext.
  • euint256/ebool B - Element value to be inserted to the list. Must match the elist type.
Returns:
  • Elist - A new elist handle

Get

getEuint256(list, index) and getEbool(list, index) return the hidden element at a plaintext position. These methods have separate names due to Solidity function overloading limitations. Example usage:
function listGet(uint16 index) public returns (euint256) {
    euint256 res = e.getEuint256(list, index);
    e.allow(res, msg.sender);
    return res;
}

function boolListGet(uint16 index) public returns (ebool) {
    ebool res = e.getEbool(boolList, index);
    e.allow(res, msg.sender);
    return res;
}
Arguments:
  • elist list - elist handle to get element from
  • uint16 index - Plaintext index position to get element at
Returns:
  • euint256 or ebool - The encrypted value at the specified index

GetOr

getOr(list, i, default) return hidden element at hidden position. Index can be either encrypted or plaintext. Returns a handle to the hidden element if the index is within range, otherwise returns the default value. Example usage:
function listGetOr(bytes memory ctIndex, bytes memory ctDefaultValue)
    public
    payable
    returns (euint256)
{
    require(msg.value >= inco.getFee() * 2, "Fee not paid");
    euint256 index = e.newEuint256(ctIndex, msg.sender);
    euint256 defaultValue = e.newEuint256(ctDefaultValue, msg.sender);
    euint256 res = e.getOr(list, index, defaultValue);
    e.allow(res, msg.sender);
    return res;
}
// [5, 10].getOr(1, 0) == 10
Arguments:
  • elist list - elist handle to get element at
  • euint256/uint256 index - Index position to get element at. Can be either plaintext or encrypted.
  • euint256/ebool default - A default element value to be returned if index is out of range. Must match the elist type.
Returns:
  • euint256/ebool - The encrypted value at the specified index, or the default value if out of range

Set

set(list, i, value) replaces an element at hidden index and returns a new modified list. Index can be either plaintext or encrypted.
Note: If the encrypted index is out of range, the operation is ignored and the same list is returned (although with a new handle, in order to not leak information). If the plaintext index is out of range, it will revert instead.
Example usage:
elist myList = e.newEList(ETypes.Uint256);
elist myNewList1 = e.append(myList, e.asEuint256(5));
elist myNewList2 = e.append(myNewList1, e.asEuint256(10));
euint256 index = e.asEuint256(1);
euint256 newValue = e.asEuint256(2);

euint256 ten = e.set(myNewList2, index, newValue);
// [5, 10].set(1, 2) == [5, 2]
Arguments:
  • elist A - elist handle to modify element in
  • euint256/uint256 i - index position of element to modify. Can be either plaintext or encrypted.
  • euint256/ebool B - element value to be changed if index is within range. Element will be appended to the list if the index is out of range. Must match the elist type.
Returns:
  • Elist - a new elist handle

Concat

concat(list_a, list_b) concatenates two elists into one, returns a new concatenated elist. The length of the new list will be length(list1)+length(list2) Example usage:
elist myList = e.newEList(ETypes.Uint256);
elist myNewList1 = e.append(myList, e.asEuint256(5));
elist myNewList2 = e.append(myNewList1, e.asEuint256(10));

elist jointList = e.concat(myNewList2, myNewList2);
// [5, 10].concat([5, 10]) == [5, 10, 5, 10]
Arguments:
  • elist list_a - elist handle to be prepended
  • elist list_b - elist handle to be appended
Returns:
  • Elist - a new elist handle containing elements from both A and B

Slice

slice(list, start, end) is like in any other language that takes in start and end both in plaintext. Returns a new sliced list of length “end-start”. If start and end are out of bounds, it will revert. The end index must be greater than the start index. Example usage:
elist myList = e.newEList(ETypes.Uint256);
elist myNewList1 = e.append(myList, e.asEuint256(5));
elist myNewList2 = e.append(myNewList1, e.asEuint256(10));
elist myNewList3 = e.append(myNewList2, e.asEuint256(15));

elist slicedList = e.slice(myNewList3, 1, 3);
// [5, 10, 15].slice(1, 3) == [10, 15]
Arguments:
  • elist A - elist handle to be sliced
  • uint16 start - Start index of the slice, in plaintext.
  • uint16 end - End index of the slice, in plaintext. Must be greater than start and within the bounds of the list length.
Returns:
  • Elist - a new sliced list with a new length of “end-start”

SliceLen

sliceLen(list, E(start), len, defaultValue) is a variant of slice() but allows to slice at some hidden index specifying a length instead of end position. Returns a new sliced list of the specified length.
Note that if the encrypted start position is out of bounds, the resulting list will be filled with the provided default value.
Example usage:
function listSlice(bytes memory ctStart, uint16 len, bytes memory ctDefaultValue)
    public
    payable
    returns (elist)
{
    require(msg.value >= inco.getFee() * 2, "Fee not paid");
    euint256 start = e.newEuint256(ctStart, msg.sender);
    euint256 defaultValue = e.newEuint256(ctDefaultValue, msg.sender);
    list = e.sliceLen(list, start, len, defaultValue);
    e.allow(list, address(this));
    e.allow(list, msg.sender);
    return list;
}
// [5, 10, 15].sliceLen(1, 2, 0) == [10, 15]
Arguments:
  • elist list - elist handle to be sliced
  • euint256 start - Encrypted start index of the slice
  • uint16 len - Length of the desired slice
  • euint256/ebool defaultValue - Default value to use if start position is out of bounds. Must match the elist type.
Returns:
  • elist - a new sliced list with the specified length

Range

range(start, end, type) creates a new list (or a “set”) and populates it with ordered values from within range. The length of the new list will be equal to “end-start”. Example usage:
elist myList = e.range(0, 5, ETypes.Uint256);
// myList = E([0, 1, 2, 3, 4])
Arguments:
  • uint16 start - Start value of the range, inclusive.
  • uint16 end - End of the range, exclusive. Must be greater than start and fit within the bit width of the listType argument.
  • ETypes listType - Type of each element in the resulting list.
Returns:
  • Elist - a new elist handle containing elements from start to end-1 with the length of “end-start”

Reverse

reverse(list) reverses elements in a list, first element becomes last, and so on. Example usage:
elist myList = e.newEList(ETypes.Uint256);
elist myNewList1 = e.append(myList, e.asEuint256(5));
elist myNewList2 = e.append(myNewList1, e.asEuint256(10));

elist reversedList = e.reverse(myNewList2);
// [5, 10].reverse() == [10, 5]
Arguments:
  • elist A - elist handle to be reversed
Returns:
  • Elist - a new elist handle with elements in reverse order

Shuffle

shuffle(list) deterministically shuffles elements within a list, returning a new shuffled list with the same length. This operation requires fee payment. Example usage:
function listShuffle() public payable returns (elist) {
    require(msg.value >= inco.getEListFee(e.length(list), ETypes.Uint256), "Fee not paid");
    list = e.shuffle(list);
    e.allow(list, address(this));
    e.allow(list, msg.sender);
    return list;
}
// [5, 10].shuffle() could return [10, 5] or [5, 10] randomly
Arguments:
  • elist list - elist handle to be shuffled
Returns:
  • elist - a new elist handle with elements shuffled where each element is equally likely to be in any position in the new list.

ShuffledRange

shuffledRange(start, end, type) is a convenience function combining range() and shuffle() from example above into one function. It creates a range of elements from start to end, unlike range() the resulting elist is unordered and completely random. This operation requires fee payment. Example usage:
function listShuffledRange(uint16 start, uint16 end) public payable returns (elist) {
    require(msg.value >= 2*inco.getEListFee(end - start, ETypes.Uint256), "Fee not paid");
    elist newRangeList = e.shuffledRange(start, end, ETypes.Uint256);
    e.allow(newRangeList, address(this));
    e.allow(newRangeList, msg.sender);
    return newRangeList;
}
// shuffledRange(1, 53, ETypes.Uint256) = E([42, 7, 19, 33, ...])
Arguments:
  • uint16 start - Start value of the range, inclusive.
  • uint16 end - End of the range, exclusive. Must be greater than start.
  • ETypes listType - Type of each element in the resulting list.
Returns:
  • elist - a new elist handle containing elements from start to end-1 with the length of “end-start” in a random order.
Since suffledRange is a convenience function that calls range() and shuffle() separately, it would consume twice the fee of regular range.

Reveal

reveal(list) makes all elements in the elist publicly readable without requiring a wallet signature. This is useful when you want to expose results to anyone without the overhead of TEE decryption.
Revealing an elist is irreversible — once revealed, all current and future viewers can read the plaintext values without any authentication.
Example usage:
function revealResults() public {
    e.reveal(list);
}
After calling reveal(), any address can read the values via the JS SDK using eListAttestedReveal without needing a wallet signature. Arguments:
  • elist list - The elist handle to reveal
Returns:
  • (none)

Submitting EList Decryption Attestation On-Chain

After decrypting an elist off-chain, you can submit the proof on-chain so your contract can act on the plaintext values. Use e.verifyEListDecryption() to validate the TEE-produced attestation.
import {euint256, ebool, e, inco, elist, ETypes, ElementAttestationWithProof} from "@inco/lightning/src/Lib.sol";

function submitDecryptionResult(
    elist encryptedList,
    ElementAttestationWithProof[] calldata proofElements,
    bytes32 proof,
    bytes[] calldata signatures
) external {
    require(
        e.verifyEListDecryption(encryptedList, proofElements, proof, signatures),
        "Invalid attestation"
    );
    // proofElements[i].value contains the i-th plaintext element
    for (uint256 i = 0; i < proofElements.length; i++) {
        _processElement(proofElements[i].value);
    }
}
Arguments:
  • elist elistHandle - The elist handle whose decryption is being attested
  • ElementAttestationWithProof[] proofElements - One entry per element (see below)
  • bytes32 proof - Merkle root or aggregated proof from the TEE
  • bytes[] signatures - Covalidator signatures over the proof
Returns:
  • bool - true if the attestation is valid, reverts or returns false otherwise

ElementAttestationWithProof

Each element in the proofElements array is one of two modes:
struct ElementAttestationWithProof {
    bytes32 pairHash;    // non-zero: privacy mode — value stays hidden
    bytes32 commitment;  // non-zero (with value): transparency mode
    bytes32 value;       // plaintext value (transparency mode only)
}
Privacy mode (pairHash != 0): The element’s plaintext value is not included. The TEE provides a pre-computed hash that proves the element was included in the elist without revealing its value. Use this for elements your contract does not need to read. Transparency mode (pairHash == 0, commitment and value set): The plaintext value is included alongside a commitment that binds it to the ciphertext. Use this for elements your contract needs to act on. You can mix both modes in a single submission — for example, reveal only the top-scored entry in an encrypted leaderboard while keeping all other entries hidden.

Partial EList Submission

You do not need to submit all elements at once. Provide the unsubmitted elements as pre-computed pairHash values (privacy mode) so the TEE can still verify the overall proof integrity:
// Submit only element at index 2; hide the rest
ElementAttestationWithProof[] memory proofElements = new ElementAttestationWithProof[](5);
proofElements[0] = ElementAttestationWithProof({ pairHash: hash0, commitment: 0, value: 0 });
proofElements[1] = ElementAttestationWithProof({ pairHash: hash1, commitment: 0, value: 0 });
proofElements[2] = ElementAttestationWithProof({ pairHash: 0, commitment: commitment2, value: value2 });
proofElements[3] = ElementAttestationWithProof({ pairHash: hash3, commitment: 0, value: 0 });
proofElements[4] = ElementAttestationWithProof({ pairHash: hash4, commitment: 0, value: 0 });

bool valid = e.verifyEListDecryption(encryptedList, proofElements, proof, signatures);
The pairHash values for hidden elements are returned by the JS SDK alongside the transparency-mode elements when you call eListAttestedDecrypt.