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

Commit 29eae22

Browse files
abcalphabetsamkim-cryptojoncinque
authored
Confidential mint burn extension: Token program changes (#7319)
* confidential mint-burn extension * add ciphertext validity proof to confidential mint * simplify burn proofs * add confidential supply * remove debug statements * add tests for confidential mint-burn * fix clippy & serde tests * fix some tests * clippy * mint burn with new proof generation * remove old mintburn proof generation * cleanup * remove cli / client changes and mint-burn tests * cleanup * more cleanup * fix clippy; serde logic * Update token/program-2022/src/extension/confidential_mint_burn/mod.rs Co-authored-by: samkim-crypto <[email protected]> * Update token/program-2022/src/extension/confidential_mint_burn/instruction.rs Co-authored-by: samkim-crypto <[email protected]> * review fixes * refactor proof location processing * comment * cleanup * fix target_os=solana ; test-sbf * review fixes * fix fmt / serde * construct supply account info from ref to extension * add conf mint/burn failure docs; remove todo * remove obsolete null check * Update token/program-2022/src/error.rs Co-authored-by: Jon C <[email protected]> * Update token/program-2022/src/extension/confidential_mint_burn/instruction.rs Co-authored-by: Jon C <[email protected]> * Update token/program-2022/src/extension/confidential_mint_burn/account_info.rs Co-authored-by: Jon C <[email protected]> * review fixes * more fixes * clippy; review fixes --------- Co-authored-by: samkim-crypto <[email protected]> Co-authored-by: Jon C <[email protected]>
1 parent c4391c1 commit 29eae22

File tree

17 files changed

+1356
-22
lines changed

17 files changed

+1356
-22
lines changed

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

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use {
55
grouped_elgamal::{
66
PodGroupedElGamalCiphertext2Handles, PodGroupedElGamalCiphertext3Handles,
77
},
8-
pedersen::PodPedersenCommitment,
98
},
109
};
1110

