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

Commit b82c844

Browse files
committed
add proof generation logic for confidential mint and burn
1 parent ba05751 commit b82c844

File tree

4 files changed

+272
-0
lines changed

4 files changed

+272
-0
lines changed
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
use {
2+
crate::{
3+
encryption::BurnAmountCiphertext, errors::TokenProofGenerationError,
4+
try_combine_lo_hi_ciphertexts, try_split_u64,
5+
},
6+
solana_zk_sdk::{
7+
encryption::{
8+
auth_encryption::{AeCiphertext, AeKey},
9+
elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
10+
pedersen::Pedersen,
11+
},
12+
zk_elgamal_proof_program::proof_data::{
13+
BatchedGroupedCiphertext2HandlesValidityProofData, BatchedRangeProofU128Data,
14+
CiphertextCommitmentEqualityProofData,
15+
},
16+
},
17+
};
18+
19+
const REMAINING_BALANCE_BIT_LENGTH: usize = 64;
20+
const BURN_AMOUNT_LO_BIT_LENGTH: usize = 16;
21+
const BURN_AMOUNT_HI_BIT_LENGTH: usize = 32;
22+
/// The padding bit length in range proofs to make the bit-length power-of-2
23+
const RANGE_PROOF_PADDING_BIT_LENGTH: usize = 16;
24+
25+
/// The proof data required for a confidential burn instruction
26+
pub struct BurnProofData {
27+
pub equality_proof_data: CiphertextCommitmentEqualityProofData,
28+
pub ciphertext_validity_proof_data: BatchedGroupedCiphertext2HandlesValidityProofData,
29+
pub range_proof_data: BatchedRangeProofU128Data,
30+
}
31+
32+
pub fn burn_split_proof_data(
33+
current_available_balance_ciphertext: &ElGamalCiphertext,
34+
current_decryptable_available_balance: &AeCiphertext,
35+
burn_amount: u64,
36+
source_elgamal_keypair: &ElGamalKeypair,
37+
aes_key: &AeKey,
38+
auditor_elgamal_pubkey: &ElGamalPubkey,
39+
) -> Result<BurnProofData, TokenProofGenerationError> {
40+
// split the burn amount into low and high bits
41+
let (burn_amount_lo, burn_amount_hi) = try_split_u64(burn_amount, BURN_AMOUNT_LO_BIT_LENGTH)
42+
.ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
43+
44+
// encrypt the burn amount under the source and auditor's ElGamal public key
45+
let (burn_amount_ciphertext_lo, burn_amount_opening_lo) = BurnAmountCiphertext::new(
46+
burn_amount_lo,
47+
source_elgamal_keypair.pubkey(),
48+
auditor_elgamal_pubkey,
49+
);
50+
51+
let (burn_amount_ciphertext_hi, burn_amount_opening_hi) = BurnAmountCiphertext::new(
52+
burn_amount_hi,
53+
source_elgamal_keypair.pubkey(),
54+
auditor_elgamal_pubkey,
55+
);
56+
57+
// decrypt the current available balance at the source
58+
let current_decrypted_available_balance = current_decryptable_available_balance
59+
.decrypt(aes_key)
60+
.ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
61+
62+
// copmute the remaining balance ciphertext
63+
let burn_amount_ciphertext_source_lo = burn_amount_ciphertext_lo
64+
.0
65+
.to_elgamal_ciphertext(0)
66+
.unwrap();
67+
let burn_amount_ciphertext_source_hi = burn_amount_ciphertext_hi
68+
.0
69+
.to_elgamal_ciphertext(0)
70+
.unwrap();
71+
72+
#[allow(clippy::arithmetic_side_effects)]
73+
let new_available_balance_ciphertext = current_available_balance_ciphertext
74+
- try_combine_lo_hi_ciphertexts(
75+
&burn_amount_ciphertext_source_lo,
76+
&burn_amount_ciphertext_source_hi,
77+
BURN_AMOUNT_LO_BIT_LENGTH,
78+
)
79+
.ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
80+
81+
// compute the remaining balance at the source
82+
let remaining_balance = current_decrypted_available_balance
83+
.checked_sub(burn_amount)
84+
.ok_or(TokenProofGenerationError::NotEnoughFunds)?;
85+
86+
let (new_available_balance_commitment, new_available_balance_opening) =
87+
Pedersen::new(remaining_balance);
88+
89+
// generate equality proof data
90+
let equality_proof_data = CiphertextCommitmentEqualityProofData::new(
91+
source_elgamal_keypair,
92+
&new_available_balance_ciphertext,
93+
&new_available_balance_commitment,
94+
&new_available_balance_opening,
95+
remaining_balance,
96+
)
97+
.map_err(TokenProofGenerationError::from)?;
98+
99+
// generate ciphertext validity data
100+
let ciphertext_validity_proof_data = BatchedGroupedCiphertext2HandlesValidityProofData::new(
101+
source_elgamal_keypair.pubkey(),
102+
auditor_elgamal_pubkey,
103+
&burn_amount_ciphertext_lo.0,
104+
&burn_amount_ciphertext_hi.0,
105+
burn_amount_lo,
106+
burn_amount_hi,
107+
&burn_amount_opening_lo,
108+
&burn_amount_opening_hi,
109+
)
110+
.map_err(TokenProofGenerationError::from)?;
111+
112+
// generate range proof data
113+
let (padding_commitment, padding_opening) = Pedersen::new(0_u64);
114+
let range_proof_data = BatchedRangeProofU128Data::new(
115+
vec![
116+
&new_available_balance_commitment,
117+
burn_amount_ciphertext_lo.get_commitment(),
118+
burn_amount_ciphertext_hi.get_commitment(),
119+
&padding_commitment,
120+
],
121+
vec![remaining_balance, burn_amount_lo, burn_amount_hi, 0],
122+
vec![
123+
REMAINING_BALANCE_BIT_LENGTH,
124+
BURN_AMOUNT_LO_BIT_LENGTH,
125+
BURN_AMOUNT_HI_BIT_LENGTH,
126+
RANGE_PROOF_PADDING_BIT_LENGTH,
127+
],
128+
vec![
129+
&new_available_balance_opening,
130+
&burn_amount_opening_lo,
131+
&burn_amount_opening_hi,
132+
&padding_opening,
133+
],
134+
)
135+
.map_err(TokenProofGenerationError::from)?;
136+
137+
Ok(BurnProofData {
138+
equality_proof_data,
139+
ciphertext_validity_proof_data,
140+
range_proof_data,
141+
})
142+
}

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,47 @@ impl FeeCiphertext {
8686
self.0.handles.get(1).unwrap()
8787
}
8888
}
89+
90+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
91+
#[repr(C)]
92+
pub struct BurnAmountCiphertext(pub(crate) GroupedElGamalCiphertext<2>);
93+
94+
impl BurnAmountCiphertext {
95+
pub fn new(
96+
amount: u64,
97+
source_pubkey: &ElGamalPubkey,
98+
auditor_pubkey: &ElGamalPubkey,
99+
) -> (Self, PedersenOpening) {
100+
let opening = PedersenOpening::new_rand();
101+
let grouped_ciphertext =
102+
GroupedElGamal::<2>::encrypt_with([source_pubkey, auditor_pubkey], amount, &opening);
103+
104+
(Self(grouped_ciphertext), opening)
105+
}
106+
107+
pub fn get_commitment(&self) -> &PedersenCommitment {
108+
&self.0.commitment
109+
}
110+
}
111+
112+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
113+
#[repr(C)]
114+
pub struct MintAmountCiphertext(pub(crate) GroupedElGamalCiphertext<2>);
115+
116+
impl MintAmountCiphertext {
117+
pub fn new(
118+
amount: u64,
119+
source_pubkey: &ElGamalPubkey,
120+
auditor_pubkey: &ElGamalPubkey,
121+
) -> (Self, PedersenOpening) {
122+
let opening = PedersenOpening::new_rand();
123+
let grouped_ciphertext =
124+
GroupedElGamal::<2>::encrypt_with([source_pubkey, auditor_pubkey], amount, &opening);
125+
126+
(Self(grouped_ciphertext), opening)
127+
}
128+
129+
pub fn get_commitment(&self) -> &PedersenCommitment {
130+
&self.0.commitment
131+
}
132+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ use {
66
},
77
};
88

