Skip to content

Commit

Permalink
create ElGamal registry program
Browse files Browse the repository at this point in the history
  • Loading branch information
samkim-crypto committed Oct 15, 2024
1 parent 1b80f81 commit 9ef1a74
Show file tree
Hide file tree
Showing 7 changed files with 324 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ members = [
"token/confidential-transfer/proof-extraction",
"token/confidential-transfer/proof-generation",
"token/confidential-transfer/proof-tests",
"token/confidential-transfer/elgamal-registry",
"token/client",
"utils/cgen",
"utils/test-client",
Expand Down
25 changes: 25 additions & 0 deletions token/confidential-transfer/elgamal-registry/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "spl-elgamal-registry"
version = "0.1.0"
description = "Solana ElGamal Registry"
authors = ["Solana Labs Maintainers <[email protected]>"]
repository = "https://github.com/solana-labs/solana-program-library"
license = "Apache-2.0"
edition = "2021"

[features]
no-entrypoint = []
test-sbf = []

[dependencies]
bytemuck = { version = "1.18.0", features = ["derive"] }
solana-program = "2.0.3"
solana-zk-sdk = "2.0.3"
spl-pod = { version = "0.4.0", path = "../../../libraries/pod" }
spl-token-confidential-transfer-proof-extraction = { version = "0.1.0", path = "../proof-extraction" }

[lib]
crate-type = ["cdylib", "lib"]

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
14 changes: 14 additions & 0 deletions token/confidential-transfer/elgamal-registry/src/entrypoint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//! Program entrypoint
#![cfg(all(target_os = "solana", not(feature = "no-entrypoint")))]

use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey};

solana_program::entrypoint!(process_instruction);
fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
crate::processor::process_instruction(program_id, accounts, instruction_data)
}
197 changes: 197 additions & 0 deletions token/confidential-transfer/elgamal-registry/src/instruction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
use {
crate::id,
solana_program::{
instruction::{AccountMeta, Instruction},
program_error::ProgramError,
pubkey::{Pubkey, PUBKEY_BYTES},
sysvar,
},
solana_zk_sdk::zk_elgamal_proof_program::{
instruction::ProofInstruction, proof_data::PubkeyValidityProofData,
},
spl_token_confidential_transfer_proof_extraction::{ProofData, ProofLocation},
};

#[derive(Clone, Debug, PartialEq)]
#[repr(u8)]
pub enum RegistryInstruction {
/// Initialize an ElGamal public key registry.
///
/// 0. `[writable]` The account to initialize
/// 1. `[]` Instructions sysvar if `VerifyPubkeyValidity` is included in the
/// same transaction or context state account if `VerifyPubkeyValidity`
/// is pre-verified into a context state account.
/// 2. `[]` (Optional) Record account if the accompanying proof is to be
/// read from a record account.
CreateRegistry {
/// The owner of the ElGamal registry account
owner: Pubkey,
/// Relative location of the `ProofInstruction::PubkeyValidityProof`
/// instruction to the `CreateElGamalRegistry` instruction in the
/// transaction. If the offset is `0`, then use a context state account
/// for the proof.
proof_instruction_offset: i8,
},
/// Update an ElGamal public key registry with a new ElGamal public key.
///
/// 0. `[writable]` The account to initialize
/// 1. `[signer]` The owner of the ElGamal public key registry
/// 2. `[]` Instructions sysvar if `VerifyPubkeyValidity` is included in the
/// same transaction or context state account if `VerifyPubkeyValidity`
/// is pre-verified into a context state account.
/// 3. `[]` (Optional) Record account if the accompanying proof is to be
/// read from a record account.
UpdateRegistry {
/// Relative location of the `ProofInstruction::PubkeyValidityProof`
/// instruction to the `UpdateElGamalRegistry` instruction in the
/// transaction. If the offset is `0`, then use a context state account
/// for the proof.
proof_instruction_offset: i8,
},
}

