spl_token_confidential_transfer_proof_extraction/
burn.rs

1use {
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
13pub struct BurnPubkeys {
14    pub source: PodElGamalPubkey,
15    pub supply: PodElGamalPubkey,
16    pub auditor: PodElGamalPubkey,
17}
18
19/// The proof context information needed to process a confidential burn
20/// instruction
21pub 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
28impl 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 available
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: supply_elgamal_pubkey,
55            third_pubkey: auditor_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            // we don't care about the padding commitment, so ignore it
87        ];
88
89        // range proof context always contains 8 commitments and therefore,
90        // this check will verify equality of all expected commitments
91        // (`zip` will not be short-circuited)
92        if !range_proof_commitments
93            .iter()
94            .zip(expected_commitments.iter())
95            .all(|(proof_commitment, expected_commitment)| proof_commitment == expected_commitment)
96        {
97            return Err(TokenProofExtractionError::PedersenCommitmentMismatch);
98        }
99
100        // check that the range proof was created for the correct number of bits
101        const REMAINING_BALANCE_BIT_LENGTH: u8 = 64;
102        const BURN_AMOUNT_LO_BIT_LENGTH: u8 = 16;
103        const BURN_AMOUNT_HI_BIT_LENGTH: u8 = 32;
104        const PADDING_BIT_LENGTH: u8 = 16;
105        let expected_bit_lengths = [
106            REMAINING_BALANCE_BIT_LENGTH,
107            BURN_AMOUNT_LO_BIT_LENGTH,
108            BURN_AMOUNT_HI_BIT_LENGTH,
109            PADDING_BIT_LENGTH,
110        ]
111        .iter();
112
113        // range proof context always contains 8 bit lengths and therefore,
114        // this check will verify equality of all expected bit lengths
115        // (`zip` will not be short-circuited)
116        if !range_proof_bit_lengths
117            .iter()
118            .zip(expected_bit_lengths)
119            .all(|(proof_len, expected_len)| proof_len == expected_len)
120        {
121            return Err(TokenProofExtractionError::RangeProofLengthMismatch);
122        }
123
124        let burn_pubkeys = BurnPubkeys {
125            source: *source_elgamal_pubkey_from_equality_proof,
126            supply: *supply_elgamal_pubkey,
127            auditor: *auditor_elgamal_pubkey,
128        };
129
130        Ok(BurnProofContext {
131            burn_amount_ciphertext_lo: PodBurnAmountCiphertext(*burn_amount_ciphertext_lo),
132            burn_amount_ciphertext_hi: PodBurnAmountCiphertext(*burn_amount_ciphertext_hi),
133            burn_pubkeys,
134            remaining_balance_ciphertext: *remaining_balance_ciphertext,
135        })
136    }
137}