Skip to content

Commit

Permalink
[token-2022] Add elgamal registry account (#7341)
Browse files Browse the repository at this point in the history
* refactor proof extraction logic into a `spl-token-confidential-transfer-proof-extraction`

* create ElGamal registry program

* add support for ElGamal registry program in the token program

* add support for `ConfigureAccountWithRegistry` in the token-client

* add tests for configure account with registry

* use local `spl-pod` for dependency

* Apply suggestions from code review

Co-authored-by: Jon C <[email protected]>

* require owner signature when creating registry account

* derive registry address in `UpdateRegistry` instruction constructor

* make `append_zk_elgamal_proof` function more general

* make `check_elgamal_registry_program_account` `pub(crate)`

* refactor zk elgamal instruction data logic into a separate file

* fix registry and token account owner check

* remove `max(1)` condition on pda creation

* revert rust version bump

* remove mutable signer in `CreateRegistry` instruction

* add an option to realloc account when configuring account with registry

* Apply suggestions from code review

Co-authored-by: Jon C <[email protected]>

* remove `ConfigureAccountWithRegistryInstructionData`

* Replace `to_bytes()` of `Pubkey` to `as_bytes()`

* use `PodStateWithExtensions` instead of `StateWithExtensions`

* add a clarifying comment on `try_calculate_account_len` dedupe

---------

Co-authored-by: Jon C <[email protected]>
  • Loading branch information
samkim-crypto and joncinque authored Oct 25, 2024
1 parent a7dc7b1 commit c59e2f3
Show file tree
Hide file tree
Showing 24 changed files with 872 additions and 46 deletions.
18 changes: 18 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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
2 changes: 2 additions & 0 deletions token/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ solana-rpc-client = "2.0.3"
solana-rpc-client-api = "2.0.3"
solana-sdk = "2.0.3"
spl-associated-token-account-client = { version = "1.0.0", path = "../../associated-token-account/client" }
spl-elgamal-registry = { version = "0.1.0", path = "../confidential-transfer/elgamal-registry"}
spl-memo = { version = "5.0", path = "../../memo/program", features = [
"no-entrypoint",
] }
spl-record = { version = "0.2.1", path = "../../record/program", features = ["no-entrypoint"] }
spl-token = { version = "6.0", path = "../program", features = [
"no-entrypoint",
] }
spl-token-confidential-transfer-proof-extraction = { version = "0.1.0", path = "../confidential-transfer/proof-extraction" }
spl-token-confidential-transfer-proof-generation = { version = "0.1.0", path = "../confidential-transfer/proof-generation" }
spl-token-2022 = { version = "5.0.2", path = "../program-2022" }
spl-token-group-interface = { version = "0.4.2", path = "../../token-group/interface" }
Expand Down
27 changes: 26 additions & 1 deletion token/client/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ use {
BaseStateWithExtensions, Extension, ExtensionType, StateWithExtensionsOwned,
},
instruction, offchain,
proof::{zk_proof_type_to_instruction, ProofData, ProofLocation},
solana_zk_sdk::{
encryption::{
auth_encryption::AeKey,
Expand All @@ -63,6 +62,9 @@ use {
},
state::{Account, AccountState, Mint, Multisig},
},
spl_token_confidential_transfer_proof_extraction::instruction::{
zk_proof_type_to_instruction, ProofData, ProofLocation,
},
spl_token_confidential_transfer_proof_generation::{
transfer::TransferProofData, transfer_with_fee::TransferWithFeeProofData,
withdraw::WithdrawProofData,
Expand Down Expand Up @@ -1972,6 +1974,29 @@ where
.await
}

/// Configures confidential transfers for a token account using an ElGamal
/// registry account
pub async fn confidential_transfer_configure_token_account_with_registry(
&self,
account: &Pubkey,
elgamal_registry_account: &Pubkey,
payer: Option<&Pubkey>,
) -> TokenResult<T::Output> {
self.process_ixs::<[&dyn Signer; 0]>(
&[
confidential_transfer::instruction::configure_account_with_registry(
&self.program_id,
account,
&self.pubkey,
elgamal_registry_account,
payer,
)?,
],
&[],
)
.await
}

/// Approves a token account for confidential transfers
pub async fn confidential_transfer_approve_account<S: Signers>(
&self,
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 Program"
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)
}
194 changes: 194 additions & 0 deletions token/confidential-transfer/elgamal-registry/src/instruction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
use {
crate::{get_elgamal_registry_address, id},
solana_program::{
instruction::{AccountMeta, Instruction},
program_error::ProgramError,
pubkey::Pubkey,
system_program, sysvar,
},
solana_zk_sdk::zk_elgamal_proof_program::{
instruction::ProofInstruction, proof_data::PubkeyValidityProofData,
},
spl_token_confidential_transfer_proof_extraction::instruction::{ProofData, ProofLocation},
};

#[derive(Clone, Debug, PartialEq)]
#[repr(u8)]
pub enum RegistryInstruction {
/// Initialize an ElGamal public key registry.
///
/// 0. `[writable]` The account to be created
/// 1. `[signer]` The wallet address (will also be the owner address for the
/// registry account)
/// 2. `[]` System program
/// 3. `[]` Instructions sysvar if `VerifyPubkeyValidity` is included in the
/// same transaction or context state account if `VerifyPubkeyValidity`
/// is pre-verified into a context state account.
/// 4. `[]` (Optional) Record account if the accompanying proof is to be
/// read from a record account.
CreateRegistry {
/// 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 ElGamal registry account
/// 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.
/// 3. `[signer]` The owner of the ElGamal public key registry
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 proof_instruction_offset =
*rest.first().ok_or(ProgramError::InvalidInstructionData)?;
Self::CreateRegistry {
proof_instruction_offset: proof_instruction_offset as i8,
}
}
1 => {
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 {
proof_instruction_offset,
} => {
buf.push(0);
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(
owner_address: &Pubkey,
proof_location: ProofLocation<PubkeyValidityProofData>,
) -> Result<Vec<Instruction>, ProgramError> {
let elgamal_registry_address = get_elgamal_registry_address(owner_address, &id());

let mut accounts = vec![
AccountMeta::new(elgamal_registry_address, false),
AccountMeta::new_readonly(*owner_address, true),
AccountMeta::new_readonly(system_program::id(), false),
];
let proof_instruction_offset = proof_instruction_offset(&mut accounts, proof_location);

let mut instructions = vec![Instruction {
program_id: id(),
accounts,
data: RegistryInstruction::CreateRegistry {
proof_instruction_offset,
}
.pack(),
}];
append_zk_elgamal_proof(&mut instructions, proof_location)?;
Ok(instructions)
}

/// Create a `RegistryInstruction::UpdateRegistry` instruction
pub fn update_registry(
owner_address: &Pubkey,
proof_location: ProofLocation<PubkeyValidityProofData>,
) -> Result<Vec<Instruction>, ProgramError> {
let elgamal_registry_address = get_elgamal_registry_address(owner_address, &id());

let mut accounts = vec![AccountMeta::new(elgamal_registry_address, false)];
let proof_instruction_offset = proof_instruction_offset(&mut accounts, proof_location);
accounts.push(AccountMeta::new_readonly(*owner_address, true));

let mut instructions = vec![Instruction {
program_id: id(),
accounts,
data: RegistryInstruction::UpdateRegistry {
proof_instruction_offset,
}
.pack(),
}];
append_zk_elgamal_proof(&mut instructions, proof_location)?;
Ok(instructions)
}

/// 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(
instructions: &mut Vec<Instruction>,
proof_data_location: ProofLocation<PubkeyValidityProofData>,
) -> Result<(), ProgramError> {
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(())
}
Loading

0 comments on commit c59e2f3

Please sign in to comment.