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

Commit 1e094bb

Browse files
committed
add elgamal registry account
1 parent df498f3 commit 1e094bb

File tree

8 files changed

+252
-32
lines changed

8 files changed

+252
-32
lines changed

rust-toolchain.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[toolchain]
2-
channel = "1.78.0"
2+
channel = "1.79.0"

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ pub enum ConfidentialTransferInstruction {
8181
///
8282
/// Accounts expected by this instruction:
8383
///
84+
/// TODO: Add an option to include an `ElGamalRegistry` address
85+
///
8486
/// * Single owner/delegate
8587
/// 0. `[writeable]` The SPL Token account.
8688
/// 1. `[]` The corresponding SPL Token mint.
@@ -106,7 +108,8 @@ pub enum ConfidentialTransferInstruction {
106108
/// account.
107109
///
108110
/// Data expected by this instruction:
109-
/// `ConfigureAccountInstructionData`
111+
/// None if an `ElGamalRegistry` address is provided in the list of accounts
112+
/// `ConfigureAccountInstructionData` otherwise
110113
ConfigureAccount,
111114

112115
/// Approves a token account for confidential transfers.

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: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use {
2222
instruction::{decode_instruction_data, decode_instruction_type},
2323
pod::{PodAccount, PodMint},
2424
processor::Processor,
25-
proof::verify_and_extract_context,
25+
proof::{verify_and_extract_context, ElGamalRegistry},
2626
},
2727
solana_program::{
2828
account_info::{next_account_info, AccountInfo},
@@ -33,6 +33,7 @@ use {
3333
pubkey::Pubkey,
3434
sysvar::Sysvar,
3535
},
36+
spl_pod::bytemuck::pod_from_bytes,
3637
spl_token_confidential_transfer_proof_extraction::{
3738
transfer::TransferProofContext, transfer_with_fee::TransferWithFeeProofContext,
3839
},
@@ -92,23 +93,62 @@ fn process_update_mint(
9293
Ok(())
9394
}
9495

96+
/// Processes a [ConfigureAccount] instruction with the assumption that an ElGamal registry is
97+
/// provided.
98+
fn process_configure_account_from_registry(
99+
program_id: &Pubkey,
100+
accounts: &[AccountInfo],
101+
) -> ProgramResult {
102+
let elgamal_registry_account = accounts.get(2).unwrap();
103+
104+
if elgamal_registry_account.owner != program_id {
105+
return Err(TokenError::OwnerMismatch.into());
106+
}
107+
108+
let elgamal_registry_account_data = &elgamal_registry_account.data.borrow();
109+
let elgamal_registry_account =
110+
pod_from_bytes::<ElGamalRegistry>(elgamal_registry_account_data)?;
111+
112+
let decryptable_zero_balance = PodAeCiphertext::default();
113+
let maximum_pending_balance_credit_counter =
114+
DEFAULT_MAXIMUM_PENDING_BALANCE_CREDIT_COUNTER.into();
115+
116+
process_configure_account(
117+
program_id,
118+
accounts,
119+
&decryptable_zero_balance,
120+
&maximum_pending_balance_credit_counter,
121+
None,
122+
Some(elgamal_registry_account),
123+
)
124+
}
125+
95126
/// Processes a [ConfigureAccount] instruction.
96127
fn process_configure_account(
97128
program_id: &Pubkey,
98129
accounts: &[AccountInfo],
99130
decryptable_zero_balance: &DecryptableBalance,
100131
maximum_pending_balance_credit_counter: &PodU64,
101-
proof_instruction_offset: i64,
132+
proof_instruction_offset: Option<i64>,
133+
elgamal_registry_account: Option<&ElGamalRegistry>,
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 = if let Some(offset) = proof_instruction_offset {
140+
// zero-knowledge proof certifies that the supplied ElGamal public key is valid
141+
let proof_context = verify_and_extract_context::<
142+
PubkeyValidityProofData,
143+
PubkeyValidityProofContext,
144+
>(account_info_iter, offset, None)?;
145+
proof_context.pubkey
146+
} else {
147+
// if proof instruction offset is `None`, then assume that the proof
148+
// was already verified in an ElGamal registry account
149+
let _elgamal_registry_account = next_account_info(account_info_iter)?;
150+
elgamal_registry_account.unwrap().elgamal_pubkey
151+
};
112152

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

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-
)?;
164+
if let Some(registry_account) = elgamal_registry_account {
165+
// if ElGamal registry was provided, then just verify that the registry owner and the
166+
// account match, then skip the signature verification check
167+
if registry_account.owner != *authority_info.key {
168+
return Err(TokenError::OwnerMismatch.into());
169+
}
170+
} else {
171+
Processor::validate_owner(
172+
program_id,
173+
&token_account.base.owner,
174+
authority_info,
175+
authority_info_data_len,
176+
account_info_iter.as_slice(),
177+
)?;
178+
}
131179

132180
check_program_account(mint_info.owner)?;
133181
let mint_data = &mut mint_info.data.borrow();
@@ -140,7 +188,7 @@ fn process_configure_account(
140188
let confidential_transfer_account =
141189
token_account.init_extension::<ConfidentialTransferAccount>(false)?;
142190
confidential_transfer_account.approved = confidential_transfer_mint.auto_approve_new_accounts;
143-
confidential_transfer_account.elgamal_pubkey = proof_context.pubkey;
191+
confidential_transfer_account.elgamal_pubkey = elgamal_pubkey;
144192
confidential_transfer_account.maximum_pending_balance_credit_counter =
145193
*maximum_pending_balance_credit_counter;
146194

@@ -1102,14 +1150,20 @@ pub(crate) fn process_instruction(
11021150
}
11031151
ConfidentialTransferInstruction::ConfigureAccount => {
11041152
msg!("ConfidentialTransferInstruction::ConfigureAccount");
1105-
let data = decode_instruction_data::<ConfigureAccountInstructionData>(input)?;
1106-
process_configure_account(
1107-
program_id,
1108-
accounts,
1109-
&data.decryptable_zero_balance,
1110-
&data.maximum_pending_balance_credit_counter,
1111-
data.proof_instruction_offset as i64,
1112-
)
1153+
if input.is_empty() {
1154+
// instruction data is empty, so assume an ElGamal registry is provided
1155+
process_configure_account_from_registry(program_id, accounts)
1156+
} else {
1157+
let data = decode_instruction_data::<ConfigureAccountInstructionData>(input)?;
1158+
process_configure_account(
1159+
program_id,
1160+
accounts,
1161+
&data.decryptable_zero_balance,
1162+
&data.maximum_pending_balance_credit_counter,
1163+
Some(data.proof_instruction_offset as i64),
1164+
None,
1165+
)
1166+
}
11131167
}
11141168
ConfidentialTransferInstruction::ApproveAccount => {
11151169
msg!("ConfidentialTransferInstruction::ApproveAccount");

token/program-2022/src/instruction.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,42 @@ pub enum TokenInstruction<'a> {
708708
/// for further details about the extended instructions that share this
709709
/// instruction prefix
710710
GroupMemberPointerExtension,
711+
/// Initialize an ElGamal public key registry for an account.
712+
///
713+
/// 0. `[writable]` The account to initialize
714+
/// 1. `[]` Instructions sysvar if `VerifyPubkeyValidity` is included in
715+
/// the same transaction or context state account if
716+
/// `VerifyPubkeyValidity` is pre-verified into a context state
717+
/// account.
718+
/// 2. `[]` (Optional) Record account if the accompanying proof is to be
719+
/// read from a record account.
720+
CreateElGamalRegistry {
721+
/// The owner of the ElGamal registry account
722+
#[cfg_attr(feature = "serde-traits", serde(with = "As::<DisplayFromStr>"))]
723+
owner: Pubkey,
724+
/// Relative location of the `ProofInstruction::PubkeyValidityProof`
725+
/// instruction to the `CreateElGamalRegistry` instruction in the
726+
/// transaction. If the offset is `0`, then use a context state account
727+
/// for the proof.
728+
proof_instruction_offset: i8,
729+
},
730+
/// Update an ElGamal public key registry with a new ElGamal public key.
731+
///
732+
/// 0. `[writable]` The account to initialize
733+
/// 1. `[signer]` The owner of the ElGamal public key registry
734+
/// 2. `[]` Instructions sysvar if `VerifyPubkeyValidity` is included in
735+
/// the same transaction or context state account if
736+
/// `VerifyPubkeyValidity` is pre-verified into a context state
737+
/// account.
738+
/// 3. `[]` (Optional) Record account if the accompanying proof is to be
739+
/// read from a record account.
740+
UpdateElGamalRegistry {
741+
/// Relative location of the `ProofInstruction::PubkeyValidityProof`
742+
/// instruction to the `UpdateElGamalRegistry` instruction in the
743+
/// transaction. If the offset is `0`, then use a context state account
744+
/// for the proof.
745+
proof_instruction_offset: i8,
746+
},
711747
}
712748
impl<'a> TokenInstruction<'a> {
713749
/// Unpacks a byte buffer into a
@@ -1018,6 +1054,20 @@ impl<'a> TokenInstruction<'a> {
10181054
&Self::GroupMemberPointerExtension => {
10191055
buf.push(41);
10201056
}
1057+
&Self::CreateElGamalRegistry {
1058+
owner,
1059+
proof_instruction_offset,
1060+
} => {
1061+
buf.push(42);
1062+
buf.extend_from_slice(owner.as_ref());
1063+
buf.extend_from_slice(&proof_instruction_offset.to_le_bytes());
1064+
}
1065+
&Self::UpdateElGamalRegistry {
1066+
proof_instruction_offset,
1067+
} => {
1068+
buf.push(43);
1069+
buf.extend_from_slice(&proof_instruction_offset.to_le_bytes());
1070+
}
10211071
};
10221072
buf
10231073
}

token/program-2022/src/pod_instruction.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,20 @@ pub(crate) struct SetAuthorityData {
5353
// The new authority option comes later, but cannot be included as
5454
// plain old data in this struct
5555
}
56+
#[repr(C)]
57+
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
58+
pub(crate) struct CreateElGamalRegistryData {
59+
/// The owner of the ElGamal registry account
60+
pub(crate) owner: Pubkey,
61+
/// The proof instruction offset
62+
pub(crate) proof_instruction_offset: i8,
63+
}
64+
#[repr(C)]
65+
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
66+
pub(crate) struct UpdateElGamalRegistryData {
67+
/// The proof instruction offset
68+
pub(crate) proof_instruction_offset: i8,
69+
}
5670

5771
/// All of the base instructions in Token-2022, reduced down to their one-byte
5872
/// discriminant.
@@ -114,6 +128,8 @@ pub(crate) enum PodTokenInstruction {
114128
// 40
115129
GroupPointerExtension,
116130
GroupMemberPointerExtension,
131+
CreateElGamalRegistry,
132+
UpdateElGamalRegistry,
117133
}
118134

119135
fn unpack_pubkey_option(input: &[u8]) -> Result<PodCOption<Pubkey>, ProgramError> {

token/program-2022/src/processor.rs

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ use {
3434
pod::{PodAccount, PodCOption, PodMint, PodMultisig},
3535
pod_instruction::{
3636
decode_instruction_data_with_coption_pubkey, AmountCheckedData, AmountData,
37-
InitializeMintData, InitializeMultisigData, PodTokenInstruction, SetAuthorityData,
37+
CreateElGamalRegistryData, InitializeMintData, InitializeMultisigData,
38+
PodTokenInstruction, SetAuthorityData, UpdateElGamalRegistryData,
3839
},
40+
proof::{verify_and_extract_context, ElGamalRegistry},
3941
state::{Account, AccountState, Mint, PackedSizeOf},
4042
},
4143
solana_program::{
@@ -50,6 +52,9 @@ use {
5052
system_instruction, system_program,
5153
sysvar::{rent::Rent, Sysvar},
5254
},
55+
solana_zk_sdk::zk_elgamal_proof_program::proof_data::pubkey_validity::{
56+
PubkeyValidityProofContext, PubkeyValidityProofData,
57+
},
5358
spl_pod::{
5459
bytemuck::{pod_from_bytes, pod_from_bytes_mut},
5560
primitives::{PodBool, PodU64},
@@ -1539,6 +1544,63 @@ impl Processor {
15391544
Ok(())
15401545
}
15411546

1547+
/// Processes an [CreateElGamalRegistry](enum.TokenInstruction.html) instruction
1548+
pub fn process_create_elgamal_registry(
1549+
accounts: &[AccountInfo],
1550+
owner: &Pubkey,
1551+
proof_instruction_offset: i64,
1552+
) -> ProgramResult {
1553+
let account_info_iter = &mut accounts.iter();
1554+
let registry_account_info = next_account_info(account_info_iter)?;
1555+
1556+
// zero-knowledge proof certifies that the supplied ElGamal public key is valid
1557+
let proof_context = verify_and_extract_context::<
1558+
PubkeyValidityProofData,
1559+
PubkeyValidityProofContext,
1560+
>(account_info_iter, proof_instruction_offset, None)?;
1561+
1562+
let registry_account_data = &mut registry_account_info.data.borrow_mut();
1563+
let registry_account = pod_from_bytes_mut::<ElGamalRegistry>(registry_account_data)?;
1564+
1565+
registry_account.owner = *owner;
1566+
registry_account.elgamal_pubkey = proof_context.pubkey;
1567+
1568+
Ok(())
1569+
}
1570+
1571+
/// Processes an [UpdateElGamalRegistry](enum.TokenInstruction.html) instruction
1572+
pub fn process_update_elgamal_registry(
1573+
program_id: &Pubkey,
1574+
accounts: &[AccountInfo],
1575+
proof_instruction_offset: i64,
1576+
) -> ProgramResult {
1577+
let account_info_iter = &mut accounts.iter();
1578+
let registry_account_info = next_account_info(account_info_iter)?;
1579+
let owner_info = next_account_info(account_info_iter)?;
1580+
let owner_info_data_len = owner_info.data_len();
1581+
1582+
// zero-knowledge proof certifies that the supplied ElGamal public key is valid
1583+
let proof_context = verify_and_extract_context::<
1584+
PubkeyValidityProofData,
1585+
PubkeyValidityProofContext,
1586+
>(account_info_iter, proof_instruction_offset, None)?;
1587+
1588+
let registry_account_data = &mut registry_account_info.data.borrow_mut();
1589+
let registry_account = pod_from_bytes_mut::<ElGamalRegistry>(registry_account_data)?;
1590+
1591+
Processor::validate_owner(
1592+
program_id,
1593+
&registry_account.owner,
1594+
owner_info,
1595+
owner_info_data_len,
1596+
account_info_iter.as_slice(),
1597+
)?;
1598+
1599+
registry_account.elgamal_pubkey = proof_context.pubkey;
1600+
1601+
Ok(())
1602+
}
1603+
15421604
/// Processes an [Instruction](enum.Instruction.html).
15431605
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
15441606
if let Ok(instruction_type) = decode_instruction_type(input) {
@@ -1793,6 +1855,24 @@ impl Processor {
17931855
&input[1..],
17941856
)
17951857
}
1858+
PodTokenInstruction::CreateElGamalRegistry => {
1859+
msg!("Instruction: InitializeElGamalRegistry");
1860+
let data = decode_instruction_data::<CreateElGamalRegistryData>(input)?;
1861+
Self::process_create_elgamal_registry(
1862+
accounts,
1863+
&data.owner,
1864+
data.proof_instruction_offset.into(),
1865+
)
1866+
}
1867+
PodTokenInstruction::UpdateElGamalRegistry => {
1868+
msg!("Instruction: UpdateElGamalRegistry");
1869+
let data = decode_instruction_data::<UpdateElGamalRegistryData>(input)?;
1870+
Self::process_update_elgamal_registry(
1871+
program_id,
1872+
accounts,
1873+
data.proof_instruction_offset.into(),
1874+
)
1875+
}
17961876
}
17971877
} else if let Ok(instruction) = TokenMetadataInstruction::unpack(input) {
17981878
token_metadata::processor::process_instruction(program_id, accounts, instruction)

0 commit comments

Comments
 (0)