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

Commit df498f3

Browse files
[confidential-transfer] Add confidential burn proof generation and extraction (#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]>
1 parent 012106e commit df498f3

File tree

9 files changed

+730
-2
lines changed

9 files changed

+730
-2
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
use {
2+
crate::{encryption::PodBurnAmountCiphertext, errors::TokenProofExtractionError},
3+
solana_zk_sdk::{
4+
encryption::pod::elgamal::{PodElGamalCiphertext, PodElGamalPubkey},
5+
zk_elgamal_proof_program::proof_data::{
6+
BatchedGroupedCiphertext3HandlesValidityProofContext, BatchedRangeProofContext,
7+
CiphertextCommitmentEqualityProofContext,
8+
},
9+
},
10+
};
11+
12+
/// The public keys associated with a confidential burn
13+
pub struct BurnPubkeys {
14+
pub source: PodElGamalPubkey,
15+
pub auditor: PodElGamalPubkey,
16+
pub supply: PodElGamalPubkey,
17+
}
18+
19+
/// The proof context information needed to process a confidential burn
20+
/// instruction
21+
pub struct BurnProofContext {
22+
pub burn_amount_ciphertext_lo: PodBurnAmountCiphertext,
23+
pub burn_amount_ciphertext_hi: PodBurnAmountCiphertext,
24+
pub burn_pubkeys: BurnPubkeys,
25+
pub remaining_balance_ciphertext: PodElGamalCiphertext,
26+
}
27+
28+
impl BurnProofContext {
29+
pub fn verify_and_extract(
30+
equality_proof_context: &CiphertextCommitmentEqualityProofContext,
31+
ciphertext_validity_proof_context: &BatchedGroupedCiphertext3HandlesValidityProofContext,
32+
range_proof_context: &BatchedRangeProofContext,
33+
) -> Result<Self, TokenProofExtractionError> {
34+
// The equality proof context consists of the source ElGamal public key, the new
35+
// source available balance ciphertext, and the new source avaialble
36+
// balance commitment. The public key should be checked with ciphertext
37+
// validity proof context for consistency and the commitment should be
38+
// checked with range proof for consistency. The public key and
39+
// the cihpertext should be returned as part of `BurnProofContext`.
40+
let CiphertextCommitmentEqualityProofContext {
41+
pubkey: source_elgamal_pubkey_from_equality_proof,
42+
ciphertext: remaining_balance_ciphertext,
43+
commitment: remaining_balance_commitment,
44+
} = equality_proof_context;
45+
46+
// The ciphertext validity proof context consists of the source ElGamal public
47+
// key, the auditor ElGamal public key, and the grouped ElGamal
48+
// ciphertexts for the low and high bits of the burn amount. The source
49+
// ElGamal public key should be checked with equality
50+
// proof for consistency and the rest of the data should be returned as part of
51+
// `BurnProofContext`.
52+
let BatchedGroupedCiphertext3HandlesValidityProofContext {
53+
first_pubkey: source_elgamal_pubkey_from_validity_proof,
54+
second_pubkey: auditor_elgamal_pubkey,
55+
third_pubkey: supply_elgamal_pubkey,
56+
grouped_ciphertext_lo: burn_amount_ciphertext_lo,
57+
grouped_ciphertext_hi: burn_amount_ciphertext_hi,
58+
} = ciphertext_validity_proof_context;
59+
60+
// The range proof context consists of the Pedersen commitments and bit-lengths
61+
// for which the range proof is proved. The commitments must consist of
62+
// three commitments pertaining to the new source available balance, the
63+
// low bits of the burn amount, and high bits of the burn
64+
// amount. These commitments must be checked for bit lengths `64`, `16`,
65+
// and `32`.
66+
let BatchedRangeProofContext {
67+
commitments: range_proof_commitments,
68+
bit_lengths: range_proof_bit_lengths,
69+
} = range_proof_context;
70+
71+
// check that the source pubkey is consistent between equality and ciphertext
72+
// validity proofs
73+
if source_elgamal_pubkey_from_equality_proof != source_elgamal_pubkey_from_validity_proof {
74+
return Err(TokenProofExtractionError::ElGamalPubkeyMismatch);
75+
}
76+
77+
// check that the range proof was created for the correct set of Pedersen
78+
// commitments
79+
let burn_amount_commitment_lo = burn_amount_ciphertext_lo.extract_commitment();
80+
let burn_amount_commitment_hi = burn_amount_ciphertext_hi.extract_commitment();
81+
82+
let expected_commitments = [
83+
*remaining_balance_commitment,
84+
burn_amount_commitment_lo,
85+
burn_amount_commitment_hi,
86+
];
87+
88+
if !range_proof_commitments
89+
.iter()
90+
.zip(expected_commitments.iter())
91+
.all(|(proof_commitment, expected_commitment)| proof_commitment == expected_commitment)
92+
{
93+
return Err(TokenProofExtractionError::PedersenCommitmentMismatch);
94+
}
95+
96+
// check that the range proof was created for the correct number of bits
97+
const REMAINING_BALANCE_BIT_LENGTH: u8 = 64;
98+
const BURN_AMOUNT_LO_BIT_LENGTH: u8 = 16;
99+
const BURN_AMOUNT_HI_BIT_LENGTH: u8 = 32;
100+
const PADDING_BIT_LENGTH: u8 = 16;
101+
let expected_bit_lengths = [
102+
REMAINING_BALANCE_BIT_LENGTH,
103+
BURN_AMOUNT_LO_BIT_LENGTH,
104+
BURN_AMOUNT_HI_BIT_LENGTH,
105+
PADDING_BIT_LENGTH,
106+
]
107+
.iter();
108+
109+
if !range_proof_bit_lengths
110+
.iter()
111+
.zip(expected_bit_lengths)
112+
.all(|(proof_len, expected_len)| proof_len == expected_len)
113+
{
114+
return Err(TokenProofExtractionError::RangeProofLengthMismatch);
115+
}
116+
117+
let burn_pubkeys = BurnPubkeys {
118+
source: *source_elgamal_pubkey_from_equality_proof,
119+
auditor: *auditor_elgamal_pubkey,
120+
supply: *supply_elgamal_pubkey,
121+
};
122+
123+
Ok(BurnProofContext {
124+
burn_amount_ciphertext_lo: PodBurnAmountCiphertext(*burn_amount_ciphertext_lo),
125+
burn_amount_ciphertext_hi: PodBurnAmountCiphertext(*burn_amount_ciphertext_hi),
126+
burn_pubkeys,
127+
remaining_balance_ciphertext: *remaining_balance_ciphertext,
128+
})
129+
}
130+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,11 @@ impl PodFeeCiphertext {
4646
.map_err(|_| TokenProofExtractionError::CiphertextExtraction)
4747
}
4848
}
49+
50+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
51+
#[repr(C)]
52+
pub struct PodBurnAmountCiphertext(pub(crate) PodGroupedElGamalCiphertext3Handles);
53+
54+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
55+
#[repr(C)]
56+
pub struct PodMintAmountCiphertext(pub(crate) PodGroupedElGamalCiphertext3Handles);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
pub mod burn;
12
pub mod encryption;
23
pub mod errors;
4+
pub mod mint;
35
pub mod transfer;
46
pub mod transfer_with_fee;
57
pub mod withdraw;
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
use {
2+
crate::{encryption::PodMintAmountCiphertext, errors::TokenProofExtractionError},
3+
solana_zk_sdk::{
4+
encryption::pod::elgamal::{PodElGamalCiphertext, PodElGamalPubkey},
5+
zk_elgamal_proof_program::proof_data::{
6+
BatchedGroupedCiphertext3HandlesValidityProofContext, BatchedRangeProofContext,
7+
CiphertextCommitmentEqualityProofContext,
8+
},
9+
},
10+
};
11+
12+
/// The public keys associated with a confidential mint
13+
pub struct MintPubkeys {
14+
pub destination: PodElGamalPubkey,
15+
pub auditor: PodElGamalPubkey,
16+
pub supply: PodElGamalPubkey,
17+
}
18+
19+
/// The proof context information needed to process a confidential mint
20+
/// instruction
21+
pub struct MintProofContext {
22+
pub mint_amount_ciphertext_lo: PodMintAmountCiphertext,
23+
pub mint_amount_ciphertext_hi: PodMintAmountCiphertext,
24+
pub mint_pubkeys: MintPubkeys,
25+
pub new_supply_ciphertext: PodElGamalCiphertext,
26+
}
27+
28+
impl MintProofContext {
29+
pub fn verify_and_extract(
30+
equality_proof_context: &CiphertextCommitmentEqualityProofContext,
31+
ciphertext_validity_proof_context: &BatchedGroupedCiphertext3HandlesValidityProofContext,
32+
range_proof_context: &BatchedRangeProofContext,
33+
) -> Result<Self, TokenProofExtractionError> {
34+
// The equality proof context consists of the supply ElGamal public key, the new
35+
// supply ciphertext, and the new supply commitment. The supply ElGamal
36+
// public key should be checked with ciphertext validity proof for
37+
// consistency and the new supply commitment should be checked with
38+
// range proof for consistency. The new supply ciphertext should be
39+
// returned as part of `MintProofContext`.
40+
let CiphertextCommitmentEqualityProofContext {
41+
pubkey: supply_elgamal_pubkey_from_equality_proof,
42+
ciphertext: new_supply_ciphertext,
43+
commitment: new_supply_commitment,
44+
} = equality_proof_context;
45+
46+
// The ciphertext validity proof context consists of the destination ElGamal
47+
// public key, the auditor ElGamal public key, and the grouped ElGamal
48+
// ciphertexts for the low and high bits of the mint amount. These
49+
// fields should be returned as part of `MintProofContext`.
50+
let BatchedGroupedCiphertext3HandlesValidityProofContext {
51+
first_pubkey: destination_elgamal_pubkey,
52+
second_pubkey: auditor_elgamal_pubkey,
53+
third_pubkey: supply_elgamal_pubkey_from_ciphertext_validity_proof,
54+
grouped_ciphertext_lo: mint_amount_ciphertext_lo,
55+
grouped_ciphertext_hi: mint_amount_ciphertext_hi,
56+
} = ciphertext_validity_proof_context;
57+
58+
// The range proof context consists of the Pedersen commitments and bit-lengths
59+
// for which the range proof is proved. The commitments must consist of
60+
// two commitments pertaining to the the
61+
// low bits of the mint amount, and high bits of the mint
62+
// amount. These commitments must be checked for bit lengths `16` and
63+
// and `32`.
64+
let BatchedRangeProofContext {
65+
commitments: range_proof_commitments,
66+
bit_lengths: range_proof_bit_lengths,
67+
} = range_proof_context;
68+
69+
// check that the supply pubkey is consistent between equality and ciphertext
70+
// validity proofs
71+
if supply_elgamal_pubkey_from_equality_proof
72+
!= supply_elgamal_pubkey_from_ciphertext_validity_proof
73+
{
74+
return Err(TokenProofExtractionError::ElGamalPubkeyMismatch);
75+
}
76+
77+
// check that the range proof was created for the correct set of Pedersen
78+
// commitments
79+
let mint_amount_commitment_lo = mint_amount_ciphertext_lo.extract_commitment();
80+
let mint_amount_commitment_hi = mint_amount_ciphertext_hi.extract_commitment();
81+
82+
let expected_commitments = [
83+
*new_supply_commitment,
84+
mint_amount_commitment_lo,
85+
mint_amount_commitment_hi,
86+
];
87+
88+
if !range_proof_commitments
89+
.iter()
90+
.zip(expected_commitments.iter())
91+
.all(|(proof_commitment, expected_commitment)| proof_commitment == expected_commitment)
92+
{
93+
return Err(TokenProofExtractionError::PedersenCommitmentMismatch);
94+
}
95+
96+
// check that the range proof was created for the correct number of bits
97+
const NEW_SUPPLY_BIT_LENGTH: u8 = 64;
98+
const MINT_AMOUNT_LO_BIT_LENGTH: u8 = 16;
99+
const MINT_AMOUNT_HI_BIT_LENGTH: u8 = 32;
100+
const PADDING_BIT_LENGTH: u8 = 16;
101+
let expected_bit_lengths = [
102+
NEW_SUPPLY_BIT_LENGTH,
103+
MINT_AMOUNT_LO_BIT_LENGTH,
104+
MINT_AMOUNT_HI_BIT_LENGTH,
105+
PADDING_BIT_LENGTH,
106+
]
107+
.iter();
108+
109+
if !range_proof_bit_lengths
110+
.iter()
111+
.zip(expected_bit_lengths)
112+
.all(|(proof_len, expected_len)| proof_len == expected_len)
113+
{
114+
return Err(TokenProofExtractionError::RangeProofLengthMismatch);
115+
}
116+
117+
let mint_pubkeys = MintPubkeys {
118+
destination: *destination_elgamal_pubkey,
119+
auditor: *auditor_elgamal_pubkey,
120+
supply: *supply_elgamal_pubkey_from_equality_proof,
121+
};
122+
123+
Ok(MintProofContext {
124+
mint_amount_ciphertext_lo: PodMintAmountCiphertext(*mint_amount_ciphertext_lo),
125+
mint_amount_ciphertext_hi: PodMintAmountCiphertext(*mint_amount_ciphertext_hi),
126+
mint_pubkeys,
127+
new_supply_ciphertext: *new_supply_ciphertext,
128+
})
129+
}
130+
}

0 commit comments

Comments
 (0)