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

Commit 40d0b38

Browse files
committed
add support for ElGamal registry program in the token program
1 parent 9ef1a74 commit 40d0b38

File tree

9 files changed

+166
-23
lines changed

9 files changed

+166
-23
lines changed

Cargo.lock

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

token/program-2022/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ num_enum = "0.7.3"
2525
solana-program = "2.0.3"
2626
solana-security-txt = "1.1.1"
2727
solana-zk-sdk = "2.0.3"
28+
spl-elgamal-registry = { version = "0.1.0", path = "../confidential-transfer/elgamal-registry", features = ["no-entrypoint"] }
2829
spl-memo = { version = "5.0", path = "../../memo/program", features = [ "no-entrypoint" ] }
2930
spl-token = { version = "6.0", path = "../program", features = ["no-entrypoint"] }
3031
spl-token-confidential-transfer-ciphertext-arithmetic = { version = "0.1.0", path = "../confidential-transfer/ciphertext-arithmetic" }

token/program-2022/src/extension/confidential_transfer/instruction.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ use {
1111
check_program_account,
1212
extension::confidential_transfer::*,
1313
instruction::{encode_instruction, TokenInstruction},
14-
proof::{ProofData, ProofLocation},
1514
},
1615
bytemuck::Zeroable,
1716
num_enum::{IntoPrimitive, TryFromPrimitive},
@@ -21,6 +20,7 @@ use {
2120
pubkey::Pubkey,
2221
sysvar,
2322
},
23+
spl_token_confidential_transfer_proof_extraction::{ProofData, ProofLocation},
2424
};
2525

2626
/// Confidential Transfer extension instructions
@@ -472,6 +472,29 @@ pub enum ConfidentialTransferInstruction {
472472
/// Data expected by this instruction:
473473
/// `TransferWithFeeInstructionData`
474474
TransferWithFee,
475+
476+
/// Configures confidential transfers for a token account.
477+
///
478+
/// This instruction is identical to the `ConfigureAccount` account except
479+
/// that a valid `ElGamalRegistry` account is expected in place of the
480+
/// `VerifyPubkeyValidity` proof.
481+
///
482+
/// An `ElGamalRegistry` account is valid if it shares the same owner with
483+
/// the token account. If a valid `ElGamalRegistry` account is provided,
484+
/// then the program skips the verification of the ElGamal pubkey
485+
/// validity proof as well as the token owner signature.
486+
///
487+
/// Accounts expected by this instruction:
488+
///
489+
/// * Single owner/delegate
490+
/// 0. `[writeable]` The SPL Token account.
491+
/// 1. `[]` The corresponding SPL Token mint.
492+
/// 2. `[]` The ElGamal registry account.
493+
/// 3. `[]` The single source account owner.
494+
///
495+
/// Data expected by this instruction:
496+
/// None
497+
ConfigureAccountWithRegistry,
475498
}
476499

477500
/// Data expected by `ConfidentialTransferInstruction::InitializeMint`
@@ -1701,3 +1724,27 @@ pub fn transfer_with_fee(
17011724

17021725
Ok(instructions)
17031726
}
1727+
1728+
/// Create a `ConfigureAccountWithRegistry` instruction
1729+
pub fn configure_account_with_registry(
1730+
token_program_id: &Pubkey,
1731+
token_account: &Pubkey,
1732+
mint: &Pubkey,
1733+
elgamal_registry: &Pubkey,
1734+
) -> Result<Instruction, ProgramError> {
1735+
check_program_account(token_program_id)?;
1736+
1737+
let accounts = vec![
1738+
AccountMeta::new(*token_account, false),
1739+
AccountMeta::new_readonly(*mint, false),
1740+
AccountMeta::new_readonly(*elgamal_registry, false),
1741+
];
1742+
1743+
Ok(encode_instruction(
1744+
token_program_id,
1745+
accounts,
1746+
TokenInstruction::ConfidentialTransferExtension,
1747+
ConfidentialTransferInstruction::ConfigureAccountWithRegistry,
1748+
&(),
1749+
))
1750+
}

token/program-2022/src/extension/confidential_transfer/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ pub const MAXIMUM_DEPOSIT_TRANSFER_AMOUNT: u64 = (u16::MAX as u64) + (1 << 16) *
2323
/// Bit length of the low bits of pending balance plaintext
2424
pub const PENDING_BALANCE_LO_BIT_LENGTH: u32 = 16;
2525

26+
/// The default maximum pending balance credit counter.
27+
pub const DEFAULT_MAXIMUM_PENDING_BALANCE_CREDIT_COUNTER: u64 = 65536;
28+
2629
/// Confidential Transfer Extension instructions
2730
pub mod instruction;
2831

token/program-2022/src/extension/confidential_transfer/processor.rs

Lines changed: 74 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use {
66
};
77
use {
88
crate::{
9-
check_program_account,
9+
check_elgamal_registry_program_account, check_program_account,
1010
error::TokenError,
1111
extension::{
1212
confidential_transfer::{instruction::*, verify_proof::*, *},
@@ -22,7 +22,6 @@ use {
2222
instruction::{decode_instruction_data, decode_instruction_type},
2323
pod::{PodAccount, PodMint},
2424
processor::Processor,
25-
proof::verify_and_extract_context,
2625
},
2726
solana_program::{
2827
account_info::{next_account_info, AccountInfo},
@@ -33,8 +32,11 @@ use {
3332
pubkey::Pubkey,
3433
sysvar::Sysvar,
3534
},
35+
spl_elgamal_registry::state::ElGamalRegistry,
36+
spl_pod::bytemuck::pod_from_bytes,
3637
spl_token_confidential_transfer_proof_extraction::{
3738
transfer::TransferProofContext, transfer_with_fee::TransferWithFeeProofContext,
39+
verify_and_extract_context,
3840
},
3941
};
4042

@@ -92,23 +94,62 @@ fn process_update_mint(
9294
Ok(())
9395
}
9496

97+
enum ElGamalPubkeySource<'a> {
98+
ProofInstructionOffset(i64),
99+
ElGamalRegistry(&'a ElGamalRegistry),
100+
}
101+
102+
/// Processes a [ConfigureAccountWithRegistry] instruction.
103+
fn process_configure_account_with_registry(
104+
program_id: &Pubkey,
105+
accounts: &[AccountInfo],
106+
) -> ProgramResult {
107+
let elgamal_registry_account = accounts.get(2).ok_or(ProgramError::NotEnoughAccountKeys)?;
108+
check_elgamal_registry_program_account(elgamal_registry_account.owner)?;
109+
110+
let elgamal_registry_account_data = &elgamal_registry_account.data.borrow();
111+
let elgamal_registry_account =
112+
pod_from_bytes::<ElGamalRegistry>(elgamal_registry_account_data)?;
113+
114+
let decryptable_zero_balance = PodAeCiphertext::default();
115+
let maximum_pending_balance_credit_counter =
116+
DEFAULT_MAXIMUM_PENDING_BALANCE_CREDIT_COUNTER.into();
117+
118+
process_configure_account(
119+
program_id,
120+
accounts,
121+
&decryptable_zero_balance,
122+
&maximum_pending_balance_credit_counter,
123+
ElGamalPubkeySource::ElGamalRegistry(elgamal_registry_account),
124+
)
125+
}
126+
95127
/// Processes a [ConfigureAccount] instruction.
96128
fn process_configure_account(
97129
program_id: &Pubkey,
98130
accounts: &[AccountInfo],
99131
decryptable_zero_balance: &DecryptableBalance,
100132
maximum_pending_balance_credit_counter: &PodU64,
101-
proof_instruction_offset: i64,
133+
elgamal_pubkey_source: ElGamalPubkeySource,
102134
) -> ProgramResult {
103135
let account_info_iter = &mut accounts.iter();
104136
let token_account_info = next_account_info(account_info_iter)?;
105137
let mint_info = next_account_info(account_info_iter)?;
106138

107-
// zero-knowledge proof certifies that the supplied ElGamal public key is valid
108-
let proof_context = verify_and_extract_context::<
109-
PubkeyValidityProofData,
110-
PubkeyValidityProofContext,
111-
>(account_info_iter, proof_instruction_offset, None)?;
139+
let elgamal_pubkey = match elgamal_pubkey_source {
140+
ElGamalPubkeySource::ProofInstructionOffset(offset) => {
141+
// zero-knowledge proof certifies that the supplied ElGamal public key is valid
142+
let proof_context = verify_and_extract_context::<
143+
PubkeyValidityProofData,
144+
PubkeyValidityProofContext,
145+
>(account_info_iter, offset, None)?;
146+
proof_context.pubkey
147+
}
148+
ElGamalPubkeySource::ElGamalRegistry(elgamal_registry_account) => {
149+
let _elgamal_registry_account = next_account_info(account_info_iter)?;
150+
elgamal_registry_account.elgamal_pubkey
151+
}
152+
};
112153

113154
let authority_info = next_account_info(account_info_iter)?;
114155
let authority_info_data_len = authority_info.data_len();
@@ -121,13 +162,25 @@ fn process_configure_account(
121162
return Err(TokenError::MintMismatch.into());
122163
}
123164

124-
Processor::validate_owner(
125-
program_id,
126-
&token_account.base.owner,
127-
authority_info,
128-
authority_info_data_len,
129-
account_info_iter.as_slice(),
130-
)?;
165+
match elgamal_pubkey_source {
166+
ElGamalPubkeySource::ProofInstructionOffset(_) => {
167+
Processor::validate_owner(
168+
program_id,
169+
&token_account.base.owner,
170+
authority_info,
171+
authority_info_data_len,
172+
account_info_iter.as_slice(),
173+
)?;
174+
}
175+
ElGamalPubkeySource::ElGamalRegistry(elgamal_registry_account) => {
176+
// if ElGamal registry was provided, then just verify that the owners of the
177+
// registry and token accounts match, then skip the signature
178+
// verification check
179+
if elgamal_registry_account.owner != *authority_info.key {
180+
return Err(TokenError::OwnerMismatch.into());
181+
}
182+
}
183+
};
131184

132185
check_program_account(mint_info.owner)?;
133186
let mint_data = &mut mint_info.data.borrow();
@@ -140,7 +193,7 @@ fn process_configure_account(
140193
let confidential_transfer_account =
141194
token_account.init_extension::<ConfidentialTransferAccount>(false)?;
142195
confidential_transfer_account.approved = confidential_transfer_mint.auto_approve_new_accounts;
143-
confidential_transfer_account.elgamal_pubkey = proof_context.pubkey;
196+
confidential_transfer_account.elgamal_pubkey = elgamal_pubkey;
144197
confidential_transfer_account.maximum_pending_balance_credit_counter =
145198
*maximum_pending_balance_credit_counter;
146199

@@ -1108,7 +1161,7 @@ pub(crate) fn process_instruction(
11081161
accounts,
11091162
&data.decryptable_zero_balance,
11101163
&data.maximum_pending_balance_credit_counter,
1111-
data.proof_instruction_offset as i64,
1164+
ElGamalPubkeySource::ProofInstructionOffset(data.proof_instruction_offset as i64),
11121165
)
11131166
}
11141167
ConfidentialTransferInstruction::ApproveAccount => {
@@ -1217,5 +1270,9 @@ pub(crate) fn process_instruction(
12171270
#[cfg(not(feature = "zk-ops"))]
12181271
Err(ProgramError::InvalidInstructionData)
12191272
}
1273+
ConfidentialTransferInstruction::ConfigureAccountWithRegistry => {
1274+
msg!("ConfidentialTransferInstruction::ConfigureAccountWithRegistry");
1275+
process_configure_account_with_registry(program_id, accounts)
1276+
}
12201277
}
12211278
}

token/program-2022/src/extension/confidential_transfer/verify_proof.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@ use {
22
crate::{
33
error::TokenError,
44
extension::{confidential_transfer::instruction::*, transfer_fee::TransferFee},
5-
proof::verify_and_extract_context,
65
},
76
solana_program::{
87
account_info::{next_account_info, AccountInfo},
98
program_error::ProgramError,
109
},
1110
spl_token_confidential_transfer_proof_extraction::{
1211
transfer::TransferProofContext, transfer_with_fee::TransferWithFeeProofContext,
13-
withdraw::WithdrawProofContext,
12+
verify_and_extract_context, withdraw::WithdrawProofContext,
1413
},
1514
std::slice::Iter,
1615
};

token/program-2022/src/extension/confidential_transfer_fee/instruction.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ use {
1111
instruction::CiphertextCiphertextEqualityProofData, DecryptableBalance,
1212
},
1313
instruction::{encode_instruction, TokenInstruction},
14-
proof::{ProofData, ProofLocation},
1514
solana_zk_sdk::{
1615
encryption::pod::elgamal::PodElGamalPubkey,
1716
zk_elgamal_proof_program::instruction::ProofInstruction,
@@ -26,6 +25,7 @@ use {
2625
sysvar,
2726
},
2827
spl_pod::optional_keys::OptionalNonZeroPubkey,
28+
spl_token_confidential_transfer_proof_extraction::{ProofData, ProofLocation},
2929
std::convert::TryFrom,
3030
};
3131

token/program-2022/src/extension/confidential_transfer_fee/processor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ use {
2727
instruction::{decode_instruction_data, decode_instruction_type},
2828
pod::{PodAccount, PodMint},
2929
processor::Processor,
30-
proof::verify_and_extract_context,
3130
solana_zk_sdk::encryption::pod::elgamal::PodElGamalPubkey,
3231
},
3332
bytemuck::Zeroable,
@@ -39,6 +38,7 @@ use {
3938
pubkey::Pubkey,
4039
},
4140
spl_pod::optional_keys::OptionalNonZeroPubkey,
41+
spl_token_confidential_transfer_proof_extraction::verify_and_extract_context,
4242
};
4343

4444
/// Processes an [InitializeConfidentialTransferFeeConfig] instruction.

token/program-2022/src/lib.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ pub mod onchain;
1414
pub mod pod;
1515
pub mod pod_instruction;
1616
pub mod processor;
17-
pub mod proof;
1817
#[cfg(feature = "serde-traits")]
1918
pub mod serialization;
2019
pub mod state;
@@ -130,3 +129,13 @@ pub fn check_system_program_account(system_program_id: &Pubkey) -> ProgramResult
130129
}
131130
Ok(())
132131
}
132+
133+
/// Checks if the supplied program ID is that of the ElGamal registry program
134+
pub fn check_elgamal_registry_program_account(
135+
elgamal_registry_account_program_id: &Pubkey,
136+
) -> ProgramResult {
137+
if elgamal_registry_account_program_id != &spl_elgamal_registry::id() {
138+
return Err(ProgramError::IncorrectProgramId);
139+
}
140+
Ok(())
141+
}

0 commit comments

Comments
 (0)