9+
pub mod burn;
910
pub mod encryption;
1011
pub mod errors;
12+
pub mod mint;
1113
pub mod transfer;
1214
pub mod transfer_with_fee;
1315
pub mod withdraw;
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use {
2+
crate::{encryption::MintAmountCiphertext, errors::TokenProofGenerationError, try_split_u64},
3+
solana_zk_sdk::{
4+
encryption::{elgamal::ElGamalPubkey, pedersen::Pedersen},
5+
zk_elgamal_proof_program::proof_data::{
6+
BatchedGroupedCiphertext2HandlesValidityProofData, BatchedRangeProofU64Data,
7+
},
8+
},
9+
};
10+
11+
const MINT_AMOUNT_LO_BIT_LENGTH: usize = 16;
12+
const MINT_AMOUNT_HI_BIT_LENGTH: usize = 32;
13+
/// The padding bit length in range proofs to make the bit-length power-of-2
14+
const RANGE_PROOF_PADDING_BIT_LENGTH: usize = 16;
15+
16+
/// The proof data required for a confidential mint instruction
17+
pub struct MintProofData {
18+
pub ciphertext_validity_proof_data: BatchedGroupedCiphertext2HandlesValidityProofData,
19+
pub range_proof_data: BatchedRangeProofU64Data,
20+
}
21+
22+
pub fn mint_split_proof_data(
23+
mint_amount: u64,
24+
destination_elgamal_pubkey: &ElGamalPubkey,
25+
auditor_elgamal_pubkey: &ElGamalPubkey,
26+
) -> Result<MintProofData, TokenProofGenerationError> {
27+
// split the mint amount into low and high bits
28+
let (mint_amount_lo, mint_amount_hi) = try_split_u64(mint_amount, MINT_AMOUNT_LO_BIT_LENGTH)
29+
.ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
30+
31+
// encrypt the mint amount under the destination and auditor's ElGamal public
32+
// keys
33+
let (mint_amount_grouped_ciphertext_lo, mint_amount_opening_lo) = MintAmountCiphertext::new(
34+
mint_amount_lo,
35+
destination_elgamal_pubkey,
36+
auditor_elgamal_pubkey,
37+
);
38+
39+
let (mint_amount_grouped_ciphertext_hi, mint_amount_opening_hi) = MintAmountCiphertext::new(
40+
mint_amount_hi,
41+
destination_elgamal_pubkey,
42+
auditor_elgamal_pubkey,
43+
);
44+
45+
// generate ciphertext validity proof data
46+
let ciphertext_validity_proof_data = BatchedGroupedCiphertext2HandlesValidityProofData::new(
47+
destination_elgamal_pubkey,
48+
auditor_elgamal_pubkey,
49+
&mint_amount_grouped_ciphertext_lo.0,
50+
&mint_amount_grouped_ciphertext_hi.0,
51+
mint_amount_lo,
52+
mint_amount_hi,
53+
&mint_amount_opening_lo,
54+
&mint_amount_opening_hi,
55+
)
56+
.map_err(TokenProofGenerationError::from)?;
57+
58+
// generate range proof data
59+
let (padding_commitment, padding_opening) = Pedersen::new(0_u64);
60+
let range_proof_data = BatchedRangeProofU64Data::new(
61+
vec![
62+
mint_amount_grouped_ciphertext_lo.get_commitment(),
63+
mint_amount_grouped_ciphertext_hi.get_commitment(),
64+
&padding_commitment,
65+
],
66+
vec![mint_amount_lo, mint_amount_hi, 0],
67+
vec![
68+
MINT_AMOUNT_LO_BIT_LENGTH,
69+
MINT_AMOUNT_HI_BIT_LENGTH,
70+
RANGE_PROOF_PADDING_BIT_LENGTH,
71+
],
72+
vec![
73+
&mint_amount_opening_lo,
74+
&mint_amount_opening_hi,
75+
&padding_opening,
76+
],
77+
)
78+
.map_err(TokenProofGenerationError::from)?;
79+
80+
Ok(MintProofData {
81+
ciphertext_validity_proof_data,
82+
range_proof_data,
83+
})
84+
}

0 commit comments

Comments
 (0)