Skip to main content
Inputs and outputs in confidential instructions are handled the same way. The Arcium network does not mutate any state itself. Both can be encrypted or plaintext. Encrypted data is passed as an Enc<Owner, T> generic type. See Types for the full reference on Enc<Shared, T> vs Enc<Mxe, T>.

Data Visibility

Parameters and return values have different visibility levels during MPC execution:
TypeWho Sees Plaintext
Plaintext (u64, bool, etc.)All ARX nodes
Enc<Shared, T>Client + MXE (after decryption)
Enc<Mxe, T>MXE only
Plaintext parameters are visible to all ARX nodes during computation. Use Enc<Shared, T> for sensitive user data.

Return Value Requirements

Values returned from an #[instruction] must be in a form that can leave the MPC circuit:
  • Encrypted: Call .from_arcis() to produce Enc<Owner, T>. The ciphertext is public bytes; the plaintext remains protected.
  • Revealed: Call .reveal() to produce plaintext. The value becomes visible to everyone.
Secret-shared values (intermediate results from .to_arcis()) cannot be returned directly—they exist only within the MPC computation.

Example

use arcis::*;

#[encrypted]
mod order_book {
    use arcis::*;

    const ORDER_BOOK_SIZE: usize = 8;

    #[derive(Copy, Clone)]
    pub struct Order {
        size: u64,
        bid: bool,
        owner: u128,
    }

    #[derive(Copy, Clone)]
    pub struct OrderBook {
        orders: [Order; ORDER_BOOK_SIZE],
    }

    #[instruction]
    pub fn add_order(
        order_ctxt: Enc<Shared, Order>,
        ob_ctxt: Enc<Mxe, OrderBook>,
    ) -> Enc<Mxe, OrderBook> {
        let order = order_ctxt.to_arcis();
        let mut ob = ob_ctxt.to_arcis();
        let mut found = false;
        for i in 0..ORDER_BOOK_SIZE {
            let overwrite = ob.orders[i].size == 0 && !found;
            if overwrite {
                ob.orders[i] = order;
            }
            found = overwrite || found;
        }
        ob_ctxt.owner.from_arcis(ob)
    }
}
This example demonstrates how to pass inputs into confidential instructions, compute on them, and return outputs. The goal is to add an order to an existing order book. In this example, order_ctxt: Enc<Shared, Order> contains data encrypted with a shared secret between the client and MXE—both can decrypt it. In contrast, ob_ctxt: Enc<Mxe, OrderBook> is encrypted exclusively for the MXE, so only the MXE nodes (acting together) can decrypt it. This pattern is useful for storing protocol state that users shouldn’t access directly.
Why use Mxe? If ob_ctxt were Enc<Shared, OrderBook>, any user could decrypt the entire order book and see everyone else’s orders. By using Enc<Mxe, OrderBook>, only the MXE cluster can access the aggregate state—individual users can only see their own inputs and the revealed outputs.
To use the parameters order_ctxt and ob_ctxt for computation, we need to convert them to corresponding secret shares for the nodes to compute in MPC. This is done by calling the to_arcis function on any Enc generic parameter. This does not reveal the plaintext data underneath to the nodes during the process. The order parameter is consumed after the confidential instruction has been processed. To output the new order book, convert it back using from_arcis on the ob_ctxt.owner field (the party that encrypted the data) to get the new Enc<Owner, T> type, and return it. For more details on how to invoke these encrypted instructions from your Solana program, see Invoking a Computation.

Efficient Data Packing

MPC operations work on field elements. For large arrays of small integers, Pack<T> provides significant compression. See Data Packing for when to use it.

Usage

pub struct GameState {
    // Pack large arrays for ~26x storage reduction
    board: Pack<[u8; 256]>,
}

#[instruction]
pub fn sum_board(input: Enc<Shared, Pack<[u8; 64]>>) -> u64 {
    let data: [u8; 64] = input.to_arcis().unpack();
    let mut sum: u64 = 0;
    for i in 0..64 {
        sum += data[i] as u64;
    }
    sum.reveal()
}
The sharedSecret comes from x25519 key exchange with the MXE cluster. See Encrypting inputs for the full setup.
Field names and order in TypeScript must exactly match your Arcis struct definition. Mismatches cause silent data corruption.
The generated packers provide:
  • Type-safe interfaces matching your Arcis struct
  • Correct field ordering (must match Arcis definition)
  • Compile-time validation with TypeScript
See Primitives: Data Packing for full Pack<T> API.

Example: Ed25519 Signatures

Complete example with client-side packing via circuits.VerifyingKey.pack().

What’s Next?

Operations

Complete operation support matrix for expressions, iterators, and generics.