From f8ce0df22c396f82e426b24c8cea2f8b8f0cfe25 Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Wed, 11 Sep 2024 12:38:12 +0900 Subject: [PATCH] add equality and range proof to confidential mint proof --- .../proof-extraction/src/mint.rs | 38 ++++++++- .../proof-generation/src/mint.rs | 78 ++++++++++++++++--- .../proof-tests/tests/proof_test.rs | 37 +++++++-- 3 files changed, 131 insertions(+), 22 deletions(-) diff --git a/token/confidential-transfer/proof-extraction/src/mint.rs b/token/confidential-transfer/proof-extraction/src/mint.rs index 887789d07a6..cffe8616dc6 100644 --- a/token/confidential-transfer/proof-extraction/src/mint.rs +++ b/token/confidential-transfer/proof-extraction/src/mint.rs @@ -1,9 +1,10 @@ use { crate::{encryption::PodMintAmountCiphertext, errors::TokenProofExtractionError}, solana_zk_sdk::{ - encryption::pod::elgamal::PodElGamalPubkey, + encryption::pod::elgamal::{PodElGamalCiphertext, PodElGamalPubkey}, zk_elgamal_proof_program::proof_data::{ BatchedGroupedCiphertext3HandlesValidityProofContext, BatchedRangeProofContext, + CiphertextCommitmentEqualityProofContext, }, }, }; @@ -21,13 +22,27 @@ pub struct MintProofContext { pub mint_amount_ciphertext_lo: PodMintAmountCiphertext, pub mint_amount_ciphertext_hi: PodMintAmountCiphertext, pub mint_pubkeys: MintPubkeys, + pub new_supply_ciphertext: PodElGamalCiphertext, } impl MintProofContext { pub fn verify_and_extract( + equality_proof_context: &CiphertextCommitmentEqualityProofContext, ciphertext_validity_proof_context: &BatchedGroupedCiphertext3HandlesValidityProofContext, range_proof_context: &BatchedRangeProofContext, ) -> Result { + // The equality proof context consists of the supply ElGamal public key, the new + // supply ciphertext, and the new supply commitment. The supply ElGamal + // public key should be checked with ciphertext validity proof for + // consistency and the new supply commitment should be checked with + // range proof for consistency. The new supply ciphertext should be + // returned as part of `MintProofContext`. + let CiphertextCommitmentEqualityProofContext { + pubkey: supply_elgamal_pubkey_from_equality_proof, + ciphertext: new_supply_ciphertext, + commitment: new_supply_commitment, + } = equality_proof_context; + // The ciphertext validity proof context consists of the destination ElGamal // public key, the auditor ElGamal public key, and the grouped ElGamal // ciphertexts for the low and high bits of the mint amount. These @@ -35,7 +50,7 @@ impl MintProofContext { let BatchedGroupedCiphertext3HandlesValidityProofContext { first_pubkey: destination_elgamal_pubkey, second_pubkey: auditor_elgamal_pubkey, - third_pubkey: supply_elgamal_pubkey, + third_pubkey: supply_elgamal_pubkey_from_ciphertext_validity_proof, grouped_ciphertext_lo: mint_amount_ciphertext_lo, grouped_ciphertext_hi: mint_amount_ciphertext_hi, } = ciphertext_validity_proof_context; @@ -51,12 +66,24 @@ impl MintProofContext { bit_lengths: range_proof_bit_lengths, } = range_proof_context; + // check that the supply pubkey is consistent between equality and ciphertext + // validity proofs + if supply_elgamal_pubkey_from_equality_proof + != supply_elgamal_pubkey_from_ciphertext_validity_proof + { + return Err(TokenProofExtractionError::ElGamalPubkeyMismatch); + } + // check that the range proof was created for the correct set of Pedersen // commitments let mint_amount_commitment_lo = mint_amount_ciphertext_lo.extract_commitment(); let mint_amount_commitment_hi = mint_amount_ciphertext_hi.extract_commitment(); - let expected_commitments = [mint_amount_commitment_lo, mint_amount_commitment_hi]; + let expected_commitments = [ + *new_supply_commitment, + mint_amount_commitment_lo, + mint_amount_commitment_hi, + ]; if !range_proof_commitments .iter() @@ -67,10 +94,12 @@ impl MintProofContext { } // check that the range proof was created for the correct number of bits + const NEW_SUPPLY_BIT_LENGTH: u8 = 64; const MINT_AMOUNT_LO_BIT_LENGTH: u8 = 16; const MINT_AMOUNT_HI_BIT_LENGTH: u8 = 32; const PADDING_BIT_LENGTH: u8 = 16; let expected_bit_lengths = [ + NEW_SUPPLY_BIT_LENGTH, MINT_AMOUNT_LO_BIT_LENGTH, MINT_AMOUNT_HI_BIT_LENGTH, PADDING_BIT_LENGTH, @@ -88,13 +117,14 @@ impl MintProofContext { let mint_pubkeys = MintPubkeys { destination: *destination_elgamal_pubkey, auditor: *auditor_elgamal_pubkey, - supply: *supply_elgamal_pubkey, + supply: *supply_elgamal_pubkey_from_equality_proof, }; Ok(MintProofContext { mint_amount_ciphertext_lo: PodMintAmountCiphertext(*mint_amount_ciphertext_lo), mint_amount_ciphertext_hi: PodMintAmountCiphertext(*mint_amount_ciphertext_hi), mint_pubkeys, + new_supply_ciphertext: *new_supply_ciphertext, }) } } diff --git a/token/confidential-transfer/proof-generation/src/mint.rs b/token/confidential-transfer/proof-generation/src/mint.rs index 7fab270415e..e25dc70a210 100644 --- a/token/confidential-transfer/proof-generation/src/mint.rs +++ b/token/confidential-transfer/proof-generation/src/mint.rs @@ -1,13 +1,22 @@ use { - crate::{encryption::MintAmountCiphertext, errors::TokenProofGenerationError, try_split_u64}, + crate::{ + encryption::MintAmountCiphertext, errors::TokenProofGenerationError, + try_combine_lo_hi_ciphertexts, try_split_u64, + }, solana_zk_sdk::{ - encryption::{elgamal::ElGamalPubkey, pedersen::Pedersen}, + encryption::{ + auth_encryption::{AeCiphertext, AeKey}, + elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey}, + pedersen::Pedersen, + }, zk_elgamal_proof_program::proof_data::{ - BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU64Data, + BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data, + CiphertextCommitmentEqualityProofData, }, }, }; +const NEW_SUPPLY_BIT_LENGTH: usize = 64; const MINT_AMOUNT_LO_BIT_LENGTH: usize = 16; const MINT_AMOUNT_HI_BIT_LENGTH: usize = 32; /// The padding bit length in range proofs to make the bit-length power-of-2 @@ -15,15 +24,19 @@ const RANGE_PROOF_PADDING_BIT_LENGTH: usize = 16; /// The proof data required for a confidential mint instruction pub struct MintProofData { + pub equality_proof_data: CiphertextCommitmentEqualityProofData, pub ciphertext_validity_proof_data: BatchedGroupedCiphertext3HandlesValidityProofData, - pub range_proof_data: BatchedRangeProofU64Data, + pub range_proof_data: BatchedRangeProofU128Data, } pub fn mint_split_proof_data( + current_supply_ciphertext: &ElGamalCiphertext, + current_decryptable_supply: &AeCiphertext, mint_amount: u64, + supply_elgamal_keypair: &ElGamalKeypair, + supply_aes_key: &AeKey, destination_elgamal_pubkey: &ElGamalPubkey, auditor_elgamal_pubkey: &ElGamalPubkey, - supply_elgamal_pubkey: &ElGamalPubkey, ) -> Result { // split the mint amount into low and high bits let (mint_amount_lo, mint_amount_hi) = try_split_u64(mint_amount, MINT_AMOUNT_LO_BIT_LENGTH) @@ -35,21 +48,62 @@ pub fn mint_split_proof_data( mint_amount_lo, destination_elgamal_pubkey, auditor_elgamal_pubkey, - supply_elgamal_pubkey, + supply_elgamal_keypair.pubkey(), ); let (mint_amount_grouped_ciphertext_hi, mint_amount_opening_hi) = MintAmountCiphertext::new( mint_amount_hi, destination_elgamal_pubkey, auditor_elgamal_pubkey, - supply_elgamal_pubkey, + supply_elgamal_keypair.pubkey(), ); + // compute the new supply ciphertext + let mint_amount_ciphertext_supply_lo = mint_amount_grouped_ciphertext_lo + .0 + .to_elgamal_ciphertext(2) + .unwrap(); + let mint_amount_ciphertext_supply_hi = mint_amount_grouped_ciphertext_hi + .0 + .to_elgamal_ciphertext(2) + .unwrap(); + + #[allow(clippy::arithmetic_side_effects)] + let new_supply_ciphertext = current_supply_ciphertext + + try_combine_lo_hi_ciphertexts( + &mint_amount_ciphertext_supply_lo, + &mint_amount_ciphertext_supply_hi, + MINT_AMOUNT_LO_BIT_LENGTH, + ) + .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) + .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?; + + let (new_supply_commitment, new_supply_opening) = Pedersen::new(new_supply); + + // generate equality proof data + let equality_proof_data = CiphertextCommitmentEqualityProofData::new( + supply_elgamal_keypair, + &new_supply_ciphertext, + &new_supply_commitment, + &new_supply_opening, + new_supply, + ) + .map_err(TokenProofGenerationError::from)?; + // generate ciphertext validity proof data let ciphertext_validity_proof_data = BatchedGroupedCiphertext3HandlesValidityProofData::new( destination_elgamal_pubkey, auditor_elgamal_pubkey, - supply_elgamal_pubkey, + supply_elgamal_keypair.pubkey(), &mint_amount_grouped_ciphertext_lo.0, &mint_amount_grouped_ciphertext_hi.0, mint_amount_lo, @@ -61,19 +115,22 @@ pub fn mint_split_proof_data( // generate range proof data let (padding_commitment, padding_opening) = Pedersen::new(0_u64); - let range_proof_data = BatchedRangeProofU64Data::new( + let range_proof_data = BatchedRangeProofU128Data::new( vec![ + &new_supply_commitment, mint_amount_grouped_ciphertext_lo.get_commitment(), mint_amount_grouped_ciphertext_hi.get_commitment(), &padding_commitment, ], - vec![mint_amount_lo, mint_amount_hi, 0], + vec![new_supply, mint_amount_lo, mint_amount_hi, 0], vec![ + NEW_SUPPLY_BIT_LENGTH, MINT_AMOUNT_LO_BIT_LENGTH, MINT_AMOUNT_HI_BIT_LENGTH, RANGE_PROOF_PADDING_BIT_LENGTH, ], vec![ + &new_supply_opening, &mint_amount_opening_lo, &mint_amount_opening_hi, &padding_opening, @@ -82,6 +139,7 @@ pub fn mint_split_proof_data( .map_err(TokenProofGenerationError::from)?; Ok(MintProofData { + equality_proof_data, ciphertext_validity_proof_data, range_proof_data, }) diff --git a/token/confidential-transfer/proof-tests/tests/proof_test.rs b/token/confidential-transfer/proof-tests/tests/proof_test.rs index 4c09d29e90e..f4c3a7f3a9e 100644 --- a/token/confidential-transfer/proof-tests/tests/proof_test.rs +++ b/token/confidential-transfer/proof-tests/tests/proof_test.rs @@ -187,14 +187,26 @@ fn test_withdraw_validity(spendable_balance: u64, withdraw_amount: u64) { #[test] fn test_mint_proof_correctness() { - test_mint_validity(0); - test_mint_validity(1); - test_mint_validity(65535); - test_mint_validity(65536); - test_mint_validity(281474976710655); + test_mint_validity(0, 0); + test_mint_validity(1, 0); + test_mint_validity(65535, 0); + test_mint_validity(65536, 0); + test_mint_validity(281474976710655, 0); + + test_mint_validity(0, 65535); + test_mint_validity(1, 65535); + test_mint_validity(65535, 65535); + test_mint_validity(65536, 65535); + test_mint_validity(281474976710655, 65535); + + test_mint_validity(0, 281474976710655); + test_mint_validity(1, 281474976710655); + test_mint_validity(65535, 281474976710655); + test_mint_validity(65536, 281474976710655); + test_mint_validity(281474976710655, 281474976710655); } -fn test_mint_validity(mint_amount: u64) { +fn test_mint_validity(mint_amount: u64, supply: u64) { let destination_keypair = ElGamalKeypair::new_rand(); let destination_pubkey = destination_keypair.pubkey(); @@ -202,23 +214,32 @@ fn test_mint_validity(mint_amount: u64) { let auditor_pubkey = auditor_keypair.pubkey(); let supply_keypair = ElGamalKeypair::new_rand(); - let supply_pubkey = supply_keypair.pubkey(); + 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, } = mint_split_proof_data( + &supply_ciphertext, + &decryptable_supply, mint_amount, + &supply_keypair, + &supply_aes_key, destination_pubkey, auditor_pubkey, - supply_pubkey, ) .unwrap(); + equality_proof_data.verify_proof().unwrap(); ciphertext_validity_proof_data.verify_proof().unwrap(); range_proof_data.verify_proof().unwrap(); MintProofContext::verify_and_extract( + equality_proof_data.context_data(), ciphertext_validity_proof_data.context_data(), range_proof_data.context_data(), )