spl_token_confidential_transfer_proof_extraction/
burn.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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,
        })
    }
}