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

Commit df9505a

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

File tree

4 files changed

+288
-0
lines changed

4 files changed

+288
-0
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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+
BatchedGroupedCiphertext3HandlesValidityProofData, 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: BatchedGroupedCiphertext3HandlesValidityProofData,
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+
supply_elgamal_pubkey: &ElGamalPubkey,
40+
) -> Result<BurnProofData, TokenProofGenerationError> {
41+
// split the burn amount into low and high bits
42+
let (burn_amount_lo, burn_amount_hi) = try_split_u64(burn_amount, BURN_AMOUNT_LO_BIT_LENGTH)
43+
.ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
44+
45+
// encrypt the burn amount under the source and auditor's ElGamal public key
46+
let (burn_amount_ciphertext_lo, burn_amount_opening_lo) = BurnAmountCiphertext::new(
47+
burn_amount_lo,
48+
source_elgamal_keypair.pubkey(),
49+
auditor_elgamal_pubkey,
50+
supply_elgamal_pubkey,
51+
);
52+
53+
let (burn_amount_ciphertext_hi, burn_amount_opening_hi) = BurnAmountCiphertext::new(
54+
burn_amount_hi,
55+
source_elgamal_keypair.pubkey(),
56+
auditor_elgamal_pubkey,
57+
supply_elgamal_pubkey,
58+
);
59+
60+
// decrypt the current available balance at the source
61+
let current_decrypted_available_balance = current_decryptable_available_balance
62+
.decrypt(aes_key)
63+
.ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
64+
65+
// copmute the remaining balance ciphertext
66+
let burn_amount_ciphertext_source_lo = burn_amount_ciphertext_lo
67+
.0
68+
.to_elgamal_ciphertext(0)
69+
.unwrap();
70+
let burn_amount_ciphertext_source_hi = burn_amount_ciphertext_hi
71+
.0
72+
.to_elgamal_ciphertext(0)
73+
.unwrap();
74+
75+
#[allow(clippy::arithmetic_side_effects)]
76+
let new_available_balance_ciphertext = current_available_balance_ciphertext
77+
- try_combine_lo_hi_ciphertexts(
78+
&burn_amount_ciphertext_source_lo,
79+
&burn_amount_ciphertext_source_hi,
80+
BURN_AMOUNT_LO_BIT_LENGTH,
81+
)
82+
.ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
83+
84+
// compute the remaining balance at the source
85+
let remaining_balance = current_decrypted_available_balance
86+
.checked_sub(burn_amount)
87+
.ok_or(TokenProofGenerationError::NotEnoughFunds)?;
88+
89+
let (new_available_balance_commitment, new_available_balance_opening) =
90+
Pedersen::new(remaining_balance);
91+
92+
// generate equality proof data
93+
let equality_proof_data = CiphertextCommitmentEqualityProofData::new(
94+
source_elgamal_keypair,
95+
&new_available_balance_ciphertext,
96+
&new_available_balance_commitment,
97+
&new_available_balance_opening,
98+
remaining_balance,
99+
)
100+
.map_err(TokenProofGenerationError::from)?;
101+
102+
// generate ciphertext validity data
103+
let ciphertext_validity_proof_data = BatchedGroupedCiphertext3HandlesValidityProofData::new(
104+
source_elgamal_keypair.pubkey(),
105+
auditor_elgamal_pubkey,
106+
supply_elgamal_pubkey,
107+
&burn_amount_ciphertext_lo.0,
108+
&burn_amount_ciphertext_hi.0,
109+
burn_amount_lo,
110+
burn_amount_hi,
111+
&burn_amount_opening_lo,
112+
&burn_amount_opening_hi,
113+
)
114+
.map_err(TokenProofGenerationError::from)?;
115+
116+
// generate range proof data
117+
let (padding_commitment, padding_opening) = Pedersen::new(0_u64);
118+
let range_proof_data = BatchedRangeProofU128Data::new(
119+
vec![
120+
&new_available_balance_commitment,
121+
burn_amount_ciphertext_lo.get_commitment(),
122+
burn_amount_ciphertext_hi.get_commitment(),
123+
&padding_commitment,
124+
],
125+
vec![remaining_balance, burn_amount_lo, burn_amount_hi, 0],
126+
vec![
127+
REMAINING_BALANCE_BIT_LENGTH,
128+
BURN_AMOUNT_LO_BIT_LENGTH,
129+
BURN_AMOUNT_HI_BIT_LENGTH,
130+
RANGE_PROOF_PADDING_BIT_LENGTH,
131+
],
132+
vec![
133+
&new_available_balance_opening,
134+
&burn_amount_opening_lo,
135+
&burn_amount_opening_hi,
136+
&padding_opening,
137+
],
138+
)
139+
.map_err(TokenProofGenerationError::from)?;
140+
141+
Ok(BurnProofData {
142+
equality_proof_data,
143+
ciphertext_validity_proof_data,
144+
range_proof_data,
145+
})
146+
}

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,55 @@ 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<3>);
93+
94+
impl BurnAmountCiphertext {
95+
pub fn new(
96+
amount: u64,
97+
source_pubkey: &ElGamalPubkey,
98+
auditor_pubkey: &ElGamalPubkey,
99+
supply_pubkey: &ElGamalPubkey,
100+
) -> (Self, PedersenOpening) {
101+
let opening = PedersenOpening::new_rand();
102+
let grouped_ciphertext = GroupedElGamal::<3>::encrypt_with(
103+
[source_pubkey, auditor_pubkey, supply_pubkey],
104+
amount,
105+
&opening,
106+
);
107+
108+
(Self(grouped_ciphertext), opening)
109+
}
110+
111+
pub fn get_commitment(&self) -> &PedersenCommitment {
112+
&self.0.commitment
113+
}
114+
}
115+
116+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
117+
#[repr(C)]
118+
pub struct MintAmountCiphertext(pub(crate) GroupedElGamalCiphertext<3>);
119+
120+
impl MintAmountCiphertext {
121+
pub fn new(
122+
amount: u64,
123+
source_pubkey: &ElGamalPubkey,
124+
auditor_pubkey: &ElGamalPubkey,
125+
supply_pubkey: &ElGamalPubkey,
126+
) -> (Self, PedersenOpening) {
127+
let opening = PedersenOpening::new_rand();
128+
let grouped_ciphertext = GroupedElGamal::<3>::encrypt_with(
129+
[source_pubkey, auditor_pubkey, supply_pubkey],
130+
amount,
131+
&opening,
132+
);
133+
134+
(Self(grouped_ciphertext), opening)
135+
}
136+
137+
pub fn get_commitment(&self) -> &PedersenCommitment {
138+
&self.0.commitment
139+
}
140+
}

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: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
BatchedGroupedCiphertext3HandlesValidityProofData, 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: BatchedGroupedCiphertext3HandlesValidityProofData,
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+
supply_elgamal_pubkey: &ElGamalPubkey,
27+
) -> Result<MintProofData, TokenProofGenerationError> {
28+
// split the mint amount into low and high bits
29+
let (mint_amount_lo, mint_amount_hi) = try_split_u64(mint_amount, MINT_AMOUNT_LO_BIT_LENGTH)
30+
.ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
31+
32+
// encrypt the mint amount under the destination and auditor's ElGamal public
33+
// keys
34+
let (mint_amount_grouped_ciphertext_lo, mint_amount_opening_lo) = MintAmountCiphertext::new(
35+
mint_amount_lo,
36+
destination_elgamal_pubkey,
37+
auditor_elgamal_pubkey,
38+
supply_elgamal_pubkey,
39+
);
40+
41+
let (mint_amount_grouped_ciphertext_hi, mint_amount_opening_hi) = MintAmountCiphertext::new(
42+
mint_amount_hi,
43+
destination_elgamal_pubkey,
44+
auditor_elgamal_pubkey,
45+
supply_elgamal_pubkey,
46+
);
47+
48+
// generate ciphertext validity proof data
49+
let ciphertext_validity_proof_data = BatchedGroupedCiphertext3HandlesValidityProofData::new(
50+
destination_elgamal_pubkey,
51+
auditor_elgamal_pubkey,
52+
supply_elgamal_pubkey,
53+
&mint_amount_grouped_ciphertext_lo.0,
54+
&mint_amount_grouped_ciphertext_hi.0,
55+
mint_amount_lo,
56+
mint_amount_hi,
57+
&mint_amount_opening_lo,
58+
&mint_amount_opening_hi,
59+
)
60+
.map_err(TokenProofGenerationError::from)?;
61+
62+
// generate range proof data
63+
let (padding_commitment, padding_opening) = Pedersen::new(0_u64);
64+
let range_proof_data = BatchedRangeProofU64Data::new(
65+
vec![
66+
mint_amount_grouped_ciphertext_lo.get_commitment(),
67+
mint_amount_grouped_ciphertext_hi.get_commitment(),
68+
&padding_commitment,
69+
],
70+
vec![mint_amount_lo, mint_amount_hi, 0],
71+
vec![
72+
MINT_AMOUNT_LO_BIT_LENGTH,
73+
MINT_AMOUNT_HI_BIT_LENGTH,
74+
RANGE_PROOF_PADDING_BIT_LENGTH,
75+
],
76+
vec![
77+
&mint_amount_opening_lo,
78+
&mint_amount_opening_hi,
79+
&padding_opening,
80+
],
81+
)
82+
.map_err(TokenProofGenerationError::from)?;
83+
84+
Ok(MintProofData {
85+
ciphertext_validity_proof_data,
86+
range_proof_data,
87+
})
88+
}

0 commit comments

Comments
 (0)