Skip to content

arihantbansal/encdata-example

Repository files navigation

EncData Patterns Example

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

Project Structure

  • programs/ - Solana Anchor program (queue computations, handle callbacks)
  • encrypted-ixs/ - Arcis encrypted instructions (confidential computing logic)

Pattern 1: EncData INPUT

Sends one pubkey for multiple ciphertexts, each with its own nonce.

Encrypted Instruction

#[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()
}

Solana Program

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(())
}

Client Usage

// 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);

Pattern 2: EncData OUTPUT

Returns ciphertext-only for observer. Smaller callback payload.

Encrypted Instruction

#[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
}

Solana Program

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(())
}

Client Usage

// 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];

Running Tests

arcium test

Starts localnet with MXE nodes, deploys the program, and runs the test suite.


Callback Type Generation

Arcium auto-generates output types based on encrypted-ix function names:

Encrypted Instruction Generated Output Type Contains
enc_data_input_sumu64 EncDataInputSumOutput { field_0: u64 } Plaintext
enc_data_outputEncData<u64> EncDataOutputOutput { field_0: EncDataStruct<1> } Ciphertext

Use cargo expand -p <program> to inspect generated types.

About

Example program built with Anchor and Arcium showcasing using EncData<T> instead of Enc<O, T>

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors