From 2eb5170dac4eb899a4eab501f861db6bdad5d62a Mon Sep 17 00:00:00 2001 From: Jacob Kaufmann Date: Thu, 7 Mar 2024 11:03:38 -0700 Subject: [PATCH] feat: add KZG methods that accept raw bytes (#41) --- benches/kzg.rs | 14 ++--- src/blob.rs | 10 ++++ src/bls.rs | 6 +- src/kzg/mod.rs | 19 +++++- src/kzg/setup.rs | 149 +++++++++++++++++++++++++++++++++++++++-------- src/kzg/spec.rs | 128 +++++++++++++++++----------------------- 6 files changed, 216 insertions(+), 110 deletions(-) diff --git a/benches/kzg.rs b/benches/kzg.rs index e041753..512dcca 100644 --- a/benches/kzg.rs +++ b/benches/kzg.rs @@ -1,6 +1,6 @@ use kateth::{ blob::Blob, - kzg::{Commitment, Proof, Setup}, + kzg::{Bytes48, Setup}, }; use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, Throughput}; @@ -14,17 +14,17 @@ pub fn benchmark(c: &mut Criterion) { let max_batch_size = *batch_sizes.last().unwrap(); let mut rng = thread_rng(); - let blobs: Vec> = (0..max_batch_size) - .map(|_| Blob::random(&mut rng)) + let blobs: Vec> = (0..max_batch_size) + .map(|_| Blob::<4096>::random(&mut rng).to_bytes()) .collect(); - let commitments: Vec = blobs + let commitments: Vec = blobs .iter() - .map(|blob| kzg.blob_to_commitment(blob)) + .map(|blob| kzg.blob_to_commitment(blob).unwrap().serialize()) .collect(); - let proofs: Vec = blobs + let proofs: Vec = blobs .iter() .zip(commitments.iter()) - .map(|(blob, commitment)| kzg.blob_proof(blob, commitment)) + .map(|(blob, commitment)| kzg.blob_proof(blob, commitment).unwrap().serialize()) .collect(); c.bench_function("blob to kzg commitment", |b| { diff --git a/src/blob.rs b/src/blob.rs index adcfc6b..065ee06 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -3,6 +3,7 @@ use crate::{ kzg::{Commitment, Polynomial, Proof, Setup}, }; +#[derive(Clone, Copy, Debug)] pub enum Error { InvalidFieldElement, InvalidLen, @@ -35,6 +36,15 @@ impl Blob { Ok(Self { elements }) } + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::with_capacity(Self::BYTES); + for element in self.elements.iter() { + let element = element.to_be_bytes(); + bytes.extend_from_slice(&element); + } + bytes + } + pub(crate) fn commitment(&self, setup: &Setup) -> Commitment { let lincomb = P1::lincomb_pippenger(setup.g1_lagrange_brp.as_slice(), self.elements.as_slice()); diff --git a/src/bls.rs b/src/bls.rs index 680a70e..274d0ce 100644 --- a/src/bls.rs +++ b/src/bls.rs @@ -355,13 +355,13 @@ impl P1 { pub const BITS: usize = 384; pub const BYTES: usize = Self::BITS / 8; - pub fn deserialize(bytes: impl AsRef<[u8; Self::BYTES]>) -> Result { + pub fn deserialize(bytes: &[u8; Self::BYTES]) -> Result { let mut affine = MaybeUninit::::uninit(); let mut out = MaybeUninit::::uninit(); unsafe { // NOTE: deserialize performs a curve check but not a subgroup check. if that changes, // then we should encounter `unreachable` for `BLST_POINT_NOT_IN_GROUP` in tests. - match blst_p1_deserialize(affine.as_mut_ptr(), bytes.as_ref().as_ptr()) { + match blst_p1_deserialize(affine.as_mut_ptr(), bytes.as_ptr()) { BLST_ERROR::BLST_SUCCESS => {} BLST_ERROR::BLST_BAD_ENCODING => return Err(ECGroupError::InvalidEncoding), BLST_ERROR::BLST_POINT_NOT_ON_CURVE => return Err(ECGroupError::NotOnCurve), @@ -515,7 +515,7 @@ impl P2 { pub const BITS: usize = 768; pub const BYTES: usize = Self::BITS / 8; - pub fn deserialize(bytes: impl AsRef<[u8; Self::BYTES]>) -> Result { + pub fn deserialize(bytes: &[u8; Self::BYTES]) -> Result { let mut affine = MaybeUninit::::uninit(); let mut out = MaybeUninit::::uninit(); unsafe { diff --git a/src/kzg/mod.rs b/src/kzg/mod.rs index 600976a..d405993 100644 --- a/src/kzg/mod.rs +++ b/src/kzg/mod.rs @@ -1,4 +1,4 @@ -use crate::bls; +use crate::{blob, bls}; mod poly; mod setup; @@ -9,10 +9,27 @@ mod spec; pub type Proof = bls::P1; pub type Commitment = bls::P1; +pub type Bytes32 = [u8; 32]; +pub type Bytes48 = [u8; 48]; + +#[derive(Clone, Copy, Debug)] pub enum Error { + Blob(blob::Error), Bls(bls::Error), } +impl From for Error { + fn from(value: blob::Error) -> Self { + Self::Blob(value) + } +} + +impl From for Error { + fn from(value: bls::Error) -> Self { + Self::Bls(value) + } +} + pub(crate) use poly::Polynomial; pub use setup::Setup; diff --git a/src/kzg/setup.rs b/src/kzg/setup.rs index 0a28e44..999cecb 100644 --- a/src/kzg/setup.rs +++ b/src/kzg/setup.rs @@ -4,9 +4,9 @@ use std::{ path::Path, }; -use super::{Commitment, Polynomial, Proof}; +use super::{Bytes32, Bytes48, Commitment, Error, Polynomial, Proof}; use crate::{ - blob::Blob, + blob::{Blob, Error as BlobError}, bls::{self, ECGroupError, Error as BlsError, Fr, P1, P2}, math, }; @@ -60,7 +60,7 @@ impl Setup { // TODO: skip unnecessary allocation let point = FixedBytes::<48>::from_slice(point); let point = - P1::deserialize(point).map_err(|err| LoadSetupError::Bls(BlsError::from(err)))?; + P1::deserialize(&point).map_err(|err| LoadSetupError::Bls(BlsError::from(err)))?; g1_lagrange[i] = point; } let g1_lagrange_brp = math::bit_reversal_permutation_boxed_array(g1_lagrange.as_slice()); @@ -75,7 +75,7 @@ impl Setup { // TODO: skip unnecessary allocation let point = FixedBytes::<96>::from_slice(point); let point = - P2::deserialize(point).map_err(|err| LoadSetupError::Bls(BlsError::from(err)))?; + P2::deserialize(&point).map_err(|err| LoadSetupError::Bls(BlsError::from(err)))?; g2_monomial[i] = point; } @@ -89,7 +89,7 @@ impl Setup { }) } - pub fn verify_proof( + fn verify_proof_inner( &self, proof: &Proof, commitment: &Commitment, @@ -101,7 +101,26 @@ impl Setup { bls::verify_pairings(pairing1, pairing2) } - pub fn verify_proof_batch( + pub fn verify_proof( + &self, + proof: &Bytes48, + commitment: &Bytes48, + point: &Bytes32, + eval: &Bytes32, + ) -> Result { + let proof = Proof::deserialize(proof).map_err(|err| Error::from(BlsError::ECGroup(err)))?; + let commitment = Commitment::deserialize(commitment) + .map_err(|err| Error::from(BlsError::ECGroup(err)))?; + let point = + Fr::from_be_slice(point).map_err(|err| Error::from(BlsError::FiniteField(err)))?; + let eval = + Fr::from_be_slice(eval).map_err(|err| Error::from(BlsError::FiniteField(err)))?; + + let verified = self.verify_proof_inner(&proof, &commitment, &point, &eval); + Ok(verified) + } + + fn verify_proof_batch( &self, proofs: impl AsRef<[Proof]>, commitments: impl AsRef<[Commitment]>, @@ -149,15 +168,40 @@ impl Setup { ) } - pub fn blob_to_commitment(&self, blob: &Blob) -> Commitment { + fn blob_to_commitment_inner(&self, blob: &Blob) -> Commitment { blob.commitment(self) } - pub fn blob_proof(&self, blob: &Blob, commitment: &Commitment) -> Proof { + pub fn blob_to_commitment(&self, blob: impl AsRef<[u8]>) -> Result { + let blob = Blob::::from_slice(blob)?; + let commitment = self.blob_to_commitment_inner(&blob); + Ok(commitment) + } + + fn blob_proof_inner(&self, blob: &Blob, commitment: &Commitment) -> Proof { blob.proof(commitment, self) } - pub fn verify_blob_proof( + pub fn blob_proof(&self, blob: impl AsRef<[u8]>, commitment: &Bytes48) -> Result { + let blob: Blob = Blob::from_slice(blob).map_err(Error::from)?; + let commitment = Commitment::deserialize(commitment) + .map_err(|err| Error::from(BlsError::ECGroup(err)))?; + let proof = self.blob_proof_inner(&blob, &commitment); + Ok(proof) + } + + pub fn proof(&self, blob: impl AsRef<[u8]>, point: &Bytes32) -> Result<(Proof, Fr), Error> { + let blob: Blob = Blob::from_slice(blob).map_err(Error::from)?; + let point = + Fr::from_be_slice(point).map_err(|err| Error::from(BlsError::FiniteField(err)))?; + + let poly = Polynomial(&blob.elements); + let (eval, proof) = poly.prove(point, self); + + Ok((proof, eval)) + } + + fn verify_blob_proof_inner( &self, blob: &Blob, commitment: &Commitment, @@ -166,10 +210,25 @@ impl Setup { let poly = Polynomial(&blob.elements); let challenge = blob.challenge(commitment); let eval = poly.evaluate(challenge, self); - self.verify_proof(proof, commitment, &challenge, &eval) + self.verify_proof_inner(proof, commitment, &challenge, &eval) } - pub fn verify_blob_proof_batch( + pub fn verify_blob_proof( + &self, + blob: impl AsRef<[u8]>, + commitment: &Bytes48, + proof: &Bytes48, + ) -> Result { + let blob: Blob = Blob::from_slice(blob).map_err(Error::from)?; + let commitment = Commitment::deserialize(commitment) + .map_err(|err| Error::from(BlsError::ECGroup(err)))?; + let proof = Proof::deserialize(proof).map_err(|err| Error::from(BlsError::ECGroup(err)))?; + + let verified = self.verify_blob_proof_inner(&blob, &commitment, &proof); + Ok(verified) + } + + fn verify_blob_proof_batch_inner( &self, blobs: impl AsRef<[Blob]>, commitments: impl AsRef<[Commitment]>, @@ -192,6 +251,37 @@ impl Setup { self.verify_proof_batch(proofs, commitments, challenges, evaluations) } + + pub fn verify_blob_proof_batch( + &self, + blobs: impl AsRef<[B]>, + commitments: impl AsRef<[Bytes48]>, + proofs: impl AsRef<[Bytes48]>, + ) -> Result + where + B: AsRef<[u8]>, + { + assert_eq!(blobs.as_ref().len(), commitments.as_ref().len()); + assert_eq!(commitments.as_ref().len(), proofs.as_ref().len()); + + let blobs: Result>, _> = + blobs.as_ref().iter().map(Blob::::from_slice).collect(); + let blobs = blobs.map_err(Error::from)?; + + let commitments: Result, _> = commitments + .as_ref() + .iter() + .map(Commitment::deserialize) + .collect(); + let commitments = commitments.map_err(|err| Error::from(BlsError::ECGroup(err)))?; + + let proofs: Result, _> = + proofs.as_ref().iter().map(Proof::deserialize).collect(); + let proofs = proofs.map_err(|err| Error::from(BlsError::ECGroup(err)))?; + + let verified = self.verify_blob_proof_batch_inner(blobs, commitments, proofs); + Ok(verified) + } } #[cfg(test)] @@ -248,11 +338,13 @@ mod tests { assert!(expected.is_none()); continue; }; + let Ok((proof, y)) = setup.proof(blob, &z) else { + assert!(expected.is_none()); + continue; + }; let (expected_proof, expected_y) = expected.unwrap(); - let poly = Polynomial(&blob.elements); - let (y, proof) = poly.prove(z, &setup); - assert_eq!(proof, expected_proof); - assert_eq!(y, expected_y); + assert_eq!(proof.serialize(), expected_proof); + assert_eq!(y.to_be_bytes(), expected_y); } } @@ -270,9 +362,12 @@ mod tests { assert!(expected.is_none()); continue; }; + let Ok(proof) = setup.blob_proof(&blob, &commitment) else { + assert!(expected.is_none()); + continue; + }; let expected = expected.unwrap(); - let proof = setup.blob_proof(&blob, &commitment); - assert_eq!(proof, expected); + assert_eq!(proof.serialize(), expected); } } @@ -287,13 +382,12 @@ mod tests { let case: BlobToCommitment = serde_yaml::from_reader(reader).unwrap(); let expected = case.output(); - let Some(blob) = case.input() else { + let Ok(commitment) = setup.blob_to_commitment(case.input()) else { assert!(expected.is_none()); continue; }; let expected = expected.unwrap(); - let commitment = setup.blob_to_commitment(&blob); - assert_eq!(commitment, expected); + assert_eq!(commitment.serialize(), expected); } } @@ -312,8 +406,11 @@ mod tests { assert!(expected.is_none()); continue; }; + let Ok(verified) = setup.verify_proof(&proof, &commitment, &z, &y) else { + assert!(expected.is_none()); + continue; + }; let expected = expected.unwrap(); - let verified = setup.verify_proof(&proof, &commitment, &z, &y); assert_eq!(verified, expected); } } @@ -333,8 +430,11 @@ mod tests { assert!(expected.is_none()); continue; }; + let Ok(verified) = setup.verify_blob_proof(&blob, &commitment, &proof) else { + assert!(expected.is_none()); + continue; + }; let expected = expected.unwrap(); - let verified = setup.verify_blob_proof(&blob, &commitment, &proof); assert_eq!(verified, expected); } } @@ -354,8 +454,11 @@ mod tests { assert!(expected.is_none()); continue; }; + let Ok(verified) = setup.verify_blob_proof_batch(&blobs, &commitments, &proofs) else { + assert!(expected.is_none()); + continue; + }; let expected = expected.unwrap(); - let verified = setup.verify_blob_proof_batch(&blobs, &commitments, &proofs); assert_eq!(verified, expected); } } diff --git a/src/kzg/spec.rs b/src/kzg/spec.rs index a6ed6e4..c6473f5 100644 --- a/src/kzg/spec.rs +++ b/src/kzg/spec.rs @@ -1,25 +1,18 @@ use alloy_primitives::{Bytes, FixedBytes}; use serde::Deserialize; -use crate::{ - blob::Blob, - bls::{Fr, P1}, -}; +use crate::bls::{Fr, P1}; -use super::{Commitment, Proof}; +use super::{Bytes32, Bytes48}; -fn blob_from_bytes(bytes: &Bytes) -> Option> { - Blob::::from_slice(bytes.as_ref()).ok() -} - -fn fr_from_bytes(bytes: &Bytes) -> Option { +fn bytes32_from_bytes(bytes: &Bytes) -> Option { let bytes = FixedBytes::<{ Fr::BYTES }>::try_from(bytes.as_ref()).ok(); - bytes.and_then(Fr::from_be_bytes) + bytes.map(Into::::into) } -fn p1_from_bytes(bytes: &Bytes) -> Option { +fn bytes48_from_bytes(bytes: &Bytes) -> Option { let bytes = FixedBytes::<{ P1::BYTES }>::try_from(bytes.as_ref()).ok()?; - P1::deserialize(bytes).ok() + Some(bytes.into()) } #[derive(Deserialize)] @@ -34,12 +27,12 @@ pub struct BlobToCommitment { } impl BlobToCommitment { - pub fn input(&self) -> Option> { - blob_from_bytes(&self.input.blob) + pub fn input(&self) -> Bytes { + self.input.blob.clone() } - pub fn output(&self) -> Option { - self.output.as_ref().and_then(p1_from_bytes) + pub fn output(&self) -> Option { + self.output.as_ref().and_then(bytes48_from_bytes) } } @@ -56,20 +49,16 @@ pub struct ComputeBlobProof { } impl ComputeBlobProof { - fn blob(&self) -> Option> { - blob_from_bytes(&self.input.blob) - } - - fn commitment(&self) -> Option { - p1_from_bytes(&self.input.commitment) + fn commitment(&self) -> Option { + bytes48_from_bytes(&self.input.commitment) } - pub fn input(&self) -> Option<(Blob, Commitment)> { - self.blob().zip(self.commitment()) + pub fn input(&self) -> Option<(Bytes, Bytes48)> { + Some(self.input.blob.clone()).zip(self.commitment()) } - pub fn output(&self) -> Option { - self.output.as_ref().and_then(p1_from_bytes) + pub fn output(&self) -> Option { + self.output.as_ref().and_then(bytes48_from_bytes) } } @@ -86,22 +75,18 @@ pub struct ComputeProof { } impl ComputeProof { - fn blob(&self) -> Option> { - blob_from_bytes(&self.input.blob) + fn z(&self) -> Option { + bytes32_from_bytes(&self.input.z) } - fn z(&self) -> Option { - fr_from_bytes(&self.input.z) + pub fn input(&self) -> Option<(Bytes, Bytes32)> { + Some(self.input.blob.clone()).zip(self.z()) } - pub fn input(&self) -> Option<(Blob, Fr)> { - self.blob().zip(self.z()) - } - - pub fn output(&self) -> Option<(Proof, Fr)> { + pub fn output(&self) -> Option<(Bytes48, Bytes32)> { self.output.as_ref().and_then(|(proof, y)| { - let proof = p1_from_bytes(proof); - let y = fr_from_bytes(y); + let proof = bytes48_from_bytes(proof); + let y = bytes32_from_bytes(y); proof.zip(y) }) } @@ -121,21 +106,17 @@ pub struct VerifyBlobProof { } impl VerifyBlobProof { - fn blob(&self) -> Option> { - blob_from_bytes(&self.input.blob) - } - - fn commitment(&self) -> Option { - p1_from_bytes(&self.input.commitment) + fn commitment(&self) -> Option { + bytes48_from_bytes(&self.input.commitment) } - fn proof(&self) -> Option { - p1_from_bytes(&self.input.proof) + fn proof(&self) -> Option { + bytes48_from_bytes(&self.input.proof) } - pub fn input(&self) -> Option<(Blob, Commitment, Proof)> { - match (self.blob(), self.commitment(), self.proof()) { - (Some(blob), Some(commitment), Some(proof)) => Some((blob, commitment, proof)), + pub fn input(&self) -> Option<(Bytes, Bytes48, Bytes48)> { + match (self.commitment(), self.proof()) { + (Some(commitment), Some(proof)) => Some((self.input.blob.clone(), commitment, proof)), _ => None, } } @@ -160,23 +141,23 @@ pub struct VerifyProof { } impl VerifyProof { - fn commitment(&self) -> Option { - p1_from_bytes(&self.input.commitment) + fn commitment(&self) -> Option { + bytes48_from_bytes(&self.input.commitment) } - fn z(&self) -> Option { - fr_from_bytes(&self.input.z) + fn z(&self) -> Option { + bytes32_from_bytes(&self.input.z) } - fn y(&self) -> Option { - fr_from_bytes(&self.input.y) + fn y(&self) -> Option { + bytes32_from_bytes(&self.input.y) } - fn proof(&self) -> Option { - p1_from_bytes(&self.input.proof) + fn proof(&self) -> Option { + bytes48_from_bytes(&self.input.proof) } - pub fn input(&self) -> Option<(Commitment, Fr, Fr, Proof)> { + pub fn input(&self) -> Option<(Bytes48, Bytes32, Bytes32, Bytes48)> { match (self.commitment(), self.z(), self.y(), self.proof()) { (Some(commitment), Some(z), Some(y), Some(proof)) => Some((commitment, z, y, proof)), _ => None, @@ -202,36 +183,31 @@ pub struct VerifyBlobProofBatch { } impl VerifyBlobProofBatch { - fn blobs(&self) -> Option>> { - let blobs: Vec> = self + fn commitments(&self) -> Option> { + let commitments: Vec = self .input - .blobs + .commitments .iter() - .filter_map(blob_from_bytes) + .filter_map(bytes48_from_bytes) .collect(); - (blobs.len() == self.input.blobs.len()).then_some(blobs) + (commitments.len() == self.input.commitments.len()).then_some(commitments) } - fn commitments(&self) -> Option> { - let commitments: Vec = self + fn proofs(&self) -> Option> { + let proofs: Vec = self .input - .commitments + .proofs .iter() - .filter_map(p1_from_bytes) + .filter_map(bytes48_from_bytes) .collect(); - (commitments.len() == self.input.commitments.len()).then_some(commitments) - } - - fn proofs(&self) -> Option> { - let proofs: Vec = self.input.proofs.iter().filter_map(p1_from_bytes).collect(); (proofs.len() == self.input.proofs.len()).then_some(proofs) } - pub fn input(&self) -> Option<(Vec>, Vec, Vec)> { - match (self.blobs(), self.commitments(), self.proofs()) { - (Some(blobs), Some(commitments), Some(proofs)) => (blobs.len() == commitments.len() + pub fn input(&self) -> Option<(Vec, Vec, Vec)> { + match (self.commitments(), self.proofs()) { + (Some(commitments), Some(proofs)) => (self.input.blobs.len() == commitments.len() && commitments.len() == proofs.len()) - .then_some((blobs, commitments, proofs)), + .then_some((self.input.blobs.clone(), commitments, proofs)), _ => None, } }