Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[token-2022] Add elgamal registry account #7341

Merged
merged 22 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3d0cca2
refactor proof extraction logic into a `spl-token-confidential-transf…
samkim-crypto Oct 17, 2024
e41f90d
create ElGamal registry program
samkim-crypto Oct 17, 2024
421cefa
add support for ElGamal registry program in the token program
samkim-crypto Oct 17, 2024
8a11d6e
add support for `ConfigureAccountWithRegistry` in the token-client
samkim-crypto Oct 17, 2024
7ee89e2
add tests for configure account with registry
samkim-crypto Oct 17, 2024
84cf6b6
use local `spl-pod` for dependency
samkim-crypto Oct 21, 2024
69001ec
Apply suggestions from code review
samkim-crypto Oct 21, 2024
acedb89
require owner signature when creating registry account
samkim-crypto Oct 21, 2024
d2f04a1
derive registry address in `UpdateRegistry` instruction constructor
samkim-crypto Oct 21, 2024
6d9cf4c
make `append_zk_elgamal_proof` function more general
samkim-crypto Oct 21, 2024
5d9dc1b
make `check_elgamal_registry_program_account` `pub(crate)`
samkim-crypto Oct 22, 2024
ca92028
refactor zk elgamal instruction data logic into a separate file
samkim-crypto Oct 22, 2024
f9d886e
fix registry and token account owner check
samkim-crypto Oct 22, 2024
0a78c52
remove `max(1)` condition on pda creation
samkim-crypto Oct 22, 2024
7afec1f
revert rust version bump
samkim-crypto Oct 22, 2024
c5f78e1
remove mutable signer in `CreateRegistry` instruction
samkim-crypto Oct 23, 2024
549b093
add an option to realloc account when configuring account with registry
samkim-crypto Oct 23, 2024
2c626e2
Apply suggestions from code review
samkim-crypto Oct 24, 2024
05bd8a6
remove `ConfigureAccountWithRegistryInstructionData`
samkim-crypto Oct 24, 2024
1213b10
Replace `to_bytes()` of `Pubkey` to `as_bytes()`
samkim-crypto Oct 24, 2024
1c3c19b
use `PodStateWithExtensions` instead of `StateWithExtensions`
samkim-crypto Oct 24, 2024
7b77b83
add a clarifying comment on `try_calculate_account_len` dedupe
samkim-crypto Oct 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading