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

Commit 31931a7

Browse files
[confidential-transfer] Add withdraw proof generation and extraction (#6954)
* add withdraw proof generation * add withdraw proof extraction * re-organize proof data using structs
1 parent 8885d7c commit 31931a7

File tree

5 files changed

+157
-1
lines changed

5 files changed

+157
-1
lines changed

token/confidential-transfer/proof-extraction/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ pub mod encryption;
22
pub mod errors;
33
pub mod transfer;
44
pub mod transfer_with_fee;
5+
pub mod withdraw;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use {
2+
crate::errors::TokenProofExtractionError,
3+
solana_zk_sdk::{
4+
encryption::pod::elgamal::{PodElGamalCiphertext, PodElGamalPubkey},
5+
zk_elgamal_proof_program::proof_data::{
6+
BatchedRangeProofContext, CiphertextCommitmentEqualityProofContext,
7+
},
8+
},
9+
};
10+
11+
const REMAINING_BALANCE_BIT_LENGTH: u8 = 64;
12+
13+
pub struct WithdrawProofContext {
14+
pub source_pubkey: PodElGamalPubkey,
15+
pub remaining_balance_ciphertext: PodElGamalCiphertext,
16+
}
17+
18+
impl WithdrawProofContext {
19+
pub fn verify_and_extract(
20+
equality_proof_context: &CiphertextCommitmentEqualityProofContext,
21+
range_proof_context: &BatchedRangeProofContext,
22+
) -> Result<Self, TokenProofExtractionError> {
23+
let CiphertextCommitmentEqualityProofContext {
24+
pubkey: source_pubkey,
25+
ciphertext: remaining_balance_ciphertext,
26+
commitment: remaining_balance_commitment,
27+
} = equality_proof_context;
28+
29+
let BatchedRangeProofContext {
30+
commitments: range_proof_commitments,
31+
bit_lengths: range_proof_bit_lengths,
32+
} = range_proof_context;
33+
34+
if range_proof_commitments.is_empty()
35+
|| range_proof_commitments[0] != *remaining_balance_commitment
36+
{
37+
return Err(TokenProofExtractionError::PedersenCommitmentMismatch);
38+
}
39+
40+
if range_proof_bit_lengths.is_empty()
41+
|| range_proof_bit_lengths[0] != REMAINING_BALANCE_BIT_LENGTH
42+
{
43+
return Err(TokenProofExtractionError::RangeProofLengthMismatch);
44+
}
45+
46+
let context_info = WithdrawProofContext {
47+
source_pubkey: *source_pubkey,
48+
remaining_balance_ciphertext: *remaining_balance_ciphertext,
49+
};
50+
51+
Ok(context_info)
52+
}
53+
}

token/confidential-transfer/proof-generation/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub mod encryption;
1010
pub mod errors;
1111
pub mod transfer;
1212
pub mod transfer_with_fee;
13+
pub mod withdraw;
1314

1415
/// The low bit length of the encrypted transfer amount
1516
pub const TRANSFER_AMOUNT_LO_BITS: usize = 16;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
use {
2+
crate::errors::TokenProofGenerationError,
3+
solana_zk_sdk::{
4+
encryption::{
5+
elgamal::{ElGamal, ElGamalCiphertext, ElGamalKeypair},
6+
pedersen::Pedersen,
7+
},
8+
zk_elgamal_proof_program::proof_data::{
9+
BatchedRangeProofU64Data, CiphertextCommitmentEqualityProofData,
10+
},
11+
},
12+
};
13+
14+
const REMAINING_BALANCE_BIT_LENGTH: usize = 64;
15+
16+
/// Proof data required for a withdraw instruction
17+
pub struct WithdrawProofData {
18+
pub equality_proof_data: CiphertextCommitmentEqualityProofData,
19+
pub range_proof_data: BatchedRangeProofU64Data,
20+
}
21+
22+
pub fn withdraw_proof_data(
23+
current_available_balance: &ElGamalCiphertext,
24+
current_balance: u64,
25+
withdraw_amount: u64,
26+
elgamal_keypair: &ElGamalKeypair,
27+
) -> Result<WithdrawProofData, TokenProofGenerationError> {
28+
// Calculate the remaining balance after withdraw
29+
let remaining_balance = current_balance
30+
.checked_sub(withdraw_amount)
31+
.ok_or(TokenProofGenerationError::NotEnoughFunds)?;
32+
33+
// Generate a Pedersen commitment for the remaining balance
34+
let (remaining_balance_commitment, remaining_balance_opening) =
35+
Pedersen::new(remaining_balance);
36+
37+
// Compute the remaining balance ciphertext
38+
#[allow(clippy::arithmetic_side_effects)]
39+
let remaining_balance_ciphertext = current_available_balance - ElGamal::encode(withdraw_amount);
40+
41+
// Generate proof data
42+
let equality_proof_data = CiphertextCommitmentEqualityProofData::new(
43+
elgamal_keypair,
44+
&remaining_balance_ciphertext,
45+
&remaining_balance_commitment,
46+
&remaining_balance_opening,
47+
remaining_balance,
48+
)
49+
.map_err(TokenProofGenerationError::from)?;
50+
51+
let range_proof_data = BatchedRangeProofU64Data::new(
52+
vec![&remaining_balance_commitment],
53+
vec![remaining_balance],
54+
vec![REMAINING_BALANCE_BIT_LENGTH],
55+
vec![&remaining_balance_opening],
56+
)
57+
.map_err(TokenProofGenerationError::from)?;
58+
59+
Ok(WithdrawProofData {
60+
equality_proof_data,
61+
range_proof_data,
62+
})
63+
}

token/confidential-transfer/proof-tests/tests/proof_test.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ use {
55
},
66
spl_token_confidential_transfer_proof_extraction::{
77
transfer::TransferProofContext, transfer_with_fee::TransferWithFeeProofContext,
8+
withdraw::WithdrawProofContext,
89
},
910
spl_token_confidential_transfer_proof_generation::{
10-
transfer::transfer_split_proof_data, transfer_with_fee::transfer_with_fee_split_proof_data,
11+
transfer::transfer_split_proof_data,
12+
transfer_with_fee::transfer_with_fee_split_proof_data,
13+
withdraw::{withdraw_proof_data, WithdrawProofData},
1114
},
1215
};
1316

@@ -140,3 +143,38 @@ fn test_transfer_with_fee_proof_validity(
140143
)
141144
.unwrap();
142145
}
146+
147+
#[test]
148+
fn test_withdraw_proof_correctness() {
149+
test_withdraw_validity(0, 0);
150+
test_withdraw_validity(77, 55);
151+
test_withdraw_validity(65535, 65535);
152+
test_withdraw_validity(65536, 65536);
153+
test_withdraw_validity(281474976710655, 281474976710655);
154+
}
155+
156+
fn test_withdraw_validity(spendable_balance: u64, withdraw_amount: u64) {
157+
let keypair = ElGamalKeypair::new_rand();
158+
159+
let spendable_ciphertext = keypair.pubkey().encrypt(spendable_balance);
160+
161+
let WithdrawProofData {
162+
equality_proof_data,
163+
range_proof_data,
164+
} = withdraw_proof_data(
165+
&spendable_ciphertext,
166+
spendable_balance,
167+
withdraw_amount,
168+
&keypair,
169+
)
170+
.unwrap();
171+
172+
equality_proof_data.verify_proof().unwrap();
173+
range_proof_data.verify_proof().unwrap();
174+
175+
WithdrawProofContext::verify_and_extract(
176+
equality_proof_data.context_data(),
177+
range_proof_data.context_data(),
178+
)
179+
.unwrap();
180+
}

0 commit comments

Comments
 (0)