solana_zk_token_sdk/instruction/
range_proof.rs

1//! The range proof instruction.
2//!
3//! A range proof certifies that a committed value in a Pedersen commitment is a number from a
4//! certain range. Currently, only 64-bit range proof `VerifyRangeProofU64` is supported in the
5//! proof program. It certifies that a committed number is an unsigned 64-bit number.
6
7#[cfg(not(target_os = "solana"))]
8use {
9    crate::{
10        encryption::pedersen::{PedersenCommitment, PedersenOpening},
11        errors::{ProofGenerationError, ProofVerificationError},
12        range_proof::RangeProof,
13        transcript::TranscriptProtocol,
14    },
15    merlin::Transcript,
16    std::convert::TryInto,
17};
18use {
19    crate::{
20        instruction::{ProofType, ZkProofData},
21        zk_token_elgamal::pod,
22    },
23    bytemuck_derive::{Pod, Zeroable},
24};
25
26/// The context data needed to verify a range-proof for a committed value in a Pedersen commitment.
27#[derive(Clone, Copy, Pod, Zeroable)]
28#[repr(C)]
29pub struct RangeProofContext {
30    pub commitment: pod::PedersenCommitment, // 32 bytes
31}
32
33/// The instruction data that is needed for the `ProofInstruction::VerifyRangeProofU64` instruction.
34///
35/// It includes the cryptographic proof as well as the context data information needed to verify
36/// the proof.
37#[derive(Clone, Copy, Pod, Zeroable)]
38#[repr(C)]
39pub struct RangeProofU64Data {
40    /// The context data for a range proof
41    pub context: RangeProofContext,
42
43    /// The proof that a committed value in a Pedersen commitment is a 64-bit value
44    pub proof: pod::RangeProofU64,
45}
46
47#[cfg(not(target_os = "solana"))]
48impl RangeProofU64Data {
49    pub fn new(
50        commitment: &PedersenCommitment,
51        amount: u64,
52        opening: &PedersenOpening,
53    ) -> Result<Self, ProofGenerationError> {
54        let pod_commitment = pod::PedersenCommitment(commitment.to_bytes());
55
56        let context = RangeProofContext {
57            commitment: pod_commitment,
58        };
59
60        let mut transcript = context.new_transcript();
61
62        // `u64::BITS` is 64, which fits in a single byte and should not overflow to `usize` for an
63        // overwhelming number of platforms. However, to be extra cautious, use `try_from` and
64        // `unwrap` here. A simple case `u64::BITS as usize` can silently overflow.
65        let bit_size = usize::try_from(u64::BITS).unwrap();
66
67        let proof = RangeProof::new(vec![amount], vec![bit_size], vec![opening], &mut transcript)?
68            .try_into()
69            .map_err(|_| ProofGenerationError::ProofLength)?;
70
71        Ok(Self { context, proof })
72    }
73}
74
75impl ZkProofData<RangeProofContext> for RangeProofU64Data {
76    const PROOF_TYPE: ProofType = ProofType::RangeProofU64;
77
78    fn context_data(&self) -> &RangeProofContext {
79        &self.context
80    }
81
82    #[cfg(not(target_os = "solana"))]
83    fn verify_proof(&self) -> Result<(), ProofVerificationError> {
84        let mut transcript = self.context_data().new_transcript();
85        let commitment = self.context.commitment.try_into()?;
86        let proof: RangeProof = self.proof.try_into()?;
87
88        let bit_size = usize::try_from(u64::BITS).unwrap();
89        proof
90            .verify(vec![&commitment], vec![bit_size], &mut transcript)
91            .map_err(|e| e.into())
92    }
93}
94
95#[allow(non_snake_case)]
96#[cfg(not(target_os = "solana"))]
97impl RangeProofContext {
98    fn new_transcript(&self) -> Transcript {
99        let mut transcript = Transcript::new(b"RangeProof");
100        transcript.append_commitment(b"commitment", &self.commitment);
101        transcript
102    }
103}
104
105#[cfg(test)]
106mod test {
107    use {super::*, crate::encryption::pedersen::Pedersen};
108
109    #[test]
110    fn test_range_proof_64_instruction_correctness() {
111        let amount = u64::MAX;
112        let (commitment, opening) = Pedersen::new(amount);
113
114        let proof_data = RangeProofU64Data::new(&commitment, amount, &opening).unwrap();
115        assert!(proof_data.verify_proof().is_ok());
116    }
117}