# Configuration and security
Source: https://docs.arcium.com/arx-nodes/configuration-and-security
Hardware declarations, keyshare management, and security considerations for Arx nodes
Each Arx node must declare its **hardware capabilities,** as this impacts the node's workload eligibility and potential revenue.
Accurate declarations are incentivized to ensure reliable performance, with penalties for nodes that overstate their capacity and fail to meet task requirements. Nodes can upgrade their specifications over time, but hardware reductions are subject to conditions to ensure that they do not impact the performance of their active Clusters. See the [Staking overview section](/staking/overview) for more.
### Keyshare management and security considerations
Arx nodes handle key shares crucial for participating in MPC protocols within Clusters and these shares cannot be compromised.
These key shares are securely stored and managed, often using **Trusted Execution Environments (TEEs)** for enhanced confidentiality and data integrity. TEEs provide isolated enclaves that safeguard key shares from unauthorized access, ensuring that only trusted processes within the node can interact with them.
Beyond this, other security considerations exist. This includes the integration of both physical and cloud-based systems to maximize uptime and prevent penalties for non-participation. The physical systems refer to the deployment of Arx node hardware in secure, redundant on-premise setups to ensure reliability. Meanwhile, cloud-based systems use scalable and distributed infrastructure provided by trusted providers to enhance failover protection and availability.
# Arx nodes overview
Source: https://docs.arcium.com/arx-nodes/overview
Arx node metadata, jurisdiction, and registration on the Arcium Network
When launching an Arx node, the operator provides node metadata and associates the node with a *Node Operator*.
**Node Operators** are metadata entities that represent the party operating one or more Arx nodes.
## Node operators
Node Operators have a one-to-many relationship with Arx nodes, meaning that Node Operators may run multiple nodes. The following metadata fields are associated with a Node Operator:
* **Jurisdiction**: Follows the ISO 3166-1 alpha-2 code standard for jurisdictions, such as `DE` for Germany or `FR` for France.
* **URL**: A webpage representing the Node Operator, such as a website, social profile, or operator reputation page.
These metadata fields are self-declared. The Arcium Network does not verify that the actual hardware location matches the jurisdiction field, but the community can evaluate location claims along with the operator's team, security approach, transparency, and cross-network reputation.
## Metadata
An Arx node's metadata is unique to that specific node and may vary from an associated Node Operator's metadata since a single operator may run multiple nodes in, for example, different jurisdictions.
This node-specific metadata consists of the following fields:
* **IP and port**: These fields correspond directly to the node deployment and specify RPC access details for the Arx node.
* **Jurisdiction**: The same ISO 3166-1 alpha-2 code standard that Node Operators follow.
As with the Node Operator jurisdiction field, an Arx node's jurisdiction field isn't verified and, therefore, will also be independently evaluated by the Arcium Network's community.
# Performance and incentives
Source: https://docs.arcium.com/arx-nodes/performance-and-incentives
Performance metrics, reputation scoring, and incentive structures for Node Operators
Performance and reliability are essential metrics for Arx nodes on the Arcium Network, directly influencing their reputation and participation.
Both Computation Customers and third-party stake delegators must critically assess the reputation of Arx nodes. Computation Customers depend on these nodes for reliable and efficient computations, while third-party delegators seek the most dependable Arx nodes to delegate their stake to, aiming to avoid slashing penalties.
Some possible criteria includes **a) their uptime**, **b) response time**, and **c) historical performance** to ensure they meet network standards.
This onchain reputation serves as a crucial criterion for Computation Customers when selecting Clusters of Nodes and for third-party delegators deciding where to stake their assets.
### Node incentives and rewards structure
Node Operators are incentivized through a dual-source reward system:
**a) Self-delegation,** and
**b) Third-party delegations**
Self-delegation ensures that operators have personal stakes in maintaining high performance and reliability, as any failure can result in penalties and loss of reputation. It also covers the costs associated with any migrations, including forced (unexpected) migrations, such as if a node goes offline due to accidents or unforeseen issues. A separate maintenance downtime allowance system also exists. Nodes also benefit from fees collected from third-party delegations, which boost computational capacity and overall earnings.
Rewards are distributed equally among all nodes within a Cluster for each computation completed during the network's epoch cycles. This ensures fairness within the Cluster for specific computations. For details on base pricing and priority fee markets, see [Pricing and incentives](/computations/pricing-and-incentives).
However, nodes with good reputations, such as consistent uptime or high processing capabilities, can attract more opportunities. These nodes are often included in multiple Clusters or receive higher job volumes within their assigned Clusters, increasing their overall rewards indirectly through their reputation and workload. This distinction highlights how nodes can use their performance and reliability to enhance their earning potential beyond the baseline distribution mechanism.
# Cluster forking and migration
Source: https://docs.arcium.com/clusters/cluster-forking-and-migration
How clusters activate, fork, and migrate nodes in the Arcium Network
As a reminder, **Clusters** in the Arcium Network are groups of Arx nodes assembled to collectively execute Multi-Party Computation (MPC) operations.
For non-Permissioned Clusters, prior to Cluster activation, one random Arx node from the Network is included in the Cluster's node set. Not explicitly defined by the Computation Customer, this random node is instead independently selected via the [Automatic alternative node selection](/clusters/node-priority-list-and-alternative-selection-criteria) mechanism (see the [Sybil resistance section](/clusters/sybil-resistance) for more on this process).
The Cluster becomes active for use once all Arx nodes assigned to it approve the assignment, including the randomly selected node. If approval is not unanimous, the **Computation Customer,** referring to the entity that initiates and commissions computations within the network, may cancel the Cluster formation process after a minimum of one complete epoch has passed. If a single Arx node assigned to a Cluster opts to reject the assignment, then the Cluster creation fails.
Clusters in the Arcium Network are designed to be adaptive to any situation, but there are scenarios where changes in the Cluster composition become necessary.
Two key mechanisms that facilitate this adaptability are:
1. **Cluster Forking:** The process where nodes within a Cluster decide to eject an MXE, leading to the formation of a new Cluster that supports the ejected MXE independently.
2. **Cluster Migration:** The movement of tasks or nodes from one Cluster to another, either due to planned exits or forced circumstances like downtime, stake reduction or **Cluster Forking**.
## Cluster forking
Cluster forking occurs when one or more Arx nodes in a Cluster decide to eject a specific MXE from their group. This might happen if the nodes determine that the MXE is involved in undesirable activities (e.g., illegal behavior) or other operational concerns arise. Here's how the process works:
1. **Initiating a Fork**: If an Arx node within a Cluster wishes to eject an MXE, it signals this intent. Other nodes in the Cluster then have until the beginning of the next epoch to either join the ejection or decide to continue supporting the MXE. At the start of the new epoch, a fork occurs only if at least one node opts to continue supporting the ejected MXE. If all nodes agree to eject the MXE, no fork occurs.
2. **Creation of a New Cluster**: Nodes that choose to eject the MXE will remain in the original Cluster, while those opting to continue supporting the MXE form a new Cluster. However, nodes supporting the ejected MXE also remain part of the original Cluster, effectively participating in both the new and the old Clusters. The costs associated with creating the new Cluster and migrating the MXE to it are split between the nodes that initiated the ejection.
3. **Maintaining Service Continuity**: The original Cluster can continue operating unaffected, providing computational support to other MXEs without disruption. Meanwhile, the new Cluster allows the ejected MXE to continue its operations, isolated from the nodes that no longer wish to participate in its activities.
## Cluster migration
Migration is the process of moving tasks from one Cluster of Arx nodes to another Cluster of Arx nodes.
There are two primary types of migration:
1. **Forced Migration:** The automatic reassignment of an Arx node's tasks to replacement nodes when it fails to meet required criteria, such as stake reduction or extended downtime. This ensures continuity within the Cluster. For example, protocol violations (e.g., repeated cheating or extended downtime) might trigger a forced migration, where the failing node's responsibilities are transferred to other available nodes.
2. **Planned Migration:** A pre-arranged transition where a Node Operator voluntarily steps away from the network, ensuring their tasks are reassigned to replacement nodes without penalties beyond standard migration costs, provided sufficient notice is given. For example, if a Node Operator plans to exit the network, they can initiate a migration one epoch in advance, allowing a handoff of their workload to other active nodes.
## Migration costs
In a forced migration, the costs are shared proportionally among all stakeholders of the migrating Arx node, the node being removed from its original Cluster. However, these costs are typically minor, reflected as a small reduction in distributed rewards. That said, since forced migrations are always the result of a slashable offense, the actual financial impact extends beyond migration costs. The Node Operator and its stakeholders would also incur a larger penalty due to slashing, making forced migrations more costly than planned exits.
In contrast, a planned migration requires the migrating Arx node to cover migration costs from its self-delegation. This ensures that the expenses of reallocating the node's tasks to new nodes within the network are accounted for, preventing potential service interruptions. New Clusters are formed based on the existing configuration, prioritizing available nodes from the original Cluster's [**Node Priority List** or employing **alternative selection mechanisms**](/clusters/node-priority-list-and-alternative-selection-criteria).
# Incentivization
Source: https://docs.arcium.com/clusters/incentivization
Staking delegation, rewards distribution, and incentive alignment for cluster nodes
The total stake delegated to a given node (both self-delegated and from third parties) enhances the node's ability to participate in more intensive tasks, while also holding nodes accountable through slashing penalties for misbehavior.
The two types of delegation are:
1. **Self-delegation**: Stake that Arx nodes commit to themselves.
2. **Third-party delegation:** External stakeholders who delegate their assets to Arx nodes.
## Rewards distribution
Computation rewards are distributed equally among all nodes participating in a Cluster for each computational job. While every active node within a Cluster earns the same reward per job, nodes with a strong reputation or consistent uptime are more likely to be added to additional Clusters. This, in turn, increases the overall number of computations they process, leading to higher cumulative rewards over time.
**Nodes must stake a minimum amount of collateral to participate.**
Self-delegation typically meets or exceeds this minimum requirement to activate participation in Clusters. Additional staking from third-party delegators helps unlock more computational resources. This structure encourages operators to maintain high performance, as higher stakes directly lead to greater resource access and ensure eligibility for Cluster participation. See the [Staking overview section](/staking/overview) for more.
# Node priority list and alternative selection criteria
Source: https://docs.arcium.com/clusters/node-priority-list-and-alternative-selection-criteria
Node priority ordering, backup activation, and alternative selection in Arcium clusters
## The node priority list
During the creation process, nodes are arranged in a priority-ordered list, which determines the activation sequence of backup nodes in the event that active nodes become unavailable.
The Node Priority List is immutable after Cluster creation. This immutability guarantees reliability and trust in the Cluster's defined properties, as other MXEs may depend on these properties to function correctly. By preventing changes after creation, the network ensures stability and consistency.
Additionally, the Node Priority List functions in conjunction with Cluster Migrations. During a migration, it acts as a reference to identify which Nodes should be migrated to, serving as viable backup options while maintaining operational integrity.
Under migration conditions, the list aims to:
* Replace unavailable nodes with minimal disruption to computational operations.
* Optimize resource allocation by prioritizing nodes with higher computational capacity and reliability.
## Automatic alternative selection
The Automatic Alternative Selection mechanism is an optional system for sourcing alternative Nodes for Clusters, and can be either enabled or disabled at the Cluster-level, upon Cluster creation.
If enabled, it becomes active when a) the Node Priority List is exhausted, or b) all nodes from the list are unavailable.
The selection process begins by filtering nodes based on specific criteria.
1. It assesses Trusted Execution Environment (TEE) requirements to ensure compatibility with the Cluster's security needs.
2. It evaluates hardware specifications, excluding nodes with insufficient resources.
3. Once filtered, the remaining nodes are ranked using a Trust Score, which accounts for factors such as the node's operation duration and slashing record, promoting the selection of reliable and trustworthy nodes.
Additionally, Clusters can customize exclusion criteria to refine the selection process. This includes maintaining an offchain **Node Blacklist** to avoid specific nodes and a **Jurisdictional Blacklist** to exclude nodes operating in certain geographic locations.
# Clusters overview
Source: https://docs.arcium.com/clusters/overview
How Arx node clusters form, execute computations, and maintain security
Clusters refer to **groups of Arx nodes that collaborate to execute encrypted computations** in the Arcium Network.
They're created by Computation Customers who define a set of Arx nodes based on specific requirements, such as computational capacity, security features, and the nodes' reputations.
During the creation process, customers specify key attributes, such as:
* **Computational Load Capacity:** The maximum computational volume a Cluster will be required to handle at any given time. Since different computations vary in size, a single large computation could demand significantly more resources than multiple smaller computations.
* **Active Node Requirements:** Defines the minimum number of active nodes required within the Cluster, ensuring consistent computational capacity. This includes the Node Priority List, which determines the activation order of nodes. While most Cluster properties, such as the [Node Priority List](/clusters/node-priority-list-and-alternative-selection-criteria), are immutable after creation, computational limits can be increased if supported by all participating nodes.
* **Security Requirements:** Outlines the security standards and protocols for the Cluster.
And more.
Customers can also reuse existing Clusters that fit their requirements, reducing setup costs and improving resource utilization. For example, a project might reuse a high-capacity Cluster for private trading algorithms instead of setting up a new one.
### Participation & admission of MXEs
Once created, a Cluster's Arx nodes must approve their participation.
This approval process allows nodes to consider various factors, including their own workload, trust in other nodes, and geographical jurisdiction. Clusters can be further configured to include random nodes selected by the network's sybil-resistance mechanisms, strengthening security by reducing the risk of collusion or centralized control.
**Apart from the requirement for node approval to add nodes to Clusters, each Cluster must admit MPC eXecution Environments (MXEs),** which contain computation definitions. MXEs act as the environment where computations are executed, defining the specific types of tasks that can be processed. This mechanism for MXE admission is entirely distinct from the Cluster invitations system.
Admission is managed through configurable behaviors: some Clusters may require unanimous approval from all nodes for new MXEs, while others may have a streamlined process that automatically admits requests. If a single Arx node rejects the admission of a given MXE to their Cluster, then the MXE fails.
This flexibility ensures that Computation Customers can quickly find a Cluster suitable for their needs, balancing speed and security.
### Parallelization and scalability
Clusters in the Arcium Network are structured to optimize computational efficiency by distributing tasks across multiple nodes and Clusters, enabling high-performance processing and scalability.
Rather than parallelizing a single computation across many nodes within a Cluster, Arcium focuses on handling different computations concurrently across the network, ensuring reliability and high availability. For details on how computations are orchestrated and queued, see [Solana integration: orchestration and execution](/solana-integration-and-multichain-coordination/solana-integration-orchestration-and-execution).
# Permissioned clusters
Source: https://docs.arcium.com/clusters/permissioned-clusters
Fully-permissioned, partially-permissioned, and permissionless cluster configurations
**Permissioned Clusters** allow organizations to control how computational tasks are executed through various customizable setups. This means that organizations can adopt configurations that align with their specific security, compliance, and performance needs.
There are three primary configurations:
1. **Fully-Permissioned:** Configured for organizations that operate their own infrastructure exclusively, using only their own internal Arx nodes to ensure data privacy and control.
2. **Partially-Permissioned:** A hybrid model that combines an organization's internal nodes with select external nodes to increase computational power while retaining some oversight.
3. **Public/Non-Permissioned:** Open to all verified nodes in the Arcium Network, providing a decentralized setup ideal for tasks that don't require strict node control.
These distinct categories offer varying degrees of decentralization, enabling collaboration on joint computations across different organizational structures, including data analysis and AI model training, without compromising the privacy of individual datasets. Many other use cases exist, such as collaborative research between competitors or confidential data sharing across departments within a corporation.
### Fully-permissioned clusters
Organizations or institutions may opt to run their own fully controlled Clusters, using only self-operated Arx nodes. This setup allows them to control the infrastructure, ensuring that only trusted nodes execute tasks.
It is especially beneficial for companies with stringent data security requirements, regulatory obligations, or those managing highly sensitive computations. For instance, financial institutions handling confidential customer data might deploy fully-permissioned Clusters to ensure that only their own highly trustworthy Arx nodes process sensitive transactions. Additionally, certain regulations such as [GDPR](https://gdpr-info.eu/) or industry-specific compliance requirements may mandate strict control over where and how data is processed, making permissioned clusters a necessity in these scenarios.
Despite the isolation, fully permissioned Clusters can still use the broader Arcium Network's consensus mechanisms for tasks like dispute resolution, base pricing, and secure data processing, ensuring they benefit from network-wide features without sacrificing confidentiality.
### Partially-permissioned clusters
This is a hybrid model whereby an organization operates some of the nodes within the Cluster, while also incorporating some external nodes as well.
Partially-permissioned Clusters are ideal for use cases where a certain number of outsider nodes (non-internal) are desired to provide oversight to an otherwise fully-permissioned setup.
For example, organizations can train AI models using internal nodes while simultaneously validating results using external nodes to ensure transparency and accuracy.
### Public/non-permissioned clusters
These Clusters are open to the wider network of Arx nodes and allow any trusted nodes to join and participate. Although specific criteria can be set, such as geographical restrictions or specific technical requirements, the default configuration ensures broad accessibility.
Notably, only Public/Non-Permissioned Clusters use the random node selection process mentioned in the Overview page, which ensures [Sybil resistance](/clusters/sybil-resistance) and promotes decentralization.
This configuration is beneficial for projects seeking greater decentralization as it ensures broader participation across the network and reduces the likelihood of collusion or centralized control.
# Sybil resistance
Source: https://docs.arcium.com/clusters/sybil-resistance
Intra-cluster and network-wide Sybil attack prevention using Proof of Stake
Sybil attacks involve a single entity creating multiple false identities to disproportionately influence the network. To counter this, the Arcium Network uses Proof of Stake consensus, requiring each node to stake a minimum amount to operate.
In the Arcium Network, Sybil resistance is crucial for maintaining the integrity and security of the system. To address potential Sybil attacks, Arcium implements strategies on two levels:
* **Intra-Cluster Sybil Resistance**: Preventing collusion among nodes within the same cluster. By requiring the inclusion of at least one randomly selected node in all non-permissioned clusters, the network ensures that even if some nodes attempt to collude, the presence of an independent node acts as a counterbalance, significantly reducing the risk of intra-cluster Sybil attacks.
* **Network-Wide Sybil Resistance**: To protect the entire network from Sybil attacks, Arcium increases cluster node-set sizes and includes a random node in each non-permissioned cluster. Network participants employ Node Operator reputation systems, encouraging community engagement to monitor and report suspicious activities. The network also imposes heavier slashing punishments for concurrent node downtimes.
### 1. Intra-cluster Sybil resistance
Intra-cluster Sybil resistance mitigates the risk of collusion among nodes within the same cluster, which could compromise the confidentiality of shared secrets. Even with a diverse node set across the broader network, smaller, centralized node sets within individual public clusters may still be vulnerable.
To address this, **the Arcium Network ensures the inclusion of at least one randomly selected node in all non-permissioned clusters.** This node, chosen from the broader network, serves as an independent counterbalance, reducing the likelihood of intra-cluster Sybil attacks. As long as at least one honest node exists in a cluster, the integrity of the cluster remains intact. These measures collectively preserve the security and confidentiality of cluster operations.
### 2. Network-wide Sybil resistance
Network-wide Sybil attacks aim to compromise protocol-wide consensus mechanisms, such as Base Price Voting and the Non-Participation Detection system. To safeguard against such attacks, the Arcium Network employs several layered strategies:
1. **Random Node Inclusion:** As mentioned above, non-permissioned clusters include at least one randomly selected node to ensure diversity and impartiality.
2. **Cluster Node-Set Scaling:** Increasing the size and diversity of cluster node sets enhances overall network security.
3. **Trusted Execution Environments (TEEs):** Nodes within clusters may optionally offer TEEs to further strengthen confidentiality and trustworthiness.
4. **Node Operator Reputation Systems:** Offchain reputation systems allow Node Operators to build trust based on past performance, community engagement, and prior experience with network validation services (from other chains and protocols).
5. **Community Monitoring:** Active community involvement provides a social defense layer, encouraging participants to monitor and report suspicious activities.
6. **Strategic Penalties:** Heavier slashing penalties are imposed for concurrent node downtimes. This discourages the setup of multiple nodes with similar configurations or in close geographic proximity, incentivizing diversification across jurisdictions, data centers, operating systems, and security practices.
These measures collectively enhance the Arcium Network's resilience, ensuring its ability to withstand and recover from coordinated Sybil attack attempts while maintaining robust security and operational reliability.
# Censorship resistance and fault handling
Source: https://docs.arcium.com/computations/censorship-resistance-and-fault-handling
Censorship resistance strategies, fault tolerance, and slashing mechanisms in Arcium
The Arcium Network is designed to ensure robust censorship resistance and fault-tolerance, safeguarding computational integrity even in the presence of node failures or malicious behavior.
Below we'll cover **a) censorship resistance strategies, b) fault handling,** and **c) slashing mechanisms.**
## Censorship resistance
Censorship resistance is critical for maintaining trust in decentralized systems. In the Arcium Network, nodes cannot selectively block or disrupt computations without facing consequences.
The architecture enforces this through several mechanisms:
1. **Cryptographic Detection of Misbehavior:** Nodes attempting to disrupt computations by submitting incorrect data, aborting tasks, or falsely claiming that other Nodes misbehaved can be, with Cerberus for example (Arcium's default [MPC Protocol](/multi-party-execution-environments-mxes/mpc-protocols)), detected cryptographically. Any deviation from the agreed-upon protocol triggers an automated process to identify the offending node.
2. **Slashing as a Deterrent:** Misbehavior results in slashing penalties, which reduce the stake of the offending node. Although malicious transactions can already be detected, this serves as further punishment and compensates for the detection cost.
## Fault handling
Fault tolerance is integral to ensuring that the network operates smoothly, even when nodes fail due to technical issues or malicious intent.
The Arcium Network handles faults through these strategies:
1. **Non-Participation Detection:** Non-participation occurs when a node fails to execute its assigned task. The network detects this using communication signals within clusters. If a node does not respond, it is flagged for review. The task can be elevated to a broader set of Nodes (outside the Cluster in question) if consensus cannot be found intra-Cluster.
2. **Cheating Detection:** Cheating occurs when a node provides incorrect computation results. The Arcium Network uses cryptographic techniques to identify invalid outputs. This ensures that any attempt to manipulate results is immediately detected and penalized.
Also, as a part of the dispute resolution mechanism, in cases where faults are detected, computations are executed redundantly by other nodes to ensure reliable outcomes. This redundancy ensures that the network remains resilient, even in high-stakes scenarios.
## Slashing mechanisms
The Arcium Network employs slashing penalties to enforce accountability.
There are two main types of slashing:
1. **Non-Participation:** Nodes failing to participate in computations face higher penalties due to the greater resource requirement needed to detect and confirm non-participation.
2. **Cheating:** Nodes providing incorrect results are penalized based on the size of the computational work they've disrupted.
# Computation tasks
Source: https://docs.arcium.com/computations/computation-tasks
Computation Units (CUs), task structure, and how computations are measured on Arcium
The **Computation Unit (or CU)** is a Network-wide constant that represents a fixed amount of computational work.
The CU is the smallest unit of computation in the Arcium Network, and all types of arithmetic operations on the Arcium Network (that Computations are made up of) are measured in CUs.
In particular, Computation base pricing uses CUs to set a baseline for Arx node compensation.
## Types of computation tasks
In the Arcium Network, computations fall into two broad categories:
**1) System Computations,** and
**2) Customer Computations**.
These task types serve different purposes on the Network, the first relating to the network's internal operations, and the latter handling external customer requests.
## System computations
System Computations are critical processes automatically generated by the network to maintain its health and operation. They include:
* **Distributed Key Generation (DKG) Computations**: Each Arx node within a Cluster receives a fragment of the overall cryptographic key, known as a key share. These key shares collectively form the key for the MPC eXecution Environment (MXE) in that Cluster, enabling secure joint computation tasks without revealing individual inputs.
* **Migration Computations**: Manage planned or forced migrations of Clusters (see [Cluster migration](/clusters/cluster-forking-and-migration)). These computations ensure that the system can dynamically adapt to changes or failures. Priority fees for migration computations are contributed collectively by the nodes in the migrating Cluster, varying based on individual node preferences and the urgency of the migration.
* **Non-Participation Detection Computations:** Triggered when non-participation is detected within a Cluster. These computations escalate the dispute resolution process by involving additional nodes to address the issue, ensuring accountability and network stability. If slashing occurs due to non-participation or other forms of misbehavior, priority fees associated with these computations are reimbursed to the participating nodes, safeguarding their interests and maintaining operational integrity.
## Customer computations
Customer Computations are tasks submitted by **Computation Customers** to execute private and confidential operations. These tasks are highly configurable to meet the diverse needs of developers and businesses.
# Defining and commissioning computations
Source: https://docs.arcium.com/computations/defining-and-commissioning-computations
How to define computation blueprints and commission them for execution on Arcium
In the Arcium Network, to perform computations, they must first be defined as **Computation Definitions**, which provide a structured framework for execution. Computation Definitions act as blueprints, ensuring that all parameters, authorities, and logic are clearly specified before the computation is instantiated. This process ensures flexibility, scalability, and secure execution.
Every Computation Definition is associated with an [MXE](/multi-party-execution-environments-mxes) (MPC eXecution Environment) and consists of the following key components:
1. **Output Specifications**: Each Computation Definition includes details about its expected outputs. These specifications define the structure and format of the data that will result from the computation. A limited amount of data can be returned via onchain finalization transactions (due to inherent Solana transaction size limitations). However, additional resulting data can be configured to be returned via pre-defined callback requests.
2. **Versioning and Upgrades**: Each definition includes a version field that tracks updates or changes. This ensures backward compatibility and guarantees that instantiated computations are executed on the correct version of the definition. Versioning allows developers to make improvements without disrupting existing operations.
3. **Execution Logic**: The computation logic is defined as a **circuit** using the [Arcis developer framework](/developers/arcis), which compiles functions marked with `#[instruction]` into individual circuits.
4. **Access Authority**: Computation Definitions specify which parties are authorized to execute them. Authority options include:
* **None**: No entity is allowed to execute this definition.
* **Private**: A single specified entity has exclusive execution rights.
* **Restricted**: A defined list of entities has execution rights.
* **Public**: Any entity in the network can execute the computation.
5. **Parameters**: Each Computation Definition may include parameters that dictate the inputs required during execution. These parameters fall into two categories:
* **Plaintext**: Raw, unencrypted inputs provided at the time of computation commissioning.
* **Ciphertext**: Inputs encrypted with a symmetric cipher and passed in at the time of computation commissioning.
6. **Execution Costs**: The circuit metadata details the number of arithmetic operations, inputs, and outputs required for execution. This metadata allows for precise cost calculations so that resource requirements can be understood upfront.
## Commissioning a computation
Once a Computation Definition is created, the next step is to commission it for execution. Commissioning involves preparing the computation for the Arcium Network, specifying all required details.
Key steps in commissioning include:
1. **Arguments**: The inputs for the computation, corresponding to the parameters defined earlier, are passed at this stage.
2. **Execution Timing**: The customer specifies when the computation should be executed, including an optional validity window with timestamps (`valid after` and `valid before`) to define its execution timeframe.
3. **Priority Fees**: Customers set priority fees to incentivize faster execution, as higher fees take precedence in the network's mempool.
4. **Callbacks**: Bespoke actions triggered after execution are defined as:
* **Success Instructions**: Static or dynamic onchain actions based on successful execution, such as storing outputs or triggering follow-up computations.
* **Failure Instructions**: Static onchain actions taken in the case of failure.
5. **Mempool Handling**: Once a computation is commissioned, it is placed in the mempool.
# Lifecycle of an Arcium computation
Source: https://docs.arcium.com/computations/lifecycle-of-an-arcium-computation
How computations transition from definition to commissioning, execution, and completion
The **Computation Lifecycle** in the Arcium Network governs how computations transition from definition to execution and completion. This structured process ensures reliable coordination, resource optimization, and precise handling of execution timelines.
## Lifecycle overview
The lifecycle of a computation in the Arcium Network follows a clear progression of steps. These steps ensure computations are securely defined, efficiently commissioned, and accurately executed.
Here are the five key stages:
1. **Definition**: A computation is defined within the context of an MXE, providing a blueprint for its execution (see the [Defining and commissioning computations section](/computations/defining-and-commissioning-computations) for details). This includes specifying inputs, outputs, logic, versioning, and access permissions.
2. **Commissioning**: A defined computation is instantiated by specifying arguments, execution windows, and other parameters required for execution.
3. **Mempool Placement**: Commissioned computations are queued in the mempool.
4. **Execution**: Nodes execute the computation securely, ensuring privacy and accuracy.
5. **Post-Execution Callbacks**: Following execution, actions defined for success or failure cases are carried out so downstream programs can update state or notify clients.
## Execution validity windows
Each computation can specify an optional **execution window** consisting of:
* **Valid After Timestamp**: The earliest time a computation can be executed.
* **Valid Before Timestamp**: The latest time a computation can be executed.
These timestamps ensure that computations are processed at the right moment. If the "valid after" timestamp is in the future, the computation remains in the mempool until that time. If the "valid before" timestamp is in the past, the computation is no longer valid and cannot be executed.
By default:
* **Valid After**: Set to zero (no delay).
* **Valid Before**: Set to infinity (no expiry).
However, these defaults are overridden when explicit timestamps are provided during commissioning.
## Post-execution callbacks
Once a computation is executed, the system triggers callbacks based on the outcome:
* **Success Callbacks**: Handle actions for successfully executed computations. These may include:
* **Dynamic Onchain Actions**: Triggering onchain processes based on computation results.
* **Static Onchain Actions**: Fixed onchain processes to address success scenarios.
* **Failure Callbacks**: Manage actions when a computation fails. These are always static and include:
* **Static Onchain Actions**: Fixed onchain processes to address failure scenarios.
Callbacks ensure that the system remains flexible and responsive, even in the event of unexpected outcomes.
# Pricing and incentives
Source: https://docs.arcium.com/computations/pricing-and-incentives
Base pricing, priority fee markets, and economic incentives for Node Operators
The Arcium Network's pricing and incentive structure is designed to ensure fair compensation for Arx nodes while enabling Computation Customers to optimize for cost, speed, and priority.
By blending fixed base pricing with dynamic priority fee markets, the system ensures both predictable costs and flexibility for urgent computations.
The Priority Fee and Base Fee are divided by the amount of [CUs](/computations/computation-tasks) used, meaning that combining computations would not affect the cost.
## Base pricing
Arcium uses base pricing, which guarantees a minimum level of economic viability for computational tasks, even during periods of low demand.
The base price is set at a **minimally economically viable level** to cover the operational costs of Node Operators, accounting for hardware maintenance, energy consumption, and infrastructure.
## Computational units
As a reminder, the execution cost of a Computation is measured in **Computation Units (CUs)**, a standardized metric representing a fixed amount of computational work.
The cost per CU is determined for each epoch through a voting process. Node Operators cast **stake-weighted votes** to decide the price of a CU in the upcoming epoch. Abstaining from voting counts as a vote for maintaining the current price.
To avoid manipulation, only the self-delegated stake of Node Operators can participate in the voting process.
Additionally:
* **Excess Delegated Stake:** If a Node Operator's self-delegated stake exceeds the amount required to activate all of its hardware, only the eligible portion below this threshold is considered.
* **Third-Party Delegators:** Delegators cannot vote, as their incentives might differ from those of Node Operators. For instance, delegators may prioritize short-term gains, whereas Node Operators have a direct, vested interested with respect to infrastructure costs and long-term network health.
Nodes are incentivized to vote when they feel adjustments to the base price are necessary for maintaining economic viability. Abstaining from voting is considered normal in stable market conditions where the current base price is already effective.
Operating the physical infrastructure also inherently locks Node Operators into long-term engagement, as they bear the ongoing costs of maintaining hardware, energy consumption, and related resources. The lock-up periods for stake function as a secondary mechanism and together, these measures uphold the reliability and sustainability of the network until base pricing adjusts in the next epoch.
Base pricing establishes a **floor for compensation**, but customers can still pay additional **priority fees** to expedite computations (explained below). This dual-layered pricing ensures both guaranteed operation and flexibility for customers with time-sensitive tasks.
## Priority fee markets
While base pricing provides a baseline cost for computations, **priority fees** enable Computation Customers to expedite their tasks by participating in dynamic fee markets. These markets incentivize Nodes to prioritize higher-paying computations, ensuring that time-sensitive tasks can move to the front of the queue when network demand is high.
Priority fees operate within **Cluster-specific markets** or **Cluster unions,** which are groups of interconnected Clusters that share overlapping nodes. Each Cluster (or union of Clusters) creates a siloed fee market where computations only compete for resources with others that share the same nodes. This segmentation ensures efficiency and prevents unrelated tasks from interfering with resource allocation.
For urgent computations, customers can offer a sufficiently high priority fee to monopolize a Cluster's resources temporarily. This allows them to bypass competing tasks within the Cluster or Cluster union, making it especially advantageous for large-scale or time-critical computations. By balancing supply and demand, priority fee markets create a flexible and efficient system for task execution, complementing the stability of base pricing.
## Economic incentives for Arx nodes
Arx nodes earn revenue from both base pricing and priority fees, ensuring sustainable operation while incentivizing high performance.
**Base pricing revenue** provides consistent income to cover operational costs, regardless of Network demand, ensuring long-term stability for Node Operators. Meanwhile, **priority fee revenue** rewards Clusters of Nodes for processing high-priority computations. This encourages efficient resource utilization and incentivizes nodes to stay competitive in the market.
While the lock-up periods for Node Operators promote long-term thinking and stability, slashing penalties for non-participation and misbehavior discourage short-term exploitation.
## Cluster-level competition
Arcium's pricing system balances customer flexibility and nodes' economic viability through dynamic, market-driven mechanisms within shared Clusters.
Dynamic Clusters ensure that fee markets naturally emerge as computations compete for the shared resources of a Cluster. When demand is high, priority fees dynamically increase, directing limited computational resources toward the most urgent and valuable tasks. This adaptive system efficiently allocates resources, ensuring that the network can meet varying customer needs without overloading nodes.
Fairness across nodes is maintained by distributing priority fees equally to each participating node in a Cluster. This approach rewards nodes equally based on their workload and ensures equitable compensation, aligning node incentives with the efficient functioning of the network.
Through this combination of dynamic competition and equitable reward distribution, Arcium fosters a robust and flexible ecosystem capable of adapting to changing demands while maintaining fairness and reliability.
## How customers optimize costs
**The total fee paid by a Computation Customer is the sum of the base fee and any chosen priority fee.**
The priority fee and base fee are both divided by the number of [**CUs**](/computations/computation-tasks) used. This ensures that pricing is adjusted for the size of the computation, allowing fair comparisons across different workloads. By normalizing costs on a per-unit basis, the network ensures that both the **base price and priority fee** can be evaluated independently of computation size. This means that whether a computation is large or small, the **per-CU cost** remains the key metric for determining execution pricing.
Computation Customers can optimize their costs by carefully managing execution parameters:
1. **Set Execution Windows**: Avoid paying high fees during peak demand by scheduling computations for off-peak times.
2. **Adjust Priority Fees**: Use priority fees strategically to balance cost and urgency. Lower fees are ideal for non-urgent computations, while higher fees ensure immediate execution.
# Intro to Arcium
Source: https://docs.arcium.com/developers
What Arcium enables, common use cases, and a guided path from setup to deployment
Arcium is a decentralized private computation network that processes encrypted data with Multi-Party Computation (MPC). It lets Solana applications run computations without revealing sensitive inputs to any single node.
## What Arcium enables
As a Solana developer, you can use Arcium to:
1. **Build privacy-preserving applications**: Add privacy without adopting a new blockchain or abandoning Anchor-style workflows.
2. **Use familiar tooling**: Write encrypted instructions in Rust with Arcis and integrate them with Solana programs.
3. **Process sensitive data**: Run computations over data such as balances, trade orders, game state, or personal information without exposing plaintext inputs.
## How it works
Your application uses an MXE to work with encrypted data in three steps:
1. The client encrypts data and sends it to your MXE program.
2. Your program submits the computation to Arcium's network of MPC nodes.
3. Nodes process the data while keeping it encrypted and return the result.
The entire process happens onchain through Solana, with each step verified and coordinated by Arcium's programs.
## Common use cases
1. **Confidential DeFi**: Build dark pools or private order books where trade sizes and prices remain hidden.
2. **Secure AI**: Enable AI model inference and training on sensitive data while keeping the data encrypted.
3. **Confidential gaming**: Build hidden information games where player moves and state remain private until revealed (e.g., card games, strategy games, auctions).
## Getting started
Arcium provides a familiar development experience for Solana developers:
* Use the `arcium` CLI (a wrapper over `anchor` CLI) to build Solana programs with Arcium
* Write encrypted instructions in Rust using the Arcis framework
* Integrate with your Solana programs using the TypeScript client library
Follow these steps to get started:
1. [Install Arcium](/developers/installation): Set up the development environment and tools.
2. [Hello World](/developers/hello-world): Create your first encrypted instruction.
3. [Computation lifecycle](/developers/computation-lifecycle): Understand how encrypted computations work.
4. [TypeScript SDK reference](https://ts.arcium.com/api): Use the API reference for TypeScript client libraries.
The Arcium Network is live on Solana mainnet. Join [Discord](https://discord.com/invite/arcium) for developer support and community discussion.
# Arcis overview
Source: https://docs.arcium.com/developers/arcis
Introduction to Arcis, a Rust-based framework for writing secure MPC circuits on the Arcium Network
Arcis is a Rust-based framework for writing secure multi-party computation (MPC) circuits that run on the Arcium Network. Create privacy-preserving applications that compute over encrypted data using familiar Rust syntax.
## What Arcis code looks like
Arcis code is standard Rust with special annotations for MPC execution:
```rust theme={null}
use arcis::*;
#[encrypted]
mod my_circuit {
use arcis::*;
#[instruction]
pub fn add_private(a: Enc, b: Enc) -> Enc {
let x = a.to_arcis(); // Encrypted → secret shares
let y = b.to_arcis();
a.owner.from_arcis(x + y) // Secret shares → encrypted
}
}
```
This computes `a + b` where both inputs remain encrypted throughout. No node ever sees the plaintext values.
Understand why MPC circuits work differently and the mental model behind Arcis.
Build your first Arcis circuit with a hands-on tutorial.
Real-world circuits: voting, games, DeFi applications.
Quick reference cheatsheet for Arcis syntax and patterns.
## Key features
* **Rust-based**: Use Rust's type safety and performance for MPC development.
* **Circuit-oriented**: Write MPC circuits using familiar Rust syntax with constraints for fixed circuit structure.
* **Privacy-focused**: Compute over encrypted data without revealing the underlying information.
## What's next?
Security patterns, optimization tips, and common pitfalls to avoid.
Learn `Enc`, `EncData`, and supported data patterns.
Pass encrypted inputs and design callback-friendly outputs.
Check what Rust operations are supported in Arcis circuits.
# Best practices
Source: https://docs.arcium.com/developers/arcis/best-practices
Performance tips, debugging techniques, and testing strategies for Arcis circuits
This guide covers practical tips for writing efficient, debuggable, and testable Arcis circuits.
**Use this page when** you are optimizing circuit performance, debugging an issue, or setting up tests.
## Understanding execution flow
For conceptual background on why MPC circuits work differently (e.g., why both if/else branches execute), see [Thinking in MPC](/developers/arcis/mental-model).
## Performance optimization
### Operation costs
See [Thinking in MPC - Cost Model](/developers/arcis/mental-model#cost-model) for the full cost breakdown.
| Operation | Cost | Notes |
| ------------------------------------- | --------- | ------------------------------------------- |
| Addition, subtraction, multiplication | Cheap | Multiplications optimized via preprocessing |
| Comparisons | Expensive | Bit decomposition required |
| Division, modulo | Expensive | Multiple internal operations |
| Dynamic indexing | O(n) | Checks all positions |
### Optimization tips
**Batch encrypted outputs when possible**:
```rust theme={null}
// Multiple separate encryptions have overhead
let enc_a = owner.from_arcis(x);
let enc_b = owner.from_arcis(y);
// If you need both values encrypted together, use a tuple type
let enc_tuple: Enc = owner.from_arcis((x, y));
// ✗ Won't compile - Enc wraps the entire value,
// so destructuring patterns don't work on Enc types
// let (enc_a, enc_b) = owner.from_arcis((x, y));
```
**Reuse comparison results**:
```rust theme={null}
// ✗ Redundant - same comparison computed twice
if x > 1000 {
do_something();
}
if x > 1000 { // Expensive comparison done AGAIN
do_another_thing();
}
// ✓ Compute once, reuse the result
let is_large = x > 1000;
if is_large {
do_something();
}
if is_large { // Reuses the boolean, no recomputation
do_another_thing();
}
```
**Prefer public constants over secret-dependent values**:
```rust theme={null}
// ✓ Constant multiplier - compiler can optimize
fn double(x: u64) -> u64 {
x * 2 // Multiplication by constant is efficient
}
// ✓ Pass known values as public inputs
fn apply_rate(amount: u64, rate_percent: u64) -> u64 {
// If rate is known ahead of time, pass it as a public input
// rather than computing it inside the secure computation
amount * rate_percent / 100
}
```
In MPC, values known before the computation (public inputs and constants) can be handled more efficiently than values computed during secure execution.
## Debugging
Arcis provides familiar debugging macros that work during circuit development.
### Print debugging
```rust theme={null}
#[instruction]
fn debug_example(a: u32, b: u32) -> u32 {
println!("Inputs: a = {}, b = {}", a, b);
let result = a + b;
println!("Result: {}", result);
// Also available: print!, eprint!, eprintln!
eprintln!("Debug: computation complete");
result
}
```
Print macros do not change circuit behavior. They are for development only. Output appears during circuit execution on Arx nodes.
### Debug assertions
Use assertions to verify invariants during development:
```rust theme={null}
#[instruction]
fn with_assertions(x: u32, y: u32) -> u32 {
debug_assert!(x > 0, "x must be positive");
debug_assert_eq!(x, x, "sanity check");
debug_assert_ne!(x, y, "x and y should differ");
x + y
}
```
`debug_assert` macros are for development verification only. They do not enforce constraints in production: use explicit conditionals for actual validation logic.
### Common debugging patterns
**Trace loop iterations**:
```rust theme={null}
for i in 0..10 {
println!("Iteration {}: value = {}", i, arr[i]);
// ... processing
}
```
**Check intermediate values**:
```rust theme={null}
let step1 = compute_step1(input);
println!("After step1: {}", step1);
let step2 = compute_step2(step1);
println!("After step2: {}", step2);
```
## Testing
### What can be unit tested
You can test:
* **Helper functions** (non-`#[instruction]` functions)
* **`#[arcis_circuit]` functions** (builtin circuits)
* **Pure logic** extracted into testable units
You **cannot** directly unit test:
* **`#[instruction]` functions** (require MPC runtime)
### Testing strategy
Extract testable logic into helper functions:
```rust theme={null}
#[encrypted]
mod circuits {
use arcis::*;
// Testable: regular function
pub fn calculate_fee(amount: u64, rate: u64) -> u64 {
amount * rate / 10000 // basis points
}
// Testable: builtin circuit
#[arcis_circuit = "min"]
pub fn min(a: u128, b: u128) -> u128 {}
// NOT directly testable: requires MPC
#[instruction]
fn transfer_with_fee(amount: u64, rate: u64) -> u64 {
let fee = calculate_fee(amount, rate);
amount - fee
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fee_calculation() {
// 2.5% fee on 10000
assert_eq!(circuits::calculate_fee(10000, 250), 250);
// 1% fee on 5000
assert_eq!(circuits::calculate_fee(5000, 100), 50);
}
#[test]
fn test_builtin_circuit() {
assert_eq!(circuits::min(10, 20), 10);
assert_eq!(circuits::min(1, 0), 0);
assert_eq!(circuits::min(4, 4), 4);
}
}
```
### Integration testing
`#[instruction]` functions cannot be unit-tested in isolation: they require the full MPC runtime. For end-to-end testing, use the TypeScript SDK to invoke deployed circuits on a test cluster.
See the [JavaScript Client documentation](/developers/js-client-library) and the [Hello World tutorial](/developers/hello-world) for integration testing setup.
## Common pitfalls
### Conditionals don't guard execution
When a condition is not a compile-time constant, both branches execute. The condition selects which result to keep, but Arx nodes perform work for both paths.
See [Thinking in MPC](/developers/arcis/mental-model#both-branches-always-execute) for the full explanation.
```rust theme={null}
// Problematic: assumes the indexing won't happen when found_match is false
if found_match {
data[secret_idx] = new_value; // Executes regardless of found_match
}
// Safe: constant-index loop with conditional assignment
for i in 0..DATA_SIZE {
let should_update = found_match && (i == secret_idx);
if should_update {
data[i] = new_value;
}
}
```
### Reveal and encryption placement
`.reveal()` and `.from_arcis()` cannot appear inside conditional blocks. See [Thinking in MPC](/developers/arcis/mental-model#reveal-and-encryption-placement) for the correct pattern.
## Error handling
### Compile-time vs runtime
| Condition | Compile-Time | Runtime |
| ------------------------- | ---------------------------- | ----------------------- |
| Division by zero | Error if divisor is constant | Undefined behavior |
| Array index out of bounds | Error if index is constant | Error during evaluation |
| Float out of range | Error for literals | Silently clamped |
**Division by secret values:** If your divisor could be zero based on secret inputs, add explicit validation:
```rust theme={null}
let is_valid = divisor != 0;
let safe_divisor = if is_valid { divisor } else { 1 };
let result = if is_valid { numerator / safe_divisor } else { 0 };
```
**Best practices:**
1. Use constant array sizes where possible
2. Validate divisors before division when they depend on secret inputs
3. Keep floats within the supported range `[-2^75, 2^75)`
## What's next?
For the operation cost breakdown, see [Thinking in MPC - Cost Model](/developers/arcis/mental-model#cost-model).
Keep this open while coding for fast syntax lookup.
Understand MPC constraints and the cost model.
# Input/output
Source: https://docs.arcium.com/developers/arcis/input-output
Working with Enc types for encrypted inputs and outputs in Arcis circuits
Inputs and outputs in encrypted 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` generic type. See [Types](/developers/arcis/types#encryption-types) for the full reference on `Enc` vs `Enc`.
## Data visibility
Parameters and return values have different visibility levels during MPC execution:
| Type | Who Sees Plaintext |
| ------------------------------- | ------------------------------- |
| Plaintext (`u64`, `bool`, etc.) | All Arx nodes |
| `Enc` | Client + MXE (after decryption) |
| `Enc` | MXE only |
Plaintext parameters are visible to all Arx nodes during computation. Use `Enc` 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`. 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
```rust theme={null}
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,
ob_ctxt: Enc,
) -> Enc {
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 encrypted 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` contains data encrypted with a shared secret between the client and MXE: both can decrypt it. In contrast, `ob_ctxt: Enc` 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`, any user could decrypt the entire order book and see everyone else's orders. By using `Enc`, 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 encrypted 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` type, and return it.
For more details on how to invoke these encrypted instructions from your Solana program, see [Invoking a computation](/developers/program).
## Efficient data packing
MPC operations work on field elements. For large arrays of small integers, `Pack` provides significant compression. See [Data packing](/developers/arcis/primitives#data-packing) for when to use it.
### Usage
```rust Arcis theme={null}
pub struct GameState {
// Pack large arrays for ~26x storage reduction
board: Pack<[u8; 256]>,
}
#[instruction]
pub fn sum_board(input: Enc>) -> 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()
}
```
```typescript Client theme={null}
import { randomBytes } from 'crypto';
import { circuits } from './build/circuits'; // Generated by Arcium compiler
import { RescueCipher } from '@arcium-hq/client';
// Your game board data
const board = new Uint8Array(256).fill(0);
// Pack using generated packer (matches Arcis struct)
const packed = circuits.GameState.pack({
board: Array.from(board)
});
// Encrypt for MPC (sharedSecret from x25519 key exchange - see Encryption docs)
const cipher = new RescueCipher(sharedSecret);
const nonce = randomBytes(16);
const ciphertext = cipher.encrypt(packed, nonce);
```
The `sharedSecret` comes from x25519 key exchange with the MXE cluster. See [Encrypting inputs](/developers/js-client-library/encryption) 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](/developers/arcis/primitives#data-packing) for full `Pack` API.
Complete example with client-side packing via circuits.VerifyingKey.pack().
## What's next?
RNG, cryptography, and data packing operations.
Queue computations from your Solana program.
# Thinking in MPC
Source: https://docs.arcium.com/developers/arcis/mental-model
Understand the mental model behind Arcis and why MPC circuits work differently from regular code
Arcis lets you write Rust that computes on encrypted data. But MPC (Multi-Party Computation) has fundamental constraints that affect how you write code. This page explains *why* these constraints exist so you can write effective Arcis programs.
**Use this page** to build intuition for how Arcis works. Understanding these concepts will help you write efficient circuits. If you prefer hands-on learning, try the [Hello World tutorial](/developers/hello-world) alongside this guide.
## How secret sharing works
When you call `.to_arcis()` on encrypted data, it does not decrypt the data. Instead, it converts the ciphertext into **secret shares** distributed across Arx nodes (Arcium's MPC execution nodes).
Think of it like splitting a secret number into random pieces:
```text theme={null}
Secret value: 42
Node A holds: 17 (random)
Node B holds: 93 (random)
Node C holds: -68 (calculated so shares sum to 42)
```
**Key insight:** Each node sees only random-looking data. No single node learns anything about the original value. But when nodes compute together following the MPC protocol, the math works out correctly.
**Security guarantee**: Under Arcium's dishonest majority model, privacy is maintained as long as at least one node remains honest, even if every other node colludes. For maximum assurance, you can [run your own node](/developers/node-setup) in a cluster. Since you trust yourself, this guarantees at least one honest participant.
```rust theme={null}
#[instruction]
pub fn double_secret(input: Enc) -> Enc {
// Convert encrypted data to secret shares across nodes
let value = input.to_arcis();
// Each node multiplies their share by 2
// The shares still reconstruct to the correct answer!
let result = value * 2;
// Convert secret shares to encrypted output
input.owner.from_arcis(result)
}
```
This is why Arcis code looks like normal Rust but runs on encrypted data: the MPC protocol handles the complexity of computing on shares.
## The circuit is compiled once
The important constraint is this: **your Arcis code compiles into a fixed circuit structure before any data flows through it.**
```mermaid theme={null}
flowchart LR
A["Rust Code
(compile)"] --> B["Fixed Circuit
Structure"] --> C["Secret Shares
Flow Through"]
```
The circuit structure (which operations happen, in what order, how many times) is locked in at compile time. Secret data flows through this fixed structure at runtime.
**This is the root cause of all Arcis constraints.** If the circuit structure could change based on secret data, observers could learn information by watching *how* the computation runs, not just *what* it outputs.
## Both branches always execute
In normal code, `if/else` picks one branch to run:
```rust theme={null}
// Normal Rust: only ONE branch executes
if condition {
do_expensive_thing(); // Runs if true
} else {
do_cheap_thing(); // Runs if false
}
```
In Arcis, when the condition is **not a compile-time constant**, both branches execute:
```rust theme={null}
// Arcis: BOTH branches execute, condition selects the result
let secret_value = encrypted_input.to_arcis(); // Now secret-shared across nodes
let is_large = secret_value > 1000; // Comparison result is also secret
if is_large { // No single node knows this value
expensive() // Always runs
} else {
cheap() // Always runs
}
// Cost = cost(expensive) + cost(cheap)
```
In the example above, after `.to_arcis()`, no individual node knows the actual value: each holds a random-looking share. The condition `is_large` is itself secret-shared, meaning no node can determine which branch "should" execute. The MPC protocol executes both branches, then uses the secret condition to select which result to keep, without revealing which branch applied.
**The rule:** If a condition is not a compile-time constant, Arcis executes both branches. This includes:
* Conditions derived from secret data (via `.to_arcis()`)
* Conditions using public runtime parameters
**The exception:** Compile-time constants like `if true { ... }` or `if CONST > 5 { ... }` allow single-branch execution because the value is known during circuit compilation.
**Compile-time constant**: A value the Arcis compiler can determine before circuit generation: literals like `10`, `const` declarations, or expressions involving only constants. Values from function parameters or `.to_arcis()` results are NOT compile-time constants.
**Cost implication**: The cost of an `if/else` is the sum of both branches, not the max. Keep branches balanced when possible.
## Fixed iteration counts
Loops must have iteration counts known at compile time:
```rust theme={null}
// ✓ Works: iteration count is fixed
for i in 0..100 {
process(data[i]);
}
// ✗ Won't compile: iteration count depends on runtime value
while secret_value < threshold {
secret_value += 1;
}
```
**Why no `while` loops?** The number of iterations would depend on secret data:
* Secret starts at 10 → 90 iterations → takes X time
* Secret starts at 99 → 1 iteration → takes X/90 time
Execution time would leak information about the secret value.
**Why no `break` or `continue`?** Same reason. Early exit based on secret data reveals information:
```rust theme={null}
// ✗ Won't compile
for i in 0..1000 {
if found_match { break; } // Would reveal when match occurred
}
```
## Fixed-size data only
Variable-length types like `Vec`, `String`, and `HashMap` are not supported:
```rust theme={null}
// ✗ Not supported
let items: Vec = vec![];
let name: String = String::new();
// ✓ Use fixed-size alternatives
let items: [u8; 100] = [0; 100];
let name: [u8; 32] = [0; 32];
```
**Why?** The circuit compiler must know exactly how much memory and how many operations your circuit needs. A `Vec` that might hold 10 or 10,000 elements would create a circuit of unknown size.
## Reveal and encryption placement
The `.reveal()` and `.from_arcis()` methods **cannot be called inside `if/else` blocks** when the condition is not a compile-time constant:
```rust theme={null}
// ✗ Won't compile - reveal inside conditional
if secret_condition {
value.reveal() // Error: cannot call reveal in conditional execution
}
// ✓ Works - select first, then reveal outside
let selected = if secret_condition { a } else { b };
selected.reveal()
```
**Why?** Both branches execute in isolation before results are merged. `.reveal()` broadcasts data to all parties. It is a global side effect that cannot be undone during the merge. If reveal happened inside a branch, it would leak which branch was taken.
The same applies to `.from_arcis()`:
```rust theme={null}
// ✗ Won't compile
if secret_condition {
owner.from_arcis(value) // Error
}
// ✓ Works
let result = if secret_condition { a } else { b };
owner.from_arcis(result)
```
## Dynamic indexing is O(n)
When the index is known at compile time, array access is O(1):
```rust theme={null}
let x = arr[5]; // Compile-time index: O(1)
```
When the index depends on secret data, it becomes O(n):
```rust theme={null}
let x = arr[secret_idx]; // Secret index: O(n)
```
**Why?** The circuit cannot reveal which index was accessed. It must check all positions and select the right one without leaking which position matched. For small arrays this is fine; for large arrays, consider your access patterns carefully.
## Cost model
Not all operations are equal in MPC. Here's a practical cost ranking:
| Operation | Relative Cost | Notes |
| ------------------------------ | ---------------------- | --------------------------- |
| Addition, subtraction | Nearly free | Local computation on shares |
| Multiplication by constant | Nearly free | Local computation |
| Multiplication | Cheap | Optimized via preprocessing |
| Comparisons (`<`, `>`, `==`) | Expensive | Bit-by-bit operations |
| Division, modulo by power of 2 | Expensive | Bit shift operations |
| Division, modulo (general) | Very expensive | Iterative algorithms |
| Dynamic array indexing | O(n) | Must check all positions |
| Sorting | O(n·log²(n)·bit\_size) | Fixed comparison pattern |
**Optimization tip:** Batch operations when possible. Multiple `.from_arcis()` calls have overhead. Restructure to minimize conversions between encrypted and secret-shared forms.
## Rust patterns that need adjustment
Arcis is Rust, but some common patterns need adaptation:
| Standard Rust | Arcis Equivalent | Why |
| ----------------------------- | ---------------------------- | ----------------------------------- |
| `Vec` | `[T; N]` | Fixed size required |
| `String` | `[u8; N]` | Fixed size required |
| `while condition { }` | `for i in 0..MAX { }` | Fixed iterations |
| `let ... else` | `if let` or `match` | `let ... else` is not supported yet |
| `break`, `continue`, `return` | Restructure logic | No early exit |
| `.filter()` | Manual loop with conditional | Would produce variable length |
| `HashMap` | Arrays with manual lookup | Fixed size required |
## Syntax constraints
A few syntax rules to keep in mind:
* **`if`, `else`, and `else if` all work normally**: when the condition is not a compile-time constant, both branches execute (MPC cost = sum of all branches)
* **`.reveal()` and `.from_arcis()` cannot be called inside `if/else` blocks when the condition is not a compile-time constant**: if the condition is a compile-time constant (like `if true`), only one branch runs and reveal is allowed inside
* **`match`, `if let`, let chains, and `matches!` are supported**: match arms may use guards, except an arm cannot combine a guard with an OR-pattern
* **No `let ... else`**: Use `if let` or `match` instead
* **No early `return`**: Functions must have a single exit point
* **No `while`, `loop`, `break`, `continue`**: Use `for` loops with fixed bounds
* **Enums and `Option` are supported**: but not as a circuit input or output, and enum discriminants cannot be set explicitly
## What you learned
* **Secret sharing splits data across nodes**: no single node sees the actual value
* **Circuits are fixed at compile time**: structure cannot depend on secret data
* **Both branches execute**: MPC cost is the sum, not max
* **Loops need fixed bounds**: no `while`, `break`, or `continue`
* **Use fixed-size types**: `[T; N]` instead of `Vec`
* **Reveal outside conditionals**: `.reveal()` and `.from_arcis()` are global operations
* **Pattern matching works within Arcis constraints**: use `match`, `if let`, let chains, and `matches!`, but avoid `let ... else`
* **Dynamic indexing is O(n)**: the circuit checks all positions
* **Comparisons are expensive**: additions and multiplications are cheap
## What's next?
Supported types including integers, arrays, and encrypted types.
Working with Enc types for encrypted inputs and outputs.
# Operations
Source: https://docs.arcium.com/developers/arcis/operations
Complete reference for supported operations, expressions, and patterns in Arcis MPC circuits
Arcis supports many of Rust's native operations and extends them for encrypted data, allowing you to write private computations using familiar Rust syntax. See the tables below for a detailed list of supported and unsupported operations.
**Use this page when** you need to check if a specific operation is supported in Arcis circuits.
## Quick summary
**Works:** `if/else`, `if let`, `match`, `for` loops, arithmetic, comparisons, iterators (except filter)
**Doesn't work:** `while`, `loop`, `break`, `continue`, `return`, `let ... else`, `.filter()`
See tables below for full details.
### Table of contents
* [Expression support](#expression-support)
* [Binary expressions](#binary-expressions)
* [Casts](#cast-expressions)
* [Literals](#literal-expressions)
* [Methods](#method-calls)
* [Paths](#paths)
* [Item support](#item-support)
* [Pattern support](#pattern-support)
* [Pattern matching](#pattern-matching)
## Expression support
| Expression Name | Example | Support | Comments |
| ----------------- | ------------------------------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Array literal | `[a, b]` | Supported | |
| Assignment | `a = b;` | Supported | |
| Async block | `async { ... }` | Unsupported | |
| Await | `foo().await` | Unsupported | |
| Binary expression | `a + b` | Partial Support | [See table below](#binary-expressions) for supported binary expressions. |
| Block expression | `{ ... }` | Supported | |
| Break | `break;` | Unsupported | |
| Function call | `f(a, b)` | Partial Support | [See table below](#function-calls) for supported functions. |
| Casts | `a as u16` | Partial Support | [See table below](#cast-expressions) for supported conversions. |
| Closures | `\|a, b \| a + b` | Supported | |
| Const block | `const { ... }` | Supported | |
| Continue | `continue;` | Unsupported | |
| Field access/set | `obj.field` | Supported | |
| For loop | `for i in expr { ... }` | Supported | `expr` must have a length known at compile-time. |
| If | `if cond { ... } else { ... }` | Supported | Complexity is O(`then_block + else_block`). |
| Indexing | `a[idx]` | Supported | Complexity is O(`a.len()`) if `idx` isn't compile-time known (all positions are checked to hide which index was accessed). |
| If let | `if let Some(x) = ...` | Partial Support | [See pattern matching](#pattern-matching). Let chains require Rust edition 2024. |
| Literals | `1u128` | Partial Support | [See table below](#literal-expressions) for supported literals. |
| Loops | `loop { ... }` | Unsupported | MPC circuits have fixed structure: variable iteration counts would require dynamic circuit size. Use `for` with compile-time bounds instead. |
| Macros | `println!("{}", q)` | Partial Support | [See table below](#macros) for supported macros. |
| Match | `match n { ... }` | Partial Support | [See pattern matching](#pattern-matching). Last arms cannot have guards. |
| Method calls | `x.foo(a, b)` | Partial Support | [See table below](#method-calls) for supported methods. |
| Parentheses | `(a + b)` | Supported | |
| Paths | `Foo::bar` | Partial Support | [See table below](#paths) for supported paths. |
| Ranges | `4..5` | Partial Support | Not supported in `arr[4..16]`. |
| Raw addresses | `&raw const foo` | Unsupported | |
| References | `&mut foo` | Supported | |
| Repeat arrays | `[4u8; 128]` | Supported | |
| Return | `return false;` | Unsupported | |
| Struct literals | `MyStruct { a: 12, b }` | Supported | |
| Try expression | `this_call_can_err()?;` | Unsupported | |
| Tuple literal | `(a, 4, c)` | Supported | |
| Unary expressions | `!x` | Partial Support | User-defined unary operations are not supported. |
| Unsafe | `unsafe { ... }` | Unsupported | |
| While loops | `while x < 64 { ... }` | Unsupported | Cannot be supported as the number of iterations is not known. |
**Why branches count:** In MPC, both sides of non-constant conditional execution are evaluated, including `if`/`else` branches and non-constant `match` arms. The condition only selects which result to use. This ensures the execution pattern does not leak information about the condition value. See [Thinking in MPC](/developers/arcis/mental-model) for details.
### Binary expressions
User-defined binary operations are currently unsupported.
| Example | Supported types |
| ---------- | -------------------------------------------- |
| `a + b` | Integers, floats, `BaseField25519` |
| `a - b` | Integers, floats, `BaseField25519` |
| `a * b` | Integers, floats, `BaseField25519` |
| `a / b` | Integers, floats |
| `a % b` | Integers |
| `a && b` | Booleans |
| `a \|\| b` | Booleans |
| `a ^ b` | Booleans |
| `a & b` | Booleans |
| `a \| b` | Booleans |
| `a << b` | None |
| `a >> b` | Integers, if `b` is known at compile time. |
| `a == b` | All. Use `derive(PartialEq)` for structs. |
| `a != b` | All. Use `derive(PartialEq)` for structs. |
| `a < b` | Booleans, integers, floats, `BaseField25519` |
| `a <= b` | Booleans, integers, floats, `BaseField25519` |
| `a >= b` | Booleans, integers, floats, `BaseField25519` |
| `a > b` | Booleans, integers, floats, `BaseField25519` |
| `a += b` | Integers, floats, `BaseField25519` |
| `a -= b` | Integers, floats, `BaseField25519` |
| `a *= b` | Integers, floats, `BaseField25519` |
| `a /= b` | Integers, floats |
| `a %= b` | Integers |
| `a ^= b` | Booleans |
| `a &= b` | Booleans |
| `a \|= b` | Booleans |
| `a <<= b` | None |
| `a >>= b` | Integers, if `b` is known at compile time |
`BaseField25519` does **not** support `/`, `%`, `>>`, or `<<`. `&`, `|`, `^` are booleans-only across all types. Use `.field_division()` or `.euclidean_division()` for division on field elements. See [BaseField25519 Operations](/developers/arcis/primitives#basefield25519-operations).
### Cast expressions
`a as MyType` is only supported:
| From Type | To Type |
| ------------ | ------------ |
| integer type | integer type |
| `bool` | integer type |
| integer type | `bool` |
| `&...&T` | `&T` |
### Function calls
The following function calls are supported:
* user-defined function calls (without recursion)
* `ArcisRNG::bool()` to generate a boolean.
* `ArcisRNG::gen_uniform::()` to generate a uniform value of type T (bool, integer, or combination). Requires explicit type parameter.
* `ArcisRNG::gen_integer_from_width(width: usize) -> u128`. Generates a secret integer between 0 and 2^width - 1 included.
* `ArcisRNG::gen_public_integer_from_width(width: usize) -> u128`. Generates a public integer between 0 and 2^width - 1 included.
* `ArcisRNG::gen_integer_in_range(min: u128, max: u128, n_attempts: usize) -> (u128, bool)`. Generates a random integer in `[min, max]` using rejection sampling. **`n_attempts` must be compile-time known**. Returns `(result, success)` where `success=false` indicates all attempts were rejected. With `n_attempts=24`, failure probability is `<2^-24`.
* `ArcisRNG::shuffle(slice)` on slices. Complexity is in `O(n*log³(n) + n*log²(n)*sizeof(T))`.
* `Mxe::get()` to be able to create MXE-owned secret data.
* `Shared::new(arcis_public_key)` to share private data with `arcis_public_key`.
* `ArcisX25519Pubkey::from_base58(base58_byte_string)` to create a public key from a base58-encoded address.
* `ArcisX25519Pubkey::from_uint8(u8_byte_slice)` to create a public key from a Uint8 array.
* `SolanaPublicKey::from_serialized(value)` to create a Solana public key from serialized form.
* `SolanaPublicKey::from_base58(byte_string)` to create a Solana public key from base58.
* `ArcisMath::sigmoid(x)` for the sigmoid activation function.
* `LogisticRegression::new(coef, intercept)` for logistic regression models.
* `LinearRegression::new(coef, intercept)` for linear regression models.
* `Pack::new(value)` to bit-pack data for onchain storage (multiple small values fit into fewer field elements).
* `ArcisX25519Pubkey::new_from_x(x: BaseField25519)` to create a public key from its Curve25519 Montgomery X-coordinate.
* `ArcisX25519Pubkey::to_x() -> BaseField25519` to extract the Montgomery X-coordinate from a public key.
* `BaseField25519::from_u8(x)` ... `BaseField25519::from_u128(x)` to convert unsigned integers to field elements. Signed variants (`from_i8` ... `from_i128`) and `from_bool`, `from_usize`, `from_isize` also available.
* `BaseField25519::power_of_two(exp)` to compute `2^exp` as a field element.
### Literal expressions
| Example | Support |
| ----------- | ----------- |
| `"foo"` | Unsupported |
| `b"foo"` | Supported |
| `c"foo"` | Unsupported |
| `b'f'` | Supported |
| `'a'` | Unsupported |
| `1` | Supported |
| `1u16` | Supported |
| `1f64` | Supported |
| `1.0e10f64` | Supported |
| `true` | Supported |
### Macros
The following macros are supported:
* `debug_assert!`, `debug_assert_ne!`, `debug_assert_eq!` to assert conditions during debugging. They do not change instruction behavior.
* `eprint!`, `eprintln!`, `print!`, `println!` to print debug output. They do not change instruction behavior.
* `matches!(expr, pattern)` to test a pattern and return `bool`. See [Pattern support](#pattern-support).
* `arcis_static_panic!(message)` to fail compilation when the branch is reached. Useful for enforcing constraints that must be known before circuit generation.
* `include_bytes!("file_path")` to include raw bytes from a file in Arcis circuits.
* `include!("file_path")` to include a file in item position (not expression position).
* `assert_current_module!(crate::path::to::module)` to enable `crate::` absolute paths within the current module. Place at the top of any module that needs to reference items via absolute paths.
* `encrypted_mod!("path/to/module.rs")` or `encrypted_mod!("path/to/module.rs", alias_name)` to use another file as a module within an `#[encrypted]` module. The target file must use the `#[encrypted_library]` attribute. Items are accessible via the filename stem (or alias) as a namespace, e.g., `module_name::ITEM`.
Example usage:
```rust theme={null}
const ARRAY_LEN: usize = 3; // Change to 1 and the example will not compile.
fn second_element(arr: &[u8]) -> u8 {
if arr.len() < 2 {
arcis_static_panic!("Array must have at least 2 elements");
}
arr[1]
}
#[instruction]
fn reveal_second_element(input: Enc>) -> u8 {
let array = input.to_arcis().unpack();
second_element(&array).reveal()
}
```
`arcis_static_panic!` triggers at compile time when the Arcis compiler evaluates the branch. Try changing `ARRAY_LEN` to `1` above: the compile error demonstrates how this macro enforces constraints that must be validated before circuit generation.
### Method calls
The following method calls are supported:
* user-defined method calls (with generics but without recursion)
* `.clone()` on all `Clone` objects.
* `.len()`, `.is_empty()`, `.swap(a, b)`, `.fill(value)`, `.reverse()`, `.iter()`, `.iter_mut()`, `.into_iter()`, `.windows(width)`, `.copy_from_slice(src)`, `.clone_from_slice(src)`, `.split_at(mid)`, `.split_at_mut(mid)`, `.rotate_left(mid)`, `.rotate_right(mid)`, `.contains(item)`, `.starts_with(needle)`, `.ends_with(needle)`, `.as_slice()`, `.as_mut_slice()` on arrays and slices.
* `.first()`, `.first_mut()`, `.last()`, `.last_mut()`, `.get(index)`, `.get_mut(index)`, `.split_first()`, `.split_first_mut()`, `.split_last()`, `.split_last_mut()`, `.first_chunk::()`, `.first_chunk_mut::()`, `.last_chunk::()`, `.last_chunk_mut::()`, `.split_first_chunk::()`, `.split_first_chunk_mut::()`, `.split_last_chunk::()`, `.split_last_chunk_mut::()` on arrays and slices. These return `Option`.
* `.as_array::()`, `.as_mut_array::()` on slices (requires Rust 1.93+). These return `Option`.
* `.map(f)`, `.each_ref()`, `.each_mut()` on arrays.
* `.sort()` on arrays of integers. Complexity is in `O(n*log²(n)*bit_size)`.
* `.enumerate()`, `.chain(other)`, `.cloned()`, `.copied()`, `.count()`, `.rev()`, `.zip(other)`, `.map(func)`, `.for_each(func)`, `.fold(init, func)`, `.sum()`, `.product()`, `.collect::>()` on iterators.
* `.take(n)`, `.skip(n)`, `.step_by(n)` on iterators when `n` is compile-time known.
* `.reveal()` if not inside a conditionally executed block (`if`/`else`, non-constant `match` arm, or guard)
* `.to_arcis()` on `Enc`s
* `.from_arcis(x)` on `Owner`s (objects of types `Mxe` or `Shared`) if not inside a conditionally executed block (`if`/`else`, non-constant `match` arm, or guard)
* `.abs()`, `.min(x)`, `.max(x)` on integers and floats
* `.abs_diff(other)`, `.is_positive()`, `.is_negative()`, `.div_ceil(other)` on integers
* `.to_le_bytes()`, `.to_be_bytes()`, `.wrapping_add(rhs)`, `.wrapping_sub(rhs)`, `.wrapping_mul(rhs)` on typed integers (does not work on integers whose type the interpreter does not know)
* `.exp()`, `.exp2()`, `.ln()`, `.log2()`, `.sqrt()` on floats.
* `.set(val)`, `.swap(other)`, `.update(f)`, `.replace(val)`, `.into_inner()`, `.get()`, `.get_mut()` on `Cell`.
* `.unpack()` on `Pack` to extract the original value from packed storage.
* `Option` combinators: `.is_some()`, `.is_none()`, `.is_some_and(f)`, `.is_none_or(f)`, `.unwrap()`, `.unwrap_or(default)`, `.unwrap_or_else(f)`, `.map(f)`, `.map_or(default, f)`, `.map_or_else(default_f, f)`, `.inspect(f)`, `.filter(predicate)`, `.and(other)`, `.and_then(f)`, `.or(other)`, `.or_else(f)`, `.xor(other)`, `.zip(other)`, `.unzip()`, `.flatten()`, `.copied()`, `.cloned()`, `.take()`, `.take_if(predicate)`, `.replace(value)`, `.insert(value)`, `.get_or_insert(value)`, `.get_or_insert_with(f)`.
* `.to_arcis_with_pubkey_and_nonce(pubkey, nonce)` on `EncData` to decrypt when the key is shared across inputs (avoids duplicate decryption gates). See [EncData](/developers/arcis/types#advanced-encdata%3Ct%3E) for details.
* `.data` on `Enc` to extract only the `EncData` ciphertext for smaller callback payloads.
* `.safe_inverse()` on `BaseField25519` to get the field inverse (returns 0 for inverse of 0).
* `.field_division(divisor)` on `BaseField25519` for field division (returns 0 for division by 0).
* `.euclidean_division(divisor)` on `BaseField25519` for signed Euclidean division (panics on division by 0).
* `.to_u8_unchecked()` ... `.to_u128_unchecked()` on `BaseField25519` to extract as unsigned int. Silently produces incorrect results if value exceeds target range. Signed variants (`to_i8_unchecked` ... `to_i128_unchecked`) and `to_bool_unchecked` also available.
### Paths
The following paths are supported:
* `IntType::BITS`, `IntType::MIN` and `IntType::MAX` where `IntType` is an integer type.
* Paths to user-defined constants, functions and structs, as long as they are inside the `#[encrypted]` area. Both `super::` paths and `crate::` paths (when `assert_current_module!` is declared) are supported.
* `std::mem::replace` and `std::mem::swap`
* `Box::leak`, `Box::new`, `Cell::new`, `Cell::from_mut`
### Code organization with modules
Arcis supports nested modules, `super::` parent references, `crate::` absolute paths, and multi-file projects. `#[instruction]` functions can be placed at any module depth, not just at the top level.
**Submodules with `super::` and `crate::` paths:**
```rust theme={null}
use arcis::*;
#[encrypted]
mod my_mxe {
use arcis::*;
assert_current_module!(crate::my_mxe);
const THRESHOLD: u64 = 1000;
// No assert_current_module! needed: this module only uses super::, not crate::
mod validation {
use arcis::*;
pub fn is_above_threshold(val: u64) -> bool {
val > super::THRESHOLD // Access parent constant via super::
}
}
mod processing {
use arcis::*;
assert_current_module!(crate::my_mxe::processing);
// #[instruction] works in nested modules
#[instruction]
pub fn process(vals: [u64; 3]) -> [u64; 3] {
let mut result = [0u64; 3];
for i in 0..3 {
// Use crate:: to reference a sibling module's function
let valid = crate::my_mxe::validation::is_above_threshold(vals[i]);
result[i] = if valid { vals[i] * 2 } else { vals[i] };
}
result
}
}
}
```
**Multi-file projects with `encrypted_mod!`:**
```rust theme={null}
// encrypted-ixs/src/lib.rs
use arcis::*;
#[encrypted]
mod my_mxe {
use arcis::*;
encrypted_mod!("helpers.rs");
type Config = helpers::Config;
#[instruction]
fn compute(cfg: Config) -> u64 {
cfg.value * helpers::MULTIPLIER
}
}
```
```rust theme={null}
// encrypted-ixs/src/helpers.rs
use arcis::*;
#[encrypted_library]
mod arcis_library {
pub const MULTIPLIER: u64 = 42;
pub struct Config {
pub value: u64,
}
}
```
Each file imported via `encrypted_mod!` must use `#[encrypted_library]` (not `#[encrypted]`). Items are accessible through the filename stem as a namespace (e.g., `helpers::Config`), or you can provide an alias: `encrypted_mod!("helpers.rs", utils)` makes items accessible as `utils::Config`.
## Item support
| Item Name | Example | Support | Comments |
| ----------------- | --------------------------- | --------------- | -------------------------------------------------------------------------------------- |
| Constant | `const MAX: u16 = 65535` | Supported | |
| Enum | `enum MyEnum { ... }` | Partial Support | Explicit discriminants are not supported. Cannot be a circuit input or output. |
| Extern | `extern ...` | Unsupported | |
| Functions | `fn foo() -> u8 { 0 }` | Partial Support | Recursive functions are not supported. |
| Impls | `impl MyType { ... }` | Supported | Generics and custom traits are supported. `MyType` must not be a reference. |
| Macro Definitions | `macro_rules! ...` | Unsupported | |
| Macro Invocations | `println!(...)` | Partial Support | [See table above](#macros) for supported macros. |
| Modules | `mod my_module { ... }` | Supported | |
| Statics | `static ...` | Unsupported | |
| Structs | `struct MyStruct { ... }` | Supported | |
| Traits | `trait MyTrait { ... }` | Partial Support | Custom traits with associated types and constants. Standard library traits forbidden.¹ |
| Type Aliases | `type MyId = usize;` | Supported | |
| Union | `union MyUnion { ... }` | Unsupported | |
| Use | `use arcis::*` | Partial Support | Only `use arcis::*` is supported. |
| Arcis Circuit | `#[arcis_circuit = "name"]` | Supported | Use a pre-built optimized circuit by name. For internal/advanced use. |
¹ **Forbidden trait implementations**: You cannot manually implement `Drop`, `Deref`, `AsRef`, `AsMut`, `From`, `Into`, `TryFrom`, `TryInto`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `Clone`, `ToOwned`, `ToString`, `Iterator`, `IntoIterator`, `DoubleEndedIterator`, `ExactSizeIterator`, `Extend`, `FromIterator`, `Fn`, `FnMut`, `FnOnce`, `Future`, `IntoFuture`, `AsyncFn`, `AsyncFnMut`, or `AsyncFnOnce`.
**Why?** These traits have special runtime semantics (drop ordering, lazy evaluation, dynamic dispatch) that cannot be correctly translated to fixed MPC circuits. The Arcis compiler provides built-in implementations that work within MPC constraints.
Use `#[derive(...)]` for `Clone`, `PartialEq`, `Default`, etc., which generates MPC-compatible implementations. `Default` also supports a manual `impl Default for T`.
## Pattern support
The following patterns are supported in function arguments, `let` statements, `if let` conditions, `matches!` calls, and `match` expressions:
* simple idents: `let ident = ...;`
* mutable idents: `let mut ident = ...;`
* ref idents: `let ref ident = ...;`
* mutable ref idents: `let ref mut ident = ...;`
* parentheses around a supported pattern: `let (...) = ...;`
* reference of a supported pattern: `let &... = ...;`
* array of supported patterns: `let [...] = ...;`
* struct of supported patterns: `let MyStruct { ... } = ...;`
* tuple of supported patterns: `let (...) = ...;`
* tuple struct of supported patterns: `let MyStruct(...) = ...;`
* type pattern of a supported pattern: `let ...: ty = ...;`
* wild pattern: `let _ = ...;`
`|` patterns are supported only in `if let`, `matches!`, and `match`; they cannot be used on a `match` arm that also has a guard. The `..` pattern is only supported inside struct patterns with named fields, e.g. `MyStruct { x: 0, .. }`.
Literal, range, and path-constant patterns are only supported in `if let`, `matches!`, and `match`. Path constants must use a path such as `module::MY_CONST`; bare const identifiers are not supported as patterns.
## Pattern matching
Arcis supports `match` expressions, `if let` (including let chains), and the `matches!` macro for branching on patterns. Patterns can be literals, ranges, path constants like `module::MY_CONST`, OR-patterns, tuples, structs (with `..` rest), arrays/slices, references, bindings, and wildcards. Match arms can use `if`-guards, except an arm cannot combine a guard with an OR-pattern.
`let ... else` remains unsupported. In `match`, guards are supported, but an arm cannot combine a guard with an OR-pattern.
### `match` expressions
```rust theme={null}
#[encrypted]
mod circuits {
use arcis::*;
pub struct Point { x: i16, y: i16 }
#[instruction]
pub fn classify(p: Point) -> i16 {
match p {
Point { x: 0, y: 0 } => 0,
Point { x: 0, y } => y,
Point { x, y: 0 } => x * 10,
Point { x, y } => x + y,
}
}
#[instruction]
pub fn bucket(x: u8) -> u8 {
match x {
v if v < 5 => 0,
v if v < 10 => 1,
_ => 2,
}
}
}
```
### `if let` and let chains
```rust theme={null}
if let Point { x: 0, y } = p {
y
} else {
-1
}
// let chain: bind in the first condition, use in the second
if let (0, val) = (a, b) && val < 10 {
1
} else {
0
}
```
Let chains (`if let ... && let ... && ...`) require Rust edition 2024. The default `arcium init` scaffold sets `edition = "2021"` in `encrypted-ixs/Cargo.toml`. Bump it to `edition = "2024"` to use this syntax.
### `matches!` macro
`matches!` returns a `bool` and supports the same pattern surface:
```rust theme={null}
let in_range = matches!(x, 0..=9);
let is_origin = matches!(p, Point { x: 0, y: 0 });
let is_short = matches!(arr.as_slice(), [] | [_]);
```
### Slice patterns
Fixed-size arrays viewed as slices can be matched with arms of different lengths:
```rust theme={null}
match arr.as_slice() {
[] => 0,
[x] => *x + 1,
[x, y] => *x + *y + 2,
[_, _, _] => 3,
_ => 99,
}
```
Item shadowing of a `let` binding is rejected. Defining `fn f()` or `const F: _` after `let f = ...` (or `let F = ...`) errors at compile time with *"Cannot have an item with the same name as a variable in scope."* `let` shadowing another `let` is still allowed.
## Generics
Arcis supports Rust generics with some constraints. Generic types must be known at compile time. Runtime polymorphism is not supported.
### Generic functions
```rust theme={null}
#[encrypted]
mod generics_example {
use arcis::*;
// Use a pre-built optimized circuit by name
// The empty function body is intentional - the circuit implementation is built-in
#[arcis_circuit = "zero"]
fn make_zero(a: T) -> T {}
fn set_zero(a: &mut T) {
*a = make_zero(*a);
}
#[instruction]
fn zero_any_type(mut arr: [u8; 10], mut val: u64) -> ([u8; 10], u64) {
set_zero(&mut arr);
set_zero::(&mut val); // Turbofish syntax works
(arr, val)
}
}
```
### Generic structs
```rust theme={null}
struct Wrapper(T);
impl Wrapper {
fn new(value: T) -> Self {
Wrapper(value)
}
fn into_inner(self) -> T {
self.0
}
}
#[instruction]
fn use_generic_struct(a: u8) -> u8 {
Wrapper::new(a).into_inner()
}
```
### Custom traits
```rust theme={null}
trait Processable {
type Output;
fn process(&self) -> Self::Output;
}
impl Processable for u8 {
type Output = u16;
fn process(&self) -> u16 {
*self as u16 * 2
}
}
fn apply_process(val: &T) -> T::Output {
val.process()
}
#[instruction]
fn trait_example(x: u8) -> u16 {
apply_process(&x)
}
```
### Generic constraints
| Feature | Supported | Notes |
| ----------------------- | --------- | ------------------------------- |
| Type parameters `` | Yes | Must be known at compile time |
| Trait bounds `T: Trait` | Yes | Including `ArcisType` |
| Associated types | Yes | `type Output;` |
| Associated constants | Yes | `const SIZE: usize;` |
| Where clauses | Yes | `where T: Clone` |
| Turbofish `::` | Yes | For explicit type specification |
| Runtime polymorphism | No | No `dyn Trait` or trait objects |
## Iterators
Most iterator methods work in Arcis, with the notable exception of `.filter()`.
### Supported iterator methods
```rust theme={null}
#[instruction]
fn iterator_examples(arr: [u8; 10]) -> u16 {
// Basic iteration
let mut sum = 0u16;
for val in arr.iter() {
sum += *val as u16;
}
// Method chaining
arr.iter()
.map(|x| *x as u16)
.map(|x| x * 2)
.sum()
}
```
### Complete iterator support
| Method | Supported | Notes |
| ------------------------ | --------- | ---------------------------------------------- |
| `.iter()` | Yes | Creates iterator of references |
| `.iter_mut()` | Yes | Mutable references |
| `.into_iter()` | Yes | Consumes collection |
| `.map(f)` | Yes | Transform elements |
| `.enumerate()` | Yes | Add indices |
| `.zip(other)` | Yes | Pair with another iterator |
| `.chain(other)` | Yes | Concatenate iterators |
| `.rev()` | Yes | Reverse order |
| `.cloned()` | Yes | Clone elements |
| `.copied()` | Yes | Copy elements |
| `.fold(init, f)` | Yes | Reduce with accumulator |
| `.sum()` | Yes | Sum all elements |
| `.product()` | Yes | Multiply all elements |
| `.count()` | Yes | Count elements |
| `.take(n)` | Yes | n must be compile-time known |
| `.skip(n)` | Yes | n must be compile-time known |
| `.step_by(n)` | Yes | n must be compile-time known |
| `.for_each(f)` | Yes | Apply function to each |
| `.collect::>()` | Yes | Collect into `Box<[_]>` |
| `.next()` | Yes | Returns `Option` |
| `.nth(n)` | Yes | Returns `Option`; n must be compile-time known |
| `.last()` | Yes | Returns `Option` |
| `.reduce(f)` | Yes | Returns `Option` |
| `.max()` | Yes | Returns `Option` |
| `.min()` | Yes | Returns `Option` |
| `.max_by_key(f)` | Yes | Returns `Option` |
| `.min_by_key(f)` | Yes | Returns `Option` |
| `.filter(f)` | **No** | Would produce variable-length output |
| `.find(f)` | **No** | Would require early exit |
| `.any(f)` | **No** | Would require early exit |
| `.all(f)` | **No** | Would require early exit |
### Filter alternative
Since `.filter()` is not supported (it produces variable-length output), use a manual loop with conditionals:
```rust theme={null}
// ✗ Not supported
arr.iter().filter(|x| **x > threshold).sum()
// ✓ Manual filter pattern
#[instruction]
fn filter_sum(arr: [u8; 10], threshold: u8) -> u16 {
let mut sum = 0u16;
for val in arr.iter() {
if *val > threshold {
sum += *val as u16;
}
}
sum
}
```
This pattern checks all elements but only accumulates those meeting the condition: same result, fixed execution structure.
## What's next?
RNG, cryptography, and data packing operations.
Performance tips, debugging, and testing strategies.
# Primitives
Source: https://docs.arcium.com/developers/arcis/primitives
Random number generation, cryptographic operations, and data packing in Arcis
Arcis provides built-in primitives for randomness, cryptography, and efficient data storage. These operations are implemented as optimized MPC circuits.
## Random number generation
The `ArcisRNG` struct provides access to randomness within MPC circuits. All random values are generated within the MPC context.
### Basic usage
```rust theme={null}
use arcis::*;
#[encrypted]
mod randomness_example {
use arcis::*;
#[instruction]
pub fn random_operations() -> (bool, u128, [u8; 32]) {
// Generate a random boolean (50/50 probability)
let coin_flip = ArcisRNG::bool();
// Generate a random integer with specific bit width
// Returns u128 in range [0, 2^width - 1]
let random_byte = ArcisRNG::gen_integer_from_width(8); // 0-255
let random_u64 = ArcisRNG::gen_integer_from_width(64); // 0 to 2^64-1
// Generate a uniformly random value of any supported type
let random_array = ArcisRNG::gen_uniform::<[u8; 32]>();
(coin_flip.reveal(), random_byte.reveal(), random_array.reveal())
}
}
```
The `width` parameter in `gen_integer_from_width` must be known at compile time.
### Public vs secret random integers
```rust theme={null}
// Secret random integer (default) - only revealed when you call .reveal()
let secret_num = ArcisRNG::gen_integer_from_width(64);
// Public random integer - visible to all Arx nodes during circuit execution
let public_num = ArcisRNG::gen_public_integer_from_width(64);
```
Use `gen_public_integer_from_width` when you need randomness that does not need to stay secret within the MPC computation (for example, nonce generation). The value is visible to Arx nodes during execution but is not automatically included in the circuit output; you still control what gets returned.
### Range-based generation
To generate integers within a specific range, use `gen_integer_in_range`:
```rust theme={null}
#[instruction]
pub fn dice_roll() -> (u128, bool) {
// Generate integer between min and max (both inclusive)
// n_attempts controls the success probability
let (roll, success) = ArcisRNG::gen_integer_in_range(1, 6, 24);
// With 24 attempts, failure probability is below 2^-24
(roll.reveal(), success.reveal())
}
```
The function uses rejection sampling. Each attempt has >50% success probability, so `n_attempts=24` gives a failure probability below 2^-24.
The `n_attempts` parameter must be known at compile time.
### Shuffling
Shuffle arrays in-place with cryptographic uniformity:
```rust theme={null}
#[instruction]
pub fn shuffle_deck(mut cards: [u8; 52]) -> [u8; 52] {
ArcisRNG::shuffle(&mut cards);
cards.reveal()
}
```
**Complexity:** O(n·log³(n) + n·log²(n)·sizeof(T))
### What works and what doesn't
```rust theme={null}
// ✓ Works
let b: bool = ArcisRNG::bool();
let n: u128 = ArcisRNG::gen_integer_from_width(64);
let arr: [u8; 32] = ArcisRNG::gen_uniform::<[u8; 32]>();
// ✗ Doesn't work - type must be explicit
let b = ArcisRNG::gen_uniform(); // Error: type inference not supported
// ✗ Doesn't work - floats cannot be generated uniformly
let f: f64 = ArcisRNG::gen_uniform::(); // Error
```
## Cryptographic operations
### SHA3 hashing
Arcis provides SHA3-256 and SHA3-512 hash functions:
```rust theme={null}
#[instruction]
pub fn hash_message(message: [u8; 64]) -> [u8; 32] {
let hasher = SHA3_256::new();
hasher.digest(&message).reveal()
}
#[instruction]
pub fn hash_512(message: [u8; 128]) -> [u8; 64] {
let hasher = SHA3_512::new();
hasher.digest(&message).reveal()
}
```
Arcis uses SHA3 (Keccak) rather than SHA-2/SHA-512 because SHA3 has a more efficient circuit structure for MPC evaluation.
### Ed25519 signatures
Arcis provides Ed25519 signature operations using SHA3-512 internally (ArcisEd25519).
#### Signature verification
```rust theme={null}
#[instruction]
pub fn verify_signature(
verifying_key: Pack, // Public key from client
message: [u8; 32],
signature: [u8; 64],
) -> bool {
let vk = verifying_key.unpack();
let sig = ArcisEd25519Signature::from_bytes(signature);
vk.verify(&message, &sig).reveal()
}
```
#### Key generation
```rust theme={null}
#[instruction]
pub fn generate_keypair() -> VerifyingKey {
// Generate a random secret key (stays secret within MPC)
let secret_key = SecretKey::new_rand();
// Derive and return only the verifying (public) key
let verifying_key = VerifyingKey::from_secret_key(&secret_key);
verifying_key.reveal()
}
```
Only the public verifying key is revealed. The secret key is never revealed in plaintext; it exists only as secret shares distributed across Arx nodes. Arcium uses a **dishonest majority** model: privacy is maintained as long as at least one node remains honest, even if every other node colludes.
#### MXE cluster signing
Sign messages using the MXE cluster's collective key:
```rust theme={null}
#[instruction]
pub fn cluster_sign(message: [u8; 32]) -> ArcisEd25519Signature {
MXESigningKey::sign(&message).reveal()
}
```
### Public key operations
Work with X25519 public keys:
```rust theme={null}
#[instruction]
pub fn compare_keys(key1: [u8; 32], key2: [u8; 32]) -> bool {
let pk1 = ArcisX25519Pubkey::from_uint8(&key1);
let pk2 = ArcisX25519Pubkey::from_uint8(&key2);
(pk1 == pk2).reveal()
}
#[instruction]
pub fn key_from_base58() -> ArcisX25519Pubkey {
// Create public key from base58-encoded string
// Note: b"..." creates a byte string literal
ArcisX25519Pubkey::from_base58(b"2uKu51kQaLseu7FySMAGWU6hpnjNvgGr3PkvUCBVTTPD")
}
```
For advanced use, work with the Montgomery X coordinate directly:
```rust theme={null}
#[instruction]
pub fn extract_coordinate(pubkey: ArcisX25519Pubkey) -> BaseField25519 {
pubkey.to_x() // Extract Montgomery X-coordinate
}
#[instruction]
pub fn rebuild_from_coordinate(x: BaseField25519) -> ArcisX25519Pubkey {
ArcisX25519Pubkey::new_from_x(x) // Rebuild from X-coordinate
}
```
Coordinate extraction is for advanced cryptographic operations such as:
* **Custom ECDH key exchange** implementations
* **Key derivation** from shared secrets
* **Interoperability** with external systems that work with raw Curve25519 coordinates
* **Zero-knowledge proof** inputs that require field elements
Most applications should use `from_base58()` or `from_uint8()` for standard public key handling.
**About `.reveal()`:** Revealing cryptographic keys or signatures makes them public to all Arx nodes. Only reveal data that is intended to be public output. For internal computations, keep values in secret-shared form.
Learn where `.reveal()` and `.from_arcis()` can be called.
## BaseField25519 operations
`BaseField25519` (integers modulo `2^255 - 19`) is the native field element for Arcis MPC circuits. Use it for raw field arithmetic without truncation or overflow: cryptographic primitives, Pedersen commitments, curve coordinate work.
For bounded arithmetic, comparison-heavy logic, or when you need bitwise operations and division operators, use regular integers (`u8`..`u128`) instead.
### Construction
| Method | Description |
| ------------------------------------------------- | ------------------------------------- |
| `BaseField25519::from_u8(x)` ... `from_u128(x)` | Convert unsigned int to field element |
| `BaseField25519::from_i8(x)` ... `from_i128(x)` | Convert signed int to field element |
| `BaseField25519::from_usize(x)` / `from_isize(x)` | Platform-sized conversions |
| `BaseField25519::from_bool(x)` | `true` → 1, `false` → 0 |
| `BaseField25519::power_of_two(exp)` | Returns `2^exp` as a field element |
All `from_*` functions work both in plaintext Rust and inside `#[encrypted]` blocks.
### Extraction (unchecked)
| Method | Description |
| ----------------------------------------------- | ----------------------- |
| `.to_u8_unchecked()` ... `.to_u128_unchecked()` | Extract as unsigned int |
| `.to_i8_unchecked()` ... `.to_i128_unchecked()` | Extract as signed int |
| `.to_bool_unchecked()` | Extract as bool |
These methods are **unchecked**: if the field element value exceeds the target type's range, the result is undefined or otherwise incorrect. The circuit will not error; it will silently produce incorrect output.
### Arithmetic
All operations wrap modulo `2^255 - 19` (not at integer type boundaries).
| Operation | Syntax | Assign variant |
| -------------- | ------- | -------------- |
| Addition | `a + b` | `a += b` |
| Subtraction | `a - b` | `a -= b` |
| Multiplication | `a * b` | `a *= b` |
| Negation | `-a` | N/A |
### Comparisons
`==`, `!=`, `<`, `<=`, `>`, `>=` all produce `bool`.
Comparisons are **unsigned**: the field element is treated as a number in `[0, p-1]`. This means `BaseField25519::from_i8(-1)` wraps to `p - 1` and compares **greater than** `BaseField25519::from_u8(0)`.
### Serialization
| Method | Description |
| ---------------------------- | --------------------------------- |
| `.to_le_bytes() -> [u8; 32]` | Little-endian byte representation |
### Division methods
```rust theme={null}
#[instruction]
pub fn field_math(a: BaseField25519, b: BaseField25519) -> (BaseField25519, BaseField25519, BaseField25519) {
// Field inverse: returns 0 for inverse of 0 (no panic)
let inv = b.safe_inverse();
// Field division: uses safe_inverse internally, division by 0 returns 0
let quotient = a.field_division(b);
// Signed Euclidean division: panics on division by 0
let euclidean = a.euclidean_division(b);
(inv.reveal(), quotient.reveal(), euclidean.reveal())
}
```
| Method | Description | Division by Zero |
| ----------------------------- | -------------------------------- | ---------------- |
| `safe_inverse()` | Returns the field inverse | Returns 0 |
| `field_division(divisor)` | Field division (a \* divisor^-1) | Returns 0 |
| `euclidean_division(divisor)` | Signed Euclidean division | Panics |
`euclidean_division` will panic at runtime if the divisor is zero. Use `field_division` if you need safe handling of zero divisors.
### Differences from regular integers
`BaseField25519` is **not** an integer type. The following operations available on `u8`..`u128` are **not supported**:
* **No `/` or `%` operators**: use `.field_division()` or `.euclidean_division()` instead
* **No `>>`, `<<`** (shift operators are not supported; `&`, `|`, `^` are booleans-only across all types)
* **No `MIN`, `MAX`, `BITS` constants**
* **No `.min()`, `.max()`, `.abs()`** and no `.sort()` on arrays of field elements
* **No `.to_be_bytes()`**: only `.to_le_bytes()`
* **No `as` casts**: use `from_*` / `to_*_unchecked` methods
`Pack` provides **no compression**: each value already occupies one full field element. Only use `Pack` with smaller types like `[u8; N]`.
## Data packing
The `Pack` type provides bit-level compression for onchain storage efficiency.
### Why packing matters
In Arcis, all values are stored as field elements (\~255 bits / 32 bytes each). Without packing:
* A single `u8` (8 bits) uses one full field element
* `[u8; 256]` uses 256 field elements
With packing, multiple small values are combined into fewer field elements:
**The math:**
* `[u8; 256]` = 256 bytes total
* Each field element packs \~26 bytes (208 usable bits)
* Packed: ⌈256 / 26⌉ = **10 field elements**
* Compression: 256 → 10 = **\~26x fewer field elements**
Without packing, each `u8` would use a full field element (256 elements total). This significantly reduces onchain storage costs and transaction sizes.
### When to use Pack
* Large arrays of small integers (`[u8; N]`, `[u16; N]`)
* Data that needs to be stored onchain
* Input/output parameters approaching transaction size limits
### Basic usage
```rust theme={null}
// Pack data for efficient storage
let packed: Pack<[u8; 64]> = Pack::new(data);
// Unpack to use the data
let data: [u8; 64] = packed.unpack();
```
**Trade-off:** Packing/unpacking has compute cost. Use `Pack` when storage savings outweigh the computation overhead, typically for arrays of 32+ small integers.
How to use generated packers with encrypted inputs in TypeScript.
### Simple example
```rust theme={null}
#[instruction]
pub fn pack_data(data: [u8; 64]) -> Pack<[u8; 64]> {
Pack::new(data) // Compress 64 bytes into ~3 field elements
}
#[instruction]
pub fn unpack_data(packed: Pack<[u8; 64]>) -> [u8; 64] {
packed.unpack() // Restore original array
}
#[instruction]
pub fn process_packed(packed: Pack<[u8; 64]>) -> u8 {
let data = packed.unpack();
let mut max = data[0];
for i in 1..64 {
if data[i] > max {
max = data[i];
}
}
max.reveal()
}
```
These basic patterns cover most `Pack` use cases. The "Practical Example" below shows advanced usage with encrypted types.
### Practical example
```rust theme={null}
const ARRAY_SIZE: usize = 64;
#[instruction]
pub fn merge_and_sort(
player_min: Enc>,
player_max: Enc>,
) -> (Enc>, Enc>) {
// Unpack the encrypted data
let mut min_array = player_min.to_arcis().unpack();
let mut max_array = player_max.to_arcis().unpack();
// Combine, sort, and split
let mut full = [0u8; 2 * ARRAY_SIZE];
full[..ARRAY_SIZE].copy_from_slice(&min_array);
full[ARRAY_SIZE..].copy_from_slice(&max_array);
full.sort();
min_array.copy_from_slice(&full[..ARRAY_SIZE]);
max_array.copy_from_slice(&full[ARRAY_SIZE..]);
// Re-pack for output
(
player_min.owner.from_arcis(Pack::new(min_array)),
player_max.owner.from_arcis(Pack::new(max_array))
)
}
```
### Pack with crypto types
Cryptographic types like `VerifyingKey` are often passed as `Pack`:
```rust theme={null}
#[instruction]
pub fn verify_with_packed_key(
key: Pack, // Efficiently packed public key
message: [u8; 32],
signature: [u8; 64],
) -> bool {
let vk = key.unpack();
let sig = ArcisEd25519Signature::from_bytes(signature);
vk.verify(&message, &sig).reveal()
}
```
## Machine learning
Arcis includes basic ML primitives for privacy-preserving inference.
### Logistic regression
```rust theme={null}
#[instruction]
pub fn predict_class(
features: Enc,
coefficients: Enc,
intercept: Enc,
) -> Enc {
let x = features.to_arcis();
let coef = coefficients.to_arcis();
let bias = intercept.to_arcis();
let model = LogisticRegression::new(&coef, bias);
let prediction = model.predict(&x, 0.5); // threshold = 0.5
features.owner.from_arcis(prediction)
}
```
### Linear regression
```rust theme={null}
#[instruction]
pub fn predict_value(
features: Enc,
coefficients: [f64; 4], // Plaintext model weights
intercept: f64,
) -> Enc {
let x = features.to_arcis();
let model = LinearRegression::new(&coefficients, intercept);
let prediction = model.predict(&x);
features.owner.from_arcis(prediction)
}
```
### Available ML functions
| Function | Description |
| ------------------------------------------- | -------------------------------- |
| `LogisticRegression::new(coef, intercept)` | Create logistic regression model |
| `LogisticRegression::predict(x, threshold)` | Binary classification |
| `LogisticRegression::predict_proba(x)` | Probability output |
| `LinearRegression::new(coef, intercept)` | Create linear regression model |
| `LinearRegression::predict(x)` | Continuous prediction |
| `ArcisMath::sigmoid(x)` | Sigmoid activation function |
| `logit(p)` | Inverse of sigmoid |
| `expit(x)` | Alias for sigmoid |
ML models support up to 100 features (`MAX_FEATURES = 100`). For larger models, consider feature selection or dimensionality reduction.
## Summary
| Primitive | Use Case | Key Methods |
| -------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `ArcisRNG` | Random values | `bool()`, `gen_integer_from_width()`, `gen_public_integer_from_width()`, `gen_integer_in_range()`, `gen_uniform()`, `shuffle()` |
| `SHA3_256/512` | Hashing | `new()`, `digest()` |
| `SecretKey` | Ed25519 keys | `new_rand()`, `from_bytes()` |
| `VerifyingKey` | Signature verification | `from_secret_key()`, `verify()` |
| `MXESigningKey` | Cluster signing | `sign()` |
| `ArcisX25519Pubkey` | Public keys | `from_base58()`, `from_uint8()`, `to_x()`, `new_from_x()` |
| `BaseField25519` | Field arithmetic | `from_*()`, `to_*_unchecked()`, `power_of_two()`, `to_le_bytes()`, `safe_inverse()`, `field_division()`, `euclidean_division()` |
| `Pack` | Efficient storage | `new()`, `unpack()` |
| `LogisticRegression` | Binary classification | `new()`, `predict()`, `predict_proba()` |
| `LinearRegression` | Regression | `new()`, `predict()` |
| `ArcisMath` | Math functions | `sigmoid()` |
## What's next?
Performance optimization, debugging, and testing strategies.
Full function and method reference.
# Quick reference
Source: https://docs.arcium.com/developers/arcis/quick-reference
A quick reference cheatsheet for Arcis syntax and patterns
Arcis is a Rust framework for writing MPC circuits on Solana. This page is a **quick reference**. For conceptual understanding, see [Thinking in MPC](/developers/arcis/mental-model).
**Use this page when** you need quick syntax lookup while coding.
## Quick reference: limitations
| Category | Supported | Not supported |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| **Control flow** | `if`, `if/else`, `else if`, `if let`, `match`, `for` loops | `while`, `loop`, `break`, `continue`, `let ... else`, early `return` |
| **Types** | Integers, floats, `BaseField25519`, arrays, tuples, structs, enums and `Option` (inside circuits, not encrypted instruction inputs/outputs) | `Vec`, `String`, `HashMap` |
| **Functions** | Helpers, closures, generics, traits | Recursion, async/await |
| **Operations** | Arithmetic, comparisons, right shift (const) | Left shift, right shift (variable) |
**Why?** MPC circuits must have fixed structure. See [Thinking in MPC](/developers/arcis/mental-model) for the full explanation.
## Basic structure
```rust theme={null}
use arcis::*;
#[encrypted]
mod my_circuit {
use arcis::*;
#[instruction]
pub fn add(a: u8, b: u8) -> u16 {
a as u16 + b as u16
}
}
```
* `#[encrypted]` marks modules containing MPC circuits
* `#[instruction]` marks entry points callable from Solana
## Working with encrypted data
```rust theme={null}
#[instruction]
pub fn process(input: Enc) -> Enc {
let value = input.to_arcis(); // Encrypted → secret shares
let result = value * 2 + 10; // Compute on shares
input.owner.from_arcis(result) // Secret shares → encrypted
}
```
| Owner | Who Can Decrypt |
| ---------------- | --------------- |
| `Enc` | Client AND MXE |
| `Enc` | MXE only |
## Types
```rust theme={null}
// Integers
let x: u8 = 255;
let y: i64 = -1000;
let z: u128 = 10000;
// Floats (emulated fixed-point)
let pi: f64 = 3.14159;
// Arrays (fixed-size only)
let arr: [u8; 10] = [0; 10];
// Tuples and structs
let pair: (u8, u16) = (1, 2);
#[derive(Copy, Clone)]
struct Point { x: i16, y: i16 }
```
See [Types](/developers/arcis/types) for complete reference.
## Control flow
```rust theme={null}
// if/else: when condition is not a compile-time constant, both branches execute
let result = if condition { a } else { b };
// if without else (for side effects)
if should_update {
counter += 1;
}
// else if chains work normally
let category = if value < 10 {
0
} else if value < 100 {
1
} else {
2
};
// for loops: fixed iteration count required
for i in 0..10 {
process(arr[i]);
}
// match expressions with literal, range, struct, tuple, array patterns and guards
let bucket = match x {
v if v < 5 => 0,
v if v < 10 => 1,
_ => 2,
};
// plain if let
if let Point { x: 0, y } = p {
y
} else {
-1
}
// if let combined with && requires edition = "2024" in encrypted-ixs/Cargo.toml
if let Point { x: 0, y } = p && y > 0 {
y
} else {
-1
}
// matches! macro
let in_range = matches!(x, 0..=9);
let is_small = matches!(x, 0 | 1 | 2);
```
## Functions
```rust theme={null}
// Helper function
fn helper(a: u8, b: u8) -> u16 {
a as u16 + b as u16
}
// Closures
let double = |x: u8| x * 2;
// Generics
fn set_zero(a: &mut T) {
*a = make_zero(*a);
}
```
See [Operations](/developers/arcis/operations#generics) for generics and traits.
## Arrays
```rust theme={null}
let arr: [u8; 10] = [0; 10];
// Constant index: O(1)
let x = arr[5];
// Secret index: O(n)
let y = arr[secret_idx];
// Methods
arr.swap(0, 1);
arr.reverse();
arr.fill(42);
arr.sort(); // O(n·log²(n)·bit_size)
```
## Iterators
```rust theme={null}
// Basic iteration
for val in arr.iter() {
sum += *val;
}
// Chaining
arr.iter().map(|x| *x * 2).sum()
```
See [Operations](/developers/arcis/operations#iterators) for complete iterator support. Note: `.filter()` is not supported.
## Encryption patterns
```rust theme={null}
// Shared: client + MXE can decrypt
fn process(input: Enc) -> Enc
// MXE-owned: only MXE can decrypt
fn process_state(state: Enc) -> Enc
// Reveal (use carefully)
let plain = secret.reveal();
// Create MXE-owned data
let mxe_data = Mxe::get().from_arcis(value);
// EncData output (smaller callback payload)
fn verify(a: Enc, observer: Shared) -> EncData {
observer.from_arcis(result).data
}
```
See [Input/Output](/developers/arcis/input-output) for details. For `EncData` usage, see [Types](/developers/arcis/types#advanced-encdata%3Ct%3E).
## Randomness
```rust theme={null}
let coin = ArcisRNG::bool();
let num = ArcisRNG::gen_integer_from_width(64);
let uniform = ArcisRNG::gen_uniform::<[u8; 32]>();
ArcisRNG::shuffle(&mut arr);
let (val, ok) = ArcisRNG::gen_integer_in_range(1, 100, 24);
```
See [Primitives](/developers/arcis/primitives#random-number-generation) for complete RNG reference.
## Cryptography
```rust theme={null}
// Hashing
let hash = SHA3_256::new().digest(&data).reveal();
// Signature verification
let valid = vk.verify(&message, &signature).reveal();
// Key generation
let sk = SecretKey::new_rand();
let vk = VerifyingKey::from_secret_key(&sk);
// MXE signing
let sig = MXESigningKey::sign(&message).reveal();
```
See [Primitives](/developers/arcis/primitives#cryptographic-operations) for complete crypto reference.
## Field arithmetic
```rust theme={null}
// Construct
let a = BaseField25519::from_u64(42);
let b = BaseField25519::power_of_two(8); // 256
// Arithmetic (mod 2^255 - 19)
let c = a + b;
let d = a * b;
let e = -a;
// Division (no / operator: use methods)
let inv = b.safe_inverse(); // 0 if b == 0
let quot = a.field_division(b); // 0 if b == 0
let euc = a.euclidean_division(b); // panics if b == 0
// Extract
let n: u64 = c.to_u64_unchecked(); // incorrect result if value exceeds u64 range
let bytes = c.to_le_bytes(); // [u8; 32]
```
See [Primitives](/developers/arcis/primitives#basefield25519-operations) for complete reference.
## Data packing
```rust theme={null}
// Pack for efficient storage
let packed = Pack::new(data);
// Unpack to use
let data: [u8; 64] = packed.unpack();
```
See [Primitives](/developers/arcis/primitives#data-packing) for details.
## Debugging
```rust theme={null}
println!("value = {}", x);
debug_assert!(x > 0, "x must be positive");
```
See [Best practices](/developers/arcis/best-practices#debugging) for debugging strategies.
## Testing
```rust theme={null}
#[cfg(test)]
mod tests {
#[test]
fn test_helper() {
// Only non-#[instruction] functions can be unit tested
assert_eq!(helper(1, 2), 3);
}
}
```
See [Best practices](/developers/arcis/best-practices#testing) for testing strategies.
## What's next?
Ready to build? Start here:
Build your first Arcis circuit step-by-step.
Invoke circuits from your Solana program.
Call circuits from TypeScript.
# Types
Source: https://docs.arcium.com/developers/arcis/types
Supported types in Arcis: integers, floats, arrays, structs, and encrypted types
The following types are supported:
* `u8`, `u16`, `u32`, `u64`, `u128`, `usize`, `i8`, `i16`, `i32`, `i64`, `i128`, `isize`
* `f64`, `f32` (emulated as fixed-point with 52 fractional bits; supported range is `[-2^75, 2^75)`. Values outside this range are unsupported.)
* tuples of supported types, including `()`
* fixed-length arrays of a supported type
* slices with compile-time known length (e.g., `&arr[..]` from fixed arrays, or `&[u8]` parameters in stdlib APIs like SHA3)
* compile-time known ranges
* (mutable) references to a supported type
* user-defined structs of supported types
* user-defined enums of supported types (but not as input or output of an encrypted instruction; explicit discriminants are unsupported)
* `Option` of a supported type (but not as input or output of an encrypted instruction)
* functions (but not as input or output of an encrypted instruction)
* `ArcisX25519Pubkey`, an Arcis public key wrapper.
* Arcis-defined `Enc`, `Mxe` and `Shared`.
* `Pack`, a wrapper for bit-packing data into fewer field elements for onchain storage.
* `EncData`, encrypted data without embedded cipher info. Use with `.to_arcis_with_pubkey_and_nonce()` when multiple values share the same key.
* `BaseField25519`, integers modulo `2^255 - 19`. The native field element for Arcis MPC circuits. Supports arithmetic (`+`, `-`, `*`, negation), unsigned comparisons, and field division methods. Construct via `BaseField25519::from_u64(x)` (and similar `from_*` methods); extract via `.to_u64_unchecked()` (only when the value fits the target type -- out-of-range values silently produce incorrect results). See [Primitives](/developers/arcis/primitives#basefield25519-operations).
**Float emulation**: Arcis emulates `f64`/`f32` as fixed-point with 52 fractional bits. This differs from IEEE 754 floats:
* Different precision characteristics than standard floats
* Supported range: `[-2^75, 2^75)`
* **Float literals outside this range produce a compile-time error:** `"Arcis only supports inputs in the range [-2**75, 2**75)"`
* **Computed values outside this range are silently clamped** to the boundary values
## Encryption types
| Type | Description |
| ---------------- | -------------------------------------------- |
| `Enc` | Encrypted data shared between client and MXE |
| `Enc` | Encrypted data for MXE only |
| `EncData` | Raw encrypted data (advanced use, see below) |
| `Shared` | Owner type for client-shared encryption |
| `Mxe` | Owner type for MXE-only encryption |
The `Owner` type parameter determines who can decrypt:
* **`Shared`**: Both client and MXE can decrypt. Use for user inputs/outputs that need client-side verification.
* **`Mxe`**: Only the MXE cluster can decrypt. Use for internal protocol state that users should not access.
## Public key types
| Type | Description |
| --------------------------- | -------------------------------------------- |
| `ArcisX25519Pubkey` | Arcis X25519 public key wrapper |
| `SolanaPublicKey` | Solana public key (32 bytes) |
| `SerializedSolanaPublicKey` | Serialized form using `{lo: u128, hi: u128}` |
## Example
```rust theme={null}
use arcis::*;
#[encrypted]
mod types_example {
use arcis::*;
#[derive(Copy, Clone)]
struct GameState {
score: u64,
level: u8,
}
#[instruction]
fn example(
user_data: Enc,
state: Enc,
) -> Enc {
let value = user_data.to_arcis();
user_data.owner.from_arcis(value * 2)
}
}
```
## Advanced: EncData\
`EncData` stores just the encrypted ciphertext without encryption metadata (pubkey + nonce).
| Type | Contains | Size |
| ------------------- | --------------------------------------------- | ---------- |
| `Enc` | pubkey (32B) + nonce (16B) + ciphertext (32B) | \~80 bytes |
| `EncData` | ciphertext only | \~32 bytes |
`EncData` omits pubkey and nonce metadata, useful for multiple outputs where callback payload size matters.
### Primary use: smaller callback payloads
Use `EncData` when returning encrypted data to observers to reduce callback payload size:
```rust theme={null}
#[instruction]
pub fn check_solana_public_key_equality(
encrypted_pk1: Enc,
encrypted_pk2: Enc,
observer: Shared,
) -> EncData {
let pk1 = SolanaPublicKey::from_serialized(encrypted_pk1.to_arcis());
let pk2 = SolanaPublicKey::from_serialized(encrypted_pk2.to_arcis());
let res = pk1 == pk2;
observer.from_arcis(res).data // Extract .data from Enc
}
```
**When to use `EncData` output:** Multiple return values where encryption metadata would be redundant.
When returned from circuits, `EncData` generates `EncDataStruct` in your Solana program, where N is the number of field elements in T (e.g., `EncData` → `EncDataStruct<1>`). See [Callback type generation](/developers/program/callback-type-generation).
The MXE encrypts outputs with `nonce + 1`. See [Encryption overview](/developers/encryption) for nonce handling.
### Secondary use: shared key input optimization
When multiple inputs share the same encryption key, `Enc` duplicates the key-derivation circuit for each input. Use `EncData` with explicit key/nonce to avoid this:
```rust theme={null}
#[instruction]
fn optimized_sum(
key: ArcisX25519Pubkey,
t_nonce: u128, t: EncData,
u_nonce: u128, u: EncData,
) -> u64 {
let t_val = t.to_arcis_with_pubkey_and_nonce(key, t_nonce);
let u_val = u.to_arcis_with_pubkey_and_nonce(key, u_nonce);
(t_val + u_val).reveal()
}
```
`EncData` is an advanced optimization with security implications:
* **Nonce uniqueness:** Each (key, nonce) pair must be unique. Reusing nonces compromises security.
* **Silent failures:** Using the wrong key or nonce produces garbage data without error; MPC cannot add runtime validation since that would leak information.
For most use cases, use `Enc` or `Enc`, which handle key management automatically.
## Unsupported types
Arcis does not currently support `HashMap`, `Vec`, or `String` because these types have variable length. Constant-size byte strings, such as `b"hello_world"`, are supported.
The `Enc` type defines encrypted data input as `Enc`, where `Owner` can be either `Mxe` or `Shared`. `Owner` determines which party can decrypt data of type `T`. See [Input/output](/developers/arcis/input-output) for encrypted input and output patterns.
**Storage representation:** All values are stored as 256-bit Curve25519 field elements. A `u8` uses the same storage as a `u128`; integer type bounds are enforced at compile time, not by storage size. Use `Pack` to compress multiple small values into fewer field elements for onchain efficiency.
## What's next?
Working with `Enc` for encrypted inputs and outputs.
MPC constraints and the operation cost model.
# Arcium.toml
Source: https://docs.arcium.com/developers/arcium-toml
Configuration reference for the Arcium CLI tooling suite
`Arcium.toml` is auto-generated by `arcium init` and configures the Arcium CLI tooling. It lives at the root of your project alongside `Anchor.toml`.
Most projects only need to edit this file when changing localnet behavior or adding a cluster offset for devnet or mainnet testing.
## Localnet configuration
The `[localnet]` section controls your local development cluster. All required fields are auto-generated by `arcium init` with sensible defaults.
| Field | Type | Required | Default | Description |
| ----------------------- | ------------------ | -------- | ------------------------------ | ------------------------------------------ |
| `nodes` | integer | Yes | `2` | Number of MPC nodes. Minimum: 2 |
| `nodes_ips` | array of `[u8; 4]` | No | Sequential from `172.20.0.100` | IPv4 addresses for each node |
| `localnet_timeout_secs` | integer | Yes | `60` | Seconds to wait for localnet startup |
| `backends` | array of string | No | `["Cerberus"]` | MPC backends (only `"Cerberus"` supported) |
Use the default values unless you need to test against a larger local cluster or a custom Docker network setup.
## Cluster configuration
The `[clusters.]` sections map network names to cluster offsets. These entries are used by `arcium test --cluster ` to resolve the cluster offset for testing.
| Field | Type | Required | Description |
| -------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `offset` | integer | Yes | Cluster offset for PDA derivation. See [Deployment - Cluster offsets](/developers/deployment#understanding-cluster-offsets) for current values. |
The CLI reads these entries when you run commands such as:
```bash theme={null}
arcium test --cluster devnet
```
For local tests without a `--cluster` argument, the CLI uses the `[localnet]` configuration.
## Full example
```toml theme={null}
[localnet]
nodes = 2
nodes_ips = [
[172, 20, 0, 100],
[172, 20, 0, 101]
]
localnet_timeout_secs = 60
backends = ["Cerberus"]
[clusters.devnet]
offset = 456
[clusters.mainnet]
offset = 2026
```
## Common mistakes
| Issue | Fix |
| ---------------------------------------------------- | -------------------------------------------------------------------- |
| `arcium test --cluster ` cannot find a cluster | Add `[clusters.]` with an `offset` field |
| Tests derive the wrong cluster account | Check that the offset matches the target network |
| Localnet startup times out | Increase `localnet_timeout_secs` or reduce local resource contention |
| Custom node IPs do not work | Keep `nodes` and `nodes_ips` lengths aligned |
# Computation lifecycle
Source: https://docs.arcium.com/developers/computation-lifecycle
How computations flow from client-side encryption through MPC execution to callback
Before diving into the details of the tooling, it's useful to understand the general architecture of Arcium. The below diagram gives a high-level overview of the lifecycle of a typical interaction with Arcium (we call these "computations").
```mermaid theme={null}
sequenceDiagram
participant Client
participant MXE Program
participant Arcium Program
participant MPC Cluster
Client->>Client: Encrypt params
Client->>MXE Program: Invoke computation with encrypted params
MXE Program->>Arcium Program: Handle & format params and send to Arcium Program
Arcium Program->>Arcium Program: Queue Computation in Cluster's Mempool
MPC Cluster->>Arcium Program: Fetch new computation from mempool
MPC Cluster->>MPC Cluster: Compute using MPC
MPC Cluster->>Arcium Program: Callback with Result
Arcium Program->>Arcium Program: Verify Result
Arcium Program->>MXE Program: Invoke callback instruction with result
MXE Program->>MXE Program: Handle Result
MXE Program->>Client: Notify of completion
```
We have 4 key actors here:
* The client: The party that wants to perform a computation, usually the user of your MXE. This is implemented using the [Arcium TypeScript client library](/developers/js-client-library).
* The MXE Program: Your app. An MXE (MPC eXecution Environment) consists of everything needed to perform computations and is implemented using the [Arcium program tooling](/developers/program):
* A smart contract that is deployed on the blockchain and is used to format and submit computations to Arcium.
* A set of encrypted instructions (we call these "computation definitions") that are used to define what parameters are needed for the computation and what the computation is. Writing these is done using [Arcis](/developers/arcis).
* Some metadata about the MXE, most importantly the MPC cluster we would like to use to compute our computations.
* The Arcium Program: The program in charge of assigning, scheduling, and verifying computations for the various MPC clusters to perform.
* The MPC Cluster: The parties that are performing the client's computations using MPC.
The MXE and its computation definitions are onchain accounts with their own lifecycle. When you no longer need them, you can close them and reclaim rent. See [Account lifecycle and closing](/developers/program/account-lifecycle).
## What's next?
Learn the Arcis framework for encrypted business logic.
How X25519 key exchange and Rescue cipher protect your data.
Queue computations from your Solana program.
# Core concepts
Source: https://docs.arcium.com/developers/core-concepts
Key terminology for Arcium development: MXE, Clusters, Encrypted Instructions, Computation Definitions, offsets, and how they connect
Key terms you'll encounter throughout Arcium development.
Your complete encrypted application composed of three parts:
* A **Solana program** that receives inputs and queues computations
* **Encrypted instructions** with encrypted logic written in Arcis
* An **MXE account** storing onchain metadata like cluster selection and the MXE's public key
When no longer needed, MXE accounts can be closed through the [account lifecycle](/developers/program/account-lifecycle).
A group of Multi-Party Computation (MPC) nodes that execute your encrypted instructions. You choose
which cluster when deploying your MXE. The selection is made at deployment time
but can be changed later via [`arcium migrate-cluster`](/developers/deployment#cluster-migration).
Each cluster has a numeric `cluster_offset` identifier (e.g., `456` on devnet)
that you specify during [deployment](/developers/deployment).
Your encrypted business logic written in **Arcis** (Rust framework) that
executes on encrypted data via MPC. Mark functions with the `#[instruction]`
macro to designate them as encrypted instructions. Examples: vote counting,
order matching, sealed-bid auctions.
The onchain coordinator on Solana that orchestrates MPC computations, routes
work to clusters, and manages callbacks and finalization. You interact with it
via **CPI** (Cross-Program Invocation) from your Solana program.
An onchain account storing compiled MPC bytecode. Created once per
encrypted instruction, it contains the compiled circuit from your Arcis
code and must be initialized via `init_computation_def()` before first use.
When no longer needed, a computation definition has its own [close lifecycle](/developers/program/account-lifecycle).
A single execution instance of an encrypted instruction. You generate a random
`computation_offset` (u64) per invocation to uniquely identify it. The computation
tracks execution status and stores encrypted results until callback.
An MPC execution node that participates in cluster computations. Each node
holds secret shares and collaborates via the MPC protocol. Clusters contain
multiple Arx nodes. The dishonest majority model means privacy holds even
if all but one node are compromised.
```mermaid theme={null}
graph TD
A[MXE Program] -->|queues computation| B[Arcium Program]
A -->|references| C[MXE Account]
B -->|routes to| D[Cluster]
D -->|executes| E[Encrypted Instructions]
D -->|returns result via callback| A
```
## Offset identifiers
Arcium uses numeric offsets to identify onchain resources. You'll encounter these throughout the SDK:
| Offset | Type | Purpose | How it's set |
| -------------------- | ----- | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `cluster_offset` | `u32` | Identifies which cluster your MXE connects to | Chosen at [deployment](/developers/deployment) (e.g., `456` on devnet) |
| `computation_offset` | `u64` | Uniquely identifies a single computation invocation | Generated randomly per invocation (`randomBytes(8)`) |
| `comp_def_offset` | `u32` | Identifies a computation definition (circuit) within your MXE | Via `comp_def_offset("instruction_name")`, which computes `sha256(name)` truncated to LE u32 |
## What's next?
Understand how computations flow from queue to callback
Learn Arcis and write encrypted business logic
# Deployment
Source: https://docs.arcium.com/developers/deployment
Deploy your MXE to Solana with cluster configuration and RPC setup
## Getting started with deployment
Deploy after `arcium build` and `arcium test` pass locally. This guide covers program preparation, cluster offsets, RPC setup, migration, and closing accounts.
This guide walks you through deploying to **devnet** first. Once you've validated your MXE on devnet, see [Deploying to Mainnet](#deploying-to-mainnet) for mainnet-specific configuration.
## What you'll need
Before deploying, make sure you have:
* Your MXE built successfully with `arcium build`
* Tests passing locally with `arcium test`
* A Solana keypair with around 2-5 SOL for deployment costs (program deployment and account initialization)
* Access to a reliable RPC endpoint
**RPC reliability is critical for deployment.** Default Solana RPC endpoints can be unreliable and drop transactions, causing deployment failures. Get an API key from [Helius](https://helius.dev), [Triton](https://triton.one), or [QuickNode](https://quicknode.com) before attempting deployment.
## Preparing your program
Before deploying, decide how your program stores computation definitions.
### Handling large circuits with offchain storage
Arcis compiled circuits can be several MB. Uploading the full bytecode onchain can require many transactions and high rent.
For larger circuits, upload the compiled `.arcis` file to public storage and initialize the computation definition with its URL and hash.
**Standard approach (works for small circuits):**
```rust theme={null}
pub fn init_add_together_comp_def(ctx: Context) -> Result<()> {
// This initializes the computation definition account
init_computation_def(ctx.accounts, None)?;
Ok(())
}
```
**Offchain approach (recommended for larger circuits):**
```rust theme={null}
// First, import the types you'll need
use arcium_client::idl::arcium::types::{CircuitSource, OffChainCircuitSource};
use arcium_macros::circuit_hash;
pub fn init_add_together_comp_def(ctx: Context) -> Result<()> {
// Point to your uploaded circuit file
init_computation_def(
ctx.accounts,
Some(CircuitSource::OffChain(OffChainCircuitSource {
source: "https://your-storage.com/path/to/add_together.arcis".to_string(),
hash: circuit_hash!("add_together"),
})),
)?;
Ok(())
}
```
The `circuit_hash!` macro embeds the SHA-256 hash of your compiled circuit at compile time. The hash is read from `build/{circuit_name}.hash`, which is generated automatically during `arcium build`. Arx nodes verify this hash when fetching your circuit to ensure the circuit hasn't been tampered with.
**Important:** Always use `circuit_hash!` for offchain circuits. Don't use a placeholder like `[0u8; 32]` - this will cause verification to fail on Arx nodes.
Offchain circuit flow:
1. Build your project with `arcium build` to generate the circuit files and hashes
2. Upload the `.arcis` files from `build/` folder to your preferred storage service
3. Update your init functions with the public URLs and `circuit_hash!` macro calls
Circuit files must be publicly readable without authentication.
### Note on cluster configuration
When testing locally, you've been using `arciumEnv.arciumClusterOffset` with `getClusterAccAddress()` in your test code. For devnet deployment, you'll use the same pattern with your chosen cluster offset - we'll show you exactly how in the post-deployment section.
## Basic deployment
The `arcium deploy` command handles both deploying your program and initializing the MXE account. Here's the basic command structure:
```bash theme={null}
arcium deploy --cluster-offset --recovery-set-size --keypair-path --rpc-url
```
Let's break down what each parameter does:
### Understanding cluster offsets
The `--cluster-offset` tells your MXE which Arcium cluster it should connect to. Think of clusters as groups of nodes that will perform your encrypted computations. Available offsets:
**Devnet cluster:**
* `456`
**Mainnet cluster:**
* `2026`
### Recovery set size
The `--recovery-set-size` parameter specifies how many nodes form the recovery set that holds encrypted key shares of your MXE's key. This enables key reconstruction when needed, whether due to node failure or cluster migration. This is required. The absolute minimum is `4`; larger clusters may require a larger recovery set, and the CLI prints the required value if the one you pass is too small.
### Choosing your RPC provider
Always pass `--rpc-url `. Solana's default RPC endpoints (`-u d`, `-u m`) can drop transactions during deployment.
**Recommended approach with a reliable RPC:**
```bash theme={null}
arcium deploy --cluster-offset 456 \
--recovery-set-size 4 \
--keypair-path ~/.config/solana/id.json \
--rpc-url
```
**If you must use the default RPC:**
```bash theme={null}
arcium deploy --cluster-offset 456 \
--recovery-set-size 4 \
--keypair-path ~/.config/solana/id.json \
-u d # 'd' for devnet, 'm' for mainnet, 'l' for localnet
```
Just be prepared for potential transaction failures with the default RPC.
## Advanced deployment options
Once you're comfortable with basic deployment, you might want to customize things further.
### Using a custom program address
If you need your program at a specific address (maybe for consistency across deployments), you can provide a program keypair:
```bash theme={null}
arcium deploy --cluster-offset 456 \
--recovery-set-size 4 \
--keypair-path ~/.config/solana/id.json \
--rpc-url \
--program-keypair ./program-keypair.json
```
### Partial deployments
Sometimes you might need to run just part of the deployment process. For instance, if you've already deployed the program but need to reinitialize the MXE account:
```bash theme={null}
# Skip program deployment, only initialize MXE account
arcium deploy --cluster-offset 456 \
--recovery-set-size 4 \
--keypair-path ~/.config/solana/id.json \
--rpc-url \
--skip-deploy
```
Or if you only want to deploy the program without initialization:
```bash theme={null}
# Deploy program only, skip MXE initialization
arcium deploy --cluster-offset 456 \
--recovery-set-size 4 \
--keypair-path ~/.config/solana/id.json \
--rpc-url \
--skip-init
```
## After deployment
### Initialize your computation definitions
Your MXE is deployed, but you still need to initialize the computation definitions. This tells the Arcium Network what encrypted operations your MXE can perform. Computation definitions only need to be initialized once - they persist onchain and don't need to be re-initialized unless you're deploying to a new program address. You can initialize them anytime after deployment completes successfully.
Remember how we mentioned you'd need to update your cluster configuration? Now's the time! You'll need to update your test or client code to derive the cluster account (and the related PDAs) from the cluster offset you selected during deployment.
**Local testing pattern:**
```typescript theme={null}
const arciumEnv = getArciumEnv();
// In your transaction...
.accountsPartial({
computationAccount: getComputationAccAddress(
arciumEnv.arciumClusterOffset,
computationOffset
),
clusterAccount: getClusterAccAddress(arciumEnv.arciumClusterOffset),
mxeAccount: getMXEAccAddress(program.programId),
mempoolAccount: getMempoolAccAddress(arciumEnv.arciumClusterOffset),
executingPool: getExecutingPoolAccAddress(arciumEnv.arciumClusterOffset),
// ... other accounts
})
```
**For devnet deployment:**
```typescript theme={null}
// Use the cluster offset from your deployment (e.g., 456)
const clusterOffset = 456;
// In your transaction...
.accountsPartial({
computationAccount: getComputationAccAddress(clusterOffset, computationOffset),
clusterAccount: getClusterAccAddress(clusterOffset),
mxeAccount: getMXEAccAddress(program.programId),
mempoolAccount: getMempoolAccAddress(clusterOffset),
executingPool: getExecutingPoolAccAddress(clusterOffset),
// ... other accounts
})
```
Make sure to use the same `cluster_offset` value that you used during deployment! This ensures your program talks to the right cluster.
Once you've updated the cluster configuration, you can run the initialization:
```typescript theme={null}
// Now with the correct cluster configured
await initAddTogetherCompDef(program, owner);
```
### Verify everything's working
Let's make sure your deployment succeeded:
```bash theme={null}
solana program show --url
```
To run your tests against the deployed program, configure the cluster offset in your `Arcium.toml`:
```toml theme={null}
[clusters.devnet]
offset =
```
Then run:
```bash theme={null}
arcium test --cluster devnet
```
The CLI reads the cluster offset from `Arcium.toml` and configures the test environment automatically. For RPC configuration, set the cluster and wallet in your `Anchor.toml`:
```toml theme={null}
[provider]
cluster = "devnet"
wallet = "~/.config/solana/id.json"
```
## Deploying to mainnet
Once you've validated your MXE on devnet, you can deploy to Arcium mainnet. The deployment process is the same, with a few key differences:
### Mainnet cluster offset
Use the mainnet cluster offset from the [Understanding Cluster Offsets](#understanding-cluster-offsets) section:
```bash theme={null}
arcium deploy --cluster-offset \
--recovery-set-size 4 \
--keypair-path ~/.config/solana/id.json \
--rpc-url
```
### Mainnet configuration
Configure the mainnet cluster offset in your `Arcium.toml` (see [Understanding Cluster Offsets](#understanding-cluster-offsets) for the current value):
```toml theme={null}
[clusters.mainnet]
offset =
```
To test against your mainnet deployment:
```bash theme={null}
arcium test --cluster mainnet
```
Mainnet deployments require real SOL. There are no airdrops. Ensure your wallet is funded before deploying.
## Common issues and solutions
The examples below use devnet. For mainnet, replace `-u d` with `-u m` and use a mainnet RPC URL.
### Dealing with dropped transactions
If your deployment fails with transaction errors, it's almost always the RPC. Switch to a dedicated provider:
```bash theme={null}
# Instead of this (unreliable):
arcium deploy --cluster-offset 456 \
--recovery-set-size \
--keypair-path ~/.config/solana/id.json \
-u d
# Use this (reliable):
arcium deploy --cluster-offset 456 \
--recovery-set-size \
--keypair-path ~/.config/solana/id.json \
--rpc-url
```
### Running out of SOL
Check your balance before deploying:
```bash theme={null}
solana balance -u devnet
```
If you need more SOL on devnet, request it via airdrop:
```bash theme={null}
solana airdrop 2 -u devnet
```
### Deployment partially failed?
If your deployment was interrupted (e.g., due to a dropped transaction or network issue), use the `--resume` flag to pick up where it left off:
```bash theme={null}
arcium deploy --cluster-offset 456 \
--recovery-set-size 4 \
--keypair-path ~/.config/solana/id.json \
--rpc-url \
--resume
```
This skips already-completed steps and retries from the point of failure. The `--resume` flag is also available on `init-mxe` and `migrate-cluster` for the same purpose. For finer control, you can also skip specific phases: `--skip-deploy` (skip program deployment, only initialize MXE) or `--skip-init` (deploy program only, skip MXE initialization).
## Managing your MXE
### Cluster migration
If you need to move your MXE from one cluster to another, use the `migrate-cluster` command:
```bash theme={null}
arcium migrate-cluster \
--keypair-path \
--cluster-offset \
--rpc-url
```
This reconstructs your MXE's keys on the new cluster using the recovery set configured during deployment. Your MXE will be temporarily unavailable during migration.
MXEs deployed with v0.9.x already have the recovery material needed for `migrate-cluster`; no separate remediation command is required before migrating them.
If a migration is interrupted, resume it with `--resume`:
```bash theme={null}
arcium migrate-cluster \
--keypair-path \
--rpc-url \
--resume
```
To abort an in-progress migration:
```bash theme={null}
arcium migrate-cluster \
--keypair-path \
--rpc-url \
--abort
```
With `--resume`/`--abort` the CLI infers the cluster offset from onchain state; `--cluster-offset` is required only on the initial call.
Do not abort and then re-initialize a migration back to the same target cluster. That opens a denial-of-service surface. If you need to retry, resume instead.
### Closing an MXE or computation definition
When you no longer need an MXE or one of its computation definitions, close the accounts to reclaim rent. Comp defs close in two steps: deactivate, wait 180 slots, then close. MXEs can close only after all user-defined comp defs are closed.
```bash theme={null}
# Comp def: deactivate, wait for TTL, then close
arcium deactivate-computation-definition \
-o \
-p \
-k \
--rpc-url
arcium close-computation-definition \
-o \
-p \
-c \
-k \
--rpc-url
# OnChain circuits also need their raw buffers closed (one call per index)
arcium close-computation-definition-buffers \
-o \
-p \
-i \
-k \
--rpc-url
# MXE
arcium close-mxe \
-p \
-k \
--rpc-url
```
Pass an explicit `--rpc-url`: the CLI defaults to mainnet without it.
For the full state machine, authority rules, error codes, and common mistakes, see [Account lifecycle and closing](/developers/program/account-lifecycle).
## What's next?
After deployment, update your client code to use the correct cluster offset, initialize your computation definitions onchain, and run end-to-end tests on devnet.
Learn patterns and optimizations for efficient encrypted instructions.
Explore complete example projects and reference implementations.
For questions or issues, reach out on [Discord](https://discord.gg/arcium).
# Encryption overview
Source: https://docs.arcium.com/developers/encryption
How encryption works in Arcium: X25519 key exchange, Rescue cipher, and data protection
Encrypted data is passed as an `Enc` generic type. `Owner` specifies who can decrypt the data (`Shared` or `Mxe`), and `T` is the encrypted data type. With `Mxe`, the nodes can collectively decrypt the data under dishonest-majority assumptions. With `Shared`, the data is encrypted with a shared secret between the client and the MXE. The wrapper contains the ciphertext, the nonce, and, for `Shared` ownership, the public key used to encrypt the data.
Encrypted data can be decrypted globally or selectively to a specific user. For global decryption, call the `reveal` method on any variable with a [supported data type](/developers/arcis/types). For selective decryption, use [sealing](/developers/encryption/sealing), which re-encrypts data for a chosen recipient.
Private inputs are encrypted using the arithmetization-oriented symmetric [Rescue cipher](https://eprint.iacr.org/2019/426). Before the cipher runs, the client and cluster perform an [X25519](https://www.rfc-editor.org/rfc/rfc7748.html#page-7) elliptic curve Diffie-Hellman key exchange to derive a shared secret. The Rescue key is derived by hashing the shared secret with the [Rescue-Prime](https://eprint.iacr.org/2020/1143.pdf) hash function, as described in [Section 4](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Cr2.pdf), Option 1. This increases the min-entropy of the key.\
Note:
1. Since the X25519 key exchange natively returns shared secrets in the finite field with $p = 2^{255} - 19$ elements, we implemented Rescue over the field $\mathbb{F}_{p}$. States in the context of Rescue are elements of the $m$-dimensional vector space $\mathbb{F}_p^m$, i.e., the Rescue cipher transforms vectors of size $m$ to vectors of the same size.
2. The security level $s$ of the cipher is set to 128 bits.
3. We use the Rescue block cipher in [Counter (CTR) mode](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf) (see Section 6.5), with fixed $m = 5$. The choice $m = 5$ is motivated by the fact that it is the smallest value that attains the minimum of recommended rounds (10), given the fixed finite field and security level. The counters have the form `[nonce, i, 0, 0, 0]`, where `nonce` is a 16-byte random value provided by the user.
4. The hash function used for key derivation is Rescue-Prime over $\mathbb{F}_{2^{255}-19}$, with `rate = 7` and `capacity = 5` (i.e., $m = 12$) and output truncated to 5 field elements. The target security level $s$ is set to 256. According to [Section 2.2](https://eprint.iacr.org/2020/1143.pdf), this offers 256 bits of security against collision, preimage and second-preimage attacks for any field of size at least 102 bits.
The decryption of `input_enc: Enc` can conveniently be obtained by calling `input_enc.to_arcis()` (the nodes do not learn `input`; they convert the ciphertext to secret-shares of `input` by running the Rescue decryption circuit in MPC). If the owner is `Shared`, the MXE and the client perform a key exchange first. Similarly, `owner.from_arcis(output)` encrypts the secret-shared `output` by running the Rescue encryption circuit in MPC.\
Note:
1. After decrypting the user-provided inputs, the MXE increments the `nonce` by 1 and uses it for encrypting the outputs. For the forthcoming interaction with the MXE, a new `nonce` must be provided.
2. The performance will benefit from reducing the number of calls to `owner.from_arcis(..)` (per owner). Ideally, put all data encrypted to `owner` in one struct.
[`EncData`](/developers/arcis/types#advanced-encdata%3Ct%3E) contains only ciphertext, omitting pubkey and nonce. Use for multiple outputs to reduce callback payload size.
## What's next?
Implement encryption in your TypeScript client.
Re-encrypt data between different parties.
# Sealing (re-encryption)
Source: https://docs.arcium.com/developers/encryption/sealing
Re-encrypt data between parties using sealing patterns
Suppose you're Alice and have secret data onchain that you want to share with Bob. You may also want to compute a function on sensitive data and share the result with Bob without revealing the data or result to anyone else.
Arcium can re-encrypt any data to a given public key. This is known as "sealing" in cryptography: restricting data access to specific recipients.
This is useful for a variety of reasons, such as compliance, end-to-end privacy, and more.
```rust theme={null}
#[encrypted]
mod circuits {
use arcis::*;
#[instruction]
pub fn verify_loan_eligibility(
alice_balance: Enc,
min_balance_required: Enc,
loan_officer: Shared
) -> Enc {
let balance = alice_balance.to_arcis();
let threshold = min_balance_required.to_arcis();
// Check if Alice meets minimum balance for loan without revealing her exact balance
let is_eligible = balance >= threshold;
// Re-encrypt the result for the loan officer
loan_officer.from_arcis(is_eligible)
}
}
```
In this example, we have an encrypted function `verify_loan_eligibility` that takes Alice's encrypted balance (encrypted with a shared secret between Alice and the MXE), the minimum balance requirement (encrypted only for the MXE), and a `Shared` type parameter representing the loan officer who will receive the result.
The function checks if Alice meets the minimum balance requirement for loan eligibility without revealing her actual balance to anyone. The boolean result is then re-encrypted specifically for the loan officer using their public key. This way, Alice's financial privacy is preserved: the loan officer only learns whether she's eligible, not her actual balance, and Arx nodes never see plaintext, only secret shares.
## What's next?
Learn how to encrypt data before sending to the MXE.
Call encrypted instructions from your Solana program.
# Hello World with Arcium
Source: https://docs.arcium.com/developers/hello-world
Build your first encrypted circuit with Arcis (Rust), a Solana program, and TypeScript tests
## Hello World
The Arcium tooling suite for writing MXEs (MPC eXecution Environments) is built on top of [Anchor](https://www.anchor-lang.com/), so if you're familiar with Anchor, you should find Arcium to be a familiar experience, except that you're using the `arcium` CLI instead of `anchor`.
To initialize a new MXE project, run:
```bash theme={null}
arcium init
```
This will create a new project with the given name and initialize it with a basic structure. The structure is the same as in an Anchor project with two differences, so it is not repeated here (for an explanation of the Anchor project structure, see the [Anchor documentation](https://www.anchor-lang.com/docs/quickstart/local)). The two differences are:
* The `Arcium.toml` file, which contains the configuration for the Arcium tooling suite.
* The `encrypted-ixs` directory. This is where we write all our code that is meant to operate on encrypted data and therefore runs in MPC. This code is written using our own Rust framework called [Arcis](/developers/arcis). This will already be populated with a simple example called `add_together.rs`. Let's take a closer look at it.
### Our first encrypted instruction
```rust theme={null}
use arcis::*;
#[encrypted]
mod circuits {
use arcis::*;
pub struct InputValues {
v1: u8,
v2: u8,
}
#[instruction]
pub fn add_together(input_ctxt: Enc) -> Enc {
let input = input_ctxt.to_arcis();
let sum = input.v1 as u16 + input.v2 as u16;
input_ctxt.owner.from_arcis(sum)
}
}
```
Let's go through it line by line. `use arcis::*;` imports all the necessary types and functions for writing encrypted instructions with Arcis. The `#[encrypted]` attribute marks a module that contains encrypted instructions. Inside this module, we define a struct `InputValues` that contains the two values we want to encrypt and pass to the encrypted instruction.
The `#[instruction]` macro marks the function as an entry point for MPC execution. While you can write helper functions without this attribute, only functions marked with `#[instruction]` will be compiled into individual circuits that can be called onchain.
The function `add_together` takes an encrypted input parameter of type `Enc`. Let's break this down:
* `Enc` is Arcium's encrypted data type
* `Shared` means the data is encrypted with a shared secret between the client and MXE (both can decrypt it)
* `InputValues` is the actual data structure being encrypted (our struct with v1 and v2)
* The alternative to `Shared` is `Mxe`, where only the MXE can decrypt the data
Inside the function:
1. `input_ctxt.to_arcis()` converts the input into a form we can operate on within the MPC environment.
2. We perform the addition operation, casting the u8 values to u16 to prevent overflow.
3. `input_ctxt.owner.from_arcis(sum)` converts the encrypted sum into an encrypted format that can be stored onchain, while maintaining encryption with the shared secret between the client and the MXE.
### Calling it from Solana
Now that we've written our first encrypted instruction, let's see how we can use it from within a Solana program. Our default project already contains a Solana program in the `programs/` directory. Let's take a closer look at it too:
```rust theme={null}
use anchor_lang::prelude::*;
use arcium_anchor::prelude::*;
// This constant identifies our encrypted instruction for onchain operations
// comp_def_offset() generates a unique identifier from the function name
const COMP_DEF_OFFSET_ADD_TOGETHER: u32 = comp_def_offset("add_together");
declare_id!("YOUR_PROGRAM_ID_HERE");
#[arcium_program]
pub mod hello_world {
use super::*;
pub fn init_add_together_comp_def(ctx: Context) -> Result<()> {
init_computation_def(ctx.accounts, None)?;
Ok(())
}
pub fn add_together(
ctx: Context,
computation_offset: u64,
ciphertext_0: [u8; 32],
ciphertext_1: [u8; 32],
pub_key: [u8; 32],
nonce: u128,
) -> Result<()> {
let args = ArgBuilder::new()
.x25519_pubkey(pub_key)
.plaintext_u128(nonce)
.encrypted_u8(ciphertext_0)
.encrypted_u8(ciphertext_1)
.build();
ctx.accounts.sign_pda_account.bump = ctx.bumps.sign_pda_account;
queue_computation(
ctx.accounts,
computation_offset,
args,
vec![AddTogetherCallback::callback_ix(
computation_offset,
&ctx.accounts.mxe_account,
&[]
)?],
1,
0, // cu_price_micro: priority fee in microlamports
0, // callback_cu_limit: compute unit limit for the callback (0 = default)
)?;
Ok(())
}
#[arcium_callback(encrypted_ix = "add_together")]
pub fn add_together_callback(
ctx: Context,
output: SignedComputationOutputs,
) -> Result<()> {
let o = match output.verify_output(
&ctx.accounts.cluster_account,
&ctx.accounts.computation_account
) {
Ok(AddTogetherOutput { field_0 }) => field_0,
Err(e) => {
msg!("Error: {}", e);
return Err(ErrorCode::AbortedComputation.into())
},
};
emit!(SumEvent {
sum: o.ciphertexts[0],
nonce: o.nonce.to_le_bytes(),
});
Ok(())
}
}
```
For brevity, the `InitAddTogetherCompDef`, `AddTogether`, and `AddTogetherCallback` account structs are not included here, but they are automatically generated when you run `arcium init`. Here's a simplified version of what `AddTogether` looks like:
```rust theme={null}
#[derive(Accounts)]
#[instruction(computation_offset: u64)]
pub struct AddTogether<'info> {
#[account(mut)]
pub payer: Signer<'info>,
// ... other required Arcium accounts (see program/ section for full details)
}
```
For the full Solana program flow, see [Invoking computations from your Solana program](/developers/program).
The key things to note here are that every MXE program is identified by the `#[arcium_program]` macro (which replaces Anchor's `#[program]` macro) and that for every encrypted instruction, we generally have three instructions in the Solana program:
* `init_add_together_comp_def`: Initializes the computation definition. This runs once before the first invocation of the encrypted instruction. See [Computation definition accounts](/developers/program/computation-def-accs).
* `add_together`: Invokes the encrypted instruction, builds the arguments, and queues the computation through the Arcium program. See [Invoking computations](/developers/program).
* `add_together_callback`: Runs after the MPC cluster finishes the encrypted instruction and returns the result. See [Invoking computations](/developers/program).
These three instructions mirror the standard [computation lifecycle](/developers/computation-lifecycle).
## Building and testing
Similar to Anchor, encrypted instructions and Solana programs are built with `arcium build`. Tests use the [`@arcium-hq/client` TypeScript library](/developers/js-client-library) by default and run with `arcium test`. Install npm dependencies first with `yarn` or `npm install` in your project directory. By default, tests run against a local cluster. To test against devnet or mainnet, use `arcium test --cluster devnet` after adding the cluster offset to `Arcium.toml`, for example `[clusters.devnet] offset = 456`.
For faster local loops, use `arcium test --test-name ` to run `tests/.ts`. After a full localnet keygen, `arcium test --skip-keygen` or `arcium localnet --skip-keygen` reuses cached MXE keys; for detached runs, call `arcium snapshot-mxe-keygen --rpc-url l` before shutting down the validator.
Let's take a quick look at the default test file. Some helper functions and imports are excluded for brevity, but you can find the complete examples in your generated project:
```typescript theme={null}
describe("Hello World", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.HelloWorld as Program;
const provider = anchor.getProvider();
const arciumEnv = getArciumEnv();
it("Is initialized!", async () => {
const owner = readKpJson(`${os.homedir()}/.config/solana/id.json`);
console.log("Initializing add together computation definition");
const initATSig = await initAddTogetherCompDef(program, owner);
console.log(
"Add together computation definition initialized with signature",
initATSig
);
const privateKey = x25519.utils.randomSecretKey();
const publicKey = x25519.getPublicKey(privateKey);
const mxePublicKey = await getMXEPublicKeyWithRetry(
provider as anchor.AnchorProvider,
program.programId
);
console.log("MXE x25519 pubkey is", mxePublicKey);
const sharedSecret = x25519.getSharedSecret(privateKey, mxePublicKey);
const cipher = new RescueCipher(sharedSecret);
const val1 = BigInt(1);
const val2 = BigInt(2);
const plaintext = [val1, val2];
const nonce = randomBytes(16);
const ciphertext = cipher.encrypt(plaintext, nonce);
const sumEventPromise = awaitEvent("sumEvent");
const computationOffset = new anchor.BN(randomBytes(8), "hex");
const queueSig = await program.methods
.addTogether(
computationOffset,
Array.from(ciphertext[0]),
Array.from(ciphertext[1]),
Array.from(publicKey),
new anchor.BN(deserializeLE(nonce).toString())
)
.accountsPartial({
computationAccount: getComputationAccAddress(
arciumEnv.arciumClusterOffset,
computationOffset
),
clusterAccount: getClusterAccAddress(arciumEnv.arciumClusterOffset),
mxeAccount: getMXEAccAddress(program.programId),
mempoolAccount: getMempoolAccAddress(arciumEnv.arciumClusterOffset),
executingPool: getExecutingPoolAccAddress(arciumEnv.arciumClusterOffset),
compDefAccount: getCompDefAccAddress(
program.programId,
Buffer.from(getCompDefAccOffset("add_together")).readUInt32LE()
),
})
.rpc({ commitment: "confirmed" });
console.log("Queue sig is ", queueSig);
const finalizeSig = await awaitComputationFinalization(
provider as anchor.AnchorProvider,
computationOffset,
program.programId,
"confirmed"
);
console.log("Finalize sig is ", finalizeSig);
const sumEvent = await sumEventPromise;
const decrypted = cipher.decrypt([sumEvent.sum], new Uint8Array(sumEvent.nonce))[0];
expect(decrypted).to.equal(val1 + val2);
});
});
```
This test demonstrates the complete flow of encrypted computations in Arcium. Here's what each key step does:
* `initAddTogetherCompDef`: Call the `init_add_together_comp_def` instruction to initialize the encrypted instruction definition. (only need to be called once after the program is deployed)
* `getMXEPublicKeyWithRetry`: Fetch the MXE's x25519 public key.
* `x25519.utils.randomSecretKey`: Generate a random private key for the x25519 key exchange.
* `x25519.getPublicKey`: Generate the public key corresponding to the private key we generated above.
* `x25519.getSharedSecret`: Generate the shared secret with the MXE cluster using a x25519 key exchange.
* `cipher = new RescueCipher(sharedSecret)`: Initialize the Rescue cipher. The constructor derives a key from the Rescue-Prime hash function; see [Encryption overview](/developers/encryption).
* `cipher.encrypt`: Encrypt the inputs for the encrypted instruction.
* `awaitEvent`: Wait for the `sumEvent` event to be emitted by the program on finalization of the computation (in the callback instruction).
* `addTogether`: Call the `add_together` instruction to invoke the encrypted instruction.
* `awaitComputationFinalization`: Since waiting for an Arcium computation is not the same as waiting for one Solana transaction (because the MPC cluster must finish the computation and invoke the callback), this function is used, which is provided by the Arcium TypeScript library.
## Ready to Deploy?
Now that you have built and tested your MXE locally, you are probably eager to see it running live! Head over to our [deployment guide](/developers/deployment) where we'll walk you through getting your MXE deployed on Solana. We'll cover everything from choosing the right RPC endpoint to initializing your computation definitions.
## What's next?
Understand MXEs, Clusters, and encrypted instructions.
Understand MPC constraints and build more complex circuits.
# Installation overview
Source: https://docs.arcium.com/developers/installation
Install the Arcium toolchain with arcup, including Rust, Solana CLI, and Anchor prerequisites
## Quick install (recommended)
On Mac and Linux, run this single command to install Arcium:
```bash theme={null}
curl --proto '=https' --tlsv1.2 -sSfL https://install.arcium.com/ | bash
```
`arcup` manages Arcium tooling versions, including the CLI and Arx node software. See the [Arcup version manager guide](/developers/installation/arcup) for version-management details.
This script will:
* Check for all required dependencies
* Install Linux build dependencies automatically (if needed)
* Download and install `arcup` for your platform
* Install the latest Arcium CLI, which interacts with the Arcium Network and manages computations
* Install the Arx node software, which performs encrypted computations in the network
### Prerequisites
Before running the installation script, make sure you have these dependencies installed:
* **Rust**: Install from the [Rust installation guide](https://www.rust-lang.org/tools/install)
* **Solana CLI 3.1.10**: Install from the [Solana CLI installation guide](https://docs.solana.com/cli/install-solana-cli-tools), then run `solana-keygen new`
* **Yarn**: Install from the [Yarn installation guide](https://yarnpkg.com/getting-started/install)
* **Anchor 1.0.2**: Install from the [Anchor installation guide](https://www.anchor-lang.com/docs/installation)
* **Docker & Docker Compose**: Install [Docker](https://docs.docker.com/engine/install/) and [Docker Compose](https://docs.docker.com/compose/install/)
The installation script will check for all these dependencies and provide clear instructions if any are missing.
## Manual installation
If you prefer to install manually, use the target-specific binaries below. `arcup` still manages Arcium tooling versions after installation; see the [Arcup version manager guide](/developers/installation/arcup).
Install `arcup`. We currently publish pre-built targets for x86 Linux and Apple Silicon. We do not support Windows at the moment.
* `x86_64_linux`
* `aarch64_macos`
You can install it by replacing `` with the target you want to install, and running the following command:
```bash x86 Linux theme={null}
TARGET=x86_64_linux && curl "https://bin.arcium.com/download/arcup_${TARGET}_0.12.0" -o ~/.cargo/bin/arcup && chmod +x ~/.cargo/bin/arcup
```
```bash Apple Silicon theme={null}
TARGET=aarch64_macos && curl "https://bin.arcium.com/download/arcup_${TARGET}_0.12.0" -o ~/.cargo/bin/arcup && chmod +x ~/.cargo/bin/arcup
```
Install the latest version of the CLI using `arcup`:
```bash theme={null}
arcup install
```
Verify the installation:
```bash theme={null}
arcium --version
```
## Shell completions
Generate tab-completion scripts for your shell:
```bash Bash theme={null}
arcium completions bash >> ~/.bashrc
```
```bash Zsh theme={null}
arcium completions zsh >> ~/.zshrc
```
```bash Fish theme={null}
arcium completions fish > ~/.config/fish/completions/arcium.fish
```
Restart your shell or source the config file to activate completions.
## Issues
Installation might fail due to a variety of reasons. This section contains a list of the most common issues and their solutions, taken from Anchor's installation guide.
### Platform-specific issues
**Windows users:** Arcium is not currently supported on Windows. We recommend using Windows Subsystem for Linux (WSL2) with Ubuntu for the best experience.
**Linux systems:** You may need additional dependencies. On Ubuntu/Debian:
```bash theme={null}
sudo apt-get update && sudo apt-get upgrade && sudo apt-get install -y pkg-config build-essential libudev-dev libssl-dev
```
### Incorrect `$PATH`
Rust binaries, including `arcup` and `arcium`, are installed to the `~/.cargo/bin` directory. Since this directory is required to be in the `PATH` environment variable, Rust installation tries to set it up automatically, but it might fail to do so on some platforms.
To verify that the `PATH` environment variable was set up correctly, run:
```shell theme={null}
which arcium
```
The output should look like (with your username):
```text theme={null}
/home/user/.cargo/bin/arcium
```
**Shell-specific PATH issues:**
If `which arcium` returns nothing, add the cargo bin directory to your PATH:
* **Bash/Zsh:** Add to `~/.bashrc` or `~/.zshrc`:
```bash theme={null}
export PATH="$HOME/.cargo/bin:$PATH"
```
* **Fish:** Add to `~/.config/fish/config.fish`:
```bash theme={null}
set -gx PATH $HOME/.cargo/bin $PATH
```
After editing, restart your terminal or run `source ~/.bashrc` (or equivalent for your shell).
## What's next?
Build your first encrypted instruction end-to-end.
Understand the Arcium architecture before building.
# Arcup version manager
Source: https://docs.arcium.com/developers/installation/arcup
Install and manage Arcium toolchain versions with arcup
The `arcup` version manager installs and manages the Arcium tooling suite, including the Arcium CLI binary and the Arx node Docker image. Use it to install the current release, switch between installed versions, and update components when new releases are available.
The [Quick start](#quick-start) section below covers basic `arcup` onboarding. For the full toolchain setup, see the [installation overview](/developers/installation). For component compatibility rules, see [Inter-component versioning](#inter-component-versioning).
## Quick start
First, check whether you already have a manually installed CLI or Arx node image:
```bash theme={null}
command -v arcium || true
docker image ls "arcium/*"
```
If you no longer need those old installs, remove the specific binary or Docker image before using `arcup`. Then verify the old CLI is gone or ready to be replaced:
```bash theme={null}
arcium --version
docker image ls "arcium/*"
```
Next, install `arcup` on your machine by following the [manual installation steps](/developers/installation#manual-installation). Then run the `arcup install` command:
```bash theme={null}
arcup install # Installs the latest releases of the Arcium components
```
Now verify that everything is installed correctly:
```bash theme={null}
arcium --version # Shows the latest CLI version
arcup version # Shows the currently installed Arcium component versions
docker images # Lists the image for the Arx node
```
You can also install older versions with `arcup install `, remove installed versions with `arcup uninstall`, and switch between installed versions with `arcup use`. See [Available commands](#available-commands) for details.
`arcium` and `arcup` may print a one-line update banner. Set `ARCIUM_NO_UPDATE_CHECK=1` to suppress it in scripts; CI and non-TTY runs are silent automatically.
## Inter-component versioning
The `arcup` version manager follows [semver](https://semver.org/) (`MAJOR.MINOR.PATCH`). Component patch versions do not need to stay in sync, but major and minor versions must match across Arcium components. Patch changes are therefore non-breaking with respect to the other Arcium components.
For example, if the current versions are:
* CLI: `0.4.5`
* Arx node: `0.4.15`
If a breaking change is made to the CLI (incrementing it to `0.5.0`), the `MINOR` version number of the Arx node is also incremented, so both become `0.5.0`. However, if only a (non-breaking) `PATCH` upgrade is made to the CLI, the CLI increments to `0.4.6` and the Arx node remains at `0.4.15`.
## Available commands
```bash theme={null}
install Install the latest (or a specific) version of Arcium components (Arx node and CLI)
update Update all Arcium components (Arx node and CLI) to the latest version
list List all installed versions
version Show currently active version
use Switch to using a specific installed version
uninstall Remove a specific version (alias: rm)
self Manage arcup itself (update, uninstall)
help Print this message or the help of the given subcommand(s)
```
## Self management
The `self` subcommand allows you to manage arcup itself:
```bash theme={null}
# Update arcup to the latest version
arcup self update
# Uninstall arcup and all Arcium components
arcup self uninstall
# Uninstall but keep Docker images
arcup self uninstall --keep-docker
# Skip confirmation prompt
arcup self uninstall --force
# Preview what would be removed (dry run)
arcup self uninstall --dry-run
```
# JavaScript client overview
Source: https://docs.arcium.com/developers/js-client-library
TypeScript libraries for encrypting data, invoking computations, and tracking results
## Overview
Arcium provides two TypeScript libraries for interacting with Arcium and deployed MXEs (MPC eXecution Environments).
Client library `@arcium-hq/client`:
* Handles secret sharing and encryption of inputs
* Submits encrypted transactions
* Manages callbacks for computation results
Reader library `@arcium-hq/reader`:
* Reads MXE data
* Views computations for a given MXE
Use the client library to build and invoke computations on MXEs, then track their outputs. Use the reader library to inspect MXE and network state. For the full flow, see the [computation lifecycle](/developers/computation-lifecycle).
## Installation
Client library:
```bash npm theme={null}
npm install @arcium-hq/client
```
```bash yarn theme={null}
yarn add @arcium-hq/client
```
```bash pnpm theme={null}
pnpm add @arcium-hq/client
```
Reader library:
```bash npm theme={null}
npm install @arcium-hq/reader
```
```bash yarn theme={null}
yarn add @arcium-hq/reader
```
```bash pnpm theme={null}
pnpm add @arcium-hq/reader
```
## API reference
For complete TypeScript SDK documentation, see the [API reference](https://ts.arcium.com/api).
## Reclaiming computation rent
Every queued computation allocates a Solana account that holds rent. After the computation lifecycle completes, you should reclaim that rent.
### After successful finalization
Once a computation reaches `Finalized` status (after the callback executes), use `claimComputationRent` to close the account and reclaim the rent:
```typescript theme={null}
import { claimComputationRent } from '@arcium-hq/client';
const sig = await claimComputationRent(
provider, // AnchorProvider
clusterOffset, // cluster the computation was queued on
computationOffset, // BN - the offset used when queueing
);
```
The signer must be the original payer who queued the computation. Using a different signer will fail with `InvalidAuthority`.
Computations that remain in `Queued` status expire after 180 slots (\~72 seconds). Expired computations can be reclaimed using the `reclaimExpiredComputationFee` instruction, which also returns any fees from the fee pool. See the [API reference](https://ts.arcium.com/api) for details.
`claimComputationRent` only closes per-computation accounts. To close long-lived MXE or computation-definition accounts, see [Account lifecycle and closing](/developers/program/account-lifecycle).
## Reading computation fees
The reader library exposes two getters for inspecting a computation's fee, depending on whether its account is still open:
```typescript theme={null}
import { getComputationFee, getComputationFeeFromQueueTx } from '@arcium-hq/reader';
// Live computation: reads the onchain fee from the open account.
// Returns null once the account is closed (rent reclaimed).
const fee = await getComputationFee(
arciumProgram,
mxeProgramId,
computationOffset,
);
// Closed computation: reconstructs the queue-time fee inputs by decoding
// the original queue_computation instruction from its transaction signature.
const queued = await getComputationFeeFromQueueTx(
arciumProgram,
queueTxSignature,
mxeProgramId,
computationOffset,
);
```
`getComputationFee` returns `{ source: 'account', ... }` with the live onchain fee, or `null` when the account has been closed. `getComputationFeeFromQueueTx` returns `{ source: 'queueTx', ... }` with the raw queue-time inputs (not lamport amounts) and requires an archive RPC that retains the original transaction and its inner instructions. See the [API reference](https://ts.arcium.com/api) for the full return shapes.
## Using the client
Prefer a more step-by-step approach? Get started with learning [how to encrypt inputs for encrypted transactions](/developers/js-client-library/encryption).
## What's next?
Step-by-step guide to encrypting data for encrypted transactions.
Await and process computation results.
Complete TypeScript SDK documentation.
# Tracking callbacks
Source: https://docs.arcium.com/developers/js-client-library/callback
Track computation progress and handle callback results
Unlike regular transactions, encrypted computations involve additional steps after your Solana transaction completes:
1. **Your transaction completes** - Encrypted data is submitted and queued in the cluster's mempool
2. **Computation waits in queue** - MPC nodes process computations from the mempool in order
3. **MPC execution** - When your computation's turn comes, MPC nodes execute it offchain
4. **Callback invocation** - Results are returned via your callback instruction
This means you can't await a transaction completion like normal Solana programs. Instead, you need to wait for the entire computation lifecycle to finish. The Arcium client library provides utilities to handle this:
## Await computation completion with `awaitComputationFinalization`
```typescript theme={null}
// Generate a random 8-byte computation offset
const computationOffset = new anchor.BN(randomBytes(8), "hex");
// `program` is the anchor program client of the MXE we're invoking
// the instruction `ourIx` on (which then invokes a computation under the hood by CPIing into the Arcium program).
// `queueSig` is the signature of said transaction.
const queueSig = await program.methods
.ourIx(
// Computation offset that you provide when invoking the instruction
computationOffset
/* other inputs */
)
.accounts(/* some accounts */)
.rpc();
// Since this is an Arcium computation, we need to wait for it to be finalized
// a little bit differently
const finalizeSig = await awaitComputationFinalization(
// Anchor provider
provider as anchor.AnchorProvider,
// Computation offset that you provide when invoking the instruction
computationOffset,
// Program ID of the MXE
program.programId,
// Solana commitment level, "confirmed" by default
"confirmed"
);
console.log("Computation was finalized with sig: ", finalizeSig);
```
Failed finalization reports a `CircuitFailureReason` such as `OffChainCircuitFetchFailed`, `OffChainCircuitHashMismatch`, `CircuitCUMismatch`, `LocalCircuitFetchFailed`, or `CircuitSerialization`.
## Read and assert the callback result
Finalization tells you the callback has run, but not what it produced. Consume the result the way you would in any Solana program: await an emitted event, or read updated account state. Events are the common path because Arcium callbacks typically relay encrypted payloads for the client to decrypt offchain.
Event-based decryption requires the callback to emit both the ciphertext and the nonce. See the callback-side `emit!` pattern in the [Arcium program guide](/developers/program).
On the client side, register the event listener **before** queuing the computation so it's attached by the time the callback fires. Helpers and imports are omitted for brevity: `awaitEvent` is a test-scaffold wrapper around `program.addEventListener`, `cipher` comes from the encryption setup in [Encrypting inputs](/developers/js-client-library/encryption), and `expect` / `expectedSum` are shown in a test context.
```typescript theme={null}
// Register the listener first to avoid a race where the callback fires
// before you've subscribed.
const sumEventPromise = awaitEvent("sumEvent");
const computationOffset = new anchor.BN(randomBytes(8), "hex");
const queueSig = await program.methods
.ourIx(computationOffset /* other inputs */)
.accounts(/* ... */)
.rpc({ commitment: "confirmed" });
await awaitComputationFinalization(
provider as anchor.AnchorProvider,
computationOffset,
program.programId,
"confirmed"
);
// Callback has fired; resolve the event, decrypt offchain, and assert.
const sumEvent = await sumEventPromise;
const decrypted = cipher.decrypt([sumEvent.sum], new Uint8Array(sumEvent.nonce))[0];
expect(decrypted).to.equal(expectedSum);
```
The two valid orderings are:
* **Event path**: `subscribe → queue → finalize → await event → decrypt → assert`
* **Account path**: `queue → finalize → read account → assert`
## What's next?
Learn how to encrypt data before sending to the MXE.
Deploy your MXE program to devnet or mainnet.
# Encrypting inputs
Source: https://docs.arcium.com/developers/js-client-library/encryption
Encrypt input data for encrypted computations
Let's say we have the following encrypted instruction that adds 2 encrypted `u8`s and returns the result encrypted:
```rust theme={null}
use arcis::*;
#[encrypted]
mod circuits {
use arcis::*;
pub struct InputValues {
v1: u8,
v2: u8,
}
#[instruction]
pub fn add_together(input_ctxt: Enc) -> Enc {
let input = input_ctxt.to_arcis();
let sum = input.v1 as u16 + input.v2 as u16;
input_ctxt.owner.from_arcis(sum)
}
}
```
We want to input the values `x = 42` and `y = 101` into this instruction. To do this, we first have to build the parameters for the encrypted instruction correctly:
```typescript theme={null}
import { RescueCipher, getArciumEnv, x25519 } from "@arcium-hq/client";
import { randomBytes } from "crypto";
// Our encrypted instruction takes two encrypted `u8` values as input, so we need to provide two ciphertext values which are represented as `[u8; 32]` in our Solana program.
const val1 = BigInt(42);
const val2 = BigInt(101);
const plaintext = [val1, val2];
```
Now that we have the inputs, we need to encrypt them. This is done using the `RescueCipher` class with some info about the MPC cluster we want to use:
```typescript theme={null}
// Fetch the MXE x25519 public key
// getMXEPublicKeyWithRetry is a helper that wraps getMXEPublicKey with retries
// See the Hello World tutorial for the full implementation
const mxePublicKey = await getMXEPublicKeyWithRetry(
provider as anchor.AnchorProvider,
program.programId
);
// Generate a random private key for x25519 elliptic curve Diffie-Hellman key exchange.
const privateKey = x25519.utils.randomSecretKey();
// Derive the public key from the private key.
const publicKey = x25519.getPublicKey(privateKey);
// Generate a random nonce for the encryption.
const nonce = randomBytes(16);
// Get the shared secret with the cluster.
const sharedSecret = x25519.getSharedSecret(privateKey, mxePublicKey);
// Initialize the cipher with the shared secret.
const cipher = new RescueCipher(sharedSecret);
// Encrypt the plaintext, and serialize it to a `[u8; 32]` array.
const ciphertext = cipher.encrypt(plaintext, nonce);
```
To decrypt the data, again it follows a similar pattern:
```typescript theme={null}
// Initialize the cipher with the shared secret.
const cipher = new RescueCipher(sharedSecret);
const plaintext = cipher.decrypt(ciphertext, nonce);
```
**Working with packed data?** If your Arcis structs use `Pack`, you'll use generated packers on the client. See [Efficient data packing](/developers/arcis/input-output#efficient-data-packing).
## What's next?
Learn how to await and process computation results.
Complete TypeScript SDK documentation.
# Current limitations
Source: https://docs.arcium.com/developers/limitations
Output size limits, Arcis language constraints, and common error codes when building encrypted applications on Arcium
## Output sizes
Outputs of encrypted instructions must fit in a single Solana transaction, because results are delivered through the callback transaction. This limits callback output to approximately 1232 bytes.
If a computation exceeds the limit, it fails with `OutputTooLarge`. Return compact results, pack small values with `Pack`, or split large work into multiple computations.
## Arcis language constraints
Arcis compiles Rust-like code into fixed MPC circuits. The circuit shape must be known at compile time, so several standard Rust patterns need different implementations:
| Pattern | Status | Use instead |
| --------------------------------------------------------------- | ------------- | ------------------------------------------------ |
| `Vec`, `String`, `HashMap` | Not supported | Fixed-size arrays, byte arrays, or structs |
| `while`, `loop` | Not supported | `for` loops with fixed bounds |
| `break`, `continue`, early `return` | Not supported | A single exit path with conditional assignments |
| `let ... else` | Not supported | `if let` or `match` |
| `.reveal()` or `.from_arcis()` inside non-constant conditionals | Not supported | Move the reveal or conversion outside the branch |
Arcis supports `if`, `else`, `else if`, `if let`, let chains, `match`, `matches!`, fixed-size arrays, structs, tuples, enums, `Option`, and fixed-bound loops. Enums and `Option` cannot be the input or output of an encrypted instruction, and enum discriminants cannot be set explicitly. See the [Operations guide](/developers/arcis/operations) for the complete support matrix.
## Common errors
| Error or symptom | Likely cause | Fix |
| ------------------------------------ | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `OutputTooLarge` | Callback output exceeds the Solana transaction size limit | Return less data, pack values, or split the computation |
| Computation never finalizes | Computation definition was not initialized, callback is missing, or cluster configuration is wrong | Initialize the computation definition, check callback registration, and verify the cluster offset |
| Decryption fails | Nonce mismatch, nonce reuse, wrong MXE key, or incorrect ciphertext order | Use a fresh 16-byte nonce per encryption and match the circuit argument order exactly |
| Shared input fails silently | Missing `x25519_pubkey` before the nonce and ciphertext | For `Enc`, pass public key, nonce, then ciphertexts |
| Callback runs but state is unchanged | Callback account was not writable or was omitted from callback accounts | Mark the account writable in both the callback account list and the Anchor account struct |
| Circuit compile failure | Unsupported Rust construct or variable-size data | Replace dynamic constructs with fixed-size Arcis patterns |
## What's next?
Performance tips and common patterns
Complete operation support matrix
Supported and unsupported types
Deploy your MXE to the network
# Migration overview
Source: https://docs.arcium.com/developers/migration
Migration guides for upgrading between Arcium versions
Choose your migration path based on your current Arcium version:
| Current Version | Target | Migration Guide |
| --------------- | ------- | -------------------------------------------------------------------------- |
| **v0.10.x** | v0.11.x | [→ v0.10.x to v0.11.x](/developers/migration/migration-v0.10.0-to-v0.11.0) |
| **v0.9.x** | v0.10.x | [→ v0.9.x to v0.10.x](/developers/migration/migration-v0.9.0-to-v0.10.0) |
| **v0.8.0** | v0.9.0 | [→ v0.8.0 to v0.9.0](/developers/migration/migration-v0.8.0-to-v0.9.0) |
| **v0.7.0** | v0.8.0 | [→ v0.7.0 to v0.8.0](/developers/migration/migration-v0.7.0-to-v0.8.0) |
| **v0.6.3** | v0.7.0 | [→ v0.6.3 to v0.7.0](/developers/migration/migration-v0.6.3-to-v0.7.0) |
| **v0.5.x** | v0.6.3 | [→ v0.5.x to v0.6.3](/developers/migration/migration-v0.5-to-v0.6) |
| **v0.4.x** | v0.5.1 | [→ v0.4.x to v0.5.1](/developers/migration/migration-v0.4-to-v0.5) |
| **v0.3.x** | v0.4.0 | [→ v0.3.x to v0.4.0](/developers/migration/migration-v0.3-to-v0.4) |
| **v0.2.x** | v0.3.0 | [→ v0.2.x to v0.3.0](/developers/migration/migration-v0.2-to-v0.3) |
| **v0.1.x** | v0.2.0 | [→ v0.1.x to v0.2.0](/developers/migration/migration-v0.1-to-v0.2) |
## Multi-step migration
**v0.1.x → v0.11.x**: Migrate through v0.2.0, then v0.3.0, then v0.4.0, then v0.5.1, then v0.6.3, then v0.7.0, then v0.8.0, then v0.9.0, then v0.10.x, then to v0.11.x.
## Need help?
* Backup your project before migrating
* Test on a copy first
* Each migration guide includes troubleshooting sections
## What's next?
Migrate from v0.10.x to v0.11.x.
Deploy your MXE after migration.
# v0.1.x to v0.2.0
Source: https://docs.arcium.com/developers/migration/migration-v0.1-to-v0.2
Migration guide from Arcium v0.1.x to v0.2.0
## 1. Update Arcium Rust dependencies
```bash theme={null}
# Update program dependencies
cd programs/*
cargo update --package arcium-client --precise 0.2.0
cargo update --package arcium-macros --precise 0.2.0
cargo update --package arcium-anchor --precise 0.2.0
# Update encrypted-ixs dependencies
cd ../../encrypted-ixs
cargo update --package arcis-imports --precise 0.2.0
```
## 2. Update Arcium TS dependencies
```bash npm theme={null}
npm install @arcium-hq/client@0.2.0
```
```bash yarn theme={null}
yarn add @arcium-hq/client@0.2.0
```
```bash pnpm theme={null}
pnpm add @arcium-hq/client@0.2.0
```
## 3. (Optional) Update your `Arcium.toml`
We no longer need the `clusters` field under `localnet` in `Arcium.toml`. New file `Arcium.toml` should look like this:
```toml theme={null}
[localnet]
# number of nodes in the single cluster of the localnet
nodes = 2
# number of seconds to wait for the localnet to come online
localnet_timeout_secs = 60
```
This change is optional, and leaving `clusters` field as is will still work.
## 4. Simplify your Arcium crate imports
No more 10 line import statements from `arcium_anchor`, `arcium_client`, `arcium_macros`, etc. All of them can be compressed such that the following:
```rust theme={null}
use arcium_anchor::{
comp_def_offset, derive_cluster_pda, derive_comp_def_pda, derive_comp_pda, derive_execpool_pda,
derive_mempool_pda, derive_mxe_pda, init_comp_def, queue_computation, ComputationOutputs,
ARCIUM_CLOCK_ACCOUNT_ADDRESS, ARCIUM_STAKING_POOL_ACCOUNT_ADDRESS, CLUSTER_PDA_SEED,
COMP_DEF_PDA_SEED, COMP_PDA_SEED, EXECPOOL_PDA_SEED, MEMPOOL_PDA_SEED, MXE_PDA_SEED,
};
use arcium_client::idl::arcium::{
accounts::{
ClockAccount, Cluster, ComputationDefinitionAccount, PersistentMXEAccount,
StakingPoolAccount,
},
program::Arcium,
types::Argument,
ID_CONST as ARCIUM_PROG_ID,
};
use arcium_macros::{
arcium_callback, arcium_program, callback_accounts, init_computation_definition_accounts,
queue_computation_accounts,
};
```
now becomes just
```rust theme={null}
use arcium_anchor::prelude::*;
```
This includes all the basic imports required to setup and run the basic Arcium example program. For additional types such as `CircuitSource`, `CallbackAccount`, they will still need to be imported separately from `arcium_client::idl::arcium::types` module.
## 5. Update your Arcium CPI calls
In your `init_comp_def` calls, you need to add an additional parameter at the third position. So, it used to look like:
```rust theme={null}
init_comp_def(ctx.accounts, true, None, None)?;
```
Now it should look like:
```rust theme={null}
init_comp_def(ctx.accounts, true, 0, None, None)?;
```
You don't need to care about the `0` parameter, it's just a placeholder for the new parameter.
## 6. Update your callback functions
No more manually handling all output bytes from an Arcium computation. The new `#[arcium_callback]` macro will handle all the deserialization of bytes for you based on your circuit's generated interface file.
```rust theme={null}
#[arcium_callback(encrypted_ix = "add_together")]
pub fn add_together_callback(
ctx: Context,
output: ComputationOutputs,
) -> Result<()> {
let bytes = if let ComputationOutputs::Bytes(bytes) = output {
bytes
} else {
return Err(ErrorCode::AbortedComputation.into());
};
emit!(SumEvent {
sum: bytes[48..80].try_into().unwrap(),
nonce: bytes[32..48].try_into().unwrap(),
});
Ok(())
}
```
becomes
```rust theme={null}
#[arcium_callback(encrypted_ix = "add_together")]
pub fn add_together_callback(
ctx: Context,
output: ComputationOutputs,
) -> Result<()> {
let o = match output {
ComputationOutputs::Success(AddTogetherOutput { field_0 }) => field_0,
_ => return Err(ErrorCode::AbortedComputation.into()),
};
emit!(SumEvent {
sum: o.ciphertexts[0],
nonce: o.nonce.to_le_bytes(),
});
Ok(())
}
```
For a comprehensive guide on how the callback type generation system works, see [Callback type generation](/developers/program/callback-type-generation).
## 7. Update your Context structs
First, all references to `PersistentMXEAccount` becomes just `MXEAccount`. This has to be done in both `queue_computation_accounts` and `init_computation_definition_accounts` context structs.
Second, we need to update the `StakingPoolAccount` to `FeePool` in your `queue_computation_accounts` Context structs.
```rust theme={null}
#[account(
mut,
address = ARCIUM_STAKING_POOL_ACCOUNT_ADDRESS,
)]
pub pool_account: Account<'info, StakingPoolAccount>,
```
now becomes
```rust theme={null}
#[account(
mut,
address = ARCIUM_FEE_POOL_ACCOUNT_ADDRESS,
)]
pub pool_account: Account<'info, FeePool>,
```
### 8. Add a new error code
In your program `ErrorCode` enum, add a new error code:
```rust theme={null}
#[error_code]
pub enum ErrorCode {
...
#[msg("The cluster is not set")]
ClusterNotSet,
}
```
### 9. Replace static `mxePublicKey`
No more constant MXE public key definition in your tests or client. Instead you can import `getMXEPublicKey` function from `@arcium-hq/client` and use it to get the MXE public key.
```typescript theme={null}
const mxePublicKey = await getMXEPublicKey(
provider as anchor.AnchorProvider,
program.programId
);
```
This might cause some issues in your tests as the function might be called before the MXE keys are fully set. In which case, we recommend using the following helper function as a wrapper around `getMXEPublicKey` to fetch MXE public key with retries:
```typescript theme={null}
async function getMXEPublicKeyWithRetry(
provider: anchor.AnchorProvider,
programId: PublicKey,
maxRetries: number = 10,
retryDelayMs: number = 500
): Promise {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const mxePublicKey = await getMXEPublicKey(provider, programId);
if (mxePublicKey) {
return mxePublicKey;
}
} catch (error) {
console.log(`Attempt ${attempt} failed to fetch MXE public key:`, error);
}
if (attempt < maxRetries) {
console.log(
`Retrying in ${retryDelayMs}ms... (attempt ${attempt}/${maxRetries})`
);
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
}
}
throw new Error(
`Failed to fetch MXE public key after ${maxRetries} attempts`
);
}
```
And voila, you should have a working program that is compatible with Arcium tooling v0.2.0!
# v0.10.x to v0.11.x
Source: https://docs.arcium.com/developers/migration/migration-v0.10.0-to-v0.11.0
Upgrade from v0.10.x to v0.11.x: the new queue_computation callback CU limit, Arcis enums and Option, multi-instruction callbacks, and node config changes
This guide covers upgrading from v0.10.x to v0.11.x. The main breaking change for app developers is a new `callback_cu_limit` argument on `queue_computation`. The toolchain is otherwise unchanged: Anchor stays at v1.0.2 and Solana CLI stays at 3.1.10, so there is no Anchor migration this time. v0.11.x also adds Arcis enums and `Option`, multi-instruction callbacks, and new required node config fields.
## Before you start
v0.11.x is a code-and-tooling upgrade. Existing MXE, cluster, and computation-definition accounts remain compatible. Rebuild and redeploy your program after adding the `callback_cu_limit` argument. Node operators must update their `node-config.toml`: three fields are now required (see [Node operators](#5-node-operators)).
## 1. Update Arcium tooling
```bash theme={null}
arcup self update
arcup update
```
Verify:
```bash theme={null}
arcium --version
```
You should see `0.11.1`.
Anchor (1.0.2) and Solana CLI (3.1.10) are unchanged from v0.10.x. If you already migrated to the Anchor v1 toolchain for v0.10.x, there is nothing further to install.
## 2. Update dependencies
Bump the Arcium Rust crates from your project root:
```bash theme={null}
cargo update --manifest-path programs/your-program-name/Cargo.toml --package arcium-client --precise 0.11.1
cargo update --manifest-path programs/your-program-name/Cargo.toml --package arcium-macros --precise 0.11.1
cargo update --manifest-path programs/your-program-name/Cargo.toml --package arcium-anchor --precise 0.11.1
```
```bash theme={null}
cargo update --manifest-path encrypted-ixs/Cargo.toml --package arcis --precise 0.11.1
```
Bump the TypeScript clients to 0.11.1:
```bash npm theme={null}
npm install @arcium-hq/client@0.11.1 @arcium-hq/reader@0.11.1
```
```bash yarn theme={null}
yarn add @arcium-hq/client@0.11.1 @arcium-hq/reader@0.11.1
```
```bash pnpm theme={null}
pnpm add @arcium-hq/client@0.11.1 @arcium-hq/reader@0.11.1
```
## 3. Add `callback_cu_limit` to `queue_computation`
`queue_computation` takes a new seventh argument, `callback_cu_limit: u32`, the compute unit limit for the callback transaction. Pass `0` to keep the default. Every call site must be updated:
```rust theme={null}
// Before (v0.10.x): 6 arguments
queue_computation(
ctx.accounts,
computation_offset,
args,
vec![/* callback instructions */],
1, // num_callback_txs
0, // cu_price_micro
)?;
// After (v0.11.x): 7 arguments
queue_computation(
ctx.accounts,
computation_offset,
args,
vec![/* callback instructions */],
1, // num_callback_txs
0, // cu_price_micro
0, // callback_cu_limit (0 = default)
)?;
```
## 4. Arcis circuit changes
Enums and `Option` are now supported in circuits. No existing code needs to change; these are additive. Both have one constraint: they cannot be the input or output of an encrypted instruction, and enum discriminants cannot be set explicitly. See [Operations](/developers/arcis/operations#item-support) and [Types](/developers/arcis/types).
## 5. Node operators
Update your Docker image tag in `docker-compose.yml`:
```yaml theme={null}
# Before
image: arcium/arx-node:v0.10.3
# After
image: arcium/arx-node:v0.11.1
```
Then pull and restart:
```bash theme={null}
docker compose pull
docker compose up -d
```
Three `node-config.toml` fields are now required; the config fails to parse without them:
```toml theme={null}
[node]
computations_limit = 10 # Max computations the node executes concurrently
[solana]
secondary_endpoint_rpc = "" # Failover RPC, ideally a different provider
secondary_endpoint_wss = "" # Failover WebSocket, ideally a different provider
```
The node fails over to the secondary endpoints when the primary RPC lags or stops responding, so point them at a different provider than your primary endpoints. The recommended `ASYNC_MPC_STREAM_WINDOW_MB`, `ASYNC_MPC_CONN_WINDOW_MB`, and `ASYNC_MPC_CC` transport-tuning environment variables are documented in [Node setup](/developers/node-setup#step-8-deploy-your-node).
Use `arcium reclaim-failure-rent` to close and refund failure-claim accounts past their challenge period.
As part of the staking rollout, the `arcium migrate-legacy-cluster`, `migrate-legacy-mxe-account`, `migrate-legacy-recovery-peer`, and `migrate-legacy-arx-node` commands migrate pre-staking accounts to the staking layout. These are not required for the v0.11 code upgrade itself; run them once the staking program is live on your network.
## 6. Verify migration
```bash theme={null}
arcium build
cargo check --all
arcium test
```
## 7. Changes summary
| Change | v0.10.x | v0.11.x |
| ---------------------- | ------------------------------------- | -------------------------------------------------------------------------------------- |
| `queue_computation` | 6 parameters | 7 parameters (adds `callback_cu_limit: u32`) |
| Solana CLI | 3.1.10 | 3.1.10 (unchanged) |
| Anchor CLI | 1.0.2 | 1.0.2 (unchanged) |
| Arcium Rust crates | 0.10.3 | 0.11.1 |
| TypeScript clients | `@arcium-hq/{client,reader}@0.10.3` | `@arcium-hq/{client,reader}@0.11.1` |
| Arx node Docker | `v0.10.3` | `v0.11.1` |
| Node config | `[node] offset`, `[solana]` endpoints | adds required `computations_limit`, `secondary_endpoint_rpc`, `secondary_endpoint_wss` |
| Arcis enums / `Option` | Unsupported | Supported (not as circuit input or output) |
## New in v0.11.x
These features are new in v0.11.x. See the linked documentation for details:
* **Arcis enums and `Option`**: unit, tuple, and struct enum variants plus the full `Option` combinator surface. See [Operations](/developers/arcis/operations).
* **Multi-instruction callbacks**: instructions may now follow the Arcium callback in the same transaction, as long as the last two instructions are lighthouse assertions.
* **Reader fee getters**: `getComputationFee` and `getComputationFeeFromQueueTx` read a computation's fee from `@arcium-hq/reader`. See [JavaScript client](/developers/js-client-library#reading-computation-fees).
* **CLI**: `arcium inspect mempool --include-expired`, `arcium inspect execpool --json`, `arcium reclaim-failure-rent`, and the `migrate-legacy-*` commands for the staking upgrade.
## What's next?
Deploy and manage your MXE.
Use enums and `Option` in your circuits.
# v0.2.x to v0.3.0
Source: https://docs.arcium.com/developers/migration/migration-v0.2-to-v0.3
Migration guide from Arcium v0.2.x to v0.3.0
## 1. Update Rust toolchain
Arcium v0.3.0 requires Rust 1.88.0. Create or update your `rust-toolchain` file:
```bash theme={null}
# Create or overwrite the rust-toolchain file
echo "1.88.0" | tee rust-toolchain > /dev/null
```
## 2. Add required Cargo patch
Add the following patch to your workspace `Cargo.toml`:
```toml theme={null}
[patch.crates-io]
proc-macro2 = { git = 'https://github.com/arcium-hq/proc-macro2.git' }
```
This patch is required for proper compilation of Arcium v0.3.0 projects. The patched `proc-macro2` crate contains fixes necessary for the Arcium macros to work correctly with Rust 1.88.0 and the new v0.3.0 architecture.
## 3. Update Arcium Rust dependencies
```bash theme={null}
# Update program dependencies
cd programs/your-program-name
cargo update --package arcium-client --precise 0.3.0
cargo update --package arcium-macros --precise 0.3.0
cargo update --package arcium-anchor --precise 0.3.0
# Update encrypted-ixs dependencies
cd ../../encrypted-ixs
cargo update --package arcis-imports --precise 0.3.0
```
## 4. Update Arcium TS dependencies
```bash npm theme={null}
npm install @arcium-hq/client@0.3.0
```
```bash yarn theme={null}
yarn add @arcium-hq/client@0.3.0
```
```bash pnpm theme={null}
pnpm add @arcium-hq/client@0.3.0
```
## 5. Enable init-if-needed feature in Cargo.toml
Add the `init-if-needed` feature to your `anchor-lang` dependency in your program's `Cargo.toml`:
```toml theme={null}
[dependencies]
anchor-lang = { version = "0.31.1", features = ["init-if-needed"] }
```
This feature is required for the Sign PDA account management in v0.3.0.
## 6. Add Sign PDA account to your queue computation accounts
You need to add a new required account (`sign_pda_account`) to all your `queue_computation_accounts` context structs:
```rust theme={null}
#[queue_computation_accounts]
#[derive(Accounts)]
pub struct YourComputationContext<'info> {
#[account(mut)]
pub payer: Signer<'info>,
// Add this new required account
#[account(
init_if_needed,
space = 9,
payer = payer,
seeds = [&SIGN_PDA_SEED],
bump,
address = derive_sign_pda!(),
)]
pub sign_pda_account: Account<'info, SignerAccount>,
// ... your other existing accounts
}
```
And in your instruction function, add this line to set the bump:
```rust theme={null}
pub fn your_computation_function(
ctx: Context,
computation_offset: u64,
// ... other parameters
) -> Result<()> {
// Add this line
ctx.accounts.sign_pda_account.bump = ctx.bumps.sign_pda_account;
// ... rest of your function
}
```
## 7. Update queue\_computation call signature
The `queue_computation` function now requires explicit callback instruction specification. Update your calls from:
```rust theme={null}
// Before v0.3.0
queue_computation(ctx.accounts, computation_offset, args, None, None)?;
```
to:
```rust theme={null}
// v0.3.0
queue_computation(
ctx.accounts,
computation_offset,
args,
None,
vec![YourCallback::callback_ix(&[])],
)?;
```
Replace `YourCallback` with the actual name of your callback struct.
For detailed examples and best practices on implementing callback instructions with custom accounts, see [Callback accounts](/developers/program/callback-accs).
## 8. Remove payer from callback accounts
Callback account structs no longer require a `payer` parameter. Update your callback accounts from:
```rust theme={null}
// Before v0.3.0
#[callback_accounts("your_computation", payer)]
#[derive(Accounts)]
pub struct YourCallback<'info> {
#[account(mut)]
pub payer: Signer<'info>,
pub arcium_program: Program<'info, Arcium>,
// ... other accounts
}
```
to:
```rust theme={null}
// v0.3.0
#[callback_accounts("your_computation")]
#[derive(Accounts)]
pub struct YourCallback<'info> {
// No payer required
pub arcium_program: Program<'info, Arcium>,
#[account(
address = derive_comp_def_pda!(COMP_DEF_OFFSET)
)]
pub comp_def_account: Account<'info, ComputationDefinitionAccount>,
#[account(address = ::anchor_lang::solana_program::sysvar::instructions::ID)]
/// CHECK: instructions_sysvar, checked by the account constraint
pub instructions_sysvar: AccountInfo<'info>,
}
```
## 9. Update x25519 function calls (TypeScript only)
If you're using x25519 key generation in your TypeScript client code, update the function name:
```typescript theme={null}
// Before v0.3.0
const privateKey = x25519.utils.randomPrivateKey();
// v0.3.0
const privateKey = x25519.utils.randomSecretKey();
```
## 10. Verify migration
After completing all migration steps, verify that everything works correctly:
### Build test
```bash theme={null}
# From your workspace root
arcium build
```
### Type checking
```bash theme={null}
# Ensure all new types compile correctly
cargo check --all
```
### Test your changes
```bash theme={null}
# Run your existing tests to ensure functionality is preserved
arcium test
```
## Complete example
Here's a complete before/after example of a typical computation function:
### Before v0.3.0:
```rust theme={null}
#[queue_computation_accounts]
#[derive(Accounts)]
pub struct Flip<'info> {
#[account(mut)]
pub payer: Signer<'info>,
// ... other accounts (no sign_pda_account)
}
pub fn flip(
ctx: Context,
computation_offset: u64,
user_choice: [u8; 32],
pub_key: [u8; 32],
nonce: u128,
) -> Result<()> {
let args = vec![
Argument::ArcisPubkey(pub_key),
Argument::PlaintextU128(nonce),
Argument::EncryptedU8(user_choice),
];
queue_computation(ctx.accounts, computation_offset, args, None, None)?;
Ok(())
}
```
### v0.3.0:
```rust theme={null}
#[queue_computation_accounts]
#[derive(Accounts)]
pub struct Flip<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init_if_needed,
space = 9,
payer = payer,
seeds = [&SIGN_PDA_SEED],
bump,
address = derive_sign_pda!(),
)]
pub sign_pda_account: Account<'info, SignerAccount>,
// ... other existing accounts
}
pub fn flip(
ctx: Context,
computation_offset: u64,
user_choice: [u8; 32],
pub_key: [u8; 32],
nonce: u128,
) -> Result<()> {
let args = vec![
Argument::ArcisPubkey(pub_key),
Argument::PlaintextU128(nonce),
Argument::EncryptedU8(user_choice),
];
ctx.accounts.sign_pda_account.bump = ctx.bumps.sign_pda_account;
queue_computation(
ctx.accounts,
computation_offset,
args,
None,
vec![FlipCallback::callback_ix(&[])],
)?;
Ok(())
}
```
That's it! Your program should now be compatible with Arcium tooling v0.3.0.
# v0.3.x to v0.4.0
Source: https://docs.arcium.com/developers/migration/migration-v0.3-to-v0.4
Migration guide from Arcium v0.3.x to v0.4.0
## 1. Update Rust toolchain and Solana CLI
Arcium v0.4.0 requires Rust 1.89.0 and Solana CLI 2.3.0. It also uses a new `rust-toolchain.toml` format and no longer requires Xargo.
### Update Rust toolchain
Replace your existing `rust-toolchain` file with the new TOML format:
```bash theme={null}
# Remove old rust-toolchain file if it exists
rm rust-toolchain 2>/dev/null || true
# Create new rust-toolchain.toml
cat > rust-toolchain.toml << 'EOF'
[toolchain]
channel = "1.89.0"
components = ["rustfmt","clippy"]
profile = "minimal"
EOF
```
### Remove Xargo configuration
Xargo is no longer needed in v0.4.0. Remove the `Xargo.toml` file from each program directory:
```bash theme={null}
# Remove Xargo.toml from all program directories
rm programs/*/Xargo.toml 2>/dev/null || true
```
### Update Solana CLI
Ensure you have Solana CLI 2.3.0 or later:
```bash theme={null}
# Check current version
solana --version
# Update to 2.3.0 if needed
sh -c "$(curl -sSfL https://release.solana.com/v2.3.0/install)"
```
## 2. Remove Cargo patch
The `proc-macro2` patch is no longer required in v0.4.0. Remove it from your workspace `Cargo.toml`:
```toml theme={null}
# Before v0.4.0
[workspace]
members = ["programs/*", "encrypted-ixs"]
resolver = "2"
[profile.release]
overflow-checks = true
lto = "fat"
codegen-units = 1
[profile.release.build-override]
opt-level = 3
incremental = false
codegen-units = 1
[patch.crates-io]
proc-macro2 = { git = 'https://github.com/arcium-hq/proc-macro2.git' }
```
```toml theme={null}
# v0.4.0
[workspace]
members = ["programs/*", "encrypted-ixs"]
resolver = "2"
[profile.release]
overflow-checks = true
lto = "fat"
codegen-units = 1
[profile.release.build-override]
opt-level = 3
incremental = false
codegen-units = 1
# No [patch.crates-io] section needed
```
## 3. Update Arcium Rust dependencies
Update your dependencies to v0.4.0 and Anchor to 0.32.1:
```bash theme={null}
# Update program dependencies
cd programs/your-program-name
cargo update --package arcium-client --precise 0.4.0
cargo update --package arcium-macros --precise 0.4.0
cargo update --package arcium-anchor --precise 0.4.0
cargo update --package anchor-lang --precise 0.32.1
# Update encrypted-ixs dependencies
cd ../../encrypted-ixs
cargo update --package arcis-imports --precise 0.4.0
```
## 4. Update Arcium TS dependencies
Update TypeScript dependencies to v0.4.0 and Anchor to 0.32.1:
```bash npm theme={null}
npm install @arcium-hq/client@0.4.0 @coral-xyz/anchor@0.32.1
```
```bash yarn theme={null}
yarn add @arcium-hq/client@0.4.0 @coral-xyz/anchor@0.32.1
```
```bash pnpm theme={null}
pnpm add @arcium-hq/client@0.4.0 @coral-xyz/anchor@0.32.1
```
## 5. Update idl-build feature in program Cargo.toml
Add `arcium-anchor/idl-build` to the `idl-build` feature in your program's `Cargo.toml`:
```toml theme={null}
# Before v0.4.0
[features]
default = []
cpi = ["no-entrypoint"]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
idl-build = ["anchor-lang/idl-build"]
```
```toml theme={null}
# v0.4.0
[features]
default = []
cpi = ["no-entrypoint"]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
idl-build = ["anchor-lang/idl-build", "arcium-anchor/idl-build"]
```
## 6. (Optional) Add new Cargo.toml features and lints
Arcium v0.4.0 introduces new optional features and lints for improved developer experience. While not required, these are recommended additions to your program's `Cargo.toml`:
### New optional features
```toml theme={null}
[features]
default = []
cpi = ["no-entrypoint"]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
idl-build = ["anchor-lang/idl-build", "arcium-anchor/idl-build"]
# New optional features below
anchor-debug = []
custom-heap = []
custom-panic = []
```
### New lints section
Add the following lints section to help catch configuration issues:
```toml theme={null}
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] }
```
These additions help with debugging and ensure proper configuration for Solana programs.
## 7. Update init\_comp\_def call signature
The `init_comp_def` function signature has changed - the first boolean parameter has been removed:
```rust theme={null}
// Before v0.4.0
pub fn init_flip_comp_def(ctx: Context) -> Result<()> {
init_comp_def(ctx.accounts, true, 0, None, None)?;
Ok(())
}
```
```rust theme={null}
// v0.4.0
pub fn init_flip_comp_def(ctx: Context) -> Result<()> {
init_comp_def(ctx.accounts, 0, None, None)?;
Ok(())
}
```
## 8. Update queue\_computation call signature
The `queue_computation` function now requires a 6th parameter `num_callback_txs` to support multi-transaction callbacks:
```rust theme={null}
// Before v0.4.0
queue_computation(
ctx.accounts,
computation_offset,
args,
None,
vec![FlipCallback::callback_ix(&[])],
)?;
```
```rust theme={null}
// v0.4.0
queue_computation(
ctx.accounts,
computation_offset,
args,
None,
vec![FlipCallback::callback_ix(&[])],
1, // num_callback_txs: number of transactions needed for callback
)?;
```
The `num_callback_txs` parameter specifies how many transactions are needed to process the callback. For most simple computations, this will be `1`. Larger computations with extensive callback data may require multiple transactions.
## 9. Update derive\_cluster\_pda! macro
The `derive_cluster_pda!` macro now requires an error code as the second parameter:
```rust theme={null}
// Before v0.4.0
#[account(
mut,
address = derive_cluster_pda!(mxe_account)
)]
pub cluster_account: Account<'info, Cluster>,
```
```rust theme={null}
// v0.4.0
#[account(
mut,
address = derive_cluster_pda!(mxe_account, ErrorCode::ClusterNotSet)
)]
pub cluster_account: Account<'info, Cluster>,
```
Make sure you have the corresponding error code defined in your program:
```rust theme={null}
#[error_code]
pub enum ErrorCode {
#[msg("The computation was aborted")]
AbortedComputation,
#[msg("Cluster not set")]
ClusterNotSet,
}
```
## 10. Verify migration
After completing all migration steps, verify that everything works correctly:
### Build test
```bash theme={null}
# From your workspace root
arcium build
```
### Type checking
```bash theme={null}
# Ensure all new types compile correctly
cargo check --all
```
### Test your changes
```bash theme={null}
# Run your existing tests to ensure functionality is preserved
arcium test
```
## Complete example
Here's a complete before/after example of a typical computation function:
### Before v0.4.0:
```rust theme={null}
// rust-toolchain file (plain text)
1.88.0
// Cargo.toml (workspace)
[workspace]
members = ["programs/*", "encrypted-ixs"]
resolver = "2"
[patch.crates-io]
proc-macro2 = { git = 'https://github.com/arcium-hq/proc-macro2.git' }
// programs/coinflip/Cargo.toml
[dependencies]
anchor-lang = { version = "0.31.1", features = ["init-if-needed"] }
arcium-client = { version = "0.3.0", default-features = false }
arcium-macros = { version = "0.3.0" }
arcium-anchor = { version = "0.3.0" }
[features]
idl-build = ["anchor-lang/idl-build"]
// programs/coinflip/src/lib.rs
pub fn init_flip_comp_def(ctx: Context) -> Result<()> {
init_comp_def(ctx.accounts, true, 0, None, None)?;
Ok(())
}
pub fn flip(
ctx: Context,
computation_offset: u64,
user_choice: [u8; 32],
pub_key: [u8; 32],
nonce: u128,
) -> Result<()> {
let args = vec![
Argument::ArcisPubkey(pub_key),
Argument::PlaintextU128(nonce),
Argument::EncryptedU8(user_choice),
];
ctx.accounts.sign_pda_account.bump = ctx.bumps.sign_pda_account;
queue_computation(
ctx.accounts,
computation_offset,
args,
None,
vec![FlipCallback::callback_ix(&[])],
)?;
Ok(())
}
#[queue_computation_accounts("flip", payer)]
#[derive(Accounts)]
#[instruction(computation_offset: u64)]
pub struct Flip<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init_if_needed,
space = 9,
payer = payer,
seeds = [&SIGN_PDA_SEED],
bump,
address = derive_sign_pda!(),
)]
pub sign_pda_account: Account<'info, SignerAccount>,
#[account(
address = derive_mxe_pda!()
)]
pub mxe_account: Account<'info, MXEAccount>,
#[account(
mut,
address = derive_cluster_pda!(mxe_account)
)]
pub cluster_account: Account<'info, Cluster>,
// ... other accounts
}
```
### v0.4.0:
```rust theme={null}
// rust-toolchain.toml (TOML format)
[toolchain]
channel = "1.89.0"
components = ["rustfmt","clippy"]
profile = "minimal"
// Cargo.toml (workspace)
[workspace]
members = ["programs/*", "encrypted-ixs"]
resolver = "2"
// No [patch.crates-io] section
// programs/coinflip/Cargo.toml
[dependencies]
anchor-lang = { version = "0.32.1", features = ["init-if-needed"] }
arcium-client = { version = "0.4.0", default-features = false }
arcium-macros = { version = "0.4.0" }
arcium-anchor = { version = "0.4.0" }
[features]
idl-build = ["anchor-lang/idl-build", "arcium-anchor/idl-build"]
# Optional features
anchor-debug = []
custom-heap = []
custom-panic = []
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] }
// programs/coinflip/src/lib.rs
pub fn init_flip_comp_def(ctx: Context) -> Result<()> {
init_comp_def(ctx.accounts, 0, None, None)?; // Removed first boolean parameter
Ok(())
}
pub fn flip(
ctx: Context,
computation_offset: u64,
user_choice: [u8; 32],
pub_key: [u8; 32],
nonce: u128,
) -> Result<()> {
let args = vec![
Argument::ArcisPubkey(pub_key),
Argument::PlaintextU128(nonce),
Argument::EncryptedU8(user_choice),
];
ctx.accounts.sign_pda_account.bump = ctx.bumps.sign_pda_account;
queue_computation(
ctx.accounts,
computation_offset,
args,
None,
vec![FlipCallback::callback_ix(&[])],
1, // Added num_callback_txs parameter
)?;
Ok(())
}
#[queue_computation_accounts("flip", payer)]
#[derive(Accounts)]
#[instruction(computation_offset: u64)]
pub struct Flip<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init_if_needed,
space = 9,
payer = payer,
seeds = [&SIGN_PDA_SEED],
bump,
address = derive_sign_pda!(),
)]
pub sign_pda_account: Account<'info, SignerAccount>,
#[account(
address = derive_mxe_pda!()
)]
pub mxe_account: Account<'info, MXEAccount>,
#[account(
mut,
address = derive_cluster_pda!(mxe_account, ErrorCode::ClusterNotSet) // Added error code parameter
)]
pub cluster_account: Account<'info, Cluster>,
// ... other accounts
}
#[error_code]
pub enum ErrorCode {
#[msg("The computation was aborted")]
AbortedComputation,
#[msg("Cluster not set")]
ClusterNotSet, // Required for derive_cluster_pda! macro
}
```
That's it! Your program should now be compatible with Arcium tooling v0.4.0.
# v0.4.x to v0.5.1
Source: https://docs.arcium.com/developers/migration/migration-v0.4-to-v0.5
Migration guide from Arcium v0.4.x to v0.5.1
## 1. Arcium.toml backends (no change required)
`Arcium.toml` stays the same for this migration. Only the **Cerberus** backend is supported at the moment; **Manticore** is unavailable. If your config already uses Cerberus, no action is needed.
## 2. Update Arcium Rust dependencies
Update your dependencies to v0.5.1:
```bash theme={null}
# Update program dependencies
cd programs/your-program-name
cargo update --package arcium-client --precise 0.5.1
cargo update --package arcium-macros --precise 0.5.1
cargo update --package arcium-anchor --precise 0.5.1
# Update encrypted-ixs dependencies
cd ../../encrypted-ixs
cargo update --package arcis-imports --precise 0.5.1
```
## 3. Update Arcium TS dependencies
```bash npm theme={null}
npm install @arcium-hq/client@0.5.1
```
```bash yarn theme={null}
yarn add @arcium-hq/client@0.5.1
```
```bash pnpm theme={null}
pnpm add @arcium-hq/client@0.5.1
```
## 4. Update TypeScript environment variable usage
The environment variable and type have changed from a public key to a cluster offset:
```typescript theme={null}
// Before v0.5.1
const arciumEnv = getArciumEnv();
console.log(arciumEnv.arciumClusterPubkey); // PublicKey
```
```typescript theme={null}
// v0.5.1
const arciumEnv = getArciumEnv();
console.log(arciumEnv.arciumClusterOffset); // number
```
**Environment variable change:**
* Before: `ARCIUM_CLUSTER_PUBKEY` (base58 pubkey string)
* After: `ARCIUM_CLUSTER_OFFSET` (integer)
## 5. Update TypeScript SDK function names
Several functions have been renamed for consistency:
**Program address function:**
```typescript theme={null}
// Before v0.5.1
import { getArciumProgAddress } from "@arcium-hq/client";
const address = getArciumProgAddress();
```
```typescript theme={null}
// v0.5.1
import { getArciumProgramId } from "@arcium-hq/client";
const address = getArciumProgramId();
```
**Account data functions:**
```typescript theme={null}
// Before v0.5.1
const mempool = await getMempoolAccData(provider, address);
const execpool = await getExecutingPoolAccData(provider, address);
```
```typescript theme={null}
// v0.5.1
const mempool = await getMempoolAccInfo(provider, address);
const execpool = await getExecutingPoolAccInfo(provider, address);
```
**Parameter name updates:**
Functions now use `mxeProgramId` (camelCase) instead of `mxeProgramID`:
```typescript theme={null}
// Before v0.5.1
await getMXEPublicKey(provider, mxeProgramID);
await uploadCircuit(provider, name, mxeProgramID, circuit);
```
```typescript theme={null}
// v0.5.1
await getMXEPublicKey(provider, mxeProgramId);
await uploadCircuit(provider, name, mxeProgramId, circuit);
```
| Old Function | New Function |
| ---------------------------- | --------------------------- |
| `getArciumProgAddress()` | `getArciumProgramId()` |
| `getMempoolAccData()` | `getMempoolAccInfo()` |
| `getExecutingPoolAccData()` | `getExecutingPoolAccInfo()` |
| `getArciumProgramReadonly()` | `getArciumProgram()` |
| `getArxAccPDA()` | `getArxNodeAccAddress()` |
## 6. Update TypeScript PDA derivation functions
Several PDA derivation functions now take `clusterOffset` instead of `programId`.
Note: `getMXEAccAddress()` still uses `program.programId` - only cluster-related PDAs changed.
```typescript theme={null}
// Before v0.5.1
import {
getMXEAccAddress,
getMempoolAccAddress,
getExecutingPoolAccAddress,
getComputationAccAddress,
} from "@arcium-hq/client";
.accountsPartial({
computationAccount: getComputationAccAddress(
program.programId,
computationOffset
),
clusterAccount: arciumEnv.arciumClusterPubkey,
mxeAccount: getMXEAccAddress(program.programId),
mempoolAccount: getMempoolAccAddress(program.programId),
executingPool: getExecutingPoolAccAddress(program.programId),
})
```
```typescript theme={null}
// v0.5.1
import {
getMXEAccAddress,
getMempoolAccAddress,
getExecutingPoolAccAddress,
getComputationAccAddress,
getClusterAccAddress, // New import
} from "@arcium-hq/client";
.accountsPartial({
computationAccount: getComputationAccAddress(
arciumEnv.arciumClusterOffset, // Changed from program.programId
computationOffset
),
clusterAccount: getClusterAccAddress(arciumEnv.arciumClusterOffset), // Now derived
mxeAccount: getMXEAccAddress(program.programId), // Unchanged
mempoolAccount: getMempoolAccAddress(arciumEnv.arciumClusterOffset), // Changed
executingPool: getExecutingPoolAccAddress(arciumEnv.arciumClusterOffset), // Changed
})
```
## 7. Update queue\_computation call
The `queue_computation` function has two changes:
1. **New parameter**: `cu_price_micro: u64` for priority fees
2. **Updated callback\_ix**: Now requires `computation_offset` and `mxe_account`
```rust theme={null}
// Before v0.5.1
queue_computation(
ctx.accounts,
computation_offset,
args,
None,
vec![MyCallback::callback_ix(&[])],
1,
)?;
```
```rust theme={null}
// v0.5.1
queue_computation(
ctx.accounts,
computation_offset,
args,
None,
vec![MyCallback::callback_ix(
computation_offset,
&ctx.accounts.mxe_account,
&[]
)?],
1,
0, // cu_price_micro: priority fee in microlamports (0 = no priority fee)
)?;
```
**Priority fee notes:**
* Set `cu_price_micro` to `0` for standard processing
* Use higher values (e.g., `50000`) for faster processing during network congestion
## 8. Update onchain argument API
Replace the `vec![Argument::...]` pattern with the new `ArgBuilder` API:
```rust theme={null}
// Before v0.5.1
let args = vec![
Argument::ArcisPubkey(pub_key),
Argument::PlaintextU128(nonce),
Argument::EncryptedU8(ciphertext_0),
Argument::EncryptedU8(ciphertext_1),
];
```
```rust theme={null}
// v0.5.1
let args = ArgBuilder::new()
.x25519_pubkey(pub_key)
.plaintext_u128(nonce)
.encrypted_u8(ciphertext_0)
.encrypted_u8(ciphertext_1)
.build();
```
### Available ArgBuilder methods
| Method | Input Type | Description |
| ------------------------ | ---------- | ------------------------------------ |
| `plaintext_bool(value)` | `bool` | Boolean value |
| `plaintext_u8(value)` | `u8` | Unsigned 8-bit integer |
| `plaintext_i8(value)` | `i8` | Signed 8-bit integer |
| `plaintext_u16(value)` | `u16` | Unsigned 16-bit integer |
| `plaintext_i16(value)` | `i16` | Signed 16-bit integer |
| `plaintext_u32(value)` | `u32` | Unsigned 32-bit integer |
| `plaintext_i32(value)` | `i32` | Signed 32-bit integer |
| `plaintext_u64(value)` | `u64` | Unsigned 64-bit integer |
| `plaintext_i64(value)` | `i64` | Signed 64-bit integer |
| `plaintext_u128(value)` | `u128` | Unsigned 128-bit integer |
| `plaintext_i128(value)` | `i128` | Signed 128-bit integer |
| `plaintext_float(value)` | `f64` | Floating point |
| `plaintext_point(value)` | `[u8; 32]` | Elliptic curve point (new in v0.5.1) |
| Method | Input Type | Description |
| ------------------------ | ---------- | -------------------------- |
| `encrypted_bool(value)` | `[u8; 32]` | Encrypted boolean |
| `encrypted_u8(value)` | `[u8; 32]` | Encrypted unsigned 8-bit |
| `encrypted_i8(value)` | `[u8; 32]` | Encrypted signed 8-bit |
| `encrypted_u16(value)` | `[u8; 32]` | Encrypted unsigned 16-bit |
| `encrypted_i16(value)` | `[u8; 32]` | Encrypted signed 16-bit |
| `encrypted_u32(value)` | `[u8; 32]` | Encrypted unsigned 32-bit |
| `encrypted_i32(value)` | `[u8; 32]` | Encrypted signed 32-bit |
| `encrypted_u64(value)` | `[u8; 32]` | Encrypted unsigned 64-bit |
| `encrypted_i64(value)` | `[u8; 32]` | Encrypted signed 64-bit |
| `encrypted_u128(value)` | `[u8; 32]` | Encrypted unsigned 128-bit |
| `encrypted_i128(value)` | `[u8; 32]` | Encrypted signed 128-bit |
| `encrypted_float(value)` | `[u8; 32]` | Encrypted floating point |
| Method | Input Type | Description |
| -------------------------------- | ---------- | -------------------------------- |
| `x25519_pubkey(value)` | `[u8; 32]` | X25519 public key for encryption |
| `arcis_ed25519_signature(value)` | `[u8; 64]` | Ed25519 signature |
| Method | Input Type | Description |
| --------------------------------- | ------------------ | ------------------------------ |
| `account(pubkey, offset, length)` | `Pubkey, u32, u32` | Reference onchain account data |
## 9. Update onchain PDA macros
PDA derivation macros now require `mxe_account` and an error code parameter:
```rust theme={null}
// Before v0.5.1
#[account(
mut,
address = derive_mempool_pda!()
)]
pub mempool_account: UncheckedAccount<'info>,
#[account(
mut,
address = derive_execpool_pda!()
)]
pub executing_pool: UncheckedAccount<'info>,
#[account(
mut,
address = derive_comp_pda!(computation_offset)
)]
pub computation_account: UncheckedAccount<'info>,
```
Don't forget to update the cluster PDA as well:
```rust theme={null}
// Before v0.5.1
#[account(
mut,
address = derive_cluster_pda!()
)]
pub cluster_account: Account<'info, Cluster>,
```
```rust theme={null}
// v0.5.1
#[account(
mut,
address = derive_cluster_pda!(mxe_account, ErrorCode::ClusterNotSet)
)]
pub cluster_account: Account<'info, Cluster>,
```
```rust theme={null}
// v0.5.1
#[account(
mut,
address = derive_mempool_pda!(mxe_account, ErrorCode::ClusterNotSet)
)]
pub mempool_account: UncheckedAccount<'info>,
#[account(
mut,
address = derive_execpool_pda!(mxe_account, ErrorCode::ClusterNotSet)
)]
pub executing_pool: UncheckedAccount<'info>,
#[account(
mut,
address = derive_comp_pda!(
computation_offset,
mxe_account,
ErrorCode::ClusterNotSet
)
)]
pub computation_account: UncheckedAccount<'info>,
```
Make sure you have the `ClusterNotSet` error code defined (this should already exist from v0.4.0):
```rust theme={null}
#[error_code]
pub enum ErrorCode {
#[msg("The computation was aborted")]
AbortedComputation,
#[msg("Cluster not set")]
ClusterNotSet,
}
```
## 10. Update callback account structs
v0.5.1 requires three additional accounts in callback structs to support BLS signature verification:
```rust theme={null}
// Before v0.5.1
#[callback_accounts("my_circuit")]
#[derive(Accounts)]
pub struct MyCircuitCallback<'info> {
pub arcium_program: Program<'info, Arcium>,
#[account(address = derive_comp_def_pda!(COMP_DEF_OFFSET_MY_CIRCUIT))]
pub comp_def_account: Account<'info, ComputationDefinitionAccount>,
#[account(address = ::anchor_lang::solana_program::sysvar::instructions::ID)]
/// CHECK: instructions_sysvar
pub instructions_sysvar: AccountInfo<'info>,
}
```
```rust theme={null}
// v0.5.1
#[callback_accounts("my_circuit")]
#[derive(Accounts)]
pub struct MyCircuitCallback<'info> {
pub arcium_program: Program<'info, Arcium>,
#[account(address = derive_comp_def_pda!(COMP_DEF_OFFSET_MY_CIRCUIT))]
pub comp_def_account: Account<'info, ComputationDefinitionAccount>,
#[account(address = derive_mxe_pda!())]
pub mxe_account: Account<'info, MXEAccount>,
/// CHECK: computation_account, checked by arcium program via constraints in the callback context.
pub computation_account: UncheckedAccount<'info>,
#[account(address = derive_cluster_pda!(mxe_account, ErrorCode::ClusterNotSet))]
pub cluster_account: Account<'info, Cluster>,
#[account(address = ::anchor_lang::solana_program::sysvar::instructions::ID)]
/// CHECK: instructions_sysvar
pub instructions_sysvar: AccountInfo<'info>,
}
```
**New required accounts:**
* `mxe_account` - The MXE program account, derived using `derive_mxe_pda!()`
* `computation_account` - The computation account (unchecked, verified by Arcium program)
* `cluster_account` - The cluster account, derived from `mxe_account`
The order matters - `cluster_account` must come after `mxe_account` since its PDA derivation depends on the MXE account reference.
These accounts are required for the `verify_output()` call in your callback handler (see next section).
## 11. Update callback handling with BLS verification
v0.5.1 introduces BLS signature verification for computation outputs. Use `SignedComputationOutputs` and call `verify_output()` to validate results:
```rust theme={null}
// Before v0.5.1 - Direct output handling
#[arcium_callback(encrypted_ix = "my_circuit")]
pub fn my_circuit_callback(
ctx: Context,
output: ComputationOutputs,
) -> Result<()> {
let result = output.unwrap();
// Use result directly...
Ok(())
}
```
```rust theme={null}
// v0.5.1 - BLS verified outputs
#[arcium_callback(encrypted_ix = "my_circuit")]
pub fn my_circuit_callback(
ctx: Context,
output: SignedComputationOutputs,
) -> Result<()> {
// verify_output() validates the BLS signature from the MXE cluster
let result = match output.verify_output(
&ctx.accounts.cluster_account,
&ctx.accounts.computation_account
) {
Ok(MyCircuitOutput { field_0 }) => field_0,
Err(e) => {
msg!("Computation verification failed: {}", e);
return Err(ErrorCode::AbortedComputation.into())
},
};
// Now use the verified result
emit!(ResultEvent {
value: result.ciphertexts[0],
nonce: result.nonce.to_le_bytes(),
});
Ok(())
}
```
**Key changes:**
* Replace `ComputationOutputs` with `SignedComputationOutputs`
* Always call `verify_output()` to validate BLS signatures
* Handle the `Result` - verification can fail if signatures don't match
* `verify_output()` requires `cluster_account` and `computation_account` references
**Error codes:**
* `BLSSignatureVerificationFailed` - The BLS signature doesn't match
* `InvalidClusterBLSPublicKey` - Cluster's BLS key is not set
* `AbortedComputation` - The computation failed in the MXE
## 12. (Optional) Update callback macros for multi-transaction callbacks
If you need multiple callback functions for the same encrypted instruction, use the new `skip_name_validation` parameter:
```rust theme={null}
// Primary callback (standard naming)
#[arcium_callback(encrypted_ix = "add_order")]
pub fn add_order_callback(
ctx: Context,
output: SignedComputationOutputs,
) -> Result<()> {
// Handle primary callback with BLS verification
let result = output.verify_output(
&ctx.accounts.cluster_account,
&ctx.accounts.computation_account
)?;
Ok(())
}
// Secondary callback (non-standard naming)
#[arcium_callback(
encrypted_ix = "add_order",
auto_serialize = false,
skip_name_validation = true
)]
pub fn emit_order_event(
ctx: Context,
output: RawComputationOutputs>
) -> Result<()> {
// Handle secondary callback (e.g., emit event)
Ok(())
}
```
## 13. (Optional) Use `circuit_hash!` macro for offchain circuits
If using offchain circuit storage, replace placeholder hashes with verified hashes:
```rust theme={null}
// Before v0.5.1
hash: [0u8; 32],
```
```rust theme={null}
// v0.5.1
use arcium_macros::circuit_hash;
hash: circuit_hash!("my_circuit"),
```
See [Deployment: handling large circuits](/developers/deployment#handling-large-circuits-with-offchain-storage) for complete offchain circuit configuration.
## 14. Verify migration
After completing all migration steps, verify that everything works correctly:
### Build test
```bash theme={null}
# From your workspace root
arcium build
```
### Type checking
```bash theme={null}
# Ensure all new types compile correctly
cargo check --all
```
### Test your changes
```bash theme={null}
# Run your existing tests to ensure functionality is preserved
arcium test
```
## 15. Complete example
Here's a complete before/after example of a typical computation function:
### Before v0.5.1:
```rust theme={null}
// Arcium.toml
[localnet]
nodes = 2
localnet_timeout_secs = 60
// programs/coinflip/src/lib.rs
pub fn flip(
ctx: Context,
computation_offset: u64,
user_choice: [u8; 32],
pub_key: [u8; 32],
nonce: u128,
) -> Result<()> {
let args = vec![
Argument::ArcisPubkey(pub_key),
Argument::PlaintextU128(nonce),
Argument::EncryptedU8(user_choice),
];
ctx.accounts.sign_pda_account.bump = ctx.bumps.sign_pda_account;
queue_computation(
ctx.accounts,
computation_offset,
args,
None,
vec![FlipCallback::callback_ix(&[])],
1,
)?;
Ok(())
}
#[queue_computation_accounts("flip", payer)]
#[derive(Accounts)]
#[instruction(computation_offset: u64)]
pub struct Flip<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
mut,
address = derive_mempool_pda!()
)]
pub mempool_account: UncheckedAccount<'info>,
#[account(
mut,
address = derive_execpool_pda!()
)]
pub executing_pool: UncheckedAccount<'info>,
#[account(
mut,
address = derive_comp_pda!(computation_offset)
)]
pub computation_account: UncheckedAccount<'info>,
// ... other accounts
}
```
```typescript theme={null}
// tests/coinflip.ts
const arciumEnv = getArciumEnv();
await program.methods
.flip(computationOffset, encryptedChoice, pubKey, nonce)
.accountsPartial({
computationAccount: getComputationAccAddress(program.programId, computationOffset),
clusterAccount: arciumEnv.arciumClusterPubkey,
mempoolAccount: getMempoolAccAddress(program.programId),
executingPool: getExecutingPoolAccAddress(program.programId),
})
.rpc();
```
### v0.5.1:
```rust theme={null}
// Arcium.toml
[localnet]
nodes = 2
localnet_timeout_secs = 60
backends = ["Cerberus"]
// programs/coinflip/src/lib.rs
use arcium_anchor::prelude::*;
const COMP_DEF_OFFSET_FLIP: u32 = comp_def_offset("flip");
pub fn flip(
ctx: Context,
computation_offset: u64,
user_choice: [u8; 32],
pub_key: [u8; 32],
nonce: u128,
) -> Result<()> {
let args = ArgBuilder::new()
.x25519_pubkey(pub_key)
.plaintext_u128(nonce)
.encrypted_u8(user_choice)
.build();
ctx.accounts.sign_pda_account.bump = ctx.bumps.sign_pda_account;
queue_computation(
ctx.accounts,
computation_offset,
args,
None,
vec![FlipCallback::callback_ix(
computation_offset,
&ctx.accounts.mxe_account,
&[]
)?],
1,
0, // cu_price_micro: priority fee (0 = no priority)
)?;
Ok(())
}
// Callback with BLS verification
#[arcium_callback(encrypted_ix = "flip")]
pub fn flip_callback(
ctx: Context,
output: SignedComputationOutputs,
) -> Result<()> {
let result = match output.verify_output(
&ctx.accounts.cluster_account,
&ctx.accounts.computation_account
) {
Ok(FlipOutput { field_0 }) => field_0,
Err(e) => {
msg!("Error: {}", e);
return Err(ErrorCode::AbortedComputation.into())
},
};
emit!(FlipResultEvent {
result: result.ciphertexts[0],
nonce: result.nonce.to_le_bytes(),
});
Ok(())
}
#[callback_accounts("flip")]
#[derive(Accounts)]
pub struct FlipCallback<'info> {
pub arcium_program: Program<'info, Arcium>,
#[account(address = derive_comp_def_pda!(COMP_DEF_OFFSET_FLIP))]
pub comp_def_account: Account<'info, ComputationDefinitionAccount>,
#[account(address = derive_mxe_pda!())]
pub mxe_account: Account<'info, MXEAccount>,
/// CHECK: computation_account, checked by arcium program
pub computation_account: UncheckedAccount<'info>,
#[account(address = derive_cluster_pda!(mxe_account, ErrorCode::ClusterNotSet))]
pub cluster_account: Account<'info, Cluster>,
#[account(address = ::anchor_lang::solana_program::sysvar::instructions::ID)]
/// CHECK: instructions_sysvar
pub instructions_sysvar: AccountInfo<'info>,
}
#[queue_computation_accounts("flip", payer)]
#[derive(Accounts)]
#[instruction(computation_offset: u64)]
pub struct Flip<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
mut,
address = derive_mempool_pda!(mxe_account, ErrorCode::ClusterNotSet)
)]
pub mempool_account: UncheckedAccount<'info>,
#[account(
mut,
address = derive_execpool_pda!(mxe_account, ErrorCode::ClusterNotSet)
)]
pub executing_pool: UncheckedAccount<'info>,
#[account(
mut,
address = derive_comp_pda!(
computation_offset,
mxe_account,
ErrorCode::ClusterNotSet
)
)]
pub computation_account: UncheckedAccount<'info>,
// ... other accounts
}
```
```typescript theme={null}
// tests/coinflip.ts
import {
getClusterAccAddress,
getMXEAccAddress,
getMempoolAccAddress,
getExecutingPoolAccAddress,
getComputationAccAddress,
getCompDefAccAddress,
getCompDefAccOffset,
getArciumEnv,
} from "@arcium-hq/client";
const arciumEnv = getArciumEnv();
await program.methods
.flip(computationOffset, encryptedChoice, pubKey, nonce)
.accountsPartial({
computationAccount: getComputationAccAddress(
arciumEnv.arciumClusterOffset,
computationOffset
),
clusterAccount: getClusterAccAddress(arciumEnv.arciumClusterOffset),
mxeAccount: getMXEAccAddress(program.programId),
mempoolAccount: getMempoolAccAddress(arciumEnv.arciumClusterOffset),
executingPool: getExecutingPoolAccAddress(arciumEnv.arciumClusterOffset),
compDefAccount: getCompDefAccAddress(
program.programId,
Buffer.from(getCompDefAccOffset("flip")).readUInt32LE()
),
})
.rpc({ skipPreflight: true, commitment: "confirmed" });
```
That's it! Your program should now be compatible with Arcium tooling v0.5.1.
## 16. Summary of breaking changes
| Change | Before v0.5.1 | v0.5.1 |
| --------------------- | ------------------------------------ | ------------------------------------------------------------------------ |
| `queue_computation` | 6 parameters | 7 parameters (add `cu_price_micro`) |
| `callback_ix` | `callback_ix(&[])` | `callback_ix(computation_offset, &mxe_account, &[])?` |
| Callback output | `ComputationOutputs` | `SignedComputationOutputs` with `verify_output()` |
| PDA functions | `program.programId` | `arciumEnv.arciumClusterOffset` |
| Environment | `ARCIUM_CLUSTER_PUBKEY` | `ARCIUM_CLUSTER_OFFSET` |
| PDA macros | `derive_*_pda!()` | `derive_*_pda!(mxe_account, ErrorCode::ClusterNotSet)` |
| TS Program ID | `getArciumProgAddress()` | `getArciumProgramId()` |
| TS Account Data | `getMempoolAccData()` | `getMempoolAccInfo()` |
| TS Parameter | `mxeProgramID` | `mxeProgramId` (camelCase) |
| `init_comp_def` | `init_comp_def(accs, 0, None, None)` | `init_comp_def(accs, None, None)` |
| `ArgBuilder` pubkey | `.arcis_x25519_pubkey(key)` | `.x25519_pubkey(key)` |
| Callback struct | 3 accounts | 6 accounts (add `mxe_account`, `computation_account`, `cluster_account`) |
| Offchain circuit hash | `[0u8; 32]` placeholder | `circuit_hash!("circuit_name")` |
# v0.5.x to v0.6.3
Source: https://docs.arcium.com/developers/migration/migration-v0.5-to-v0.6
Migrate from Arcium v0.5.x to v0.6.3 with program redeploy
This release requires a **program redeploy** due to a change in the Arcium program ID. Follow this guide to migrate your MXE from v0.5.x to v0.6.3.
## 1. Redeploy required
The Arcium program ID has changed. You must redeploy your MXE program to use v0.6.3.
| Version | Program ID |
| ------- | ---------------------------------------------- |
| v0.5.x | `BpaW2ZmCJnDwizWY8eM34JtVqp2kRgnmQcedSVc9USdP` |
| v0.6.3 | `Arcj82pX7HxYKLR92qvgZUAd7vGS1k4hQvAFcPATFdEQ` |
After updating dependencies and code (steps below), run:
```bash theme={null}
arcium deploy --cluster-offset --recovery-set-size --keypair-path --rpc-url
```
## 2. Update Rust dependencies
Update your program dependencies to v0.6.3:
```bash theme={null}
cd programs/your-program-name
cargo update --package arcium-client --precise 0.6.3
cargo update --package arcium-macros --precise 0.6.3
cargo update --package arcium-anchor --precise 0.6.3
```
Update your encrypted-ixs dependencies:
```bash theme={null}
cd ../../encrypted-ixs
cargo update --package arcis --precise 0.6.3
```
`arcis-imports` no longer exists in v0.6.3. You must migrate to the `arcis` crate (see section 7).
## 3. Update TypeScript dependencies
```bash npm theme={null}
npm install @arcium-hq/client@0.6.3
```
```bash yarn theme={null}
yarn add @arcium-hq/client@0.6.3
```
```bash pnpm theme={null}
pnpm add @arcium-hq/client@0.6.3
```
## 4. Rename SignerAccount to ArciumSignerAccount (Rust)
The `SignerAccount` type has been renamed to `ArciumSignerAccount`. Update all references in your program:
```rust theme={null}
// Before v0.6
#[account(
init_if_needed,
payer = payer,
space = 8 + 1,
seeds = [b"SignerAccount"],
bump,
address = derive_sign_pda!(),
)]
pub sign_pda_account: Account<'info, SignerAccount>,
```
```rust theme={null}
// v0.6.3
#[account(
init_if_needed,
payer = payer,
space = 8 + 1,
seeds = [b"ArciumSignerAccount"],
bump,
address = derive_sign_pda!(),
)]
pub sign_pda_account: Account<'info, ArciumSignerAccount>,
```
The seed in the `seeds` attribute must also be updated from `b"SignerAccount"` to `b"ArciumSignerAccount"`.
## 5. Update PDA seed in TypeScript
If you derive the signer PDA manually in your TypeScript code, update the seed:
```typescript theme={null}
// Before v0.6
const signPda = PublicKey.findProgramAddressSync(
[Buffer.from("SignerAccount")],
program.programId
)[0];
```
```typescript theme={null}
// v0.6.3
const signPda = PublicKey.findProgramAddressSync(
[Buffer.from("ArciumSignerAccount")],
program.programId
)[0];
```
## 6. (Optional) Configure Arcium.toml for non-localnet testing
v0.6.3 adds the `--cluster` flag to run tests against devnet, mainnet, or custom clusters directly.
### Prerequisites
| Mode | Requirements |
| ------------------ | --------------------------------------------------------- |
| Localnet (default) | Docker running, ports available |
| Remote clusters | Cluster offset in `Arcium.toml`, RPC URL in `Anchor.toml` |
### Configure cluster offsets
Add cluster configurations to your `Arcium.toml`:
```toml theme={null}
[localnet]
nodes = 2
localnet_timeout_secs = 60
backends = ["Cerberus"]
# Cluster config for devnet testing
# Note: Cluster 123 is v0.5.4, cluster 456 is v0.7.0
[clusters.devnet]
offset = 456
```
### Run tests
```bash theme={null}
# Localnet (default) - starts Docker, validator, Arx nodes
arcium test
# Devnet - uses cluster config from Arcium.toml
arcium test --cluster devnet
```
### RPC configuration
For non-localnet testing, configure your RPC endpoint in `Anchor.toml`:
```toml theme={null}
[provider]
cluster = "devnet"
wallet = "~/.config/solana/id.json"
```
Non-localnet tests skip Docker, local validator, and Arx nodes entirely. The CLI reads the cluster offset from `Arcium.toml` and sets the `ARCIUM_CLUSTER_OFFSET` environment variable for your tests.
If you run `arcium test --cluster devnet` without configuring `[clusters.devnet]` in `Arcium.toml`, you'll see an error with instructions to add the configuration.
## 7. Migrate from arcis-imports to arcis
The `arcis-imports` crate no longer exists in v0.6.3. You must migrate to the `arcis` crate:
```toml theme={null}
# Before v0.6 (Cargo.toml)
[dependencies]
arcis-imports = "0.5.1"
```
```toml theme={null}
# v0.6.3 (Cargo.toml)
[dependencies]
arcis = "0.6.3"
blake3 = "=1.8.2"
```
Blake3 must be pinned to exactly 1.8.2. Newer versions use Rust edition 2024, which is incompatible with Anchor programs.
In your circuit code, update the import:
```rust theme={null}
// Before v0.6
use arcis_imports::*;
```
```rust theme={null}
// v0.6.3
use arcis::*;
```
## 8. Add `mut` to clock\_account
The `clock_account` now requires the `mut` attribute in your account constraints:
```rust theme={null}
// Before v0.6
#[account(
address = ARCIUM_CLOCK_ACCOUNT_ADDRESS
)]
pub clock_account: Account<'info, ClockAccount>,
```
```rust theme={null}
// v0.6.3
#[account(
mut,
address = ARCIUM_CLOCK_ACCOUNT_ADDRESS
)]
pub clock_account: Account<'info, ClockAccount>,
```
## 9. Verify migration
After completing all steps, verify your migration:
```bash theme={null}
# Build
arcium build
# Type check
cargo check --all
# Run tests
arcium test
```
## 10. Summary of breaking changes
| Change | Before v0.6 | v0.6.3 |
| ---------------------- | ---------------------------------------------- | ---------------------------------------------- |
| Program ID | `BpaW2ZmCJnDwizWY8eM34JtVqp2kRgnmQcedSVc9USdP` | `Arcj82pX7HxYKLR92qvgZUAd7vGS1k4hQvAFcPATFdEQ` |
| Signer Account Type | `SignerAccount` | `ArciumSignerAccount` |
| Signer PDA Seed | `"SignerAccount"` | `"ArciumSignerAccount"` |
| Rust Dependencies | `0.5.x` | `0.6.3` |
| TypeScript Client | `@arcium-hq/client@0.5.x` | `@arcium-hq/client@0.6.3` |
| Arcis Crate | `arcis-imports` | `arcis` |
| blake3 (encrypted-ixs) | Not required | `blake3 = "=1.8.2"` |
| clock\_account | No `mut` | Requires `mut` |
# v0.6.3 to v0.7.0
Source: https://docs.arcium.com/developers/migration/migration-v0.6.3-to-v0.7.0
Migrate from Arcium v0.6.3 to v0.7.0 with LUT support and tree-shaking
This guide covers upgrading from v0.6.3 to v0.7.0. The main changes are new Address Lookup Table (LUT) support, tree-shaking support, and callback\_url removal.
## 1. Update Rust dependencies
```bash theme={null}
cd programs/your-program-name
cargo update --package arcium-client --precise 0.7.0
cargo update --package arcium-macros --precise 0.7.0
cargo update --package arcium-anchor --precise 0.7.0
```
```bash theme={null}
cd ../../encrypted-ixs
cargo update --package arcis --precise 0.7.0
```
## 2. Update TypeScript dependencies
```bash npm theme={null}
npm install @arcium-hq/client@0.7.0
```
```bash yarn theme={null}
yarn add @arcium-hq/client@0.7.0
```
```bash pnpm theme={null}
pnpm add @arcium-hq/client@0.7.0
```
## 3. New: Address lookup table support
v0.7.0 adds Address Lookup Table (LUT) support, enabling more space in callback transactions for user data. Add two new accounts to your computation definition structs.
### Rust: add LUT accounts
Add these fields to your `InitCompDef` structs:
```rust theme={null}
#[account(mut, address = derive_mxe_lut_pda!(mxe_account.lut_offset_slot))]
/// CHECK: address_lookup_table, checked by arcium program.
pub address_lookup_table: UncheckedAccount<'info>,
#[account(address = LUT_PROGRAM_ID)]
/// CHECK: lut_program is the Address Lookup Table program.
pub lut_program: UncheckedAccount<'info>,
```
Import `LUT_PROGRAM_ID` from `arcium_anchor`. The `lut_offset_slot` is stored in the MXE account.
### TypeScript: add LUT address to accounts
Use the new `getLookupTableAddress` function:
```typescript theme={null}
import {
getMXEAccAddress,
getLookupTableAddress,
getArciumProgram,
} from "@arcium-hq/client";
const arciumProgram = getArciumProgram(provider as anchor.AnchorProvider);
const mxeAccount = getMXEAccAddress(program.programId);
const mxeAcc = await arciumProgram.account.mxeAccount.fetch(mxeAccount);
const lutAddress = getLookupTableAddress(program.programId, mxeAcc.lutOffsetSlot);
const sig = await program.methods
.initAddTogetherCompDef()
.accounts({
compDefAccount: compDefPDA,
payer: owner.publicKey,
mxeAccount,
addressLookupTable: lutAddress,
})
.signers([owner])
.rpc();
```
`uploadCircuit()` is now idempotent - it checks circuit state before uploading and skips if already finalized.
## 4. Tree-shaking support
Both `@arcium-hq/client` and `@arcium-hq/reader` now include `"sideEffects": false` in their package.json, enabling bundlers to tree-shake unused code for smaller bundle sizes. No code changes required - this is automatic when using modern bundlers like Webpack, Rollup, or esbuild.
## 5. Remove callback\_url from queue\_computation
The `callback_url` parameter has been removed from `queue_computation`.
**Before (v0.6.3):**
```rust theme={null}
queue_computation(
ctx.accounts,
computation_offset,
args,
None, // callback_url - REMOVE THIS
vec![...],
1,
0,
)?;
```
**After (v0.7.0):**
```rust theme={null}
queue_computation(
ctx.accounts,
computation_offset,
args,
vec![...],
1,
0,
)?;
```
## 6. Verify migration
```bash theme={null}
arcium build
cargo check --all
arcium test
```
## 7. Changes summary
| Change | v0.6.3 | v0.7.0 |
| ----------------------- | ---------------------------------- | ---------------------------------------------------- |
| LUT accounts | N/A | New: `address_lookup_table` + `lut_program` required |
| `derive_mxe_lut_pda!` | N/A | `derive_mxe_lut_pda!(mxe_account.lut_offset_slot)` |
| `getLookupTableAddress` | N/A | `getLookupTableAddress(programId, lutOffsetSlot)` |
| `queue_computation` | Has `callback_url: Option` | Parameter removed |
| Tree-shaking | Not supported | `sideEffects: false` in client and reader |
| Rust Dependencies | 0.6.3 | 0.7.0 |
| TypeScript Client | @arcium-hq/client\@0.6.3 | @arcium-hq/client\@0.7.0 |
# v0.7.0 to v0.8.0
Source: https://docs.arcium.com/developers/migration/migration-v0.7.0-to-v0.8.0
Migration guide from Arcium v0.7.0 to v0.8.0
This guide covers upgrading from v0.7.0 to v0.8.0. The main changes are version bumps, tooling updates, and a new `--skip-local-circuit` flag for `arcium test`. There are no Rust or TypeScript API breaking changes.
## 1. Update Arcium tooling
```bash theme={null}
arcup self update
arcup update
```
This updates `arcup` itself, then updates the Arcium CLI.
Verify:
```bash theme={null}
arcium --version
```
## 2. Update Rust dependencies
```bash theme={null}
cd programs/your-program-name
cargo update --package arcium-client --precise 0.8.0
cargo update --package arcium-macros --precise 0.8.0
cargo update --package arcium-anchor --precise 0.8.0
```
```bash theme={null}
cd ../../encrypted-ixs
cargo update --package arcis --precise 0.8.0
```
## 3. Update TypeScript dependencies
```bash npm theme={null}
npm install @arcium-hq/client@0.8.0
```
```bash yarn theme={null}
yarn add @arcium-hq/client@0.8.0
```
```bash pnpm theme={null}
pnpm add @arcium-hq/client@0.8.0
```
## 4. Verify migration
```bash theme={null}
arcium build
cargo check --all
arcium test
```
If your project uses offchain circuits, use `arcium test --skip-local-circuit` to skip running them locally. This reduces load and speeds up test runs.
## 5. Changes summary
| Change | v0.7.0 | v0.8.0 |
| ---------------------------------- | ------------------------- | ---------------------------------------------- |
| `arcium test --skip-local-circuit` | N/A | New: skip offchain circuits in local test runs |
| Rust dependencies | 0.7.0 | 0.8.0 |
| TypeScript client | `@arcium-hq/client@0.7.0` | `@arcium-hq/client@0.8.0` |
# v0.8.0 to v0.9.x
Source: https://docs.arcium.com/developers/migration/migration-v0.8.0-to-v0.9.0
Upgrade from v0.8.0 to v0.9.x: CLI flag changes, mxe-keys merged into mxe-info, and Arcis improvements
This guide covers upgrading from v0.8.0 to v0.9.x, including new features added in v0.9.7. The main changes are version bumps, CLI flag changes, the `mxe-keys` command being merged into `mxe-info`, and several Arcis and CLI improvements.
## 1. Update Arcium tooling
```bash theme={null}
arcup self update
arcup update
```
Verify:
```bash theme={null}
arcium --version
```
## 2. Update Rust dependencies
```bash theme={null}
cd programs/your-program-name
cargo update --package arcium-client --precise 0.9.7
cargo update --package arcium-macros --precise 0.9.7
cargo update --package arcium-anchor --precise 0.9.7
```
```bash theme={null}
cd ../../encrypted-ixs
cargo update --package arcis --precise 0.9.7
```
## 3. Update TypeScript dependencies
```bash npm theme={null}
npm install @arcium-hq/client@0.9.7
```
```bash yarn theme={null}
yarn add @arcium-hq/client@0.9.7
```
```bash pnpm theme={null}
pnpm add @arcium-hq/client@0.9.7
```
Multiple functions now accept an optional `confirmOptions` parameter (`ConfirmOptions` from `@solana/web3.js`) to control transaction confirmation behavior (commitment level, preflight checks, etc.). Affected functions: `uploadCircuit()`, `initMxePart1()`, `initMxePart2()`, `recoverMxe()`, `initKeyRecoveryExecution()`, `submitKeyRecoveryShare()`, and `finalizeKeyRecoveryExecution()`.
## 4. CLI changes
### Deploy and `init-mxe` commands
The short flag for keypair changed from `-kp` to `-k` due to the migration to clap v4 (which requires single-character short flags). If you use the long form `--keypair-path`, no changes are needed.
```bash theme={null}
# Before (v0.8.0)
arcium deploy -kp ~/.config/solana/id.json ...
# After (v0.9.0)
arcium deploy -k ~/.config/solana/id.json ...
# Or use the long form (unchanged):
arcium deploy --keypair-path ~/.config/solana/id.json ...
```
### `--authority` flag removed
The `--authority` / `-a` flag has been **removed** from both `deploy` and `init-mxe` commands. The MXE authority is now always set to the keypair signer (payer). If you previously used `--authority` to specify a separate MXE authority pubkey, you must now use the intended authority keypair directly via `--keypair-path`.
### `mxe-keys` removed
The `arcium mxe-keys` command has been removed. Its output (X25519, Ed25519, ElGamal keys) is now included in `arcium mxe-info`.
### `mxe-info` expanded
`arcium mxe-info` now additionally displays:
* MXE status (Active/Migration)
* X25519 public key
* Ed25519 verifying key
* ElGamal public key
* Recovery cluster peer offsets
## 5. Node operators
Update your Docker image tag in `docker-compose.yml`:
```yaml theme={null}
# Before
image: arcium/arx-node:v0.8.5
# After
image: arcium/arx-node:v0.9.7
```
Then pull and restart:
```bash theme={null}
docker compose pull
docker compose up -d
```
## 6. Verify migration
```bash theme={null}
arcium build
cargo check --all
arcium test
```
## 7. Changes summary
| Change | v0.8.0 | v0.9.x |
| ------------------ | -------------------------------- | -------------------------------------------- |
| Deploy short flag | `-kp` | `-k` |
| `--authority` flag | Available on `deploy`/`init-mxe` | Removed (keypair signer is always authority) |
| MXE key info | `arcium mxe-keys` | Merged into `arcium mxe-info` |
| Rust dependencies | 0.8.0 | 0.9.x |
| TypeScript client | `@arcium-hq/client@0.8.0` | `@arcium-hq/client@0.9.x` |
| Arx node Docker | `v0.8.5` | `v0.9.x` |
## New in v0.9.x
These features are new in v0.9.x. See the linked documentation for details:
* **BaseField25519 division operations**: `safe_inverse()`, `field_division()`, and `euclidean_division()` methods on `BaseField25519`. See [Primitives](/developers/arcis/primitives#basefield25519-operations).
* **New macros**: `include_bytes!()`, `include!()`, and `encrypted_mod!()` for code organization and data embedding. See [Operations](/developers/arcis/operations#macros).
* **Cluster migration**: New `migrate-cluster` CLI command to move MXEs between clusters. See [Deployment](/developers/deployment#cluster-migration).
* **`claimComputationRent` helper**: New TypeScript client function to reclaim SOL rent from finalized computation accounts. See [JS Client Library](/developers/js-client-library#reclaiming-computation-rent).
* **`--resume` flag**: Resume interrupted `deploy`, `init-mxe`, and `init-key-recovery-material` operations instead of restarting from scratch.
* **Shell completions**: `arcium completions ` generates tab-completion scripts for bash, zsh, fish, elvish, and PowerShell.
* **Arcis module improvements**: `#[instruction]` works in nested submodules, `super::` paths reference parent modules, and `crate::` paths work with `assert_current_module!`. See [Operations](/developers/arcis/operations#code-organization-with-modules).
# v0.9.x to v0.10.x
Source: https://docs.arcium.com/developers/migration/migration-v0.9.0-to-v0.10.0
Upgrade from v0.9.x to v0.10.x: Anchor v1, Solana CLI 3, account close lifecycle, and Arcis pattern matching
This guide covers upgrading from v0.9.x to v0.10.x. The main breaking changes come from the Anchor v1 / Solana CLI 3 toolchain bump, removed key-recovery helper APIs, and a few Arcium helper signature changes. v0.10.x also adds close-account commands, an interruptible `migrate-cluster`, Arcis `match` / `if let` / `matches!` support, and an update-notification banner.
## Before you start
v0.10.x is a code-and-toolchain upgrade. Existing v0.9.x MXE, cluster, and key-recovery accounts remain compatible. Computation definitions initialized with `finalization_authority = None` remain layout-compatible. In v0.9.x, this was the third argument to `init_comp_def`. Recreate or validate any v0.9.x computation definition that used `Some(finalization_authority)` before relying on it. Only redeploy your app program if you rebuild or change it for v0.10.x.
This guide covers Arcium-specific migration work. Anchor v1 has additional program, interface definition language (IDL), dependency, and CI changes. Before deploying a rebuilt program, run through Solana Foundation's [Anchor v0.32 to v1 migration checklist](https://github.com/solana-foundation/solana-dev-skill/blob/main/skill/references/anchor/migrating-v0.32-to-v1.md), especially if your program publishes an onchain IDL, uses `declare_program!`, raw `AccountInfo`, direct SPL Token dependencies, Borsh helpers, or external cross-program invocation (CPI) crates. Close legacy IDL accounts with the v0.32 CLI while the old program is still live.
## 1. Update Arcium tooling
```bash theme={null}
arcup self update
arcup update
```
Verify:
```bash theme={null}
arcium --version
```
You should see `0.10.3`.
## 2. Update Solana and Anchor toolchains
Both toolchains have major version bumps. Install them before rebuilding.
**Solana CLI 3.1.10** (was 2.3.0):
```bash theme={null}
sh -c "$(curl -sSfL https://release.anza.xyz/v3.1.10/install)"
solana --version
```
**Anchor 1.0.2** (was 0.32.1). The `avm` source moved from `coral-xyz/anchor` to `solana-foundation/anchor`:
```bash theme={null}
cargo install --git https://github.com/solana-foundation/anchor avm --locked --force
avm install 1.0.2
avm use 1.0.2
anchor --version
```
## 3. Update Rust dependencies
In your program's `Cargo.toml`:
```toml theme={null}
# Before
anchor-lang = { version = "0.32.1", features = ["init-if-needed"] }
anchor-spl = "0.32.1"
# After
anchor-lang = { version = "1.0.2", features = ["init-if-needed"] }
anchor-spl = "1.0.2"
```
Refresh the Arcium crate locks from your project root:
```bash theme={null}
cargo update --manifest-path programs/your-program-name/Cargo.toml --package arcium-client --precise 0.10.3
cargo update --manifest-path programs/your-program-name/Cargo.toml --package arcium-macros --precise 0.10.3
cargo update --manifest-path programs/your-program-name/Cargo.toml --package arcium-anchor --precise 0.10.3
```
```bash theme={null}
cargo update --manifest-path encrypted-ixs/Cargo.toml --package arcis --precise 0.10.3
```
If your `encrypted-ixs/Cargo.toml` pins `blake3` or `proc-macro-crate`, you can drop those pins. They were Anchor 0.32 workarounds and are no longer needed.
This is the minimum Arcium dependency bump. Use the Anchor v1 checklist linked in [Before you start](#before-you-start) for workspace `resolver`, direct `solana-*` crates, LiteSVM, SPL Token interface crates, and external cross-program invocation (CPI) dependencies.
## 4. Update TypeScript dependencies
The Anchor npm package was renamed:
```jsonc theme={null}
// Before
"@coral-xyz/anchor": "0.32.1"
// After
"@anchor-lang/core": "^1.0.2"
```
Bump the Arcium clients to 0.10.3:
```bash npm theme={null}
npm uninstall @coral-xyz/anchor
npm install @anchor-lang/core@^1.0.2 @arcium-hq/client@0.10.3 @arcium-hq/reader@0.10.3
```
```bash yarn theme={null}
yarn remove @coral-xyz/anchor
yarn add @anchor-lang/core@^1.0.2 @arcium-hq/client@0.10.3 @arcium-hq/reader@0.10.3
```
```bash pnpm theme={null}
pnpm remove @coral-xyz/anchor
pnpm add @anchor-lang/core@^1.0.2 @arcium-hq/client@0.10.3 @arcium-hq/reader@0.10.3
```
Update every import:
```ts theme={null}
// Before
import * as anchor from "@coral-xyz/anchor";
import { Program, AnchorProvider, BN } from "@coral-xyz/anchor";
// After
import * as anchor from "@anchor-lang/core";
import { Program, AnchorProvider, BN } from "@anchor-lang/core";
```
TypeScript is also bumped to `^5.7.3` (was `^4.3.5`). Update if you pin it.
## 5. Anchor program changes
This section covers the Anchor v1 changes that commonly show up in Arcium programs. It is not a full Anchor v1 migration checklist.
### Instructions sysvar address constant
`anchor_lang::solana_program::sysvar::instructions` no longer exists. Use the constant re-exported by `arcium-anchor`, and switch `AccountInfo` to `UncheckedAccount` (Anchor 1 no longer accepts `AccountInfo<'info>` in account structs):
```rust theme={null}
// Before
#[account(address = ::anchor_lang::solana_program::sysvar::instructions::ID)]
/// CHECK: instructions_sysvar, checked by the account constraint
pub instructions_sysvar: AccountInfo<'info>,
// After
#[account(address = ::arcium_anchor::solana_instructions_sysvar::ID)]
/// CHECK: instructions_sysvar, checked by the account constraint
pub instructions_sysvar: UncheckedAccount<'info>,
```
### Box-wrap heavy accounts on the queue side
Anchor 1 tightened stack-frame limits. Large Arcium accounts (`MXEAccount`, `Cluster`, `ComputationDefinitionAccount`) blow the stack on the queue path unless boxed. Wrap them in `Box<...>` in your `#[queue_computation_accounts]` struct:
```rust theme={null}
// Before
pub mxe_account: Account<'info, MXEAccount>,
pub cluster_account: Account<'info, Cluster>,
pub comp_def_account: Account<'info, ComputationDefinitionAccount>,
// After
pub mxe_account: Box>,
pub cluster_account: Box>,
pub comp_def_account: Box>,
```
Callback-side accounts (`#[callback_accounts]`) can stay un-boxed: the callback path has less stack pressure.
### Computation-definition helper changes
Replace deprecated `init_comp_def` calls with `init_computation_def`. The old third `finalization_authority` argument was removed:
```rust theme={null}
// Before
init_comp_def(ctx.accounts, None, None)?;
// After
init_computation_def(ctx.accounts, None)?;
```
Generated `init*CompDef` TypeScript methods also no longer take a `finalizationAuth` argument:
```ts theme={null}
// Before
await program.methods.initAddTogetherCompDef(finalizationAuth).rpc();
// After
await program.methods.initAddTogetherCompDef().rpc();
```
The PDA helpers that took `ErrorCode::ClusterNotSet` now use one-argument forms. The MXE cluster is non-optional in v0.10.x, so the macros read `mxe_account.cluster` directly and no longer need a caller-supplied local error:
```rust theme={null}
// Before
derive_mempool_pda!(mxe_account, ErrorCode::ClusterNotSet)
derive_execpool_pda!(mxe_account, ErrorCode::ClusterNotSet)
derive_comp_pda!(computation_offset, mxe_account, ErrorCode::ClusterNotSet)
derive_cluster_pda!(mxe_account, ErrorCode::ClusterNotSet)
// After
derive_mempool_pda!(mxe_account)
derive_execpool_pda!(mxe_account)
derive_comp_pda!(computation_offset, mxe_account)
derive_cluster_pda!(mxe_account)
```
If `ErrorCode::ClusterNotSet` was the only reason your `#[error_code]` enum defined it, you can remove the variant to keep the IDL clean and avoid a dead-code warning. (The variant still exists in Arcium's core error enum; this is purely about your own program's local enum.)
```rust theme={null}
// Optional cleanup if no longer referenced
#[error_code]
pub enum ErrorCode {
#[msg("The computation was aborted")]
AbortedComputation,
// Removed: ClusterNotSet was only used by the deprecated PDA macro args above.
}
```
### SPL token CPI: pass `Pubkey`, not `AccountInfo`
`CpiContext::new` / `new_with_signer` now expect a `Pubkey` for the program argument:
```rust theme={null}
// Before
let cpi_program = ctx.accounts.token_program.to_account_info();
// After
let cpi_program = ctx.accounts.token_program.key();
```
This only affects programs that do SPL transfers.
## 6. TypeScript client changes
Beyond the package rename:
### `initMxePart2` argument order changed
`recoveryPeers` moved from position 4 to position 6. Positional callers will silently pass wrong values: review every call site.
```ts theme={null}
// Before
initMxePart2(
provider,
clusterOffset,
mxeProgramId,
recoveryPeers,
keygenOffset,
keyRecoveryInitOffset,
lutOffset,
);
// After
initMxePart2(
provider,
clusterOffset,
mxeProgramId,
keygenOffset,
keyRecoveryInitOffset,
recoveryPeers,
lutOffset,
);
```
The following helpers were removed from `@arcium-hq/client`. Key recovery now runs through CLI flows (`arcium init-mxe --resume` and `arcium migrate-cluster --resume`):
* `queueKeyRecoveryInit`
* `recoverMxe`
* `initKeyRecoveryExecution`
* `submitKeyRecoveryShare`
* `finalizeKeyRecoveryExecution`
If your `@arcium-hq/reader` code subscribes to computation events, the callback parameter type narrowed from `ArciumEventData` to `ComputationEventData`. The runtime shape is unchanged; update annotations only.
Avoid asserting a fixed number of cluster accounts in tests. Localnet runs without a recovery cluster can return only the primary cluster account:
```ts theme={null}
// Brittle: assumes the localnet always has a recovery cluster
expect(clusterAccs.length).to.equal(2);
// After: filter by offset instead
const primary = clusterAccs.find(c => c.offset === clusterOffset);
```
## 7. CLI changes
`init-key-recovery-material` and `migrate-cluster --skip-recovery` have been removed. Their replacements are split by lifecycle:
* interrupted MXE initialization / key-recovery-material setup: rerun `arcium init-mxe --resume`
* interrupted cluster migration: use `arcium migrate-cluster --resume` or `--abort`
```bash theme={null}
# Resume an interrupted cluster migration
arcium migrate-cluster \
--keypair-path \
--rpc-url \
--resume
# Abort a stuck cluster migration
arcium migrate-cluster \
--keypair-path \
--rpc-url \
--abort
```
`--cluster-offset` is required on the initial call only; with `--resume`/`--abort` the CLI infers it from onchain state.
### `init-mxe --resume` now covers key-recovery material
`arcium init-mxe --resume` existed in v0.9.x. In v0.10.x it also resumes the key-recovery-material step that used to live behind `init-key-recovery-material`.
Keep passing `--recovery-set-size `. The minimum is `4`, but larger clusters may require more. If the value does not match onchain state, the CLI prints the expected `--cluster-offset` and `--recovery-set-size`.
```bash theme={null}
arcium init-mxe \
--keypair-path ~/.config/solana/id.json \
--callback-program \
--cluster-offset \
--recovery-set-size 8 \
--rpc-url \
--resume
```
### `arcium test --detach` skips the auto-snapshot
Non-detached `arcium test` runs now auto-call `arcium snapshot-mxe-keygen` at the end so subsequent `--skip-keygen` runs can reuse cached MXE keys. With `--detach` this step is skipped: the validator outlives the CLI, so you must call `arcium snapshot-mxe-keygen --rpc-url l` yourself before tearing the validator down:
```bash theme={null}
arcium test --detach
# ... interact with the running validator ...
arcium snapshot-mxe-keygen --rpc-url l
# then teardown
```
### `--skip-keygen` exports `ARCIUM_SKIP_KEY_RECOVERY_INIT=1`
When `arcium test --skip-keygen` or `arcium localnet --skip-keygen` reuses cached MXE keys, the CLI also sets `ARCIUM_SKIP_KEY_RECOVERY_INIT=1` in the test environment. Use it to skip legacy key-recovery-init setup when the MXE starts in a post-keygen state. Custom tests that always queue key-recovery-init will fail on `--skip-keygen` reruns without honoring this flag.
### `arcium test --test-name` rewrites Anchor.toml
`--test-name ` runs only `tests/.ts` by temporarily rewriting the `[scripts] test` glob in `Anchor.toml` from `*.ts` to `.ts`. The CLI reverts the file on success. If a run terminates abnormally (Ctrl-C, SIGKILL), the rewrite may persist. Restore the original glob manually if so.
### `arcium mxe-info` output changes
Two cosmetic shifts in `mxe-info` output: `Ed25519 verifying key` was renamed to `ArcisEd25519 pubkey`, and a new `Key recovery material status: Ok|Not finalized` line was added. Scripts parsing `mxe-info` output need updating.
For closing MXEs and computation definitions, see [Account lifecycle and closing](/developers/program/account-lifecycle).
## 8. Anchor.toml cleanup
The `[registry]` block was stale in v0.9.x scaffolds and can be removed:
```toml theme={null}
# Stale: remove
[registry]
url = "https://api.apr.dev"
```
The `[test.validator] startup_wait = 10000` block is optional and scaffold-dependent. The v0.10.3 example programs drop it, but `arcium init` still appends it for new projects. Leave it in place if your tests need the longer validator startup wait. Some v0.10.3 examples include an empty `[hooks]` section; matching it is optional.
## 9. Arcis circuit changes
Item shadowing of a let-binding is now rejected. Code like this used to compile and won't anymore:
```rust theme={null}
// Rejected in v0.10.x
let f = || false;
fn f() -> bool { true }
f();
```
A `let` can still shadow a previous `let`. Only mixing items (`fn`, `const`) with same-named values is rejected.
Arcis also gained `match`, `if let`, and `matches!` support. Let chains require `edition = "2024"` in `encrypted-ixs/Cargo.toml`. See [Operations](/developers/arcis/operations#pattern-matching).
## 10. Node operators
Update your Docker image tag in `docker-compose.yml`:
```yaml theme={null}
# Before
image: arcium/arx-node:v0.9.7
# After
image: arcium/arx-node:v0.10.3
```
Then pull and restart:
```bash theme={null}
docker compose pull
docker compose up -d
```
If you run a trusted dealer, the config schema changed: fields moved under a `[dealer]` section, `local_addr` was renamed to `local_ip`, and a new required `master_seed_path` was added. Treat `master_seed_path` as the highest-sensitivity host secret: leaking it compromises all keyshares. See [Node setup](/developers/node-setup#step-6-configure-your-node).
## 11. Troubleshooting
* **Anchor/sysvar compile errors**: check [Anchor program changes](#5-anchor-program-changes).
* **`init_comp_def` not found**: use `init_computation_def(ctx.accounts, None)` or the `OffChain` form in [Computation definition accounts](/developers/program/computation-def-accs#offchain-circuit-sources).
* **PDA helper macro arity errors**: remove the second `ErrorCode::ClusterNotSet` argument.
* **`init-key-recovery-material` missing**: rerun `arcium init-mxe --resume`.
* **`migrate-cluster --skip-recovery` missing**: use `--resume` or `--abort`.
* **Other Anchor v1 compile errors**: run through the Anchor v0.32 to v1 checklist linked in [Before you start](#before-you-start).
* **Close-account errors**: see [Account lifecycle and closing](/developers/program/account-lifecycle#error-reference).
## 12. Verify migration
Before deploying, complete the Anchor v1 checklist linked in [Before you start](#before-you-start), including legacy IDL cleanup if your program publishes an onchain IDL.
```bash theme={null}
arcium build
cargo check --all
arcium test
```
## 13. Changes summary
| Change | v0.9.x | v0.10.x |
| ---------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------- |
| Solana CLI | 2.3.0 | 3.1.10 |
| Anchor CLI | 0.32.1 | 1.0.2 |
| `avm` source | `coral-xyz/anchor` | `solana-foundation/anchor` |
| `anchor-lang` / `anchor-spl` | 0.32.1 | 1.0.2 |
| npm Anchor package | `@coral-xyz/anchor` | `@anchor-lang/core` |
| TypeScript | `^4.3.5` | `^5.7.3` |
| Instructions sysvar | `::anchor_lang::solana_program::sysvar::instructions::ID` | `::arcium_anchor::solana_instructions_sysvar::ID` |
| Sysvar field type | `AccountInfo<'info>` | `UncheckedAccount<'info>` |
| Queue-side MXE accounts | `Account<'info, MXEAccount>` | `Box>` (same for Cluster, CompDef) |
| SPL CPI program arg | `token_program.to_account_info()` | `token_program.key()` |
| Comp-def init helper | `init_comp_def(ctx.accounts, None, None)` | `init_computation_def(ctx.accounts, None)` |
| Generated comp-def TS | `initFooCompDef(finalizationAuth)` | `initFooCompDef()` |
| PDA helper arity | `derive_*_pda!(..., ErrorCode::ClusterNotSet)` | `derive_*_pda!(...)` |
| `initMxePart2` arg order | `recoveryPeers` at position 4 | `recoveryPeers` at position 6 |
| Key recovery TS helpers | `recoverMxe`, `queueKeyRecoveryInit`, `submitKeyRecoveryShare`, … | Removed: driven by `init-mxe --resume` / `migrate-cluster --resume` |
| `migrate-cluster` flags | `--skip-recovery` | `--resume` / `--abort` state machine |
| `init-key-recovery-material` | CLI subcommand | Removed (folded into `init-mxe --resume`) |
| Arx node Docker | `v0.9.7` | `v0.10.3` |
| Arcium Rust crates | 0.9.x | 0.10.3 |
| TypeScript clients | `@arcium-hq/{client,reader}@0.9.x` | `@arcium-hq/{client,reader}@0.10.3` |
## New in v0.10.x
These features are new in v0.10.x. See the linked documentation for details:
* **Close MXE / computation definition**: `arcium deactivate-computation-definition` → TTL → `close-computation-definition` and `close-mxe` reclaim rent from accounts you no longer need. See [Account lifecycle and closing](/developers/program/account-lifecycle).
* **Interruptible cluster migration**: `arcium migrate-cluster --resume` / `--abort` replace `--skip-recovery`. `arcium init-mxe --resume` also resumes key-recovery-material setup. See [Deployment](/developers/deployment#cluster-migration).
* **Faster localnet reruns**: `arcium snapshot-mxe-keygen --rpc-url l` + `arcium localnet --skip-keygen` / `arcium test --skip-keygen` reuse cached MXE keys between runs.
* **Run a single test**: `arcium test --test-name ` restricts the run to `tests/.ts`.
* **Arcis pattern matching**: `match`, `if let`, and the `matches!` macro are now supported. Let chains require `edition = "2024"` in `encrypted-ixs/Cargo.toml`. See [Operations](/developers/arcis/operations#pattern-matching).
* **Update-notification banner**: `arcium` and `arcup` show a one-line banner when a new release is available. Suppress with `ARCIUM_NO_UPDATE_CHECK=1`; the banner is automatically silent in CI.
* **Better callback failure errors**: callback failures now report a `CircuitFailureReason` (offchain fetch failed, hash mismatch, CU mismatch, serialization, …) instead of a single generic error.
* **`arcup version`**: prints the currently-active toolchain version (alias `arcup v`).
## What's next?
Deploy and manage your MXE.
Use the new pattern-matching syntax in your circuits.
# Node setup
Source: https://docs.arcium.com/developers/node-setup
Run an Arx node: hardware requirements and network registration
## Overview
As a Node Operator, you'll set up your own Arx node to participate in the Arcium Network. This guide walks you through each step of the process.
First, you'll prepare your environment by installing the necessary tools and generating security keys. Then, you'll get your node registered onchain and configure it to run. Finally, you'll connect to other nodes in a cluster and start doing computations.
By the end, you will:
* Install the Arcium tooling
* Generate required keypairs
* Fund accounts with SOL
* Initialize onchain node accounts
* Configure your node
* Join or create a cluster
* Deploy your node with Docker
## Prerequisites
Before starting, ensure you have the following installed:
* **Rust**: Install from [rustup.rs](https://rustup.rs/)
* **Solana CLI 3.1.10**: Install from [Solana's documentation](https://docs.solana.com/cli/install-solana-cli-tools)
* **Docker & Docker Compose**: Install from [Docker's documentation](https://docs.docker.com/get-docker/)
* **OpenSSL**: Install from [OpenSSL's documentation](https://www.openssl.org/source/) (usually pre-installed on macOS/Linux)
* **Git**: For cloning repositories and version control
You'll also need:
* A reliable internet connection
* Basic familiarity with command-line tools
**Recommended system requirements:**
| Resource | Recommendation |
| --------- | ------------------------------------ |
| RAM | 32GB+ |
| CPU | 12+ cores, 2.8GHz+ base |
| Bandwidth | 1 Gbit/s minimum |
| Disk | Minimal (node is not disk-intensive) |
| GPU | Not required |
**Network requirements - open these ports:**
| Port | Protocol | Purpose |
| ---- | --------- | -------------------------- |
| 8001 | TCP & UDP | MPC protocol communication |
| 8002 | TCP & UDP | BLS signature aggregation |
| 8012 | TCP & UDP | TD preprocessing |
| 8013 | TCP & UDP | TD registration |
| 9091 | TCP | Prometheus metrics |
**Windows users:** Arcium doesn't run natively on Windows yet. Use Windows Subsystem for Linux (WSL2) with Ubuntu to follow this guide.
## Step 1: Set up your workspace
Create a dedicated folder for your node setup to keep everything organized:
```bash theme={null}
mkdir arcium-node-setup
cd arcium-node-setup
```
Stay in this directory for all remaining steps. All file paths and Docker commands assume you're working from `arcium-node-setup/`.
You'll also need to know your public IP address for the next steps. Here's a quick way to find it:
```bash theme={null}
curl https://ipecho.net/plain ; echo
```
## Step 2: Install Arcium tooling
The Arcium tooling suite includes the CLI and Arx node software. Install it using the automated installer:
```bash theme={null}
curl --proto '=https' --tlsv1.2 -sSfL https://install.arcium.com/ | bash
```
This script will:
* Check for all required dependencies
* Install `arcup` (Arcium's version manager)
* Install the latest Arcium CLI
* Install the Arx node software
Verify the installation:
```bash theme={null}
arcium --version && arcup --version
```
If you prefer manual installation, see the [installation guide](/developers/installation) for detailed instructions.
## Step 3: Generate required keypairs
Your Arx node needs five different keypairs for secure operation. Create these in your `arcium-node-setup` directory:
### 3.1 Node authority keypair
This Solana keypair identifies your node and handles onchain operations:
```bash theme={null}
solana-keygen new --outfile node-keypair.json --no-bip39-passphrase
```
The `--no-bip39-passphrase` flag creates a keypair without a passphrase for easier automation.
### 3.2 Callback authority keypair
This Solana keypair signs callback computations and must be different from your node keypair for security separation:
```bash theme={null}
solana-keygen new --outfile callback-kp.json --no-bip39-passphrase
```
### 3.3 Identity keypair
This keypair handles node-to-node communication and must be in PKCS#8 format:
```bash theme={null}
openssl genpkey -algorithm Ed25519 -out identity.pem
```
### 3.4 BLS keypair
This keypair is used for BLS (Boneh-Lynn-Shacham) threshold signatures on MPC computation callbacks. Generate it using the Arcium CLI:
```bash theme={null}
arcium gen-bls-key bls-keypair.json
```
This creates a 32-byte private key stored as a JSON array format.
### 3.5 X25519 keypair
This keypair is used for encrypted communication between nodes:
```bash theme={null}
arcium generate-x25519 -o x25519-keypair.json
```
This creates a 32-byte X25519 private key stored in JSON array format.
Keep these keypairs safe and private. Back them up to a secure location outside your VPS - you'll need them to restore your node if something goes wrong. Never share them with anyone.
## Step 4: Fund your accounts
Your node and callback accounts need SOL for transaction fees. Transfer SOL to both accounts and verify balances:
```bash theme={null}
# Check your account addresses
solana address --keypair node-keypair.json
solana address --keypair callback-kp.json
# Verify balances
solana balance "$(solana address --keypair node-keypair.json)"
solana balance "$(solana address --keypair callback-kp.json)"
```
On devnet, you can use `solana airdrop 2 -u devnet` or the [web faucet](https://faucet.solana.com/) to get free SOL for testing.
On mainnet, SOL has real economic value. Ensure you have sufficient funds before proceeding.
## Step 5: Initialize node accounts
Now we'll register your node with the Arcium Network by creating its onchain accounts. This step tells the blockchain about your node and its capabilities.
Always pass `--rpc-url ` to `arcium` commands. `solana config set --url ...` affects Solana CLI commands only; `arcium` defaults to mainnet when `--rpc-url` is omitted.
For guidance on choosing a reliable endpoint, see the [RPC Provider Recommendations](#rpc-provider-recommendations).
Use the `init-arx-accs` command to initialize all required onchain accounts for your node:
```bash theme={null}
arcium init-arx-accs \
--keypair-path node-keypair.json \
--callback-keypair-path callback-kp.json \
--peer-keypair-path identity.pem \
--bls-keypair-path bls-keypair.json \
--x25519-keypair-path x25519-keypair.json \
--node-offset \
--ip-address \
--rpc-url
```
### Required parameters:
* `--keypair-path`: Path to your node authority keypair
* `--callback-keypair-path`: Path to your callback authority keypair
* `--peer-keypair-path`: Path to your identity keypair (PEM format)
* `--bls-keypair-path`: Path to your BLS keypair (JSON array format)
* `--x25519-keypair-path`: Path to your X25519 keypair (JSON array format)
* `--node-offset`: Your node's unique ID number on the network. Choose any unique number. If you get an error during setup saying your number is already taken, just pick a different one and try again.
* `--ip-address`: Your node's public IP address
* `--rpc-url`: Solana RPC endpoint (mainnet or devnet)
If successful, you'll see confirmation that your node accounts have been initialized onchain.
## Step 6: Configure your node
The configuration file specifies which network to connect to, how to communicate with other nodes, and various operational settings.
Create a `node-config.toml` file in your `arcium-node-setup` directory:
```toml theme={null}
[node]
offset = # Your node offset from step 5
computations_limit = 10 # Max computations the node executes concurrently
[solana]
endpoint_rpc = "" # e.g., https://api.mainnet-beta.solana.com or https://api.devnet.solana.com
endpoint_wss = "" # Replace with your RPC provider WebSocket URL (e.g., wss://api.mainnet-beta.solana.com)
secondary_endpoint_rpc = "" # Failover RPC, ideally a different provider
secondary_endpoint_wss = "" # Failover WebSocket, ideally a different provider
```
On v0.11.x, `computations_limit`, `secondary_endpoint_rpc`, and `secondary_endpoint_wss` are required: the config fails to parse without them. The node fails over to the secondary endpoints when the primary RPC lags or stops responding, so point them at a different provider than your primary endpoints.
**Upgrading from v0.7.0?** The `[network]` section, `cluster`, `commitment`, `hardware_claim`, `starting_epoch`, and `ending_epoch` fields have been removed. Update your `node-config.toml` to the simplified format above.
### Trusted dealer config
If you operate a trusted dealer, the config expects every field under `[dealer]` and requires `master_seed_path`:
```toml theme={null}
[dealer]
private_key_path = "/usr/trusted-dealer/identity.json"
master_seed_path = "/usr/trusted-dealer/master_seed.json"
cluster_offsets = [456]
solana_rpc_url = ""
n_peers = 4
local_ip = "0.0.0.0"
rate_limit_initial_tokens = 10000000
rate_limit_tokens_per_second = 100000
rate_limit_max_tokens = 10000000
```
Replace `456` and `4` with your cluster offset and peer count.
`local_addr` was renamed to `local_ip`. Treat `master_seed_path` as the highest-sensitivity host secret; leaking it compromises keyshares.
## Step 7: Cluster operations
Clusters are groups of nodes that collaborate on MPC computations. For background on cluster concepts, see [Clusters overview](/clusters/overview).
Most operators should **join an existing cluster**. Only create your own cluster if you're coordinating a group of nodes.
To join an existing cluster, you must first be **proposed by the cluster authority**. Once proposed, accept the invitation:
```bash theme={null}
arcium join-cluster true \
--keypair-path node-keypair.json \
--node-offset