@@ -14,10 +13,6 @@ use {
1413
pub struct PodTransferAmountCiphertext(pub(crate) PodGroupedElGamalCiphertext3Handles);
1514

1615
impl PodTransferAmountCiphertext {
17-
pub fn extract_commitment(&self) -> PodPedersenCommitment {
18-
self.0.extract_commitment()
19-
}
20-
2116
pub fn try_extract_ciphertext(
2217
&self,
2318
index: usize,
@@ -33,10 +28,6 @@ impl PodTransferAmountCiphertext {
3328
pub struct PodFeeCiphertext(pub(crate) PodGroupedElGamalCiphertext2Handles);
3429

3530
impl PodFeeCiphertext {
36-
pub fn extract_commitment(&self) -> PodPedersenCommitment {
37-
self.0.extract_commitment()
38-
}
39-
4031
pub fn try_extract_ciphertext(
4132
&self,
4233
index: usize,
@@ -51,6 +42,28 @@ impl PodFeeCiphertext {
5142
#[repr(C)]
5243
pub struct PodBurnAmountCiphertext(pub(crate) PodGroupedElGamalCiphertext3Handles);
5344

45+
impl PodBurnAmountCiphertext {
46+
pub fn try_extract_ciphertext(
47+
&self,
48+
index: usize,
49+
) -> Result<PodElGamalCiphertext, TokenProofExtractionError> {
50+
self.0
51+
.try_extract_ciphertext(index)
52+
.map_err(|_| TokenProofExtractionError::CiphertextExtraction)
53+
}
54+
}
55+
5456
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5557
#[repr(C)]
5658
pub struct PodMintAmountCiphertext(pub(crate) PodGroupedElGamalCiphertext3Handles);
59+
60+
impl PodMintAmountCiphertext {
61+
pub fn try_extract_ciphertext(
62+
&self,
63+
index: usize,
64+
) -> Result<PodElGamalCiphertext, TokenProofExtractionError> {
65+
self.0
66+
.try_extract_ciphertext(index)
67+
.map_err(|_| TokenProofExtractionError::CiphertextExtraction)
68+
}
69+
}

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

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ use {
66
solana_program::{
77
account_info::{next_account_info, AccountInfo},
88
entrypoint::ProgramResult,
9-
instruction::Instruction,
9+
instruction::{AccountMeta, Instruction},
1010
msg,
1111
program_error::ProgramError,
1212
pubkey::Pubkey,
13-
sysvar::instructions::get_instruction_relative,
13+
sysvar::{self, instructions::get_instruction_relative},
1414
},
1515
solana_zk_sdk::zk_elgamal_proof_program::{
1616
self,
@@ -146,6 +146,56 @@ pub fn verify_and_extract_context<'a, T: Pod + ZkProofData<U>, U: Pod>(
146146
}
147147
}
148148

149+
/// Processes a proof location for instruction creation. Adds relevant accounts
150+
/// to supplied account vector
151+
///
152+
/// If the proof location is an instruction offset the corresponding proof
153+
/// instruction is created and added to the `proof_instructions` vector.
154+
pub fn process_proof_location<T, U>(
155+
accounts: &mut Vec<AccountMeta>,
156+
expected_instruction_offset: &mut i8,
157+
proof_instructions: &mut Vec<Instruction>,
158+
proof_location: ProofLocation<T>,
159+
push_sysvar_to_accounts: bool,
160+
proof_instruction_type: ProofInstruction,
161+
) -> Result<i8, ProgramError>
162+
where
163+
T: Pod + ZkProofData<U>,
164+
U: Pod,
165+
{
166+
match proof_location {
167+
ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => {
168+
let proof_instruction_offset: i8 = proof_instruction_offset.into();
169+
if &proof_instruction_offset != expected_instruction_offset {
170+
return Err(ProgramError::InvalidInstructionData);
171+
}
172+
173+
if push_sysvar_to_accounts {
174+
accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false));
175+
}
176+
match proof_data {
177+
ProofData::InstructionData(data) => proof_instructions
178+
.push(proof_instruction_type.encode_verify_proof::<T, U>(None, data)),
179+
ProofData::RecordAccount(address, offset) => {
180+
accounts.push(AccountMeta::new_readonly(*address, false));
181+
proof_instructions.push(
182+
proof_instruction_type
183+
.encode_verify_proof_from_account(None, address, offset),
184+
)
185+
}
186+
};
187+
*expected_instruction_offset = expected_instruction_offset
188+
.checked_add(1)
189+
.ok_or(ProgramError::InvalidInstructionData)?;
190+
Ok(proof_instruction_offset)
191+
}
192+
ProofLocation::ContextStateAccount(context_state_account) => {
193+
accounts.push(AccountMeta::new_readonly(*context_state_account, false));
194+
Ok(0)
195+
}
196+
}
197+
}
198+
149199
/// Converts a zk proof type to a corresponding ZK ElGamal proof program
150200
/// instruction that verifies the proof.
151201
pub fn zk_proof_type_to_instruction(

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@ pub struct MintProofData {
2727
pub equality_proof_data: CiphertextCommitmentEqualityProofData,
2828
pub ciphertext_validity_proof_data: BatchedGroupedCiphertext3HandlesValidityProofData,
2929
pub range_proof_data: BatchedRangeProofU128Data,
30+
pub new_decryptable_supply: AeCiphertext,
3031
}
3132

3233
pub fn mint_split_proof_data(
3334
current_supply_ciphertext: &ElGamalCiphertext,
34-
current_decryptable_supply: &AeCiphertext,
3535
mint_amount: u64,
36+
current_supply: u64,
3637
supply_elgamal_keypair: &ElGamalKeypair,
3738
supply_aes_key: &AeKey,
3839
destination_elgamal_pubkey: &ElGamalPubkey,
@@ -77,11 +78,6 @@ pub fn mint_split_proof_data(
7778
)
7879
.ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
7980

80-
// decrypt the current supply
81-
let current_supply = current_decryptable_supply
82-
.decrypt(supply_aes_key)
83-
.ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
84-
8581
// compute the new supply
8682
let new_supply = current_supply
8783
.checked_add(mint_amount)
@@ -142,5 +138,6 @@ pub fn mint_split_proof_data(
142138
equality_proof_data,
143139
ciphertext_validity_proof_data,
144140
range_proof_data,
141+
new_decryptable_supply: supply_aes_key.encrypt(new_supply),
145142
})
146143
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,16 +217,16 @@ fn test_mint_validity(mint_amount: u64, supply: u64) {
217217
let supply_aes_key = AeKey::new_rand();
218218

219219
let supply_ciphertext = supply_keypair.pubkey().encrypt(supply);
220-
let decryptable_supply = supply_aes_key.encrypt(supply);
221220

222221
let MintProofData {
223222
equality_proof_data,
224223
ciphertext_validity_proof_data,
225224
range_proof_data,
225+
new_decryptable_supply: _,
226226
} = mint_split_proof_data(
227227
&supply_ciphertext,
228-
&decryptable_supply,
229228
mint_amount,
229+
supply,
230230
&supply_keypair,
231231
&supply_aes_key,
232232
destination_pubkey,

token/program-2022/src/error.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,11 @@ pub enum TokenError {
258258
/// Fee calculation failed
259259
#[error("Fee calculation failed")]
260260
FeeCalculation,
261+
262+
//65
263+
/// Withdraw / Deposit not allowed for confidential-mint-burn
264+
#[error("Withdraw / Deposit not allowed for confidential-mint-burn")]
265+
IllegalMintBurnConversion,
261266
}
262267
impl From<TokenError> for ProgramError {
263268
fn from(e: TokenError) -> Self {
@@ -445,6 +450,9 @@ impl PrintProgramError for TokenError {
445450
TokenError::FeeCalculation => {
446451
msg!("Transfer fee calculation failed")
447452
}
453+
TokenError::IllegalMintBurnConversion => {
454+
msg!("Conversions from normal to confidential token balance and vice versa are illegal if the confidential-mint-burn extension is enabled")
455+
}
448456
}
449457
}
450458
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
use {
2+
super::ConfidentialMintBurn,
3+
crate::error::TokenError,
4+
bytemuck::{Pod, Zeroable},
5+
solana_zk_sdk::{
6+
encryption::{
7+
auth_encryption::{AeCiphertext, AeKey},
8+
elgamal::{ElGamalCiphertext, ElGamalKeypair},
9+
pedersen::PedersenOpening,
10+
pod::{
11+
auth_encryption::PodAeCiphertext,
12+
elgamal::{PodElGamalCiphertext, PodElGamalPubkey},
13+
},
14+
},
15+
zk_elgamal_proof_program::proof_data::CiphertextCiphertextEqualityProofData,
16+
},
17+
};
18+
19+
/// Confidential Mint Burn extension information needed to construct a
20+
/// `RotateSupplyElgamalPubkey` instruction.
21+
#[repr(C)]
22+
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
23+
pub struct SupplyAccountInfo {
24+
/// The available balance (encrypted by `supply_elgamal_pubkey`)
25+
pub current_supply: PodElGamalCiphertext,
26+
/// The decryptable supply
27+
pub decryptable_supply: PodAeCiphertext,
28+
/// The supply's elgamal pubkey
29+
pub supply_elgamal_pubkey: PodElGamalPubkey,
30+
}
31+
32+
impl SupplyAccountInfo {
33+
/// Creates a SupplyAccountInfo from ConfidentialMintBurn extension account
34+
/// data
35+
pub fn new(extension: &ConfidentialMintBurn) -> Self {
36+
Self {
37+
current_supply: extension.confidential_supply,
38+
decryptable_supply: extension.decryptable_supply,
39+
supply_elgamal_pubkey: extension.supply_elgamal_pubkey,
40+
}
41+
}
42+
43+
/// Computes the current supply from the decryptable supply and the
44+
/// difference between the decryptable supply and the elgamal encrypted
45+
/// supply ciphertext
46+
pub fn decrypt_current_supply(
47+
&self,
48+
aes_key: &AeKey,
49+
elgamal_keypair: &ElGamalKeypair,
50+
) -> Result<u64, TokenError> {
51+
// decrypt the decryptable supply
52+
let current_decyptable_supply = AeCiphertext::try_from(self.decryptable_supply)
53+
.map_err(|_| TokenError::MalformedCiphertext)?
54+
.decrypt(aes_key)
55+
.ok_or(TokenError::MalformedCiphertext)?;
56+
57+
// get the difference between the supply ciphertext and the decryptable supply
58+
// explanation see https://github.com/solana-labs/solana-program-library/pull/6881#issuecomment-2385579058
59+
let decryptable_supply_ciphertext =
60+
elgamal_keypair.pubkey().encrypt(current_decyptable_supply);
61+
#[allow(clippy::arithmetic_side_effects)]
62+
let supply_delta_ciphertext = decryptable_supply_ciphertext
63+
- ElGamalCiphertext::try_from(self.current_supply)
64+
.map_err(|_| TokenError::MalformedCiphertext)?;
65+
let decryptable_to_current_diff = elgamal_keypair
66+
.secret()
67+
.decrypt_u32(&supply_delta_ciphertext)
68+
.ok_or(TokenError::MalformedCiphertext)?;
69+
70+
// compute the current supply
71+
current_decyptable_supply
72+
.checked_sub(decryptable_to_current_diff)
73+
.ok_or(TokenError::Overflow)
74+
}
75+
76+
/// Generates the `CiphertextCiphertextEqualityProofData` needed for a
77+
/// `RotateSupplyElgamalPubkey` instruction
78+
pub fn generate_rotate_supply_elgamal_pubkey_proof(
79+
&self,
80+
aes_key: &AeKey,
81+
current_supply_elgamal_keypair: &ElGamalKeypair,
82+
new_supply_elgamal_keypair: &ElGamalKeypair,
83+
) -> Result<CiphertextCiphertextEqualityProofData, TokenError> {
84+
let current_supply =
85+
self.decrypt_current_supply(aes_key, current_supply_elgamal_keypair)?;
86+
87+
let new_supply_opening = PedersenOpening::new_rand();
88+
let new_supply_ciphertext = new_supply_elgamal_keypair
89+
.pubkey()
90+
.encrypt_with(current_supply, &new_supply_opening);
91+
92+
CiphertextCiphertextEqualityProofData::new(
93+
current_supply_elgamal_keypair,
94+
new_supply_elgamal_keypair.pubkey(),
95+
&self
96+
.current_supply
97+
.try_into()
98+
.map_err(|_| TokenError::MalformedCiphertext)?,
99+
&new_supply_ciphertext,
100+
&new_supply_opening,
101+
current_supply,
102+
)
103+
.map_err(|_| TokenError::ProofGeneration)
104+
}
105+
}

0 commit comments

Comments
 (0)