Skip to content

Commit

Permalink
Confidential mint burn extension: Token program changes (#7319)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
3 people authored Oct 31, 2024
1 parent c4391c1 commit 29eae22
Show file tree
Hide file tree
Showing 17 changed files with 1,356 additions and 22 deletions.
31 changes: 22 additions & 9 deletions token/confidential-transfer/proof-extraction/src/encryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use {
grouped_elgamal::{
PodGroupedElGamalCiphertext2Handles, PodGroupedElGamalCiphertext3Handles,
},
pedersen::PodPedersenCommitment,
},
};

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

impl PodTransferAmountCiphertext {
pub fn extract_commitment(&self) -> PodPedersenCommitment {
self.0.extract_commitment()
}

pub fn try_extract_ciphertext(
&self,
index: usize,
Expand All @@ -33,10 +28,6 @@ impl PodTransferAmountCiphertext {
pub struct PodFeeCiphertext(pub(crate) PodGroupedElGamalCiphertext2Handles);

impl PodFeeCiphertext {
pub fn extract_commitment(&self) -> PodPedersenCommitment {
self.0.extract_commitment()
}

pub fn try_extract_ciphertext(
&self,
index: usize,
Expand All @@ -51,6 +42,28 @@ impl PodFeeCiphertext {
#[repr(C)]
pub struct PodBurnAmountCiphertext(pub(crate) PodGroupedElGamalCiphertext3Handles);

impl PodBurnAmountCiphertext {
pub fn try_extract_ciphertext(
&self,
index: usize,
) -> Result<PodElGamalCiphertext, TokenProofExtractionError> {
self.0
.try_extract_ciphertext(index)
.map_err(|_| TokenProofExtractionError::CiphertextExtraction)
}
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(C)]
pub struct PodMintAmountCiphertext(pub(crate) PodGroupedElGamalCiphertext3Handles);

impl PodMintAmountCiphertext {
pub fn try_extract_ciphertext(
&self,
index: usize,
) -> Result<PodElGamalCiphertext, TokenProofExtractionError> {
self.0
.try_extract_ciphertext(index)
.map_err(|_| TokenProofExtractionError::CiphertextExtraction)
}
}
54 changes: 52 additions & 2 deletions token/confidential-transfer/proof-extraction/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ use {
solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
instruction::Instruction,
instruction::{AccountMeta, Instruction},
msg,
program_error::ProgramError,
pubkey::Pubkey,
sysvar::instructions::get_instruction_relative,
sysvar::{self, instructions::get_instruction_relative},
},
solana_zk_sdk::zk_elgamal_proof_program::{
self,
Expand Down Expand Up @@ -146,6 +146,56 @@ pub fn verify_and_extract_context<'a, T: Pod + ZkProofData<U>, U: Pod>(
}
}

/// Processes a proof location for instruction creation. Adds relevant accounts
/// to supplied account vector
///
/// If the proof location is an instruction offset the corresponding proof
/// instruction is created and added to the `proof_instructions` vector.
pub fn process_proof_location<T, U>(
accounts: &mut Vec<AccountMeta>,
expected_instruction_offset: &mut i8,
proof_instructions: &mut Vec<Instruction>,
proof_location: ProofLocation<T>,
push_sysvar_to_accounts: bool,
proof_instruction_type: ProofInstruction,
) -> Result<i8, ProgramError>
where
T: Pod + ZkProofData<U>,
U: Pod,
{
match proof_location {
ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) => {
let proof_instruction_offset: i8 = proof_instruction_offset.into();
if &proof_instruction_offset != expected_instruction_offset {
return Err(ProgramError::InvalidInstructionData);
}

if push_sysvar_to_accounts {
accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false));
}
match proof_data {
ProofData::InstructionData(data) => proof_instructions
.push(proof_instruction_type.encode_verify_proof::<T, U>(None, data)),
ProofData::RecordAccount(address, offset) => {
accounts.push(AccountMeta::new_readonly(*address, false));
proof_instructions.push(
proof_instruction_type
.encode_verify_proof_from_account(None, address, offset),
)
}
};
*expected_instruction_offset = expected_instruction_offset
.checked_add(1)
.ok_or(ProgramError::InvalidInstructionData)?;
Ok(proof_instruction_offset)
}
ProofLocation::ContextStateAccount(context_state_account) => {
accounts.push(AccountMeta::new_readonly(*context_state_account, false));
Ok(0)
}
}
}

/// Converts a zk proof type to a corresponding ZK ElGamal proof program
/// instruction that verifies the proof.
pub fn zk_proof_type_to_instruction(
Expand Down
9 changes: 3 additions & 6 deletions token/confidential-transfer/proof-generation/src/mint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ pub struct MintProofData {
pub equality_proof_data: CiphertextCommitmentEqualityProofData,
pub ciphertext_validity_proof_data: BatchedGroupedCiphertext3HandlesValidityProofData,
pub range_proof_data: BatchedRangeProofU128Data,
pub new_decryptable_supply: AeCiphertext,
}

pub fn mint_split_proof_data(
current_supply_ciphertext: &ElGamalCiphertext,
current_decryptable_supply: &AeCiphertext,
mint_amount: u64,
current_supply: u64,
supply_elgamal_keypair: &ElGamalKeypair,
supply_aes_key: &AeKey,
destination_elgamal_pubkey: &ElGamalPubkey,
Expand Down Expand Up @@ -77,11 +78,6 @@ pub fn mint_split_proof_data(
)
.ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;

// decrypt the current supply
let current_supply = current_decryptable_supply
.decrypt(supply_aes_key)
.ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;

// compute the new supply
let new_supply = current_supply
.checked_add(mint_amount)
Expand Down Expand Up @@ -142,5 +138,6 @@ pub fn mint_split_proof_data(
equality_proof_data,
ciphertext_validity_proof_data,
range_proof_data,
new_decryptable_supply: supply_aes_key.encrypt(new_supply),
})
}
4 changes: 2 additions & 2 deletions token/confidential-transfer/proof-tests/tests/proof_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,16 +217,16 @@ fn test_mint_validity(mint_amount: u64, supply: u64) {
let supply_aes_key = AeKey::new_rand();

let supply_ciphertext = supply_keypair.pubkey().encrypt(supply);
let decryptable_supply = supply_aes_key.encrypt(supply);

let MintProofData {
equality_proof_data,
ciphertext_validity_proof_data,
range_proof_data,
new_decryptable_supply: _,
} = mint_split_proof_data(
&supply_ciphertext,
&decryptable_supply,
mint_amount,
supply,
&supply_keypair,
&supply_aes_key,
destination_pubkey,
Expand Down
8 changes: 8 additions & 0 deletions token/program-2022/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,11 @@ pub enum TokenError {
/// Fee calculation failed
#[error("Fee calculation failed")]
FeeCalculation,

//65
/// Withdraw / Deposit not allowed for confidential-mint-burn
#[error("Withdraw / Deposit not allowed for confidential-mint-burn")]
IllegalMintBurnConversion,
}
impl From<TokenError> for ProgramError {
fn from(e: TokenError) -> Self {
Expand Down Expand Up @@ -445,6 +450,9 @@ impl PrintProgramError for TokenError {
TokenError::FeeCalculation => {
msg!("Transfer fee calculation failed")
}
TokenError::IllegalMintBurnConversion => {
msg!("Conversions from normal to confidential token balance and vice versa are illegal if the confidential-mint-burn extension is enabled")
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use {
super::ConfidentialMintBurn,
crate::error::TokenError,
bytemuck::{Pod, Zeroable},
solana_zk_sdk::{
encryption::{
auth_encryption::{AeCiphertext, AeKey},
elgamal::{ElGamalCiphertext, ElGamalKeypair},
pedersen::PedersenOpening,
pod::{
auth_encryption::PodAeCiphertext,
elgamal::{PodElGamalCiphertext, PodElGamalPubkey},
},
},
zk_elgamal_proof_program::proof_data::CiphertextCiphertextEqualityProofData,
},
};

/// Confidential Mint Burn extension information needed to construct a
/// `RotateSupplyElgamalPubkey` instruction.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
pub struct SupplyAccountInfo {
/// The available balance (encrypted by `supply_elgamal_pubkey`)
pub current_supply: PodElGamalCiphertext,
/// The decryptable supply
pub decryptable_supply: PodAeCiphertext,
/// The supply's elgamal pubkey
pub supply_elgamal_pubkey: PodElGamalPubkey,
}

impl SupplyAccountInfo {
/// Creates a SupplyAccountInfo from ConfidentialMintBurn extension account
/// data
pub fn new(extension: &ConfidentialMintBurn) -> Self {
Self {
current_supply: extension.confidential_supply,
decryptable_supply: extension.decryptable_supply,
supply_elgamal_pubkey: extension.supply_elgamal_pubkey,
}
}

/// Computes the current supply from the decryptable supply and the
/// difference between the decryptable supply and the elgamal encrypted
/// supply ciphertext
pub fn decrypt_current_supply(
&self,
aes_key: &AeKey,
elgamal_keypair: &ElGamalKeypair,
) -> Result<u64, TokenError> {
// decrypt the decryptable supply
let current_decyptable_supply = AeCiphertext::try_from(self.decryptable_supply)
.map_err(|_| TokenError::MalformedCiphertext)?
.decrypt(aes_key)
.ok_or(TokenError::MalformedCiphertext)?;

// get the difference between the supply ciphertext and the decryptable supply
// explanation see https://github.com/solana-labs/solana-program-library/pull/6881#issuecomment-2385579058
let decryptable_supply_ciphertext =
elgamal_keypair.pubkey().encrypt(current_decyptable_supply);
#[allow(clippy::arithmetic_side_effects)]
let supply_delta_ciphertext = decryptable_supply_ciphertext
- ElGamalCiphertext::try_from(self.current_supply)
.map_err(|_| TokenError::MalformedCiphertext)?;
let decryptable_to_current_diff = elgamal_keypair
.secret()
.decrypt_u32(&supply_delta_ciphertext)
.ok_or(TokenError::MalformedCiphertext)?;

// compute the current supply
current_decyptable_supply
.checked_sub(decryptable_to_current_diff)
.ok_or(TokenError::Overflow)
}

/// Generates the `CiphertextCiphertextEqualityProofData` needed for a
/// `RotateSupplyElgamalPubkey` instruction
pub fn generate_rotate_supply_elgamal_pubkey_proof(
&self,
aes_key: &AeKey,
current_supply_elgamal_keypair: &ElGamalKeypair,
new_supply_elgamal_keypair: &ElGamalKeypair,
) -> Result<CiphertextCiphertextEqualityProofData, TokenError> {
let current_supply =
self.decrypt_current_supply(aes_key, current_supply_elgamal_keypair)?;

let new_supply_opening = PedersenOpening::new_rand();
let new_supply_ciphertext = new_supply_elgamal_keypair
.pubkey()
.encrypt_with(current_supply, &new_supply_opening);

CiphertextCiphertextEqualityProofData::new(
current_supply_elgamal_keypair,
new_supply_elgamal_keypair.pubkey(),
&self
.current_supply
.try_into()
.map_err(|_| TokenError::MalformedCiphertext)?,
&new_supply_ciphertext,
&new_supply_opening,
current_supply,
)
.map_err(|_| TokenError::ProofGeneration)
}
}
Loading

0 comments on commit 29eae22

Please sign in to comment.