Arcium
Encrypted computation on Solana via MPC. Data stays encrypted during computation. The arcium CLI (wraps Anchor) handles init, build, test, and deploy — use MCP for current flags and options.
MCP Tools: search_arcium_docs for discovery (returns page path), then query_docs_filesystem_arcium_docs with cat <path>.mdx for full-page reads (e.g., cat /developers/arcis/mental-model.mdx).
When to Use
Use when:
- You need trustless computation -- cryptographically guaranteed, no single party sees the data
- Multiple parties compute on combined data without revealing inputs
- On-chain state must remain encrypted but computable
- Privacy: sealed-bid auctions, voting, hidden game state, dark pools, confidential DeFi
Constraints:
- Fixed loop bounds required (no variable-length iteration)
Mental Model
Arcium apps have three coupled surfaces. Most bugs are mismatches across their boundaries:
| Surface |
Responsibility |
Common Boundary Bugs |
| Circuit (Arcis/Rust) |
Pure fixed-shape MPC logic |
Variable loops, dynamic collections, .reveal() inside conditionals |
| Program (Anchor/Rust) |
Orchestration: init + queue + callback |
Macro name mismatch, callback accounts not writable, wrong ArgBuilder order |
| Client (TypeScript) |
Key exchange, encryption, submission, decryption |
Nonce reuse, missing .x25519_pubkey() for Shared, param order ≠ circuit order |
MPC constraints (from how secret sharing works):
- Both branches of
if/else execute unless the condition is a compile-time constant — cost = sum of both branches, not max
- Loops must have fixed bounds — no
while, break, continue
- Comparisons are expensive; arithmetic (add/multiply) is nearly free
.reveal() and .from_arcis() cannot be called inside conditionals (exception: compile-time constant conditions)
- All data must be fixed-size — no
Vec, String, HashMap; use [T; N]
Intent Router
Identify what you're building, then read the linked reference before coding. For API details, CLI flags, deployment, and versions, use MCP directly.
| Intent |
Read |
MCP Query |
| First Arcium app |
minimal-circuit.md |
"hello world tutorial" |
| Choose a pattern (stateless, stateful, multi-party) |
patterns.md |
"arcium examples" |
Circuit syntax (#[encrypted], #[instruction]) |
patterns.md |
"arcis encrypted instruction" |
| Shared vs Mxe encryption |
See Encryption Context below |
"Shared vs Mxe encryption" |
| ArgBuilder ordering / ciphertext errors |
troubleshooting.md -- ArgBuilder Ordering Errors |
"ArgBuilder encrypted plaintext" |
| Callback not firing / computation stuck |
troubleshooting.md -- Computation Never Finalizes |
"arcium_callback queue_computation" |
| Nonce / decryption errors |
troubleshooting.md -- Nonce Errors |
"RescueCipher encrypt nonce" |
| Client-side encryption (RescueCipher, x25519) |
minimal-circuit.md -- Test section |
"RescueCipher encrypt nonce" |
| Threshold signing / secure randomness |
— |
"MXESigningKey sign" or "ArcisRNG" |
| Deployment (devnet/mainnet) |
— |
"arcium deploy cluster-offset" |
| Version / installation requirements |
— |
"arcium installation anchor solana" |
Core Pattern: Three Functions
Every computation needs three functions in your Solana program:
| Function |
Purpose |
When Called |
init_<name>_comp_def |
Initialize computation definition |
Once per instruction |
<name> |
Build args + queue computation |
Each request |
<name>_callback |
Handle result from Arx nodes |
After MPC completes |
const COMP_DEF_OFFSET_FLIP: u32 = comp_def_offset("flip");
// 1. INIT (once per instruction type)
pub fn init_flip_comp_def(ctx: Context<InitFlipCompDef>) -> Result<()> {
init_comp_def(ctx.accounts, None, None)
}
// 2. QUEUE (each computation)
pub fn flip(ctx: Context<Flip>, offset: u64, ...) -> Result<()> {
let args = ArgBuilder::new()...build();
queue_computation(ctx.accounts, offset, args,
vec![FlipCallback::callback_ix(offset, &ctx.accounts.mxe_account, &[])?],
1, 0,
)?;
Ok(())
}
// 3. CALLBACK (after MPC completes)
#[arcium_callback(encrypted_ix = "flip")]
pub fn flip_callback(ctx: Context<FlipCallback>,
output: SignedComputationOutputs<FlipOutput>) -> Result<()> {
let result = output.verify_output(...)?;
// Use result...
}
Encryption size: RescueCipher encrypts any scalar to 32 bytes regardless of type.
Formula: ciphertext_size = 32 * number_of_scalar_values. See troubleshooting.md for the full size table.
Encryption Context
| Scenario |
Use |
| User inputs, results returned to user |
Enc<Shared, T> |
| Internal state users shouldn't access |
Enc<Mxe, T> |
| State persisted across computations |
Enc<Mxe, T> |
| Final reveal to all parties |
.reveal() |
Gotchas
Reference during development to avoid common mistakes.
NEVER:
- NEVER reuse a nonce — every
cipher.encrypt() call needs a fresh randomBytes(16)
- NEVER combine multiple ciphertexts into one ArgBuilder call — each encrypted scalar is its own
[u8; 32] call
- NEVER omit
.x25519_pubkey() for Enc<Shared, T> (silent failure); Enc<Mxe, T> skips it
Critical (silent failures)
- Macro string matching: All macro strings must exactly match
#[instruction] fn NAME across #[arcium_callback], comp_def_offset(), #[init_computation_definition_accounts], #[queue_computation_accounts], #[callback_accounts]
- ArgBuilder ordering: Calls must match circuit parameter order left-to-right. For
Enc<Shared, T>: .x25519_pubkey() then .plaintext_u128(nonce) then ciphertexts. For Enc<Mxe, T>: .plaintext_u128(nonce) then ciphertexts. Missing .x25519_pubkey() for Shared = silent failure.
- Division by secret zero: Guard divisors with the safe divisor pattern -- both branches execute in MPC, so the division always runs. See patterns.md — Safe Division.
- Combined ciphertext arrays: Each encrypted scalar needs a separate
[u8; 32] ArgBuilder call — do NOT pass [u8; 64] for a two-scalar type. See troubleshooting.md — Ciphertext Size Mismatch.
Warning (wrong results)
- Nonce reuse: Same nonce for multiple encryptions = garbled output. Use unique
randomBytes(16) per encryption.
- Callback account writability: Pass extra accounts via
CallbackAccount { pubkey, is_writable: true } in callback_ix(..., &[...]). Also mark #[account(mut)] in callback struct. Accounts cannot be created or resized during callbacks.
- Output struct naming: Circuit
fn add_together generates AddTogetherOutput. Single returns use field_0 (a SharedEncryptedStruct<1> or MXEEncryptedStruct<1> with .ciphertexts and .nonce). Tuple returns nest field_0, field_1, etc.
Tips
- Prefer arithmetic over comparisons (cheaper in MPC)
- Comparisons/divisions are cheaper with narrower types (
u64 vs u128); storage cost is identical
Debug Triage Order
Start here when a computation fails or returns wrong results.
When a computation fails, returns wrong results, or never finalizes — check in this order:
- Names match exactly —
#[instruction] fn NAME must match across #[arcium_callback(encrypted_ix = "NAME")], comp_def_offset("NAME"), and all account macros
- Comp def initialized —
init_*_comp_def must be called once before any computation
- ArgBuilder param order — calls must match circuit fn parameters left-to-right
- Shared params include pubkey —
.x25519_pubkey() before .plaintext_u128(nonce) before ciphertexts (missing = silent failure)
- Nonce is unique — fresh
randomBytes(16) per encryption, same nonce passed to program
- Callback registered and writable —
callback_ix(...) passed in queue_computation call, accounts set in BOTH CallbackAccount { pubkey, is_writable: true } AND #[account(mut)] in callback struct
- Environment correct — cluster offset matches network, MXE public key available (retry with backoff), RPC endpoint reliable
For detailed error solutions: troubleshooting.md
Verification Checklist
Pre-deploy gate. Run through before deploying or submitting a PR.
Circuit:
Program:
Client:
Deploy:
Resources
1---2name: arcium3description: Build and debug encrypted Solana applications with Arcium — data stays private during computation, no single party sees it. Use when writing Arcis circuits (#[encrypted], #[instruction]), wiring Anchor programs with init/queue_computation/callback flows, choosing Shared vs Mxe encrypted state, encrypting inputs with @arcium-hq/client (RescueCipher, x25519), or debugging ArgBuilder ordering, nonce, callback, or computation finalization failures. Covers dark pools, sealed-bid auctions, encrypted voting, hidden game state, confidential DeFi, secure randomness, and threshold signing. Also use for getting started with your first Arcium app.4license: MIT5---67# Arcium89Encrypted computation on Solana via MPC. Data stays encrypted during computation. The `arcium` CLI (wraps Anchor) handles init, build, test, and deploy — use MCP for current flags and options.1011**MCP Tools**: `search_arcium_docs` for discovery (returns page path), then `query_docs_filesystem_arcium_docs` with `cat <path>.mdx` for full-page reads (e.g., `cat /developers/arcis/mental-model.mdx`).1213## When to Use1415**Use when:**16- You need trustless computation -- cryptographically guaranteed, no single party sees the data17- Multiple parties compute on combined data without revealing inputs18- On-chain state must remain encrypted but computable19- Privacy: sealed-bid auctions, voting, hidden game state, dark pools, confidential DeFi2021**Constraints:**22- Fixed loop bounds required (no variable-length iteration)2324## Mental Model2526Arcium apps have three coupled surfaces. Most bugs are mismatches across their boundaries:2728| Surface | Responsibility | Common Boundary Bugs |29|---------|---------------|----------------------|30| **Circuit** (Arcis/Rust) | Pure fixed-shape MPC logic | Variable loops, dynamic collections, `.reveal()` inside conditionals |31| **Program** (Anchor/Rust) | Orchestration: init + queue + callback | Macro name mismatch, callback accounts not writable, wrong ArgBuilder order |32| **Client** (TypeScript) | Key exchange, encryption, submission, decryption | Nonce reuse, missing `.x25519_pubkey()` for Shared, param order ≠ circuit order |3334**MPC constraints** (from how secret sharing works):35- Both branches of `if/else` execute unless the condition is a compile-time constant — cost = sum of both branches, not max36- Loops must have fixed bounds — no `while`, `break`, `continue`37- Comparisons are expensive; arithmetic (add/multiply) is nearly free38- `.reveal()` and `.from_arcis()` cannot be called inside conditionals (exception: compile-time constant conditions)39- All data must be fixed-size — no `Vec`, `String`, `HashMap`; use `[T; N]`4041## Intent Router4243Identify what you're building, then read the linked reference before coding. For API details, CLI flags, deployment, and versions, use MCP directly.4445| Intent | Read | MCP Query |46|--------|------|-----------|47| First Arcium app | [minimal-circuit.md](examples/minimal-circuit.md) | "hello world tutorial" |48| Choose a pattern (stateless, stateful, multi-party) | [patterns.md](examples/patterns.md) | "arcium examples" |49| Circuit syntax (`#[encrypted]`, `#[instruction]`) | [patterns.md](examples/patterns.md) | "arcis encrypted instruction" |50| Shared vs Mxe encryption | See [Encryption Context](#encryption-context) below | "Shared vs Mxe encryption" |51| ArgBuilder ordering / ciphertext errors | [troubleshooting.md -- ArgBuilder Ordering Errors](references/troubleshooting.md#argbuilder-ordering-errors) | "ArgBuilder encrypted plaintext" |52| Callback not firing / computation stuck | [troubleshooting.md -- Computation Never Finalizes](references/troubleshooting.md#computation-never-finalizes) | "arcium_callback queue_computation" |53| Nonce / decryption errors | [troubleshooting.md -- Nonce Errors](references/troubleshooting.md#nonce-errors) | "RescueCipher encrypt nonce" |54| Client-side encryption (RescueCipher, x25519) | [minimal-circuit.md](examples/minimal-circuit.md) -- Test section | "RescueCipher encrypt nonce" |55| Threshold signing / secure randomness | — | "MXESigningKey sign" or "ArcisRNG" |56| Deployment (devnet/mainnet) | — | "arcium deploy cluster-offset" |57| Version / installation requirements | — | "arcium installation anchor solana" |5859## Core Pattern: Three Functions6061Every computation needs three functions in your Solana program:6263| Function | Purpose | When Called |64|----------|---------|-------------|65| `init_<name>_comp_def` | Initialize computation definition | Once per instruction |66| `<name>` | Build args + queue computation | Each request |67| `<name>_callback` | Handle result from Arx nodes | After MPC completes |6869```rust70const COMP_DEF_OFFSET_FLIP: u32 = comp_def_offset("flip");7172// 1. INIT (once per instruction type)73pub fn init_flip_comp_def(ctx: Context<InitFlipCompDef>) -> Result<()> {74 init_comp_def(ctx.accounts, None, None)75}7677// 2. QUEUE (each computation)78pub fn flip(ctx: Context<Flip>, offset: u64, ...) -> Result<()> {79 let args = ArgBuilder::new()...build();80 queue_computation(ctx.accounts, offset, args,81 vec![FlipCallback::callback_ix(offset, &ctx.accounts.mxe_account, &[])?],82 1, 0,83 )?;84 Ok(())85}8687// 3. CALLBACK (after MPC completes)88#[arcium_callback(encrypted_ix = "flip")]89pub fn flip_callback(ctx: Context<FlipCallback>,90 output: SignedComputationOutputs<FlipOutput>) -> Result<()> {91 let result = output.verify_output(...)?;92 // Use result...93}94```9596**Encryption size**: RescueCipher encrypts any scalar to 32 bytes regardless of type.97Formula: `ciphertext_size = 32 * number_of_scalar_values`. See [troubleshooting.md](references/troubleshooting.md) for the full size table.9899## Encryption Context100101| Scenario | Use |102|----------|-----|103| User inputs, results returned to user | `Enc<Shared, T>` |104| Internal state users shouldn't access | `Enc<Mxe, T>` |105| State persisted across computations | `Enc<Mxe, T>` |106| Final reveal to all parties | `.reveal()` |107108## Gotchas109110> Reference during development to avoid common mistakes.111112**NEVER:**113- NEVER reuse a nonce — every `cipher.encrypt()` call needs a fresh `randomBytes(16)`114- NEVER combine multiple ciphertexts into one ArgBuilder call — each encrypted scalar is its own `[u8; 32]` call115- NEVER omit `.x25519_pubkey()` for `Enc<Shared, T>` (silent failure); `Enc<Mxe, T>` skips it116117### Critical (silent failures)118- **Macro string matching**: All macro strings must exactly match `#[instruction] fn NAME` across `#[arcium_callback]`, `comp_def_offset()`, `#[init_computation_definition_accounts]`, `#[queue_computation_accounts]`, `#[callback_accounts]`119- **ArgBuilder ordering**: Calls must match circuit parameter order left-to-right. For `Enc<Shared, T>`: `.x25519_pubkey()` then `.plaintext_u128(nonce)` then ciphertexts. For `Enc<Mxe, T>`: `.plaintext_u128(nonce)` then ciphertexts. Missing `.x25519_pubkey()` for Shared = silent failure.120- **Division by secret zero**: Guard divisors with the safe divisor pattern -- both branches execute in MPC, so the division always runs. See [patterns.md — Safe Division](examples/patterns.md).121- **Combined ciphertext arrays**: Each encrypted scalar needs a separate `[u8; 32]` ArgBuilder call — do NOT pass `[u8; 64]` for a two-scalar type. See [troubleshooting.md — Ciphertext Size Mismatch](references/troubleshooting.md#ciphertext-size-mismatch).122123### Warning (wrong results)124- **Nonce reuse**: Same nonce for multiple encryptions = garbled output. Use unique `randomBytes(16)` per encryption.125- **Callback account writability**: Pass extra accounts via `CallbackAccount { pubkey, is_writable: true }` in `callback_ix(..., &[...])`. Also mark `#[account(mut)]` in callback struct. Accounts cannot be created or resized during callbacks.126- **Output struct naming**: Circuit `fn add_together` generates `AddTogetherOutput`. Single returns use `field_0` (a `SharedEncryptedStruct<1>` or `MXEEncryptedStruct<1>` with `.ciphertexts` and `.nonce`). Tuple returns nest `field_0`, `field_1`, etc.127128### Tips129- Prefer arithmetic over comparisons (cheaper in MPC)130- Comparisons/divisions are cheaper with narrower types (`u64` vs `u128`); storage cost is identical131132## Debug Triage Order133134> Start here when a computation fails or returns wrong results.135136When a computation fails, returns wrong results, or never finalizes — check in this order:1371381. **Names match exactly** — `#[instruction] fn NAME` must match across `#[arcium_callback(encrypted_ix = "NAME")]`, `comp_def_offset("NAME")`, and all account macros1392. **Comp def initialized** — `init_*_comp_def` must be called once before any computation1403. **ArgBuilder param order** — calls must match circuit fn parameters left-to-right1414. **Shared params include pubkey** — `.x25519_pubkey()` before `.plaintext_u128(nonce)` before ciphertexts (missing = silent failure)1425. **Nonce is unique** — fresh `randomBytes(16)` per encryption, same nonce passed to program1436. **Callback registered and writable** — `callback_ix(...)` passed in `queue_computation` call, accounts set in BOTH `CallbackAccount { pubkey, is_writable: true }` AND `#[account(mut)]` in callback struct1447. **Environment correct** — cluster offset matches network, MXE public key available (retry with backoff), RPC endpoint reliable145146For detailed error solutions: [troubleshooting.md](references/troubleshooting.md)147148## Verification Checklist149150> Pre-deploy gate. Run through before deploying or submitting a PR.151152**Circuit:**153- [ ] `arcium build` compiles without errors154- [ ] No `break`/`continue`/`return`/variable-length loops155- [ ] `#[instruction]` fn names are consistent across all macros156157**Program:**158- [ ] `init_*_comp_def` called before first computation (once per instruction type)159- [ ] Every circuit fn has init + invoke + callback instructions160- [ ] `#[arcium_callback(encrypted_ix = "...")]` matches circuit fn name exactly161- [ ] Extra callback accounts passed via `CallbackAccount { pubkey, is_writable: true }` AND `#[account(mut)]` in callback struct162163**Client:**164- [ ] Unique nonce per encryption (no reuse across calls)165- [ ] ArgBuilder call order matches circuit fn parameter order left-to-right166- [ ] `.x25519_pubkey()` included for every `Enc<Shared, T>` parameter167- [ ] Cluster offset matches deployment environment168169**Deploy:**170- [ ] `arcium test` passes locally before deploy171- [ ] RPC endpoint is reliable (not default Solana RPC)172173## Resources174175- **MCP tools** (primary for API details, CLI flags, deployment, versions): `search_arcium_docs` + `query_docs_filesystem_arcium_docs` — [docs.arcium.com/mcp](https://docs.arcium.com/mcp)176- **Docs**: [docs.arcium.com/developers](https://docs.arcium.com/developers/)177- **Examples**: [github.com/arcium-hq/examples](https://github.com/arcium-hq/examples)178- **TypeScript SDK**: [ts.arcium.com/api](https://ts.arcium.com/api)179- **Patterns**: [patterns.md](examples/patterns.md) — 15 curated circuit patterns180- **Troubleshooting**: [troubleshooting.md](references/troubleshooting.md) — hard-to-debug errors181- **Minimal working app**: [minimal-circuit.md](examples/minimal-circuit.md) — circuit + program + test