Skip to main content

gorb_ct_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        ];
92
93        if !range_proof_commitments
94            .iter()
95            .zip(expected_commitments.iter())
96            .all(|(proof_commitment, expected_commitment)| proof_commitment == expected_commitment)
97        {
98            return Err(TokenProofExtractionError::PedersenCommitmentMismatch);
99        }
100
101        // check that the range proof was created for the correct number of bits
102        const REMAINING_BALANCE_BIT_LENGTH: u8 = 64;
103        const TRANSFER_AMOUNT_LO_BIT_LENGTH: u8 = 16;
104        const TRANSFER_AMOUNT_HI_BIT_LENGTH: u8 = 32;
105        const PADDING_BIT_LENGTH: u8 = 16;
106        let expected_bit_lengths = [
107            REMAINING_BALANCE_BIT_LENGTH,
108            TRANSFER_AMOUNT_LO_BIT_LENGTH,
109            TRANSFER_AMOUNT_HI_BIT_LENGTH,
110            PADDING_BIT_LENGTH,
111        ]
112        .iter();
113
114        if !range_proof_bit_lengths
115            .iter()
116            .zip(expected_bit_lengths)
117            .all(|(proof_len, expected_len)| proof_len == expected_len)
118        {
119            return Err(TokenProofExtractionError::RangeProofLengthMismatch);
120        }
121
122        let transfer_pubkeys = TransferPubkeys {
123            source: *source_pubkey_from_equality_proof,
124            destination: *destination_pubkey,
125            auditor: *auditor_pubkey,
126        };
127
128        let context_info = TransferProofContext {
129            ciphertext_lo: PodTransferAmountCiphertext(*transfer_amount_ciphertext_lo),
130            ciphertext_hi: PodTransferAmountCiphertext(*transfer_amount_ciphertext_hi),
131            transfer_pubkeys,
132            new_source_ciphertext: *new_source_ciphertext,
133        };
134
135        Ok(context_info)
136    }
137}