Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: add module for spec test formats #43

Merged
merged 2 commits into from
Feb 22, 2024
Merged
Changes from 1 commit
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
Next Next commit
refactor: add module for spec tests formats
jacobkaufmann committed Feb 22, 2024
commit 02a5728860c274e61ae6b13f9964fb0001f16ba2
3 changes: 3 additions & 0 deletions src/kzg/mod.rs
Original file line number Diff line number Diff line change
@@ -3,6 +3,9 @@ use crate::bls;
mod poly;
mod setup;

#[cfg(test)]
mod spec;

pub type Proof = bls::P1;
pub type Commitment = bls::P1;

398 changes: 69 additions & 329 deletions src/kzg/setup.rs
Original file line number Diff line number Diff line change
@@ -198,7 +198,10 @@ impl<const G1: usize, const G2: usize> Setup<G1, G2> {
mod tests {
use super::*;

use crate::blob::Blob;
use crate::kzg::spec::{
BlobToCommitment, ComputeBlobProof, ComputeProof, VerifyBlobProof, VerifyBlobProofBatch,
VerifyProof,
};

use std::{
fs::{self, File},
@@ -207,241 +210,9 @@ mod tests {
sync::Arc,
};

use crate::bls::P1;

use alloy_primitives::{Bytes, FixedBytes};

const FIELD_ELEMENTS_PER_BLOB: usize = 4096;
const SETUP_G2_LEN: usize = 65;

#[derive(serde::Deserialize, serde::Serialize)]
struct ComputeKzgProofInputUnchecked {
pub blob: Bytes,
pub z: Bytes,
}

#[derive(serde::Deserialize, serde::Serialize)]
struct ComputeKzgProofUnchecked {
input: ComputeKzgProofInputUnchecked,
output: Option<(FixedBytes<{ Proof::BYTES }>, FixedBytes<{ Fr::BYTES }>)>,
}

struct ComputeKzgProofInput {
pub blob: Blob<FIELD_ELEMENTS_PER_BLOB>,
pub z: Fr,
}

impl ComputeKzgProofInput {
pub fn from_unchecked(unchecked: ComputeKzgProofInputUnchecked) -> Result<Self, ()> {
let blob = Blob::from_slice(unchecked.blob).map_err(|_| ())?;
match Fr::from_be_slice(unchecked.z) {
Ok(z) => Ok(ComputeKzgProofInput { blob, z }),
Err(_) => Err(()),
}
}
}

#[derive(serde::Deserialize, serde::Serialize)]
struct BlobToCommitmentInputUnchecked {
pub blob: Bytes,
}

#[derive(serde::Deserialize, serde::Serialize)]
struct BlobToCommitmentUnchecked {
input: BlobToCommitmentInputUnchecked,
output: Option<FixedBytes<{ Commitment::BYTES }>>,
}

struct BlobToCommitmentInput {
pub blob: Blob<FIELD_ELEMENTS_PER_BLOB>,
}

impl BlobToCommitmentInput {
pub fn from_unchecked(unchecked: BlobToCommitmentInputUnchecked) -> Result<Self, ()> {
let blob = Blob::from_slice(unchecked.blob).map_err(|_| ())?;
Ok(Self { blob })
}
}

#[derive(serde::Deserialize, serde::Serialize)]
struct ComputeBlobKzgProofInputUnchecked {
blob: Bytes,
commitment: Bytes,
}

#[derive(serde::Deserialize, serde::Serialize)]
struct ComputeBlobKzgProofUnchecked {
input: ComputeBlobKzgProofInputUnchecked,
output: Option<FixedBytes<{ Proof::BYTES }>>,
}

struct ComputeBlobKzgProofInput {
blob: Blob<FIELD_ELEMENTS_PER_BLOB>,
commitment: Commitment,
}

impl ComputeBlobKzgProofInput {
pub fn from_unchecked(unchecked: ComputeBlobKzgProofInputUnchecked) -> Result<Self, ()> {
let blob = Blob::from_slice(unchecked.blob).map_err(|_| ())?;
if unchecked.commitment.len() != Commitment::BYTES {
return Err(());
}
let commitment = FixedBytes::<{ Commitment::BYTES }>::from_slice(&unchecked.commitment);
let commitment = Commitment::deserialize(commitment).map_err(|_| ())?;
Ok(Self { blob, commitment })
}
}

#[derive(serde::Deserialize, serde::Serialize)]
struct VerifyKzgProofUnchecked {
input: VerifyKzgProofInputUnchecked,
output: Option<bool>,
}

#[derive(serde::Deserialize, serde::Serialize)]
struct VerifyKzgProofInputUnchecked {
pub commitment: Bytes,
pub z: Bytes,
pub y: Bytes,
pub proof: Bytes,
}

struct VerifyKzgProofInput {
pub commitment: Commitment,
pub z: Fr,
pub y: Fr,
pub proof: Proof,
}

impl VerifyKzgProofInput {
pub fn from_unchecked(unchecked: VerifyKzgProofInputUnchecked) -> Result<Self, ()> {
if unchecked.commitment.len() != Commitment::BYTES {
return Err(());
}
let commitment = FixedBytes::<{ Commitment::BYTES }>::from_slice(&unchecked.commitment);
let commitment = Commitment::deserialize(commitment).map_err(|_| ())?;

let z = Fr::from_be_slice(unchecked.z).map_err(|_| ())?;
let y = Fr::from_be_slice(unchecked.y).map_err(|_| ())?;

if unchecked.proof.len() != Proof::BYTES {
return Err(());
}
let proof = FixedBytes::<{ Proof::BYTES }>::from_slice(&unchecked.proof);
let proof = Proof::deserialize(proof).map_err(|_| ())?;

Ok(Self {
commitment,
z,
y,
proof,
})
}
}

#[derive(serde::Deserialize, serde::Serialize)]
struct VerifyBlobKzgProofUnchecked {
input: VerifyBlobKzgProofInputUnchecked,
output: Option<bool>,
}

#[derive(serde::Deserialize, serde::Serialize)]
struct VerifyBlobKzgProofInputUnchecked {
pub blob: Bytes,
pub commitment: Bytes,
pub proof: Bytes,
}

struct VerifyBlobKzgProofInput {
pub blob: Blob<FIELD_ELEMENTS_PER_BLOB>,
pub commitment: Commitment,
pub proof: Proof,
}

impl VerifyBlobKzgProofInput {
pub fn from_unchecked(unchecked: VerifyBlobKzgProofInputUnchecked) -> Result<Self, ()> {
let blob = Blob::from_slice(unchecked.blob).map_err(|_| ())?;
if unchecked.commitment.len() != Commitment::BYTES {
return Err(());
}
let commitment = FixedBytes::<{ Commitment::BYTES }>::from_slice(&unchecked.commitment);
let commitment = Commitment::deserialize(commitment).map_err(|_| ())?;
if unchecked.proof.len() != Proof::BYTES {
return Err(());
}
let proof = FixedBytes::<{ Proof::BYTES }>::from_slice(&unchecked.proof);
let proof = Proof::deserialize(proof).map_err(|_| ())?;
Ok(Self {
blob,
commitment,
proof,
})
}
}

#[derive(serde::Deserialize, serde::Serialize)]
struct VerifyBlobKzgProofBatchInputUnchecked {
blobs: Vec<Bytes>,
commitments: Vec<Bytes>,
proofs: Vec<Bytes>,
}

#[derive(serde::Deserialize, serde::Serialize)]
struct VerifyBlobKzgProofBatchUnchecked {
input: VerifyBlobKzgProofBatchInputUnchecked,
output: Option<bool>,
}

struct VerifyBlobKzgProofBatchInput {
blobs: Vec<Blob<FIELD_ELEMENTS_PER_BLOB>>,
commitments: Vec<Commitment>,
proofs: Vec<Proof>,
}

impl VerifyBlobKzgProofBatchInput {
pub fn from_unchecked(
unchecked: VerifyBlobKzgProofBatchInputUnchecked,
) -> Result<Self, ()> {
if unchecked.blobs.len() != unchecked.commitments.len()
|| unchecked.blobs.len() != unchecked.proofs.len()
{
return Err(());
}

let mut blobs = vec![];
for blob in unchecked.blobs {
let blob = Blob::from_slice(blob).map_err(|_| ())?;
blobs.push(blob);
}

let mut commitments = vec![];
for commitment in unchecked.commitments {
if commitment.len() != Commitment::BYTES {
return Err(());
}
let commitment = FixedBytes::<{ Commitment::BYTES }>::from_slice(&commitment);
let commitment = Commitment::deserialize(commitment).map_err(|_| ())?;
commitments.push(commitment);
}

let mut proofs = vec![];
for proof in unchecked.proofs {
if proof.len() != Proof::BYTES {
return Err(());
}
let proof = FixedBytes::<{ Proof::BYTES }>::from_slice(&proof);
let proof = Proof::deserialize(proof).map_err(|_| ())?;
proofs.push(proof);
}

Ok(Self {
blobs,
commitments,
proofs,
})
}
}

fn setup() -> Setup<FIELD_ELEMENTS_PER_BLOB, SETUP_G2_LEN> {
let path = format!("{}/trusted_setup_4096.json", env!("CARGO_MANIFEST_DIR"));
let path = PathBuf::from(path);
@@ -468,29 +239,20 @@ mod tests {
let setup = setup();
let setup = Arc::new(setup);

let files = consensus_spec_test_files("compute_kzg_proof");

for file in files {
for file in consensus_spec_test_files("compute_kzg_proof") {
let reader = BufReader::new(file);
let case: ComputeKzgProofUnchecked = serde_yaml::from_reader(reader).unwrap();

match ComputeKzgProofInput::from_unchecked(case.input) {
Ok(input) => {
let (proof, eval) = case.output.unwrap();
let expected_eval = Fr::from_be_bytes(eval).unwrap();
let expected_proof = P1::deserialize(proof).unwrap();

let poly = Polynomial(&input.blob.elements);
let eval = poly.evaluate(input.z, &setup);
let (_eval, proof) = poly.prove(input.z, &setup);

assert_eq!(eval, expected_eval);
assert_eq!(proof, expected_proof);
}
Err(_) => {
assert!(case.output.is_none());
}
}
let case: ComputeProof = serde_yaml::from_reader(reader).unwrap();

let expected = case.output();
let Some((blob, z)) = case.input() 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);
}
}

@@ -499,26 +261,18 @@ mod tests {
let setup = setup();
let setup = Arc::new(setup);

let files = consensus_spec_test_files("compute_blob_kzg_proof");

for file in files {
for file in consensus_spec_test_files("compute_blob_kzg_proof") {
let reader = BufReader::new(file);
let case: ComputeBlobKzgProofUnchecked = serde_yaml::from_reader(reader).unwrap();

match ComputeBlobKzgProofInput::from_unchecked(case.input) {
Ok(input) => {
let proof = case.output.unwrap();
let proof = P1::deserialize(proof).unwrap();
let expected_proof = Proof::from(proof);

let proof = setup.blob_proof(&input.blob, &input.commitment);

assert_eq!(proof, expected_proof);
}
Err(_) => {
assert!(case.output.is_none());
}
}
let case: ComputeBlobProof = serde_yaml::from_reader(reader).unwrap();

let expected = case.output();
let Some((blob, commitment)) = case.input() else {
assert!(expected.is_none());
continue;
};
let expected = expected.unwrap();
let proof = setup.blob_proof(&blob, &commitment);
assert_eq!(proof, expected);
}
}

@@ -528,26 +282,18 @@ mod tests {
let setup = setup();
let setup = Arc::new(setup);

let files = consensus_spec_test_files("blob_to_kzg_commitment");

for file in files {
for file in consensus_spec_test_files("blob_to_kzg_commitment") {
let reader = BufReader::new(file);
let case: BlobToCommitmentUnchecked = serde_yaml::from_reader(reader).unwrap();

match BlobToCommitmentInput::from_unchecked(case.input) {
Ok(input) => {
let comm = case.output.unwrap();
let comm = P1::deserialize(comm).unwrap();
let expected_comm = Commitment::from(comm);

let comm = input.blob.commitment(&setup);

assert_eq!(comm, expected_comm);
}
Err(_) => {
assert!(case.output.is_none());
}
}
let case: BlobToCommitment = serde_yaml::from_reader(reader).unwrap();

let expected = case.output();
let Some(blob) = case.input() else {
assert!(expected.is_none());
continue;
};
let expected = expected.unwrap();
let commitment = setup.blob_to_commitment(&blob);
assert_eq!(commitment, expected);
}
}

@@ -559,18 +305,16 @@ mod tests {

for file in consensus_spec_test_files("verify_kzg_proof") {
let reader = BufReader::new(file);
let case: VerifyKzgProofUnchecked = serde_yaml::from_reader(reader).unwrap();

match VerifyKzgProofInput::from_unchecked(case.input) {
Ok(input) => {
let check =
setup.verify_proof(&input.proof, &input.commitment, &input.z, &input.y);
assert_eq!(check, case.output.unwrap());
}
Err(_) => {
assert!(case.output.is_none());
}
}
let case: VerifyProof = serde_yaml::from_reader(reader).unwrap();

let expected = case.output();
let Some((commitment, z, y, proof)) = case.input() else {
assert!(expected.is_none());
continue;
};
let expected = expected.unwrap();
let verified = setup.verify_proof(&proof, &commitment, &z, &y);
assert_eq!(verified, expected);
}
}

@@ -582,18 +326,16 @@ mod tests {

for file in consensus_spec_test_files("verify_blob_kzg_proof") {
let reader = BufReader::new(file);
let case: VerifyBlobKzgProofUnchecked = serde_yaml::from_reader(reader).unwrap();

match VerifyBlobKzgProofInput::from_unchecked(case.input) {
Ok(input) => {
let check =
setup.verify_blob_proof(&input.blob, &input.commitment, &input.proof);
assert_eq!(check, case.output.unwrap());
}
Err(_) => {
assert!(case.output.is_none());
}
}
let case: VerifyBlobProof = serde_yaml::from_reader(reader).unwrap();

let expected = case.output();
let Some((blob, commitment, proof)) = case.input() else {
assert!(expected.is_none());
continue;
};
let expected = expected.unwrap();
let verified = setup.verify_blob_proof(&blob, &commitment, &proof);
assert_eq!(verified, expected);
}
}

@@ -605,18 +347,16 @@ mod tests {

for file in consensus_spec_test_files("verify_blob_kzg_proof_batch") {
let reader = BufReader::new(file);
let case: VerifyBlobKzgProofBatchUnchecked = serde_yaml::from_reader(reader).unwrap();

match VerifyBlobKzgProofBatchInput::from_unchecked(case.input) {
Ok(input) => {
let check =
setup.verify_blob_proof_batch(input.blobs, input.commitments, input.proofs);
assert_eq!(check, case.output.unwrap());
}
Err(_) => {
assert!(case.output.is_none());
}
}
let case: VerifyBlobProofBatch = serde_yaml::from_reader(reader).unwrap();

let expected = case.output();
let Some((blobs, commitments, proofs)) = case.input() else {
assert!(expected.is_none());
continue;
};
let expected = expected.unwrap();
let verified = setup.verify_blob_proof_batch(&blobs, &commitments, &proofs);
assert_eq!(verified, expected);
}
}
}
242 changes: 242 additions & 0 deletions src/kzg/spec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
use alloy_primitives::{Bytes, FixedBytes};
use serde::Deserialize;

use crate::{
blob::Blob,
bls::{Fr, P1},
};

use super::{Commitment, Proof};

fn blob_from_bytes<const N: usize>(bytes: &Bytes) -> Option<Blob<N>> {
Blob::<N>::from_slice(bytes.as_ref()).ok()
}

fn fr_from_bytes(bytes: &Bytes) -> Option<Fr> {
let bytes = FixedBytes::<{ Fr::BYTES }>::try_from(bytes.as_ref()).ok();
bytes.and_then(Fr::from_be_bytes)
}

fn p1_from_bytes(bytes: &Bytes) -> Option<P1> {
let bytes = FixedBytes::<{ P1::BYTES }>::try_from(bytes.as_ref()).ok()?;
Commitment::deserialize(bytes).ok()
}

#[derive(Deserialize)]
struct BlobToCommitmentInput {
blob: Bytes,
}

#[derive(Deserialize)]
pub struct BlobToCommitment {
input: BlobToCommitmentInput,
output: Option<Bytes>,
}

impl BlobToCommitment {
pub fn input<const N: usize>(&self) -> Option<Blob<N>> {
blob_from_bytes(&self.input.blob)
}

pub fn output(&self) -> Option<Commitment> {
self.output.as_ref().and_then(p1_from_bytes)
}
}

#[derive(Deserialize)]
struct ComputeBlobProofInput {
blob: Bytes,
commitment: Bytes,
}

#[derive(Deserialize)]
pub struct ComputeBlobProof {
input: ComputeBlobProofInput,
output: Option<Bytes>,
}

impl ComputeBlobProof {
fn blob<const N: usize>(&self) -> Option<Blob<N>> {
blob_from_bytes(&self.input.blob)
}

fn commitment(&self) -> Option<Commitment> {
p1_from_bytes(&self.input.commitment)
}

pub fn input<const N: usize>(&self) -> Option<(Blob<N>, Commitment)> {
self.blob().zip(self.commitment())
}

pub fn output(&self) -> Option<Proof> {
self.output.as_ref().and_then(p1_from_bytes)
}
}

#[derive(Deserialize)]
struct ComputeProofInput {
blob: Bytes,
z: Bytes,
}

#[derive(Deserialize)]
pub struct ComputeProof {
input: ComputeProofInput,
output: Option<(Bytes, Bytes)>,
}

impl ComputeProof {
fn blob<const N: usize>(&self) -> Option<Blob<N>> {
blob_from_bytes(&self.input.blob)
}

fn z(&self) -> Option<Fr> {
fr_from_bytes(&self.input.z)
}

pub fn input<const N: usize>(&self) -> Option<(Blob<N>, Fr)> {
self.blob().zip(self.z())
}

pub fn output(&self) -> Option<(Proof, Fr)> {
self.output.as_ref().and_then(|(proof, y)| {
let proof = p1_from_bytes(proof);
let y = fr_from_bytes(y);
proof.zip(y)
})
}
}

#[derive(Deserialize)]
struct VerifyBlobProofInput {
blob: Bytes,
commitment: Bytes,
proof: Bytes,
}

#[derive(Deserialize)]
pub struct VerifyBlobProof {
input: VerifyBlobProofInput,
output: Option<bool>,
}

impl VerifyBlobProof {
fn blob<const N: usize>(&self) -> Option<Blob<N>> {
blob_from_bytes(&self.input.blob)
}

fn commitment(&self) -> Option<Commitment> {
p1_from_bytes(&self.input.commitment)
}

fn proof(&self) -> Option<Proof> {
p1_from_bytes(&self.input.proof)
}

pub fn input<const N: usize>(&self) -> Option<(Blob<N>, Commitment, Proof)> {
match (self.blob(), self.commitment(), self.proof()) {
(Some(blob), Some(commitment), Some(proof)) => Some((blob, commitment, proof)),
_ => None,
}
}

pub fn output(&self) -> Option<bool> {
self.output
}
}

#[derive(Deserialize)]
struct VerifyProofInput {
commitment: Bytes,
z: Bytes,
y: Bytes,
proof: Bytes,
}

#[derive(Deserialize)]
pub struct VerifyProof {
input: VerifyProofInput,
output: Option<bool>,
}

impl VerifyProof {
fn commitment(&self) -> Option<Commitment> {
p1_from_bytes(&self.input.commitment)
}

fn z(&self) -> Option<Fr> {
fr_from_bytes(&self.input.z)
}

fn y(&self) -> Option<Fr> {
fr_from_bytes(&self.input.y)
}

fn proof(&self) -> Option<Proof> {
p1_from_bytes(&self.input.proof)
}

pub fn input(&self) -> Option<(Commitment, Fr, Fr, Proof)> {
match (self.commitment(), self.z(), self.y(), self.proof()) {
(Some(commitment), Some(z), Some(y), Some(proof)) => Some((commitment, z, y, proof)),
_ => None,
}
}

pub fn output(&self) -> Option<bool> {
self.output
}
}

#[derive(Deserialize)]
struct VerifyBlobProofBatchInput {
blobs: Vec<Bytes>,
commitments: Vec<Bytes>,
proofs: Vec<Bytes>,
}

#[derive(Deserialize)]
pub struct VerifyBlobProofBatch {
input: VerifyBlobProofBatchInput,
output: Option<bool>,
}

impl VerifyBlobProofBatch {
fn blobs<const N: usize>(&self) -> Option<Vec<Blob<N>>> {
let blobs: Vec<Blob<N>> = self
.input
.blobs
.iter()
.filter_map(blob_from_bytes)
.collect();
(blobs.len() == self.input.blobs.len()).then_some(blobs)
}

fn commitments(&self) -> Option<Vec<Commitment>> {
let commitments: Vec<Commitment> = self
.input
.commitments
.iter()
.filter_map(p1_from_bytes)
.collect();
(commitments.len() == self.input.commitments.len()).then_some(commitments)
}

fn proofs(&self) -> Option<Vec<Proof>> {
let proofs: Vec<Proof> = self.input.proofs.iter().filter_map(p1_from_bytes).collect();
(proofs.len() == self.input.proofs.len()).then_some(proofs)
}

pub fn input<const N: usize>(&self) -> Option<(Vec<Blob<N>>, Vec<Commitment>, Vec<Proof>)> {
match (self.blobs(), self.commitments(), self.proofs()) {
(Some(blobs), Some(commitments), Some(proofs)) => (blobs.len() == commitments.len()
&& commitments.len() == proofs.len())
.then_some((blobs, commitments, proofs)),
_ => None,
}
}

pub fn output(&self) -> Option<bool> {
self.output
}
}