solana_zk_token_sdk/instruction/
fee_sigma.rs

1//! The fee sigma proof instruction.
2//!
3//! A fee sigma proof certifies that a Pedersen commitment to a transfer fee for SPL Token 2022 is
4//! well-formed.
5//!
6//! A formal documentation of how transfer fees and fee sigma proof are computed can be found in
7//! the [`ZK Token proof`] program documentation.
8//!
9//! [`ZK Token proof`]: https://docs.solanalabs.com/runtime/zk-token-proof
10
11#[cfg(not(target_os = "solana"))]
12use {
13    crate::{
14        encryption::pedersen::{PedersenCommitment, PedersenOpening},
15        errors::{ProofGenerationError, ProofVerificationError},
16        sigma_proofs::fee_proof::FeeSigmaProof,
17        transcript::TranscriptProtocol,
18    },
19    merlin::Transcript,
20    std::convert::TryInto,
21};
22use {
23    crate::{
24        instruction::{ProofType, ZkProofData},
25        zk_token_elgamal::pod,
26    },
27    bytemuck_derive::{Pod, Zeroable},
28};
29
30/// The instruction data that is needed for the `ProofInstruction::VerifyFeeSigma` instruction.
31///
32/// It includes the cryptographic proof as well as the context data information needed to verify
33/// the proof.
34#[derive(Clone, Copy, Pod, Zeroable)]
35#[repr(C)]
36pub struct FeeSigmaProofData {
37    pub context: FeeSigmaProofContext,
38
39    pub proof: pod::FeeSigmaProof,
40}
41
42/// The context data needed to verify a pubkey validity proof.
43///
44/// We refer to [`ZK Token proof`] for the formal details on how the fee sigma proof is computed.
45///
46/// [`ZK Token proof`]: https://docs.solanalabs.com/runtime/zk-token-proof
47#[derive(Clone, Copy, Pod, Zeroable)]
48#[repr(C)]
49pub struct FeeSigmaProofContext {
50    /// The Pedersen commitment to the transfer fee
51    pub fee_commitment: pod::PedersenCommitment,
52
53    /// The Pedersen commitment to the real delta fee.
54    pub delta_commitment: pod::PedersenCommitment,
55
56    /// The Pedersen commitment to the claimed delta fee.
57    pub claimed_commitment: pod::PedersenCommitment,
58
59    /// The maximum cap for a transfer fee
60    pub max_fee: pod::PodU64,
61}
62
63#[cfg(not(target_os = "solana"))]
64impl FeeSigmaProofData {
65    pub fn new(
66        fee_commitment: &PedersenCommitment,
67        delta_commitment: &PedersenCommitment,
68        claimed_commitment: &PedersenCommitment,
69        fee_opening: &PedersenOpening,
70        delta_opening: &PedersenOpening,
71        claimed_opening: &PedersenOpening,
72        fee_amount: u64,
73        delta_fee: u64,
74        max_fee: u64,
75    ) -> Result<Self, ProofGenerationError> {
76        let pod_fee_commitment = pod::PedersenCommitment(fee_commitment.to_bytes());
77        let pod_delta_commitment = pod::PedersenCommitment(delta_commitment.to_bytes());
78        let pod_claimed_commitment = pod::PedersenCommitment(claimed_commitment.to_bytes());
79        let pod_max_fee = max_fee.into();
80
81        let context = FeeSigmaProofContext {
82            fee_commitment: pod_fee_commitment,
83            delta_commitment: pod_delta_commitment,
84            claimed_commitment: pod_claimed_commitment,
85            max_fee: pod_max_fee,
86        };
87
88        let mut transcript = context.new_transcript();
89
90        let proof = FeeSigmaProof::new(
91            (fee_amount, fee_commitment, fee_opening),
92            (delta_fee, delta_commitment, delta_opening),
93            (claimed_commitment, claimed_opening),
94            max_fee,
95            &mut transcript,
96        )
97        .into();
98
99        Ok(Self { context, proof })
100    }
101}
102
103impl ZkProofData<FeeSigmaProofContext> for FeeSigmaProofData {
104    const PROOF_TYPE: ProofType = ProofType::FeeSigma;
105
106    fn context_data(&self) -> &FeeSigmaProofContext {
107        &self.context
108    }
109
110    #[cfg(not(target_os = "solana"))]
111    fn verify_proof(&self) -> Result<(), ProofVerificationError> {
112        let mut transcript = self.context.new_transcript();
113
114        let fee_commitment = self.context.fee_commitment.try_into()?;
115        let delta_commitment = self.context.delta_commitment.try_into()?;
116        let claimed_commitment = self.context.claimed_commitment.try_into()?;
117        let max_fee = self.context.max_fee.into();
118        let proof: FeeSigmaProof = self.proof.try_into()?;
119
120        proof
121            .verify(
122                &fee_commitment,
123                &delta_commitment,
124                &claimed_commitment,
125                max_fee,
126                &mut transcript,
127            )
128            .map_err(|e| e.into())
129    }
130}
131
132#[cfg(not(target_os = "solana"))]
133impl FeeSigmaProofContext {
134    fn new_transcript(&self) -> Transcript {
135        let mut transcript = Transcript::new(b"FeeSigmaProof");
136        transcript.append_commitment(b"fee-commitment", &self.fee_commitment);
137        transcript.append_commitment(b"delta-commitment", &self.fee_commitment);
138        transcript.append_commitment(b"claimed-commitment", &self.fee_commitment);
139        transcript.append_u64(b"max-fee", self.max_fee.into());
140        transcript
141    }
142}
143
144#[cfg(test)]
145mod test {
146    use {super::*, crate::encryption::pedersen::Pedersen, curve25519_dalek::scalar::Scalar};
147
148    #[test]
149    fn test_fee_sigma_instruction_correctness() {
150        // transfer fee amount is below max fee
151        let transfer_amount: u64 = 1;
152        let max_fee: u64 = 3;
153
154        let fee_rate: u16 = 400;
155        let fee_amount: u64 = 1;
156        let delta_fee: u64 = 9600;
157
158        let (transfer_commitment, transfer_opening) = Pedersen::new(transfer_amount);
159        let (fee_commitment, fee_opening) = Pedersen::new(fee_amount);
160
161        let scalar_rate = Scalar::from(fee_rate);
162        let delta_commitment =
163            &fee_commitment * Scalar::from(10_000_u64) - &transfer_commitment * &scalar_rate;
164        let delta_opening =
165            &fee_opening * &Scalar::from(10_000_u64) - &transfer_opening * &scalar_rate;
166
167        let (claimed_commitment, claimed_opening) = Pedersen::new(delta_fee);
168
169        let proof_data = FeeSigmaProofData::new(
170            &fee_commitment,
171            &delta_commitment,
172            &claimed_commitment,
173            &fee_opening,
174            &delta_opening,
175            &claimed_opening,
176            fee_amount,
177            delta_fee,
178            max_fee,
179        )
180        .unwrap();
181
182        assert!(proof_data.verify_proof().is_ok());
183
184        // transfer fee amount is equal to max fee
185        let transfer_amount: u64 = 55;
186        let max_fee: u64 = 3;
187
188        let fee_rate: u16 = 555;
189        let fee_amount: u64 = 4;
190
191        let (transfer_commitment, transfer_opening) = Pedersen::new(transfer_amount);
192        let (fee_commitment, fee_opening) = Pedersen::new(max_fee);
193
194        let scalar_rate = Scalar::from(fee_rate);
195        let delta_commitment =
196            &fee_commitment * &Scalar::from(10000_u64) - &transfer_commitment * &scalar_rate;
197        let delta_opening =
198            &fee_opening * &Scalar::from(10000_u64) - &transfer_opening * &scalar_rate;
199
200        let (claimed_commitment, claimed_opening) = Pedersen::new(0_u64);
201
202        let proof_data = FeeSigmaProofData::new(
203            &fee_commitment,
204            &delta_commitment,
205            &claimed_commitment,
206            &fee_opening,
207            &delta_opening,
208            &claimed_opening,
209            fee_amount,
210            delta_fee,
211            max_fee,
212        )
213        .unwrap();
214
215        assert!(proof_data.verify_proof().is_ok());
216    }
217}