Skip to main content

Overview

The verifyCiphertextCiphertextEquality function creates instructions to verify that two ElGamal ciphertexts encrypt the same plaintext message. This is useful for proving consistency between encrypted values without revealing the underlying value.

Function Signature

async function verifyCiphertextCiphertextEquality({
  rpc,
  payer,
  proofData,
  contextState,
  programId = ZK_ELGAMAL_PROOF_PROGRAM_ADDRESS,
}: VerifyCiphertextCiphertextEqualityArgs): Promise<Instruction[]>

Parameters

rpc
Rpc<GetMinimumBalanceForRentExemptionApi>
required
Solana RPC client with rent exemption API support
payer
TransactionSigner
required
Transaction signer that will pay for the account creation fees
proofData
ProofDataInput
required
The ciphertext equality proof data. Can be either:
  • Uint8Array - Raw proof bytes for ephemeral verification
  • { account: Address; offset: number } - Reference to proof data stored in a record account
contextState
ContextStateArgs
Optional context state configuration for storing the verified proof on-chain:
  • contextAccount: KeyPairSigner - New account to store the proof
  • authority: Address - Authority that can close the context account
programId
Address
ZK ElGamal Proof program address. Defaults to ZK_ELGAMAL_PROOF_PROGRAM_ADDRESS

Returns

Promise<Instruction[]> - Array of instructions to execute:
  1. Create context account instruction (if contextState is provided)
  2. Verify ciphertext equality proof instruction

Usage Examples

Basic Ephemeral Verification

Verify that two ciphertexts encrypt the same value:
import { verifyCiphertextCiphertextEquality } from '@solana/zk-elgamal-proof';
import {
  ElGamalKeypair,
  CiphertextCiphertextEqualityProofData,
  PedersenOpening,
} from '@solana/zk-sdk/node';

// Create two ciphertexts that encrypt the same value
const keypair1 = new ElGamalKeypair();
const keypair2 = new ElGamalKeypair();
const amount = 55n;

const ciphertext1 = keypair1.pubkey().encryptU64(amount);

const opening2 = new PedersenOpening();
const ciphertext2 = keypair2.pubkey().encryptWith(amount, opening2);

// Generate equality proof
const proof = new CiphertextCiphertextEqualityProofData(
  keypair1,
  keypair2.pubkey(),
  ciphertext1,
  ciphertext2,
  opening2,
  amount,
);
const proofData = proof.toBytes();

// Verify ephemerally
const ixs = await verifyCiphertextCiphertextEquality({
  rpc: client.rpc,
  payer,
  proofData,
});

await sendAndConfirmInstructions(client, payer, ixs);

With Context State Storage

Store the verified proof on-chain:
import { generateKeyPairSigner } from '@solana/kit';

const contextAccount = await generateKeyPairSigner();

const ixs = await verifyCiphertextCiphertextEquality({
  rpc: client.rpc,
  payer,
  proofData,
  contextState: {
    contextAccount,
    authority: payer.address,
  },
});

await sendAndConfirmInstructions(client, payer, ixs);

// Verify context account was created
const account = await client.rpc
  .getAccountInfo(contextAccount.address, { encoding: 'base64' })
  .send();

Using Proof from Record Account

Reference proof data stored in a record account:
import { createRecord, createWriteInstruction, RECORD_META_DATA_SIZE } from '@solana-program/record';

// Initialize record account and write proof
const recordAuthority = await generateKeyPairSigner();
const { recordKeypair, ixs: initIxs } = await createRecord({
  rpc: client.rpc,
  payer,
  authority: recordAuthority.address,
  dataLength: BigInt(proofData.length),
});

const writeIx = createWriteInstruction({
  recordAccount: recordKeypair.address,
  authority: recordAuthority,
  offset: 0n,
  data: proofData,
});

await sendAndConfirmInstructions(client, payer, [...initIxs, writeIx]);

// Verify using record account
const verifyIxs = await verifyCiphertextCiphertextEquality({
  rpc: client.rpc,
  payer,
  proofData: {
    account: recordKeypair.address,
    offset: Number(RECORD_META_DATA_SIZE),
  },
  contextState: {
    contextAccount,
    authority: payer.address,
  },
});

await sendAndConfirmInstructions(client, payer, verifyIxs);

Notes

Proof RequirementsThe proof must demonstrate that both ciphertexts encrypt the same plaintext value. The prover needs:
  • The secret key for the first ciphertext
  • The public key for the second ciphertext
  • The Pedersen opening for the second ciphertext
  • The plaintext amount
Invalid Proof DataIf the proof data is invalid or the ciphertexts encrypt different values, the transaction will fail during execution.

Use Cases

  • Proving balance consistency across different encrypted representations
  • Verifying confidential transfers maintain the same amount
  • Demonstrating encrypted values match without revealing the plaintext

Build docs developers (and LLMs) love