impl RegistryInstruction {
/// Unpacks a byte buffer into a `RegistryInstruction`
pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
let (&tag, rest) = input
.split_first()
.ok_or(ProgramError::InvalidInstructionData)?;

Ok(match tag {
0 => {
let owner = rest
.get(..PUBKEY_BYTES)
.and_then(|x| Pubkey::try_from(x).ok())
.ok_or(ProgramError::InvalidInstructionData)?;
let proof_instruction_offset =
*rest.first().ok_or(ProgramError::InvalidInstructionData)?;
Self::CreateRegistry {
owner,
proof_instruction_offset: proof_instruction_offset as i8,
}
}
2 => {
let proof_instruction_offset =
*rest.first().ok_or(ProgramError::InvalidInstructionData)?;
Self::UpdateRegistry {
proof_instruction_offset: proof_instruction_offset as i8,
}
}
_ => return Err(ProgramError::InvalidInstructionData),
})
}

/// Packs a `RegistryInstruction` into a byte buffer.
pub fn pack(&self) -> Vec<u8> {
let mut buf = vec![];
match self {
Self::CreateRegistry {
owner,
proof_instruction_offset,
} => {
buf.push(0);
buf.extend_from_slice(owner.as_ref());
buf.extend_from_slice(&proof_instruction_offset.to_le_bytes());
}
Self::UpdateRegistry {
proof_instruction_offset,
} => {
buf.push(1);
buf.extend_from_slice(&proof_instruction_offset.to_le_bytes());
}
};
buf
}
}

/// Create a `RegistryInstruction::CreateRegistry` instruction
pub fn create_registry(
registry_account: &Pubkey,
owner: &Pubkey,
proof_location: ProofLocation<PubkeyValidityProofData>,
) -> Result<Vec<Instruction>, ProgramError> {
let mut accounts = vec![AccountMeta::new(*registry_account, false)];
let proof_instruction_offset = proof_instruction_offset(&mut accounts, proof_location);

let registry_instruction = Instruction {
program_id: id(),
accounts,
data: RegistryInstruction::CreateRegistry {
owner: *owner,
proof_instruction_offset,
}
.pack(),
};
append_zk_elgamal_proof(registry_instruction, proof_location)
}

/// Create a `RegistryInstruction::UpdateRegistry` instruction
pub fn update_registry(
registry_account: &Pubkey,
owner: &Pubkey,
proof_location: ProofLocation<PubkeyValidityProofData>,
) -> Result<Vec<Instruction>, ProgramError> {
let mut accounts = vec![
AccountMeta::new(*registry_account, false),
AccountMeta::new_readonly(*owner, true),
];
let proof_instruction_offset = proof_instruction_offset(&mut accounts, proof_location);

let registry_instruction = Instruction {
program_id: id(),
accounts,
data: RegistryInstruction::UpdateRegistry {
proof_instruction_offset,
}
.pack(),
};
append_zk_elgamal_proof(registry_instruction, proof_location)
}

/// Takes a `ProofLocation`, updates the list of accounts, and returns a
/// suitable proof location
fn proof_instruction_offset(
accounts: &mut Vec<AccountMeta>,
proof_location: ProofLocation<PubkeyValidityProofData>,
) -> i8 {
match proof_location {
ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => {
accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false));
if let ProofData::RecordAccount(record_address, _) = proof_data {
accounts.push(AccountMeta::new_readonly(*record_address, false));
}
proof_instruction_offset.into()
}
ProofLocation::ContextStateAccount(context_state_account) => {
accounts.push(AccountMeta::new_readonly(*context_state_account, false));
0
}
}
}

