Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit c59e2f3

Browse files
[token-2022] Add elgamal registry account (#7341)
* 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 <me@jonc.dev> * 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 <me@jonc.dev> * 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 <me@jonc.dev>
1 parent a7dc7b1 commit c59e2f3

File tree

24 files changed

+872
-46
lines changed

24 files changed

+872
-46
lines changed

Cargo.lock

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ members = [
7575
"token/confidential-transfer/proof-extraction",
7676
"token/confidential-transfer/proof-generation",
7777
"token/confidential-transfer/proof-tests",
78+
"token/confidential-transfer/elgamal-registry",
7879
"token/client",
7980
"utils/cgen",
8081
"utils/test-client",

token/client/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ solana-rpc-client = "2.0.3"
2020
solana-rpc-client-api = "2.0.3"
2121
solana-sdk = "2.0.3"
2222
spl-associated-token-account-client = { version = "1.0.0", path = "../../associated-token-account/client" }
23+
spl-elgamal-registry = { version = "0.1.0", path = "../confidential-transfer/elgamal-registry"}
2324
spl-memo = { version = "5.0", path = "../../memo/program", features = [
2425
"no-entrypoint",
2526
] }
2627
spl-record = { version = "0.2.1", path = "../../record/program", features = ["no-entrypoint"] }
2728
spl-token = { version = "6.0", path = "../program", features = [
2829
"no-entrypoint",
2930
] }
31+
spl-token-confidential-transfer-proof-extraction = { version = "0.1.0", path = "../confidential-transfer/proof-extraction" }
3032
spl-token-confidential-transfer-proof-generation = { version = "0.1.0", path = "../confidential-transfer/proof-generation" }
3133
spl-token-2022 = { version = "5.0.2", path = "../program-2022" }
3234
spl-token-group-interface = { version = "0.4.2", path = "../../token-group/interface" }

token/client/src/token.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ use {
4747
BaseStateWithExtensions, Extension, ExtensionType, StateWithExtensionsOwned,
4848
},
4949
instruction, offchain,
50-
proof::{zk_proof_type_to_instruction, ProofData, ProofLocation},
5150
solana_zk_sdk::{
5251
encryption::{
5352
auth_encryption::AeKey,
@@ -63,6 +62,9 @@ use {
6362
},
6463
state::{Account, AccountState, Mint, Multisig},
6564
},
65+
spl_token_confidential_transfer_proof_extraction::instruction::{
66+
zk_proof_type_to_instruction, ProofData, ProofLocation,
67+
},
6668
spl_token_confidential_transfer_proof_generation::{
6769
transfer::TransferProofData, transfer_with_fee::TransferWithFeeProofData,
6870
withdraw::WithdrawProofData,
@@ -1972,6 +1974,29 @@ where
19721974
.await
19731975
}
19741976

1977+
/// Configures confidential transfers for a token account using an ElGamal
1978+
/// registry account
1979+
pub async fn confidential_transfer_configure_token_account_with_registry(
1980+
&self,
1981+
account: &Pubkey,
1982+
elgamal_registry_account: &Pubkey,
1983+
payer: Option<&Pubkey>,
1984+
) -> TokenResult<T::Output> {
1985+
self.process_ixs::<[&dyn Signer; 0]>(
1986+
&[
1987+
confidential_transfer::instruction::configure_account_with_registry(
1988+
&self.program_id,
1989+
account,
1990+
&self.pubkey,
1991+
elgamal_registry_account,
1992+
payer,
1993+
)?,
1994+
],
1995+
&[],
1996+
)
1997+
.await
1998+
}
1999+
19752000
/// Approves a token account for confidential transfers
19762001
pub async fn confidential_transfer_approve_account<S: Signers>(
19772002
&self,
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[package]
2+
name = "spl-elgamal-registry"
3+
version = "0.1.0"
4+
description = "Solana ElGamal Registry Program"
5+
authors = ["Solana Labs Maintainers <maintainers@solanalabs.com>"]
6+
repository = "https://github.com/solana-labs/solana-program-library"
7+
license = "Apache-2.0"
8+
edition = "2021"
9+
10+
[features]
11+
no-entrypoint = []
12+
test-sbf = []
13+
14+
[dependencies]
15+
bytemuck = { version = "1.18.0", features = ["derive"] }
16+
solana-program = "2.0.3"
17+
solana-zk-sdk = "2.0.3"
18+
spl-pod = { version = "0.4.0", path = "../../../libraries/pod" }
19+
spl-token-confidential-transfer-proof-extraction = { version = "0.1.0", path = "../proof-extraction" }
20+
21+
[lib]
22+
crate-type = ["cdylib", "lib"]
23+
24+
[package.metadata.docs.rs]
25+
targets = ["x86_64-unknown-linux-gnu"]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//! Program entrypoint
2+
3+
#![cfg(all(target_os = "solana", not(feature = "no-entrypoint")))]
4+
5+
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey};
6+
7+
solana_program::entrypoint!(process_instruction);
8+
fn process_instruction(
9+
program_id: &Pubkey,
10+
accounts: &[AccountInfo],
11+
instruction_data: &[u8],
12+
) -> ProgramResult {
13+
crate::processor::process_instruction(program_id, accounts, instruction_data)
14+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
use {
2+
crate::{get_elgamal_registry_address, id},
3+
solana_program::{
4+
instruction::{AccountMeta, Instruction},
5+
program_error::ProgramError,
6+
pubkey::Pubkey,
7+
system_program, sysvar,
8+
},
9+
solana_zk_sdk::zk_elgamal_proof_program::{
10+
instruction::ProofInstruction, proof_data::PubkeyValidityProofData,
11+
},
12+
spl_token_confidential_transfer_proof_extraction::instruction::{ProofData, ProofLocation},
13+
};
14+
15+
#[derive(Clone, Debug, PartialEq)]
16+
#[repr(u8)]
17+
pub enum RegistryInstruction {
18+
/// Initialize an ElGamal public key registry.
19+
///
20+
/// 0. `[writable]` The account to be created
21+
/// 1. `[signer]` The wallet address (will also be the owner address for the
22+
/// registry account)
23+
/// 2. `[]` System program
24+
/// 3. `[]` Instructions sysvar if `VerifyPubkeyValidity` is included in the
25+
/// same transaction or context state account if `VerifyPubkeyValidity`
26+
/// is pre-verified into a context state account.
27+
/// 4. `[]` (Optional) Record account if the accompanying proof is to be
28+
/// read from a record account.
29+
CreateRegistry {
30+
/// Relative location of the `ProofInstruction::PubkeyValidityProof`
31+
/// instruction to the `CreateElGamalRegistry` instruction in the
32+
/// transaction. If the offset is `0`, then use a context state account
33+
/// for the proof.
34+
proof_instruction_offset: i8,
35+
},
36+
/// Update an ElGamal public key registry with a new ElGamal public key.
37+
///
38+
/// 0. `[writable]` The ElGamal registry account
39+
/// 1. `[]` Instructions sysvar if `VerifyPubkeyValidity` is included in the
40+
/// same transaction or context state account if `VerifyPubkeyValidity`
41+
/// is pre-verified into a context state account.
42+
/// 2. `[]` (Optional) Record account if the accompanying proof is to be
43+
/// read from a record account.
44+
/// 3. `[signer]` The owner of the ElGamal public key registry
45+
UpdateRegistry {
46+
/// Relative location of the `ProofInstruction::PubkeyValidityProof`
47+
/// instruction to the `UpdateElGamalRegistry` instruction in the
48+
/// transaction. If the offset is `0`, then use a context state account
49+
/// for the proof.
50+
proof_instruction_offset: i8,
51+
},
52+
}
53+
54+
impl RegistryInstruction {
55+
/// Unpacks a byte buffer into a `RegistryInstruction`
56+
pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
57+
let (&tag, rest) = input
58+
.split_first()
59+
.ok_or(ProgramError::InvalidInstructionData)?;
60+
61+
Ok(match tag {
62+
0 => {
63+
let proof_instruction_offset =
64+
*rest.first().ok_or(ProgramError::InvalidInstructionData)?;
65+
Self::CreateRegistry {
66+
proof_instruction_offset: proof_instruction_offset as i8,
67+
}
68+
}
69+
1 => {
70+
let proof_instruction_offset =
71+
*rest.first().ok_or(ProgramError::InvalidInstructionData)?;
72+
Self::UpdateRegistry {
73+
proof_instruction_offset: proof_instruction_offset as i8,
74+
}
75+
}
76+
_ => return Err(ProgramError::InvalidInstructionData),
77+
})
78+
}
79+
80+
/// Packs a `RegistryInstruction` into a byte buffer.
81+
pub fn pack(&self) -> Vec<u8> {
82+
let mut buf = vec![];
83+
match self {
84+
Self::CreateRegistry {
85+
proof_instruction_offset,
86+
} => {
87+
buf.push(0);
88+
buf.extend_from_slice(&proof_instruction_offset.to_le_bytes());
89+
}
90+
Self::UpdateRegistry {
91+
proof_instruction_offset,
92+
} => {
93+
buf.push(1);
94+
buf.extend_from_slice(&proof_instruction_offset.to_le_bytes());
95+
}
96+
};
97+
buf
98+
}
99+
}
100+
101+
/// Create a `RegistryInstruction::CreateRegistry` instruction
102+
pub fn create_registry(
103+
owner_address: &Pubkey,
104+
proof_location: ProofLocation<PubkeyValidityProofData>,
105+
) -> Result<Vec<Instruction>, ProgramError> {
106+
let elgamal_registry_address = get_elgamal_registry_address(owner_address, &id());
107+
108+
let mut accounts = vec![
109+
AccountMeta::new(elgamal_registry_address, false),
110+
AccountMeta::new_readonly(*owner_address, true),
111+
AccountMeta::new_readonly(system_program::id(), false),
112+
];
113+
let proof_instruction_offset = proof_instruction_offset(&mut accounts, proof_location);
114+
115+
let mut instructions = vec![Instruction {
116+
program_id: id(),
117+
accounts,
118+
data: RegistryInstruction::CreateRegistry {
119+
proof_instruction_offset,
120+
}
121+
.pack(),
122+
}];
123+
append_zk_elgamal_proof(&mut instructions, proof_location)?;
124+
Ok(instructions)
125+
}
126+
127+
/// Create a `RegistryInstruction::UpdateRegistry` instruction
128+
pub fn update_registry(
129+
owner_address: &Pubkey,
130+
proof_location: ProofLocation<PubkeyValidityProofData>,
131+
) -> Result<Vec<Instruction>, ProgramError> {
132+
let elgamal_registry_address = get_elgamal_registry_address(owner_address, &id());
133+
134+
let mut accounts = vec![AccountMeta::new(elgamal_registry_address, false)];
135+
let proof_instruction_offset = proof_instruction_offset(&mut accounts, proof_location);
136+
accounts.push(AccountMeta::new_readonly(*owner_address, true));
137+
138+
let mut instructions = vec![Instruction {
139+
program_id: id(),
140+
accounts,
141+
data: RegistryInstruction::UpdateRegistry {
142+
proof_instruction_offset,
143+
}
144+
.pack(),
145+
}];
146+
append_zk_elgamal_proof(&mut instructions, proof_location)?;
147+
Ok(instructions)
148+
}
149+
150+
/// Takes a `ProofLocation`, updates the list of accounts, and returns a
151+
/// suitable proof location
152+
fn proof_instruction_offset(
153+
accounts: &mut Vec<AccountMeta>,
154+
proof_location: ProofLocation<PubkeyValidityProofData>,
155+
) -> i8 {
156+
match proof_location {
157+
ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => {
158+
accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false));
159+
if let ProofData::RecordAccount(record_address, _) = proof_data {
160+
accounts.push(AccountMeta::new_readonly(*record_address, false));
161+
}
162+
proof_instruction_offset.into()
163+
}
164+
ProofLocation::ContextStateAccount(context_state_account) => {
165+
accounts.push(AccountMeta::new_readonly(*context_state_account, false));
166+
0
167+
}
168+
}
169+
}
170+
171+
/// Takes a `RegistryInstruction` and appends the pubkey validity proof
172+
/// instruction
173+
fn append_zk_elgamal_proof(
174+
instructions: &mut Vec<Instruction>,
175+
proof_data_location: ProofLocation<PubkeyValidityProofData>,
176+
) -> Result<(), ProgramError> {
177+
if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
178+
proof_data_location
179+
{
180+
let proof_instruction_offset: i8 = proof_instruction_offset.into();
181+
if proof_instruction_offset != 1 {
182+
return Err(ProgramError::InvalidArgument);
183+
}
184+
match proof_data {
185+
ProofData::InstructionData(data) => instructions
186+
.push(ProofInstruction::VerifyPubkeyValidity.encode_verify_proof(None, data)),
187+
ProofData::RecordAccount(address, offset) => instructions.push(
188+
ProofInstruction::VerifyPubkeyValidity
189+
.encode_verify_proof_from_account(None, address, offset),
190+
),
191+
}
192+
}
193+
Ok(())
194+
}

0 commit comments

Comments
 (0)