This project is structured pretty similarly to how a regular Solana Anchor project is structured. The main difference lies in there being two places to write code here:
- The
programsdir like usual Anchor programs - The
encrypted-ixsdir for confidential computing instructions
When working with plaintext data, we can edit it inside our program as normal. When working with confidential data though, state transitions take place off-chain using the Arcium network as a co-processor. For this, we then always need two instructions in our program: one that gets called to initialize a confidential computation, and one that gets called when the computation is done and supplies the resulting data. Additionally, since the types and operations in a Solana program and in a confidential computing environment are a bit different, we define the operations themselves in the encrypted-ixs dir using our Rust-based framework called Arcis. To link all of this together, we provide a few macros that take care of ensuring the correct accounts and data are passed for the specific initialization and callback functions:
// encrypted-ixs/add_together.rs
use arcis_imports::*;
#[encrypted]
mod circuits {
use arcis_imports::*;
pub struct InputValues {
v1: u8,
v2: u8,
}
#[instruction]
pub fn add_together(input_ctxt: Enc<Shared, InputValues>) -> Enc<Shared, u16> {
let input = input_ctxt.to_arcis();
let sum = input.v1 as u16 + input.v2 as u16;
input_ctxt.owner.from_arcis(sum)
}
}
// programs/my_program/src/lib.rs
declare_id!("<some ID>");
#[arcium_program]
pub mod my_program {
use super::*;
pub fn init_add_together_comp_def(ctx: Context<InitAddTogetherCompDef>) -> Result<()> {
init_comp_def(ctx.accounts, true, None, None)?;
Ok(())
}
pub fn add_together(
ctx: Context<AddTogether>,
computation_offset: u64,
ciphertext_0: [u8; 32],
ciphertext_1: [u8; 32],
pub_key: [u8; 32],
nonce: u128,
) -> Result<()> {
let args = vec![
Argument::ArcisPubkey(pub_key),
Argument::PlaintextU128(nonce),
Argument::EncryptedU8(ciphertext_0),
Argument::EncryptedU8(ciphertext_1),
];
queue_computation(ctx.accounts, computation_offset, args, vec![], None)?;
Ok(())
}
#[arcium_callback(encrypted_ix = "add_together")]
pub fn add_together_callback(
ctx: Context<AddTogetherCallback>,
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(())
}
}
#[queue_computation_accounts("add_together", payer)]
#[derive(Accounts)]
#[instruction(computation_offset: u64)]
pub struct AddTogether<'info> {
#[account(mut)]
pub payer: Signer<'info>,
// ... other required accounts
}
#[callback_accounts("add_together", payer)]
#[derive(Accounts)]
pub struct AddTogetherCallback<'info> {
#[account(mut)]
pub payer: Signer<'info>,
// ... other required accounts
pub some_extra_acc: AccountInfo<'info>,
}
#[init_computation_definition_accounts("add_together", payer)]
#[derive(Accounts)]
pub struct InitAddTogetherCompDef<'info> {
#[account(mut)]
pub payer: Signer<'info>,
// ... other required accounts
}