Skip to content

Commit

Permalink
add elgamal registry account
Browse files Browse the repository at this point in the history
  • Loading branch information
samkim-crypto committed Oct 8, 2024
1 parent df498f3 commit 1e094bb
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 32 deletions.
2 changes: 1 addition & 1 deletion rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[toolchain]
channel = "1.78.0"
channel = "1.79.0"
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ pub enum ConfidentialTransferInstruction {
///
/// Accounts expected by this instruction:
///
/// TODO: Add an option to include an `ElGamalRegistry` address
///
/// * Single owner/delegate
/// 0. `[writeable]` The SPL Token account.
/// 1. `[]` The corresponding SPL Token mint.
Expand All @@ -106,7 +108,8 @@ pub enum ConfidentialTransferInstruction {
/// account.
///
/// Data expected by this instruction:
/// `ConfigureAccountInstructionData`
/// None if an `ElGamalRegistry` address is provided in the list of accounts
/// `ConfigureAccountInstructionData` otherwise
ConfigureAccount,

/// Approves a token account for confidential transfers.
Expand Down
3 changes: 3 additions & 0 deletions token/program-2022/src/extension/confidential_transfer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ pub const MAXIMUM_DEPOSIT_TRANSFER_AMOUNT: u64 = (u16::MAX as u64) + (1 << 16) *
/// Bit length of the low bits of pending balance plaintext
pub const PENDING_BALANCE_LO_BIT_LENGTH: u32 = 16;

/// The default maximum pending balance credit counter.
pub const DEFAULT_MAXIMUM_PENDING_BALANCE_CREDIT_COUNTER: u64 = 65536;

/// Confidential Transfer Extension instructions
pub mod instruction;

Expand Down
100 changes: 77 additions & 23 deletions token/program-2022/src/extension/confidential_transfer/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use {
instruction::{decode_instruction_data, decode_instruction_type},
pod::{PodAccount, PodMint},
processor::Processor,
proof::verify_and_extract_context,
proof::{verify_and_extract_context, ElGamalRegistry},
},
solana_program::{
account_info::{next_account_info, AccountInfo},
Expand All @@ -33,6 +33,7 @@ use {
pubkey::Pubkey,
sysvar::Sysvar,
},
spl_pod::bytemuck::pod_from_bytes,
spl_token_confidential_transfer_proof_extraction::{
transfer::TransferProofContext, transfer_with_fee::TransferWithFeeProofContext,
},
Expand Down Expand Up @@ -92,23 +93,62 @@ fn process_update_mint(
Ok(())
}

/// Processes a [ConfigureAccount] instruction with the assumption that an ElGamal registry is
/// provided.
fn process_configure_account_from_registry(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
let elgamal_registry_account = accounts.get(2).unwrap();

if elgamal_registry_account.owner != program_id {
return Err(TokenError::OwnerMismatch.into());
}

let elgamal_registry_account_data = &elgamal_registry_account.data.borrow();
let elgamal_registry_account =
pod_from_bytes::<ElGamalRegistry>(elgamal_registry_account_data)?;

let decryptable_zero_balance = PodAeCiphertext::default();
let maximum_pending_balance_credit_counter =
DEFAULT_MAXIMUM_PENDING_BALANCE_CREDIT_COUNTER.into();

process_configure_account(
program_id,
accounts,
&decryptable_zero_balance,
&maximum_pending_balance_credit_counter,
None,
Some(elgamal_registry_account),
)
}

/// Processes a [ConfigureAccount] instruction.
fn process_configure_account(
program_id: &Pubkey,
accounts: &[AccountInfo],
decryptable_zero_balance: &DecryptableBalance,
maximum_pending_balance_credit_counter: &PodU64,
proof_instruction_offset: i64,
proof_instruction_offset: Option<i64>,
elgamal_registry_account: Option<&ElGamalRegistry>,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let token_account_info = next_account_info(account_info_iter)?;
let mint_info = next_account_info(account_info_iter)?;

// 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, None)?;
let elgamal_pubkey = if let Some(offset) = 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, offset, None)?;
proof_context.pubkey
} else {
// if proof instruction offset is `None`, then assume that the proof
// was already verified in an ElGamal registry account
let _elgamal_registry_account = next_account_info(account_info_iter)?;
elgamal_registry_account.unwrap().elgamal_pubkey
};

let authority_info = next_account_info(account_info_iter)?;
let authority_info_data_len = authority_info.data_len();
Expand All @@ -121,13 +161,21 @@ fn process_configure_account(
return Err(TokenError::MintMismatch.into());
}

Processor::validate_owner(
program_id,
&token_account.base.owner,
authority_info,
authority_info_data_len,
account_info_iter.as_slice(),
)?;
if let Some(registry_account) = elgamal_registry_account {
// if ElGamal registry was provided, then just verify that the registry owner and the
// account match, then skip the signature verification check
if registry_account.owner != *authority_info.key {
return Err(TokenError::OwnerMismatch.into());
}
} else {
Processor::validate_owner(
program_id,
&token_account.base.owner,
authority_info,
authority_info_data_len,
account_info_iter.as_slice(),
)?;
}

check_program_account(mint_info.owner)?;
let mint_data = &mut mint_info.data.borrow();
Expand All @@ -140,7 +188,7 @@ fn process_configure_account(
let confidential_transfer_account =
token_account.init_extension::<ConfidentialTransferAccount>(false)?;
confidential_transfer_account.approved = confidential_transfer_mint.auto_approve_new_accounts;
confidential_transfer_account.elgamal_pubkey = proof_context.pubkey;
confidential_transfer_account.elgamal_pubkey = elgamal_pubkey;
confidential_transfer_account.maximum_pending_balance_credit_counter =
*maximum_pending_balance_credit_counter;

Expand Down Expand Up @@ -1102,14 +1150,20 @@ pub(crate) fn process_instruction(
}
ConfidentialTransferInstruction::ConfigureAccount => {
msg!("ConfidentialTransferInstruction::ConfigureAccount");
let data = decode_instruction_data::<ConfigureAccountInstructionData>(input)?;
process_configure_account(
program_id,
accounts,
&data.decryptable_zero_balance,
&data.maximum_pending_balance_credit_counter,
data.proof_instruction_offset as i64,
)
if input.is_empty() {
// instruction data is empty, so assume an ElGamal registry is provided
process_configure_account_from_registry(program_id, accounts)
} else {
let data = decode_instruction_data::<ConfigureAccountInstructionData>(input)?;
process_configure_account(
program_id,
accounts,
&data.decryptable_zero_balance,
&data.maximum_pending_balance_credit_counter,
Some(data.proof_instruction_offset as i64),
None,
)
}
}
ConfidentialTransferInstruction::ApproveAccount => {
msg!("ConfidentialTransferInstruction::ApproveAccount");
Expand Down
50 changes: 50 additions & 0 deletions token/program-2022/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,42 @@ pub enum TokenInstruction<'a> {
/// for further details about the extended instructions that share this
/// instruction prefix
GroupMemberPointerExtension,
/// Initialize an ElGamal public key registry for an account.
///
/// 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.
CreateElGamalRegistry {
/// The owner of the ElGamal registry account
#[cfg_attr(feature = "serde-traits", serde(with = "As::<DisplayFromStr>"))]
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.
UpdateElGamalRegistry {
/// 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<'a> TokenInstruction<'a> {
/// Unpacks a byte buffer into a
Expand Down Expand Up @@ -1018,6 +1054,20 @@ impl<'a> TokenInstruction<'a> {
&Self::GroupMemberPointerExtension => {
buf.push(41);
}
&Self::CreateElGamalRegistry {
owner,
proof_instruction_offset,
} => {
buf.push(42);
buf.extend_from_slice(owner.as_ref());
buf.extend_from_slice(&proof_instruction_offset.to_le_bytes());
}
&Self::UpdateElGamalRegistry {
proof_instruction_offset,
} => {
buf.push(43);
buf.extend_from_slice(&proof_instruction_offset.to_le_bytes());
}
};
buf
}
Expand Down
16 changes: 16 additions & 0 deletions token/program-2022/src/pod_instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,20 @@ pub(crate) struct SetAuthorityData {
// The new authority option comes later, but cannot be included as
// plain old data in this struct
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
pub(crate) struct CreateElGamalRegistryData {
/// The owner of the ElGamal registry account
pub(crate) owner: Pubkey,
/// The proof instruction offset
pub(crate) proof_instruction_offset: i8,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
pub(crate) struct UpdateElGamalRegistryData {
/// The proof instruction offset
pub(crate) proof_instruction_offset: i8,
}

/// All of the base instructions in Token-2022, reduced down to their one-byte
/// discriminant.
Expand Down Expand Up @@ -114,6 +128,8 @@ pub(crate) enum PodTokenInstruction {
// 40
GroupPointerExtension,
GroupMemberPointerExtension,
CreateElGamalRegistry,
UpdateElGamalRegistry,
}

fn unpack_pubkey_option(input: &[u8]) -> Result<PodCOption<Pubkey>, ProgramError> {
Expand Down
82 changes: 81 additions & 1 deletion token/program-2022/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ use {
pod::{PodAccount, PodCOption, PodMint, PodMultisig},
pod_instruction::{
decode_instruction_data_with_coption_pubkey, AmountCheckedData, AmountData,
InitializeMintData, InitializeMultisigData, PodTokenInstruction, SetAuthorityData,
CreateElGamalRegistryData, InitializeMintData, InitializeMultisigData,
PodTokenInstruction, SetAuthorityData, UpdateElGamalRegistryData,
},
proof::{verify_and_extract_context, ElGamalRegistry},
state::{Account, AccountState, Mint, PackedSizeOf},
},
solana_program::{
Expand All @@ -50,6 +52,9 @@ use {
system_instruction, system_program,
sysvar::{rent::Rent, Sysvar},
},
solana_zk_sdk::zk_elgamal_proof_program::proof_data::pubkey_validity::{
PubkeyValidityProofContext, PubkeyValidityProofData,
},
spl_pod::{
bytemuck::{pod_from_bytes, pod_from_bytes_mut},
primitives::{PodBool, PodU64},
Expand Down Expand Up @@ -1539,6 +1544,63 @@ impl Processor {
Ok(())
}

/// Processes an [CreateElGamalRegistry](enum.TokenInstruction.html) instruction
pub fn process_create_elgamal_registry(
accounts: &[AccountInfo],
owner: &Pubkey,
proof_instruction_offset: i64,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let registry_account_info = next_account_info(account_info_iter)?;

// 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, None)?;

let registry_account_data = &mut registry_account_info.data.borrow_mut();
let registry_account = pod_from_bytes_mut::<ElGamalRegistry>(registry_account_data)?;

registry_account.owner = *owner;
registry_account.elgamal_pubkey = proof_context.pubkey;

Ok(())
}

/// Processes an [UpdateElGamalRegistry](enum.TokenInstruction.html) instruction
pub fn process_update_elgamal_registry(
program_id: &Pubkey,
accounts: &[AccountInfo],
proof_instruction_offset: i64,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let registry_account_info = next_account_info(account_info_iter)?;
let owner_info = next_account_info(account_info_iter)?;
let owner_info_data_len = owner_info.data_len();

// 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, None)?;

let registry_account_data = &mut registry_account_info.data.borrow_mut();
let registry_account = pod_from_bytes_mut::<ElGamalRegistry>(registry_account_data)?;

Processor::validate_owner(
program_id,
&registry_account.owner,
owner_info,
owner_info_data_len,
account_info_iter.as_slice(),
)?;

registry_account.elgamal_pubkey = proof_context.pubkey;

Ok(())
}

/// Processes an [Instruction](enum.Instruction.html).
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
if let Ok(instruction_type) = decode_instruction_type(input) {
Expand Down Expand Up @@ -1793,6 +1855,24 @@ impl Processor {
&input[1..],
)
}
PodTokenInstruction::CreateElGamalRegistry => {
msg!("Instruction: InitializeElGamalRegistry");
let data = decode_instruction_data::<CreateElGamalRegistryData>(input)?;
Self::process_create_elgamal_registry(
accounts,
&data.owner,
data.proof_instruction_offset.into(),
)
}
PodTokenInstruction::UpdateElGamalRegistry => {
msg!("Instruction: UpdateElGamalRegistry");
let data = decode_instruction_data::<UpdateElGamalRegistryData>(input)?;
Self::process_update_elgamal_registry(
program_id,
accounts,
data.proof_instruction_offset.into(),
)
}
}
} else if let Ok(instruction) = TokenMetadataInstruction::unpack(input) {
token_metadata::processor::process_instruction(program_id, accounts, instruction)
Expand Down
Loading

0 comments on commit 1e094bb

Please sign in to comment.