Skip to content

Commit

Permalink
[confidential-transfer] Add confidential burn proof generation and ex…
Browse files Browse the repository at this point in the history
…traction (#6955)

* add proof generation logic for confidential mint and burn

* add proof extraction logic for confidential mint and burn

* add tests for confidential mint and burn

* Apply suggestions from code review

Co-authored-by: Jon C <[email protected]>

* add equality and range proof to confidential mint proof

* rename `aes_key` to `source_aes_key` in confidential burn

---------

Co-authored-by: Jon C <[email protected]>
  • Loading branch information
samkim-crypto and joncinque authored Sep 13, 2024
1 parent 012106e commit df498f3
Show file tree
Hide file tree
Showing 9 changed files with 730 additions and 2 deletions.
130 changes: 130 additions & 0 deletions token/confidential-transfer/proof-extraction/src/burn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use {
crate::{encryption::PodBurnAmountCiphertext, errors::TokenProofExtractionError},
solana_zk_sdk::{
encryption::pod::elgamal::{PodElGamalCiphertext, PodElGamalPubkey},
zk_elgamal_proof_program::proof_data::{
BatchedGroupedCiphertext3HandlesValidityProofContext, BatchedRangeProofContext,
CiphertextCommitmentEqualityProofContext,
},
},
};

/// The public keys associated with a confidential burn
pub struct BurnPubkeys {
pub source: PodElGamalPubkey,
pub auditor: PodElGamalPubkey,
pub supply: PodElGamalPubkey,
}

/// The proof context information needed to process a confidential burn
/// instruction
pub struct BurnProofContext {
pub burn_amount_ciphertext_lo: PodBurnAmountCiphertext,
pub burn_amount_ciphertext_hi: PodBurnAmountCiphertext,
pub burn_pubkeys: BurnPubkeys,
pub remaining_balance_ciphertext: PodElGamalCiphertext,
}

impl BurnProofContext {
pub fn verify_and_extract(
equality_proof_context: &CiphertextCommitmentEqualityProofContext,
ciphertext_validity_proof_context: &BatchedGroupedCiphertext3HandlesValidityProofContext,
range_proof_context: &BatchedRangeProofContext,
) -> Result<Self, TokenProofExtractionError> {
// The equality proof context consists of the source ElGamal public key, the new
// source available balance ciphertext, and the new source avaialble
// balance commitment. The public key should be checked with ciphertext
// validity proof context for consistency and the commitment should be
// checked with range proof for consistency. The public key and
// the cihpertext should be returned as part of `BurnProofContext`.
let CiphertextCommitmentEqualityProofContext {
pubkey: source_elgamal_pubkey_from_equality_proof,
ciphertext: remaining_balance_ciphertext,
commitment: remaining_balance_commitment,
} = equality_proof_context;

// The ciphertext validity proof context consists of the source ElGamal public
// key, the auditor ElGamal public key, and the grouped ElGamal
// ciphertexts for the low and high bits of the burn amount. The source
// ElGamal public key should be checked with equality
// proof for consistency and the rest of the data should be returned as part of
// `BurnProofContext`.
let BatchedGroupedCiphertext3HandlesValidityProofContext {
first_pubkey: source_elgamal_pubkey_from_validity_proof,
second_pubkey: auditor_elgamal_pubkey,
third_pubkey: supply_elgamal_pubkey,
grouped_ciphertext_lo: burn_amount_ciphertext_lo,
grouped_ciphertext_hi: burn_amount_ciphertext_hi,
} = ciphertext_validity_proof_context;

// The range proof context consists of the Pedersen commitments and bit-lengths
// for which the range proof is proved. The commitments must consist of
// three commitments pertaining to the new source available balance, the
// low bits of the burn amount, and high bits of the burn
// amount. These commitments must be checked for bit lengths `64`, `16`,
// and `32`.
let BatchedRangeProofContext {
commitments: range_proof_commitments,
bit_lengths: range_proof_bit_lengths,
} = range_proof_context;

// check that the source pubkey is consistent between equality and ciphertext
// validity proofs
if source_elgamal_pubkey_from_equality_proof != source_elgamal_pubkey_from_validity_proof {
return Err(TokenProofExtractionError::ElGamalPubkeyMismatch);
}

// check that the range proof was created for the correct set of Pedersen
// commitments
let burn_amount_commitment_lo = burn_amount_ciphertext_lo.extract_commitment();
let burn_amount_commitment_hi = burn_amount_ciphertext_hi.extract_commitment();

let expected_commitments = [
*remaining_balance_commitment,
burn_amount_commitment_lo,
burn_amount_commitment_hi,
];

if !range_proof_commitments
.iter()
.zip(expected_commitments.iter())
.all(|(proof_commitment, expected_commitment)| proof_commitment == expected_commitment)
{
return Err(TokenProofExtractionError::PedersenCommitmentMismatch);
}

// check that the range proof was created for the correct number of bits
const REMAINING_BALANCE_BIT_LENGTH: u8 = 64;
const BURN_AMOUNT_LO_BIT_LENGTH: u8 = 16;
const BURN_AMOUNT_HI_BIT_LENGTH: u8 = 32;
const PADDING_BIT_LENGTH: u8 = 16;
let expected_bit_lengths = [
REMAINING_BALANCE_BIT_LENGTH,
BURN_AMOUNT_LO_BIT_LENGTH,
BURN_AMOUNT_HI_BIT_LENGTH,
PADDING_BIT_LENGTH,
]
.iter();

if !range_proof_bit_lengths
.iter()
.zip(expected_bit_lengths)
.all(|(proof_len, expected_len)| proof_len == expected_len)
{
return Err(TokenProofExtractionError::RangeProofLengthMismatch);
}

let burn_pubkeys = BurnPubkeys {
source: *source_elgamal_pubkey_from_equality_proof,
auditor: *auditor_elgamal_pubkey,
supply: *supply_elgamal_pubkey,
};

Ok(BurnProofContext {
burn_amount_ciphertext_lo: PodBurnAmountCiphertext(*burn_amount_ciphertext_lo),
burn_amount_ciphertext_hi: PodBurnAmountCiphertext(*burn_amount_ciphertext_hi),
burn_pubkeys,
remaining_balance_ciphertext: *remaining_balance_ciphertext,
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,11 @@ impl PodFeeCiphertext {
.map_err(|_| TokenProofExtractionError::CiphertextExtraction)
}
}

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

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(C)]
pub struct PodMintAmountCiphertext(pub(crate) PodGroupedElGamalCiphertext3Handles);
2 changes: 2 additions & 0 deletions token/confidential-transfer/proof-extraction/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
pub mod burn;
pub mod encryption;
pub mod errors;
pub mod mint;
pub mod transfer;
pub mod transfer_with_fee;
pub mod withdraw;
130 changes: 130 additions & 0 deletions token/confidential-transfer/proof-extraction/src/mint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use {
crate::{encryption::PodMintAmountCiphertext, errors::TokenProofExtractionError},
solana_zk_sdk::{
encryption::pod::elgamal::{PodElGamalCiphertext, PodElGamalPubkey},
zk_elgamal_proof_program::proof_data::{
BatchedGroupedCiphertext3HandlesValidityProofContext, BatchedRangeProofContext,
CiphertextCommitmentEqualityProofContext,
},
},
};

/// The public keys associated with a confidential mint
pub struct MintPubkeys {
pub destination: PodElGamalPubkey,
pub auditor: PodElGamalPubkey,
pub supply: PodElGamalPubkey,
}

/// The proof context information needed to process a confidential mint
/// instruction
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<Self, TokenProofExtractionError> {
// 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
// fields should be returned as part of `MintProofContext`.
let BatchedGroupedCiphertext3HandlesValidityProofContext {
first_pubkey: destination_elgamal_pubkey,
second_pubkey: auditor_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;

// The range proof context consists of the Pedersen commitments and bit-lengths
// for which the range proof is proved. The commitments must consist of
// two commitments pertaining to the the
// low bits of the mint amount, and high bits of the mint
// amount. These commitments must be checked for bit lengths `16` and
// and `32`.
let BatchedRangeProofContext {
commitments: range_proof_commitments,
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 = [
*new_supply_commitment,
mint_amount_commitment_lo,
mint_amount_commitment_hi,
];

if !range_proof_commitments
.iter()
.zip(expected_commitments.iter())
.all(|(proof_commitment, expected_commitment)| proof_commitment == expected_commitment)
{
return Err(TokenProofExtractionError::PedersenCommitmentMismatch);
}

// 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,
]
.iter();

if !range_proof_bit_lengths
.iter()
.zip(expected_bit_lengths)
.all(|(proof_len, expected_len)| proof_len == expected_len)
{
return Err(TokenProofExtractionError::RangeProofLengthMismatch);
}

let mint_pubkeys = MintPubkeys {
destination: *destination_elgamal_pubkey,
auditor: *auditor_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,
})
}
}
Loading

0 comments on commit df498f3

Please sign in to comment.