Skip to main content

EList

The Inco Lightning SDK provides encrypted lists (EList) — confidential, immutable, homogeneous dynamic arrays of Euint128 or Ebool values. Like the scalar operations, every elist operation is a cross-program invocation that returns a new handle; the covalidator network performs the actual list computation off-chain. The EList, EListType types are documented in Types. Element values are passed as raw u128 handles (Euint128.0 / Ebool.0), and encrypted indices are Euint128.
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.

Constructor Operations

Read Operations

Modifying Operations

Operations that produce a new list return a new EList handle.
Every operation that produces a new handle requires calling allow() to grant decryption access. Use the remaining_accounts pattern to pass allowance PDAs and grant access in the same transaction. See Access Control for simulation patterns on the client side.

Creating new EList from existing handles

new_elist_from_handles(handles, list_type) creates a new list from an 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.

Creating new EList from user inputs

Sometimes it’s desired to create an elist from user inputs directly, like from a javascript dApp. new_elist_from_inputs(inputs, list_type, user) takes 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.

Length and ListType

The length and element type of a list travel inside the EList handle itself and are read directly from the struct fields — no CPI is required.
Note: It is IMPORTANT to keep in mind that the length of the elist is ALWAYS PUBLIC and encoded inside the handle itself. It’s an intentional design choice to make program 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.

Append

list_append(list, value) appends a Euint128 or Ebool element at the end of a list, returning a new modified list handle.

Insert

list_insert(list, index, value) inserts a hidden element at an encrypted position, returning a new modified list.
Note however that if the index is out of range, it works similarly to list_append() and appends the element at the end of the list.

Get

list_get(list, index) returns the hidden element at a plaintext position as a raw u128 handle.
If the plaintext index is out of range, the operation reverts with IndexOutOfRange.

GetOr

list_get_or(list, index, default_value) returns the hidden element at an encrypted position. Returns a handle to the hidden element if the index is within range, otherwise returns the default value.

Set

list_set(list, index, value) replaces an element at an encrypted index and returns a new modified list.
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).

Concat

list_concat(lhs, rhs) concatenates two elists into one and returns a new concatenated elist. The length of the new list will be length(lhs) + length(rhs).
Both lists must have the same element type, otherwise it reverts with ListTypeMismatch.

Slice

list_slice(list, start, len, default_value) slices at a hidden start position for a specified length, returning 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.

Range

list_range(start, end, list_type) creates a new list (or a “set”) and populates it with ordered values from within the range. The length of the new list will be equal to end - start.
start must be less than or equal to end, otherwise it reverts with InvalidRange. The range values [start, end) must fit within the bit width of the list_type.

Reverse

list_reverse(list) reverses elements in a list — the first element becomes last, and so on.

Shuffle

list_shuffle(list, counter) deterministically shuffles elements within a list, returning a new shuffled list with the same length. The caller-supplied counter is mixed into the operation as a nonce.
Handles are deterministically derived from operations, so the same operation on the same list with the same inputs always produces the same handle.