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

Commit 8566c56

Browse files
committed
update withdraw instruction
1 parent 639e016 commit 8566c56

File tree

5 files changed

+184
-48
lines changed

5 files changed

+184
-48
lines changed

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

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ use {
1515
zk_elgamal_proof_program::proof_data::ZeroCiphertextProofData,
1616
},
1717
spl_pod::primitives::PodU64,
18-
spl_token_confidential_transfer_proof_generation::transfer::{
19-
transfer_split_proof_data, TransferProofData,
18+
spl_token_confidential_transfer_proof_generation::{
19+
transfer::{transfer_split_proof_data, TransferProofData},
20+
transfer_with_fee::{transfer_with_fee_split_proof_data, TransferWithFeeProofData},
21+
withdraw::{withdraw_proof_data, WithdrawProofData},
2022
},
2123
};
2224

@@ -176,20 +178,20 @@ impl WithdrawAccountInfo {
176178
withdraw_amount: u64,
177179
elgamal_keypair: &ElGamalKeypair,
178180
aes_key: &AeKey,
179-
) -> Result<WithdrawData, TokenError> {
181+
) -> Result<WithdrawProofData, TokenError> {
180182
let current_available_balance = self
181183
.available_balance
182184
.try_into()
183185
.map_err(|_| TokenError::MalformedCiphertext)?;
184186
let current_decrypted_available_balance = self.decrypted_available_balance(aes_key)?;
185187

186-
WithdrawData::new(
188+
withdraw_proof_data(
189+
&current_available_balance,
190+
current_decrypted_available_balance,
187191
withdraw_amount,
188192
elgamal_keypair,
189-
current_decrypted_available_balance,
190-
&current_available_balance,
191193
)
192-
.map_err(|_| TokenError::ProofGeneration)
194+
.map_err(|e| -> TokenError { e.into() })
193195
}
194196

195197
/// Update the decryptable available balance.
@@ -268,6 +270,43 @@ impl TransferAccountInfo {
268270
.map_err(|e| -> TokenError { e.into() })
269271
}
270272

273+
/// Create a transfer proof data that is split into equality, ciphertext validity (transfer
274+
/// amount), percentage-with-cap, ciphertext validity (fee), and range proofs.
275+
pub fn generate_split_transfer_with_fee_proof_data(
276+
&self,
277+
transfer_amount: u64,
278+
source_elgamal_keypair: &ElGamalKeypair,
279+
aes_key: &AeKey,
280+
destination_elgamal_pubkey: &ElGamalPubkey,
281+
auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
282+
withdraw_withheld_authority_elgamal_pubkey: &ElGamalPubkey,
283+
fee_rate_basis_points: u16,
284+
maximum_fee: u64,
285+
) -> Result<TransferWithFeeProofData, TokenError> {
286+
let current_available_balance = self
287+
.available_balance
288+
.try_into()
289+
.map_err(|_| TokenError::MalformedCiphertext)?;
290+
let current_decryptable_available_balance = self
291+
.decryptable_available_balance
292+
.try_into()
293+
.map_err(|_| TokenError::MalformedCiphertext)?;
294+
295+
transfer_with_fee_split_proof_data(
296+
&current_available_balance,
297+
&current_decryptable_available_balance,
298+
transfer_amount,
299+
source_elgamal_keypair,
300+
aes_key,
301+
destination_elgamal_pubkey,
302+
auditor_elgamal_pubkey,
303+
withdraw_withheld_authority_elgamal_pubkey,
304+
fee_rate_basis_points,
305+
maximum_fee,
306+
)
307+
.map_err(|e| -> TokenError { e.into() })
308+
}
309+
271310
/// Update the decryptable available balance.
272311
pub fn new_decryptable_available_balance(
273312
&self,

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

Lines changed: 86 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -205,36 +205,37 @@ pub enum ConfidentialTransferInstruction {
205205
/// Withdraw SPL Tokens from the available balance of a confidential token
206206
/// account.
207207
///
208+
/// In order for this instruction to be successfully processed, it must be accompanied by the
209+
/// following list of `zk_elgamal_proof` program instructions:
210+
/// - `VerifyCiphertextCommitmentEquality`
211+
/// - `VerifyBatchedRangeProofU64`
212+
/// These instructions can be accompanied in the same transaction or can be pre-verified into a
213+
/// context state account, in which case, only their context state account address need to be
214+
/// provided.
215+
///
208216
/// Fails if the source or destination accounts are frozen.
209217
/// Fails if the associated mint is extended as `NonTransferable`.
210218
///
211-
/// In order for this instruction to be successfully processed, it must be
212-
/// accompanied by the `VerifyWithdraw` instruction of the
213-
/// `zk_token_proof` program in the same transaction or the address of a
214-
/// context state account for the proof must be provided.
215-
///
216219
/// Accounts expected by this instruction:
217220
///
218221
/// * Single owner/delegate
219222
/// 0. `[writable]` The SPL Token account.
220223
/// 1. `[]` The token mint.
221-
/// 2. `[]` Instructions sysvar if `VerifyWithdraw` is included in the
222-
/// same transaction or context state account if `VerifyWithdraw` is
223-
/// pre-verified into a context state account.
224-
/// 3. `[]` (Optional) Record account if the accompanying proof is to be
225-
/// read from a record account.
226-
/// 4. `[signer]` The single source account owner.
224+
/// 2. `[]` (Optional) Instructions sysvar if at least one of the `zk_elgamal_proof`
225+
/// instructions are included in the same transaction.
226+
/// 3. `[]` (Optional) Equality proof record account or context state account.
227+
/// 4. `[]` (Optional) Range proof record account or context state account.
228+
/// 5. `[signer]` The single source account owner.
227229
///
228230
/// * Multisignature owner/delegate
229231
/// 0. `[writable]` The SPL Token account.
230232
/// 1. `[]` The token mint.
231-
/// 2. `[]` Instructions sysvar if `VerifyWithdraw` is included in the
232-
/// same transaction or context state account if `VerifyWithdraw` is
233-
/// pre-verified into a context state account.
234-
/// 3. `[]` (Optional) Record account if the accompanying proof is to be
235-
/// read from a record account.
236-
/// 4. `[]` The multisig source account owner.
237-
/// 5.. `[signer]` Required M signer accounts for the SPL Token Multisig
233+
/// 2. `[]` (Optional) Instructions sysvar if at least one of the `zk_elgamal_proof`
234+
/// instructions are included in the same transaction.
235+
/// 3. `[]` (Optional) Equality proof record account or context state account.
236+
/// 4. `[]` (Optional) Range proof record account or context state account.
237+
/// 5. `[]` The multisig source account owner.
238+
/// 6.. `[signer]` Required M signer accounts for the SPL Token Multisig
238239
/// account.
239240
///
240241
/// Data expected by this instruction:
@@ -548,10 +549,14 @@ pub struct WithdrawInstructionData {
548549
/// The new decryptable balance if the withdrawal succeeds
549550
#[cfg_attr(feature = "serde-traits", serde(with = "aeciphertext_fromstr"))]
550551
pub new_decryptable_available_balance: DecryptableBalance,
551-
/// Relative location of the `ProofInstruction::VerifyWithdraw` instruction
552+
/// Relative location of the `ProofInstruction::VerifyCiphertextCommitmentEquality` instruction
552553
/// to the `Withdraw` instruction in the transaction. If the offset is
553554
/// `0`, then use a context state account for the proof.
554-
pub proof_instruction_offset: i8,
555+
pub equality_proof_instruction_offset: i8,
556+
/// Relative location of the `ProofInstruction::BatchedRangeProofU64` instruction
557+
/// to the `Withdraw` instruction in the transaction. If the offset is
558+
/// `0`, then use a context state account for the proof.
559+
pub range_proof_instruction_offset: i8,
555560
}
556561

557562
/// Data expected by `ConfidentialTransferInstruction::Transfer`
@@ -948,17 +953,38 @@ pub fn inner_withdraw(
948953
new_decryptable_available_balance: DecryptableBalance,
949954
authority: &Pubkey,
950955
multisig_signers: &[&Pubkey],
951-
proof_data_location: ProofLocation<WithdrawData>,
956+
equality_proof_data_location: ProofLocation<CiphertextCommitmentEqualityProofData>,
957+
range_proof_data_location: ProofLocation<BatchedRangeProofU64Data>,
952958
) -> Result<Instruction, ProgramError> {
953959
check_program_account(token_program_id)?;
954960
let mut accounts = vec![
955961
AccountMeta::new(*token_account, false),
956962
AccountMeta::new_readonly(*mint, false),
957963
];
958964

959-
let proof_instruction_offset = match proof_data_location {
965+
// if at least one of the proof locations is an instruction offset, sysvar
966+
// account is needed
967+
if equality_proof_data_location.is_instruction_offset()
968+
|| range_proof_data_location.is_instruction_offset()
969+
{
970+
accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false));
971+
}
972+
973+
let equality_proof_instruction_offset = match equality_proof_data_location {
974+
ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => {
975+
if let ProofData::RecordAccount(record_address, _) = proof_data {
976+
accounts.push(AccountMeta::new_readonly(*record_address, false));
977+
}
978+
proof_instruction_offset.into()
979+
}
980+
ProofLocation::ContextStateAccount(context_state_account) => {
981+
accounts.push(AccountMeta::new_readonly(*context_state_account, false));
982+
0
983+
}
984+
};
985+
986+
let range_proof_instruction_offset = match range_proof_data_location {
960987
ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => {
961-
accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false));
962988
if let ProofData::RecordAccount(record_address, _) = proof_data {
963989
accounts.push(AccountMeta::new_readonly(*record_address, false));
964990
}
@@ -988,7 +1014,8 @@ pub fn inner_withdraw(
9881014
amount: amount.into(),
9891015
decimals,
9901016
new_decryptable_available_balance,
991-
proof_instruction_offset,
1017+
equality_proof_instruction_offset,
1018+
range_proof_instruction_offset,
9921019
},
9931020
))
9941021
}
@@ -1004,7 +1031,8 @@ pub fn withdraw(
10041031
new_decryptable_available_balance: PodAeCiphertext,
10051032
authority: &Pubkey,
10061033
multisig_signers: &[&Pubkey],
1007-
proof_data_location: ProofLocation<WithdrawData>,
1034+
equality_proof_data_location: ProofLocation<CiphertextCommitmentEqualityProofData>,
1035+
range_proof_data_location: ProofLocation<BatchedRangeProofU64Data>,
10081036
) -> Result<Vec<Instruction>, ProgramError> {
10091037
let mut instructions = vec![inner_withdraw(
10101038
token_program_id,
@@ -1015,24 +1043,41 @@ pub fn withdraw(
10151043
new_decryptable_available_balance.into(),
10161044
authority,
10171045
multisig_signers,
1018-
proof_data_location,
1046+
equality_proof_data_location,
1047+
range_proof_data_location,
10191048
)?];
10201049

10211050
if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
1022-
proof_data_location
1051+
equality_proof_data_location
10231052
{
1024-
// This constructor appends the proof instruction right after the `Withdraw`
1025-
// instruction. This means that the proof instruction offset must be
1026-
// always be 1. To use an arbitrary proof instruction offset, use the
1027-
// `inner_withdraw` constructor.
10281053
let proof_instruction_offset: i8 = proof_instruction_offset.into();
10291054
if proof_instruction_offset != 1 {
10301055
return Err(TokenError::InvalidProofInstructionOffset.into());
10311056
}
10321057
match proof_data {
1033-
ProofData::InstructionData(data) => instructions.push(verify_withdraw(None, data)),
1058+
ProofData::InstructionData(data) => instructions.push(
1059+
ProofInstruction::VerifyCiphertextCommitmentEquality
1060+
.encode_verify_proof(None, data),
1061+
),
10341062
ProofData::RecordAccount(address, offset) => instructions.push(
1035-
ProofInstruction::VerifyWithdraw
1063+
ProofInstruction::VerifyCiphertextCommitmentEquality
1064+
.encode_verify_proof_from_account(None, address, offset),
1065+
),
1066+
};
1067+
};
1068+
1069+
if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
1070+
range_proof_data_location
1071+
{
1072+
let proof_instruction_offset: i8 = proof_instruction_offset.into();
1073+
if proof_instruction_offset != 2 {
1074+
return Err(TokenError::InvalidProofInstructionOffset.into());
1075+
}
1076+
match proof_data {
1077+
ProofData::InstructionData(data) => instructions
1078+
.push(ProofInstruction::VerifyBatchedRangeProofU64.encode_verify_proof(None, data)),
1079+
ProofData::RecordAccount(address, offset) => instructions.push(
1080+
ProofInstruction::VerifyBatchedRangeProofU64
10361081
.encode_verify_proof_from_account(None, address, offset),
10371082
),
10381083
};
@@ -1120,6 +1165,10 @@ pub fn inner_transfer(
11201165
multisig_signers.is_empty(),
11211166
));
11221167

1168+
for multisig_signer in multisig_signers.iter() {
1169+
accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
1170+
}
1171+
11231172
Ok(encode_instruction(
11241173
token_program_id,
11251174
accounts,
@@ -1476,6 +1525,10 @@ pub fn inner_transfer_with_fee(
14761525
multisig_signers.is_empty(),
14771526
));
14781527

1528+
for multisig_signer in multisig_signers.iter() {
1529+
accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
1530+
}
1531+
14791532
Ok(encode_instruction(
14801533
token_program_id,
14811534
accounts,

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -359,18 +359,19 @@ fn process_withdraw(
359359
amount: u64,
360360
expected_decimals: u8,
361361
new_decryptable_available_balance: DecryptableBalance,
362-
proof_instruction_offset: i64,
362+
equality_proof_instruction_offset: i64,
363+
range_proof_instruction_offset: i64,
363364
) -> ProgramResult {
364365
let account_info_iter = &mut accounts.iter();
365366
let token_account_info = next_account_info(account_info_iter)?;
366367
let mint_info = next_account_info(account_info_iter)?;
367368

368369
// zero-knowledge proof certifies that the account has enough available balance
369370
// to withdraw the amount.
370-
let proof_context = verify_and_extract_context::<WithdrawData, WithdrawProofContext>(
371+
let proof_context = verify_withdraw_proof(
371372
account_info_iter,
372-
proof_instruction_offset,
373-
None,
373+
equality_proof_instruction_offset,
374+
range_proof_instruction_offset,
374375
)?;
375376

376377
let authority_info = next_account_info(account_info_iter)?;
@@ -421,7 +422,7 @@ fn process_withdraw(
421422
// Check that the encryption public key associated with the confidential
422423
// extension is consistent with the public key that was actually used to
423424
// generate the zkp.
424-
if confidential_transfer_account.elgamal_pubkey != proof_context.pubkey {
425+
if confidential_transfer_account.elgamal_pubkey != proof_context.source_pubkey {
425426
return Err(TokenError::ConfidentialTransferElGamalPubkeyMismatch.into());
426427
}
427428

@@ -436,7 +437,8 @@ fn process_withdraw(
436437
}
437438
// Check that the final available balance ciphertext is consistent with the
438439
// actual ciphertext for which the zero-knowledge proof was generated for.
439-
if confidential_transfer_account.available_balance != proof_context.final_ciphertext {
440+
if confidential_transfer_account.available_balance != proof_context.remaining_balance_ciphertext
441+
{
440442
return Err(TokenError::ConfidentialTransferBalanceMismatch.into());
441443
}
442444

@@ -1139,7 +1141,8 @@ pub(crate) fn process_instruction(
11391141
data.amount.into(),
11401142
data.decimals,
11411143
data.new_decryptable_available_balance,
1142-
data.proof_instruction_offset as i64,
1144+
data.equality_proof_instruction_offset as i64,
1145+
data.range_proof_instruction_offset as i64,
11431146
)
11441147
}
11451148
#[cfg(not(feature = "zk-ops"))]

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,52 @@ use {
1010
},
1111
spl_token_confidential_transfer_proof_extraction::{
1212
transfer::TransferProofContext, transfer_with_fee::TransferWithFeeProofContext,
13+
withdraw::WithdrawProofContext,
1314
},
1415
std::slice::Iter,
1516
};
1617

18+
/// Verify zero-knowledge proofs needed for a [Withdraw] instruction and return the corresponding
19+
/// proof context.
20+
#[cfg(feature = "zk-ops")]
21+
pub fn verify_withdraw_proof(
22+
account_info_iter: &mut Iter<AccountInfo>,
23+
equality_proof_instruction_offset: i64,
24+
range_proof_instruction_offset: i64,
25+
) -> Result<WithdrawProofContext, ProgramError> {
26+
let sysvar_account_info =
27+
if equality_proof_instruction_offset != 0 || range_proof_instruction_offset != 0 {
28+
Some(next_account_info(account_info_iter)?)
29+
} else {
30+
None
31+
};
32+
33+
let equality_proof_context = verify_and_extract_context::<
34+
CiphertextCommitmentEqualityProofData,
35+
CiphertextCommitmentEqualityProofContext,
36+
>(
37+
account_info_iter,
38+
equality_proof_instruction_offset,
39+
sysvar_account_info,
40+
)?;
41+
42+
let range_proof_context =
43+
verify_and_extract_context::<BatchedRangeProofU64Data, BatchedRangeProofContext>(
44+
account_info_iter,
45+
range_proof_instruction_offset,
46+
sysvar_account_info,
47+
)?;
48+
49+
// The `WithdrawProofContext` constructor verifies the consistency of the
50+
// individual proof context and generates a `WithdrawProofContext` struct
51+
// that is used to process the rest of the token-2022 logic.
52+
let transfer_proof_context =
53+
WithdrawProofContext::verify_and_extract(&equality_proof_context, &range_proof_context)
54+
.map_err(|e| -> TokenError { e.into() })?;
55+
56+
Ok(transfer_proof_context)
57+
}
58+
1759
/// Verify zero-knowledge proof needed for a [Transfer] instruction without fee
1860
/// and return the corresponding proof context.
1961
#[cfg(feature = "zk-ops")]

0 commit comments

Comments
 (0)