Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions zk-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ solana-instruction = { workspace = true, features = ["std"] }
solana-pubkey = { workspace = true, features = ["bytemuck"] }
solana-sdk-ids = { workspace = true }
thiserror = { workspace = true }
sha3 = { workspace = true }

[target.'cfg(not(target_os = "solana"))'.dependencies]
aes-gcm-siv = { workspace = true }
Expand Down
2 changes: 2 additions & 0 deletions zk-sdk/src/range_proof/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub enum RangeProofVerificationError {
MaximumGeneratorLengthExceeded,
#[error("commitments and bit lengths vectors have different lengths")]
VectorLengthMismatch,


}

#[cfg(not(target_os = "solana"))]
Expand Down
2 changes: 2 additions & 0 deletions zk-sdk/src/zk_elgamal_proof_program/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ pub enum ProofVerificationError {
IllegalCommitmentLength,
#[error("illegal amount bit length")]
IllegalAmountBitLength,
#[error("ciphertext mismatch")]
CiphertextMismatch,
}

#[derive(Clone, Debug, Eq, PartialEq)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ impl BatchedRangeProofU256Data {
amounts: Vec<u64>,
bit_lengths: Vec<usize>,
openings: Vec<&PedersenOpening>,


) -> Result<Self, ProofGenerationError> {
// Range proof on 256 bit length could potentially result in an unexpected behavior and
// therefore, restrict the bit length to be at most 128. This check is not needed for the
Expand Down Expand Up @@ -90,7 +92,10 @@ impl ZkProofData<BatchedRangeProofContext> for BatchedRangeProofU256Data {
fn verify_proof(&self) -> Result<(), ProofVerificationError> {
let (commitments, bit_lengths) = self.context.try_into()?;
let num_commitments = commitments.len();

// checks the context ciphertext is same as given on-chain
// if ciphertext != onchain_ciphertext {
// return Err(ProofVerificationError::CiphertextMismatch);
// }
// This check is unique to the 256-bit proof. For 64-bit and 128-bit proofs,
// the total sum constraint already guarantees that no single bit length can
// exceed 128. However, for a 256-bit proof, bit lengths can sum to 256
Expand All @@ -107,6 +112,7 @@ impl ZkProofData<BatchedRangeProofContext> for BatchedRangeProofU256Data {
return Err(ProofVerificationError::IllegalCommitmentLength);
}


let mut transcript = self.context_data().new_transcript();
let proof: RangeProof = self.proof.try_into()?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ const MAX_SINGLE_BIT_LENGTH: usize = 128;
pub struct BatchedRangeProofContext {
pub commitments: [PodPedersenCommitment; MAX_COMMITMENTS],
pub bit_lengths: [u8; MAX_COMMITMENTS],


}

#[allow(non_snake_case)]
Expand Down Expand Up @@ -104,6 +106,7 @@ impl BatchedRangeProofContext {
Ok(BatchedRangeProofContext {
commitments: pod_commitments,
bit_lengths: pod_bit_lengths,

})
}
}
Expand All @@ -128,6 +131,7 @@ impl TryInto<(Vec<PedersenCommitment>, Vec<usize>)> for BatchedRangeProofContext
.map(|bit_length| bit_length as usize)
.collect();


Ok((commitments, bit_lengths))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
//! The batched range proof instructions.
//!
//! A batched range proof is a cryptographic method that proves a set of committed values
//! fall within specified bit-ranges, without revealing the values themselves. It is more
//! efficient than verifying individual range proofs for each commitment.
//!
//! This module provides three instructions for batched range proofs, each corresponding to a
//! different total bit length:
//! - `VerifyBatchedRangeProof64`: For proofs where the sum of bit lengths is 64.
//! - `VerifyBatchedRangeProof128`: For proofs where the sum of bit lengths is 128.
//! - `VerifyBatchedRangeProof256`: For proofs where the sum of bit lengths is 256.
//!
//! For example, to generate a batched range proof for a sequence of commitments `[C_1, C_2, C_3]`
//! with corresponding bit-lengths `[32, 32, 64]`, one must use `VerifyBatchedRangeProof128`,
//! since the sum of bit-lengths is `32 + 32 + 64 = 128`.
//!
//! The maximum number of commitments that can be batched together is fixed at 8. Each individual
//! bit length `n_i` must be at most 128.

// pub mod batched_range_proof_u128;
// pub mod batched_range_proof_u256;
// pub mod batched_range_proof_u64;
use sha3::{Sha3_256, Digest};
pub mod range_proof;
#[allow(dead_code)]

use crate::encryption::pod::pedersen::PodPedersenCommitment;
#[cfg(not(target_os = "solana"))]
use {
crate::{
encryption::pedersen::{PedersenCommitment, PedersenOpening},
zk_elgamal_proof_program::errors::{ProofGenerationError, ProofVerificationError},
},
bytemuck::{bytes_of, Zeroable},
curve25519_dalek::traits::IsIdentity,
merlin::Transcript,
std::convert::TryInto,
};

/// The maximum number of Pedersen commitments that can be processed in a single batched range proof.
const MAX_COMMITMENTS: usize = 8;

/// A bit length in a batched range proof must be at most 128.
///
/// A 256-bit range proof on a single Pedersen commitment is meaningless and hence enforce an upper
/// bound as the largest power-of-two number less than 256.
// #[cfg(not(target_os = "solana"))]
// const MAX_SINGLE_BIT_LENGTH: usize = 128;

/// The context data needed to verify a range-proof for a Pedersen committed value.
///
/// This struct holds the public information that a batched range proof certifies. It includes the
/// Pedersen commitments and their corresponding bit lengths. This context is shared by all
/// `VerifyBatchedRangeProof{N}` instructions.
#[derive(Clone, Copy,bytemuck_derive::Pod, bytemuck_derive::Zeroable)]
#[repr(C)]
pub struct CiphertextRangeProofContext {
pub commitments: [PodPedersenCommitment; MAX_COMMITMENTS],
pub bit_lengths: [u8; MAX_COMMITMENTS],
pub ciphertext_hash: [u8;32]

}

#[allow(non_snake_case)]
#[cfg(not(target_os = "solana"))]
impl CiphertextRangeProofContext {
fn new_transcript(&self) -> Transcript {
let mut transcript = Transcript::new(b"batched-range-proof-instruction");
transcript.append_message(b"ciphertext_hash", bytes_of(&self.ciphertext_hash));
transcript.append_message(b"commitments", bytes_of(&self.commitments));
transcript.append_message(b"bit-lengths", bytes_of(&self.bit_lengths));
transcript
}

fn new(
commitments: &[&PedersenCommitment],
amounts: &[u64],
bit_lengths: &[usize],
openings: &[&PedersenOpening],
ciphertext: &Vec<u8>
) -> Result<Self, ProofGenerationError> {
// the number of commitments is capped at 8
let num_commitments = commitments.len();
if num_commitments > MAX_COMMITMENTS
|| num_commitments != amounts.len()
|| num_commitments != bit_lengths.len()
|| num_commitments != openings.len()
{
return Err(ProofGenerationError::IllegalCommitmentLength);
}

let mut pod_commitments = [PodPedersenCommitment::zeroed(); MAX_COMMITMENTS];
for (i, commitment) in commitments.iter().enumerate() {
// all-zero commitment is invalid
//
// this check only exists in the prover logic to enforce safe practice
// identity commitments are not rejected by range proof verification logic itself
if commitment.get_point().is_identity() {
return Err(ProofGenerationError::InvalidCommitment);
}
pod_commitments[i] = PodPedersenCommitment(commitment.to_bytes());
}

let mut pod_bit_lengths = [0; MAX_COMMITMENTS];
for (i, bit_length) in bit_lengths.iter().enumerate() {
pod_bit_lengths[i] = (*bit_length)
.try_into()
.map_err(|_| ProofGenerationError::IllegalAmountBitLength)?;
}
let mut hasher = Sha3_256::new();
hasher.update(ciphertext);
let ciphertext_hash: [u8; 32] = hasher.finalize().into();

Ok(CiphertextRangeProofContext {
commitments: pod_commitments,
bit_lengths: pod_bit_lengths,
ciphertext_hash
})
}
}

#[cfg(not(target_os = "solana"))]
impl TryInto<(Vec<PedersenCommitment>, Vec<usize>, [u8;32])> for CiphertextRangeProofContext {
type Error = ProofVerificationError;

fn try_into(self) -> Result<(Vec<PedersenCommitment>, Vec<usize>, [u8;32]), Self::Error> {
let commitments = self
.commitments
.into_iter()
.take_while(|commitment| *commitment != PodPedersenCommitment::zeroed())
.map(|commitment| commitment.try_into())
.collect::<Result<Vec<PedersenCommitment>, _>>()
.map_err(|_| ProofVerificationError::ProofContext)?;

let bit_lengths: Vec<_> = self
.bit_lengths
.into_iter()
.take(commitments.len())
.map(|bit_length| bit_length as usize)
.collect();


Ok((commitments, bit_lengths, self.ciphertext_hash))
}
}
Loading