Demonstrates two EncData patterns for Arcium confidential computing:
- Pattern 1: Multiple encrypted inputs with shared pubkey → plaintext output
- Pattern 2: Encrypted input → encrypted output for observer
programs/- Solana Anchor program (queue computations, handle callbacks)encrypted-ixs/- Arcis encrypted instructions (confidential computing logic)
Sends one pubkey for multiple ciphertexts, each with its own nonce.
#[instruction]
pub fn enc_data_input_sum(
pubkey: ArcisX25519Pubkey, // Shared pubkey for both values
a_nonce: u128, // Nonce for first value
a_data: EncData<u64>, // Ciphertext only (no pubkey/nonce)
b_nonce: u128, // Nonce for second value
b_data: EncData<u64>,
) -> u64 { // Plaintext output (revealed)
let a = a_data.to_arcis_with_pubkey_and_nonce(pubkey, a_nonce);
let b = b_data.to_arcis_with_pubkey_and_nonce(pubkey, b_nonce);
(a + b).reveal()
}ArgBuilder sequence must match encrypted-ix parameter order:
let args = ArgBuilder::new()
.x25519_pubkey(pubkey) // 1. Shared pubkey
.plaintext_u128(a_nonce) // 2. First nonce
.encrypted_u64(a_ciphertext) // 3. First ciphertext
.plaintext_u128(b_nonce) // 4. Second nonce
.encrypted_u64(b_ciphertext) // 5. Second ciphertext
.build();#[arcium_callback(encrypted_ix = "enc_data_input_sum")]
pub fn enc_data_input_sum_callback(
ctx: Context<EncDataInputSumCallback>,
output: SignedComputationOutputs<EncDataInputSumOutput>,
) -> Result<()> {
match output.verify_output(&ctx.accounts.cluster_account, &ctx.accounts.computation_account) {
Ok(EncDataInputSumOutput { field_0 }) => {
// field_0 is u64 (plaintext result)
emit!(SumResultEvent { result: field_0 });
}
Err(_) => return Err(ErrorCode::AbortedComputation.into()),
}
Ok(())
}// Same key, different nonces for each value
const cipher = new RescueCipher(sharedSecret);
const nonceA = randomBytes(16);
const nonceB = randomBytes(16);
const ctA = cipher.encrypt([val1], nonceA);
const ctB = cipher.encrypt([val2], nonceB);
// Result is plaintext u64
expect(sumEvent.result).to.equal(val1 + val2);Returns ciphertext-only for observer. Smaller callback payload.
#[instruction]
pub fn enc_data_output(
value: Enc<Shared, u64>, // Full encrypted value
observer: Shared, // Observer's pubkey + nonce
) -> EncData<u64> { // Ciphertext only (smaller callback)
let doubled = value.to_arcis() * 2;
observer.from_arcis(doubled).data
}let args = ArgBuilder::new()
.x25519_pubkey(value_pubkey)
.plaintext_u128(value_nonce)
.encrypted_u64(value_ciphertext)
.x25519_pubkey(observer_pubkey)
.plaintext_u128(observer_nonce)
.build();#[arcium_callback(encrypted_ix = "enc_data_output")]
pub fn enc_data_output_callback(
ctx: Context<EncDataOutputCallback>,
output: SignedComputationOutputs<EncDataOutputOutput>,
) -> Result<()> {
match output.verify_output(&ctx.accounts.cluster_account, &ctx.accounts.computation_account) {
Ok(EncDataOutputOutput { field_0 }) => {
// field_0.ciphertexts[0] is the encrypted result
// NO nonce in callback - client uses observer_nonce + 1
emit!(EncDataResultEvent {
ciphertext: field_0.ciphertexts[0],
});
}
Err(_) => return Err(ErrorCode::AbortedComputation.into()),
}
Ok(())
}// IMPORTANT: Decryption nonce = observer_nonce + 1
const resultNonce = deserializeLE(observerNonce) + BigInt(1);
const resultNonceBytes = Buffer.alloc(16);
let n = resultNonce;
for (let i = 0; i < 16; i++) {
resultNonceBytes[i] = Number(n & BigInt(0xff));
n >>= BigInt(8);
}
const decrypted = observerCipher.decrypt(
[resultEvent.ciphertext],
resultNonceBytes
)[0];arcium testStarts localnet with MXE nodes, deploys the program, and runs the test suite.
Arcium auto-generates output types based on encrypted-ix function names:
| Encrypted Instruction | Generated Output Type | Contains |
|---|---|---|
enc_data_input_sum → u64 |
EncDataInputSumOutput { field_0: u64 } |
Plaintext |
enc_data_output → EncData<u64> |
EncDataOutputOutput { field_0: EncDataStruct<1> } |
Ciphertext |
Use cargo expand -p <program> to inspect generated types.