use std::iter::successors;
use bls12_381::{reduce_bytes_to_scalar_bias, traits::*, G1Point, Scalar};
use itertools::{chain, izip, Itertools};
use kzg_single_open::bitreverse_slice;
use polynomial::{domain::Domain, poly_coeff::PolyCoeff};
use serialization::{
deserialize_blob_to_scalars, deserialize_bytes_to_scalar, deserialize_compressed_g1,
types::{Bytes48Ref, KZGCommitment, SerializedScalar},
};
use sha2::{Digest, Sha256};
use crate::{BlobRef, Context, Error, VerifierError};
impl Context {
pub fn verify_kzg_proof(
&self,
commitment: Bytes48Ref,
z: SerializedScalar,
y: SerializedScalar,
proof: Bytes48Ref,
) -> Result<(), Error> {
let commitment = deserialize_compressed_g1(commitment)?;
let proof = deserialize_compressed_g1(proof)?;
let z = deserialize_bytes_to_scalar(&z)?;
let y = deserialize_bytes_to_scalar(&y)?;
self.verifier.verify_kzg_proof(commitment, z, y, proof)?;
Ok(())
}
pub fn verify_blob_kzg_proof(
&self,
blob: BlobRef,
commitment: Bytes48Ref,
proof: Bytes48Ref,
) -> Result<(), Error> {
let blob_scalar = deserialize_blob_to_scalars(blob)?;
let commitment_g1 = deserialize_compressed_g1(commitment)?;
let proof = deserialize_compressed_g1(proof)?;
let z = compute_fiat_shamir_challenge(blob, *commitment);
let y = blob_scalar_to_polynomial(&self.verifier.domain, &blob_scalar).eval(&z);
self.verifier.verify_kzg_proof(commitment_g1, z, y, proof)?;
Ok(())
}
#[allow(clippy::needless_pass_by_value)]
pub fn verify_blob_kzg_proof_batch(
&self,
blobs: Vec<BlobRef>,
commitments: Vec<Bytes48Ref>,
proofs: Vec<Bytes48Ref>,
) -> Result<(), Error> {
let same_length = (blobs.len() == commitments.len()) & (blobs.len() == proofs.len());
if !same_length {
return Err(VerifierError::BatchVerificationInputsMustHaveSameLength {
blobs_len: blobs.len(),
commitments_len: commitments.len(),
proofs_len: proofs.len(),
}
.into());
}
let blobs_scalar = blobs
.iter()
.map(|blob| deserialize_blob_to_scalars(*blob))
.try_collect::<_, Vec<_>, _>()?;
let commitments_g1 = commitments
.iter()
.map(|commitment| deserialize_compressed_g1(*commitment))
.try_collect::<_, Vec<_>, _>()?;
let proofs_g1 = proofs
.iter()
.map(|proof| deserialize_compressed_g1(*proof))
.try_collect::<_, Vec<_>, _>()?;
let (zs, ys) = izip!(&blobs, &blobs_scalar, &commitments)
.map(|(blob, blob_scalar, commitment)| {
let z = compute_fiat_shamir_challenge(blob, **commitment);
let y = blob_scalar_to_polynomial(&self.verifier.domain, blob_scalar).eval(&z);
(z, y)
})
.unzip::<_, _, Vec<_>, Vec<_>>();
let domain_size = self.verifier.domain.roots.len();
let r_powers = compute_r_powers_for_verify_kzg_proof_batch(
domain_size,
&commitments,
&zs,
&ys,
&proofs,
);
self.verifier
.verify_kzg_proof_batch(&commitments_g1, &zs, &ys, &proofs_g1, &r_powers)?;
Ok(())
}
}
pub(crate) fn blob_scalar_to_polynomial(domain: &Domain, blob_scalar: &[Scalar]) -> PolyCoeff {
let mut polynomial = blob_scalar.to_vec();
bitreverse_slice(&mut polynomial);
domain.ifft_scalars(polynomial)
}
pub(crate) fn compute_fiat_shamir_challenge(blob: BlobRef, commitment: KZGCommitment) -> Scalar {
const DOMAIN_SEP: &str = "FSBLOBVERIFY_V1_";
let bytes_per_commitment = G1Point::compressed_size();
let bytes_per_blob = blob.len();
let bytes_per_field_element = Scalar::NUM_BITS.div_ceil(8) as usize;
let field_elements_per_blob = blob.len() / bytes_per_field_element;
let hash_input_size = DOMAIN_SEP.len()
+ 16 + bytes_per_blob + bytes_per_commitment ;
let mut hash_input: Vec<u8> = Vec::with_capacity(hash_input_size);
hash_input.extend(DOMAIN_SEP.as_bytes());
hash_input.extend((field_elements_per_blob as u128).to_be_bytes());
hash_input.extend(blob);
hash_input.extend(commitment);
assert_eq!(hash_input.len(), hash_input_size);
let mut hasher = Sha256::new();
hasher.update(hash_input);
let result: [u8; 32] = hasher.finalize().into();
reduce_bytes_to_scalar_bias(result)
}
pub fn compute_r_powers_for_verify_kzg_proof_batch(
domain_size: usize,
commitments: &[Bytes48Ref],
zs: &[Scalar],
ys: &[Scalar],
proofs: &[Bytes48Ref],
) -> Vec<Scalar> {
const DOMAIN_SEP: &str = "RCKZGBATCH___V1_";
let bytes_per_commitment = G1Point::compressed_size();
let bytes_per_field_element = Scalar::NUM_BITS.div_ceil(8) as usize;
let n = commitments.len();
let hash_input_size = DOMAIN_SEP.len()
+ 8 + 8 + n * (
bytes_per_commitment + bytes_per_field_element + bytes_per_field_element + bytes_per_commitment );
let mut hash_input: Vec<u8> = Vec::with_capacity(hash_input_size);
hash_input.extend(DOMAIN_SEP.as_bytes());
hash_input.extend((domain_size as u64).to_be_bytes());
hash_input.extend((n as u64).to_be_bytes());
izip!(commitments, zs, ys, proofs).for_each(|(commitment, z, y, proof)| {
hash_input.extend(chain![
**commitment,
z.to_bytes_be(),
y.to_bytes_be(),
**proof
]);
});
assert_eq!(hash_input.len(), hash_input_size);
let mut hasher = Sha256::new();
hasher.update(hash_input);
let result: [u8; 32] = hasher.finalize().into();
let r = reduce_bytes_to_scalar_bias(result);
successors(Some(Scalar::ONE), |power| Some(power * r))
.take(n)
.collect()
}