spl_token_confidential_transfer_proof_extraction/
transfer.rs

1use {
2    crate::{encryption::PodTransferAmountCiphertext, 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 transfer public keys associated with a transfer.
13pub struct TransferPubkeys {
14    /// Source ElGamal public key
15    pub source: PodElGamalPubkey,
16    /// Destination ElGamal public key
17    pub destination: PodElGamalPubkey,
18    /// Auditor ElGamal public key
19    pub auditor: PodElGamalPubkey,
20}
21
22/// The proof context information needed to process a `Transfer` instruction.
23pub struct TransferProofContext {
24    /// Ciphertext containing the low 16 bits of the transfer amount
25    pub ciphertext_lo: PodTransferAmountCiphertext,
26    /// Ciphertext containing the high 32 bits of the transfer amount
27    pub ciphertext_hi: PodTransferAmountCiphertext,
28    /// The transfer public keys associated with a transfer
29    pub transfer_pubkeys: TransferPubkeys,
30    /// The new source available balance ciphertext
31    pub new_source_ciphertext: PodElGamalCiphertext,
32}
33
34impl TransferProofContext {
35    pub fn verify_and_extract(
36        equality_proof_context: &CiphertextCommitmentEqualityProofContext,
37        ciphertext_validity_proof_context: &BatchedGroupedCiphertext3HandlesValidityProofContext,
38        range_proof_context: &BatchedRangeProofContext,
39    ) -> Result<Self, TokenProofExtractionError> {
40        // The equality proof context consists of the source ElGamal public key, the new
41        // source available balance ciphertext, and the new source available
42        // commitment. The public key and ciphertext should be returned as parts
43        // of `TransferProofContextInfo` and the commitment should be checked
44        // with range proof for consistency.
45        let CiphertextCommitmentEqualityProofContext {
46            pubkey: source_pubkey_from_equality_proof,
47            ciphertext: new_source_ciphertext,
48            commitment: new_source_commitment,
49        } = equality_proof_context;
50
51        // The ciphertext validity proof context consists of the destination ElGamal
52        // public key, auditor ElGamal public key, and the transfer amount
53        // ciphertexts. All of these fields should be returned as part of
54        // `TransferProofContextInfo`. In addition, the commitments pertaining
55        // to the transfer amount ciphertexts should be checked with range proof for
56        // consistency.
57        let BatchedGroupedCiphertext3HandlesValidityProofContext {
58            first_pubkey: source_pubkey_from_validity_proof,
59            second_pubkey: destination_pubkey,
60            third_pubkey: auditor_pubkey,
61            grouped_ciphertext_lo: transfer_amount_ciphertext_lo,
62            grouped_ciphertext_hi: transfer_amount_ciphertext_hi,
63        } = ciphertext_validity_proof_context;
64
65        // The range proof context consists of the Pedersen commitments and bit-lengths
66        // for which the range proof is proved. The commitments must consist of
67        // three commitments pertaining to the new source available balance, the
68        // low bits of the transfer amount, and high bits of the transfer
69        // amount. These commitments must be checked for bit lengths `64`, `16`,
70        // and `32`.
71        let BatchedRangeProofContext {
72            commitments: range_proof_commitments,
73            bit_lengths: range_proof_bit_lengths,
74        } = range_proof_context;
75
76        // check that the source pubkey is consistent between equality and ciphertext
77        // validity proofs
78        if source_pubkey_from_equality_proof != source_pubkey_from_validity_proof {
79            return Err(TokenProofExtractionError::ElGamalPubkeyMismatch);
80        }
81
82        // check that the range proof was created for the correct set of Pedersen
83        // commitments
84        let transfer_amount_commitment_lo = transfer_amount_ciphertext_lo.extract_commitment();
85        let transfer_amount_commitment_hi = transfer_amount_ciphertext_hi.extract_commitment();
86
87        let expected_commitments = [
88            *new_source_commitment,
89            transfer_amount_commitment_lo,
90            transfer_amount_commitment_hi,
91            // we don't care about the padding commitment, so ignore it
92        ];
93
94        // range proof context always contains 8 commitments and therefore,
95        // this check will verify equality of all expected commitments
96        // (`zip` will not be short-circuited)
97        if !range_proof_commitments
98            .iter()
99            .zip(expected_commitments.iter())
100            .all(|(proof_commitment, expected_commitment)| proof_commitment == expected_commitment)
101        {
102            return Err(TokenProofExtractionError::PedersenCommitmentMismatch);
103        }
104
105        // check that the range proof was created for the correct number of bits
106        const REMAINING_BALANCE_BIT_LENGTH: u8 = 64;
107        const TRANSFER_AMOUNT_LO_BIT_LENGTH: u8 = 16;
108        const TRANSFER_AMOUNT_HI_BIT_LENGTH: u8 = 32;
109        const PADDING_BIT_LENGTH: u8 = 16;
110        let expected_bit_lengths = [
111            REMAINING_BALANCE_BIT_LENGTH,
112            TRANSFER_AMOUNT_LO_BIT_LENGTH,
113            TRANSFER_AMOUNT_HI_BIT_LENGTH,
114            PADDING_BIT_LENGTH,
115        ]
116        .iter();
117
118        // range proof context always contains 8 bit lengths and therefore,
119        // this check will verify equality of all expected bit lengths
120        // (`zip` will not be short-circuited)
121        if !range_proof_bit_lengths
122            .iter()
123            .zip(expected_bit_lengths)
124            .all(|(proof_len, expected_len)| proof_len == expected_len)
125        {
126            return Err(TokenProofExtractionError::RangeProofLengthMismatch);
127        }
128
129        let transfer_pubkeys = TransferPubkeys {
130            source: *source_pubkey_from_equality_proof,
131            destination: *destination_pubkey,
132            auditor: *auditor_pubkey,
133        };
134
135        let context_info = TransferProofContext {
136            ciphertext_lo: PodTransferAmountCiphertext(*transfer_amount_ciphertext_lo),
137            ciphertext_hi: PodTransferAmountCiphertext(*transfer_amount_ciphertext_hi),
138            transfer_pubkeys,
139            new_source_ciphertext: *new_source_ciphertext,
140        };
141
142        Ok(context_info)
143    }
144}