solana_zk_token_sdk/instruction/batched_range_proof/
mod.rs

1//! The batched range proof instructions.
2//!
3//! A batched range proof is defined with respect to a sequence of commitments `[C_1, ..., C_N]`
4//! and bit-lengths `[n_1, ..., n_N]`. It certifies that each `C_i` is a commitment to a number of
5//! bit-length `n_i`.
6//!
7//! There are three batched range proof instructions: `VerifyBatchedRangeProof64`,
8//! `VerifyBatchedRangeProof128`, and `VerifyBatchedRangeProof256`. The value `N` in
9//! `VerifyBatchedRangeProof{N}` specifies the sum of the bit-lengths that the proof is certifying
10//! for a sequence of commitments.
11//!
12//! For example to generate a batched range proof on a sequence of commitments `[C_1, C_2, C_3]` on
13//! a sequence of bit-lengths `[32, 32, 64]`, one must use `VerifyBatchedRangeProof128` as 128 is
14//! the sum of all bit-lengths.
15//!
16//! The maximum number of commitments is fixed at 8. Each bit-length in `[n_1, ..., n_N]` must be a
17//! power-of-two positive integer less than 128.
18
19pub mod batched_range_proof_u128;
20pub mod batched_range_proof_u256;
21pub mod batched_range_proof_u64;
22
23use crate::zk_token_elgamal::pod;
24#[cfg(not(target_os = "solana"))]
25use {
26    crate::{
27        encryption::pedersen::{PedersenCommitment, PedersenOpening},
28        errors::{ProofGenerationError, ProofVerificationError},
29    },
30    bytemuck::{bytes_of, Zeroable},
31    curve25519_dalek::traits::IsIdentity,
32    merlin::Transcript,
33    std::convert::TryInto,
34};
35
36const MAX_COMMITMENTS: usize = 8;
37
38/// A bit length in a batched range proof must be at most 128.
39///
40/// A 256-bit range proof on a single Pedersen commitment is meaningless and hence enforce an upper
41/// bound as the largest power-of-two number less than 256.
42#[cfg(not(target_os = "solana"))]
43const MAX_SINGLE_BIT_LENGTH: usize = 128;
44
45/// The context data needed to verify a range-proof for a Pedersen committed value.
46///
47/// The context data is shared by all `VerifyBatchedRangeProof{N}` instructions.
48#[derive(Clone, Copy, bytemuck_derive::Pod, bytemuck_derive::Zeroable)]
49#[repr(C)]
50pub struct BatchedRangeProofContext {
51    pub commitments: [pod::PedersenCommitment; MAX_COMMITMENTS],
52    pub bit_lengths: [u8; MAX_COMMITMENTS],
53}
54
55#[allow(non_snake_case)]
56#[cfg(not(target_os = "solana"))]
57impl BatchedRangeProofContext {
58    fn new_transcript(&self) -> Transcript {
59        let mut transcript = Transcript::new(b"BatchedRangeProof");
60        transcript.append_message(b"commitments", bytes_of(&self.commitments));
61        transcript.append_message(b"bit-lengths", bytes_of(&self.bit_lengths));
62        transcript
63    }
64
65    fn new(
66        commitments: &[&PedersenCommitment],
67        amounts: &[u64],
68        bit_lengths: &[usize],
69        openings: &[&PedersenOpening],
70    ) -> Result<Self, ProofGenerationError> {
71        // the number of commitments is capped at 8
72        let num_commitments = commitments.len();
73        if num_commitments > MAX_COMMITMENTS
74            || num_commitments != amounts.len()
75            || num_commitments != bit_lengths.len()
76            || num_commitments != openings.len()
77        {
78            return Err(ProofGenerationError::IllegalCommitmentLength);
79        }
80
81        let mut pod_commitments = [pod::PedersenCommitment::zeroed(); MAX_COMMITMENTS];
82        for (i, commitment) in commitments.iter().enumerate() {
83            // all-zero commitment is invalid
84            if commitment.get_point().is_identity() {
85                return Err(ProofGenerationError::InvalidCommitment);
86            }
87            pod_commitments[i] = pod::PedersenCommitment(commitment.to_bytes());
88        }
89
90        let mut pod_bit_lengths = [0; MAX_COMMITMENTS];
91        for (i, bit_length) in bit_lengths.iter().enumerate() {
92            pod_bit_lengths[i] = (*bit_length)
93                .try_into()
94                .map_err(|_| ProofGenerationError::IllegalAmountBitLength)?;
95        }
96
97        Ok(BatchedRangeProofContext {
98            commitments: pod_commitments,
99            bit_lengths: pod_bit_lengths,
100        })
101    }
102}
103
104#[cfg(not(target_os = "solana"))]
105impl TryInto<(Vec<PedersenCommitment>, Vec<usize>)> for BatchedRangeProofContext {
106    type Error = ProofVerificationError;
107
108    fn try_into(self) -> Result<(Vec<PedersenCommitment>, Vec<usize>), Self::Error> {
109        let commitments = self
110            .commitments
111            .into_iter()
112            .take_while(|commitment| *commitment != pod::PedersenCommitment::zeroed())
113            .map(|commitment| commitment.try_into())
114            .collect::<Result<Vec<PedersenCommitment>, _>>()
115            .map_err(|_| ProofVerificationError::ProofContext)?;
116
117        let bit_lengths: Vec<_> = self
118            .bit_lengths
119            .into_iter()
120            .take(commitments.len())
121            .map(|bit_length| bit_length as usize)
122            .collect();
123
124        Ok((commitments, bit_lengths))
125    }
126}