/// Takes a `RegistryInstruction` and appends the pubkey validity proof
/// instruction
fn append_zk_elgamal_proof(
registry_instruction: Instruction,
proof_data_location: ProofLocation<PubkeyValidityProofData>,
) -> Result<Vec<Instruction>, ProgramError> {
let mut instructions = vec![registry_instruction];

if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
proof_data_location
{
let proof_instruction_offset: i8 = proof_instruction_offset.into();
if proof_instruction_offset != 1 {
return Err(ProgramError::InvalidArgument);
}
match proof_data {
ProofData::InstructionData(data) => instructions
.push(ProofInstruction::VerifyPubkeyValidity.encode_verify_proof(None, data)),
ProofData::RecordAccount(address, offset) => instructions.push(
ProofInstruction::VerifyPubkeyValidity
.encode_verify_proof_from_account(None, address, offset),
),
}
}
Ok(instructions)
}
7 changes: 7 additions & 0 deletions token/confidential-transfer/elgamal-registry/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod entrypoint;
pub mod error;
pub mod instruction;
pub mod processor;
pub mod state;

solana_program::declare_id!("regVYJW7tcT8zipN5YiBvHsvR5jXW1uLFxaHSbugABg");
64 changes: 64 additions & 0 deletions token/confidential-transfer/elgamal-registry/src/processor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use {
crate::{instruction::RegistryInstruction, state::ElGamalRegistry},
solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
program_error::ProgramError,
pubkey::Pubkey,
},
solana_zk_sdk::zk_elgamal_proof_program::proof_data::pubkey_validity::{
PubkeyValidityProofContext, PubkeyValidityProofData,
},
spl_pod::bytemuck::pod_from_bytes_mut,
spl_token_confidential_transfer_proof_extraction::verify_and_extract_context,
};

/// Instruction processor
pub fn process_instruction(
_program_id: &Pubkey,
accounts: &[AccountInfo],
input: &[u8],
) -> ProgramResult {
let instruction = RegistryInstruction::unpack(input)?;
let account_info_iter = &mut accounts.iter();
let registry_account_info = next_account_info(account_info_iter)?;
let registry_account_data = &mut registry_account_info.data.borrow_mut();
let registry_account = pod_from_bytes_mut::<ElGamalRegistry>(registry_account_data)?;

let proof_instruction_offset = match instruction {
RegistryInstruction::CreateRegistry {
owner,
proof_instruction_offset,
} => {
// set the owner; ElGamal pubkey is set after the zkp verification below
registry_account.owner = owner;
proof_instruction_offset
}
RegistryInstruction::UpdateRegistry {
proof_instruction_offset,
} => {
// check the owner; ElGamal pubkey is set after the zkp verification below
let owner_info = next_account_info(account_info_iter)?;
validate_owner(owner_info, &registry_account.owner)?;
proof_instruction_offset
}
};
// zero-knowledge proof certifies that the supplied ElGamal public key is valid
let proof_context = verify_and_extract_context::<
PubkeyValidityProofData,
PubkeyValidityProofContext,
>(account_info_iter, proof_instruction_offset as i64, None)?;
registry_account.elgamal_pubkey = proof_context.pubkey;

Ok(())
}

fn validate_owner(owner_info: &AccountInfo, expected_owner: &Pubkey) -> ProgramResult {
if expected_owner != owner_info.key {
return Err(ProgramError::InvalidAccountOwner);
}
if !owner_info.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
Ok(())
}
16 changes: 16 additions & 0 deletions token/confidential-transfer/elgamal-registry/src/state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use {
bytemuck::{Pod, Zeroable},
solana_program::pubkey::Pubkey,
solana_zk_sdk::encryption::pod::elgamal::PodElGamalPubkey,
};

/// ElGamal public key registry. It contains an ElGamal public key that is
/// associated with a wallet account, but independent of any specific mint.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
pub struct ElGamalRegistry {
/// The owner of the registry
pub owner: Pubkey,
/// The ElGamal public key associated with an account
pub elgamal_pubkey: PodElGamalPubkey,
}

0 comments on commit 9ef1a74

Please sign in to comment.