DA kzg initial approach (#309)
* Added kzg crate * Added basic commitment and proof creation methods * Added blob verification * DA kzg custom blob sizes (#325) * Added types module * Added dynamic size methods * Added compute and verify test * Fix using wrong constant on compute_commitment * Criterion setup and first benchmark * Move constants to settings struct * Add tiny docs * Pad blob is data shorter than it should --------- Co-authored-by: Gusto <bacvinka@gmail.com> --------- Co-authored-by: Gusto <bacvinka@gmail.com>
This commit is contained in:
parent
ebbdf14406
commit
2bd8ea92b1
@ -10,6 +10,7 @@ members = [
|
||||
"nomos-services/mempool",
|
||||
"nomos-services/http",
|
||||
"nomos-da-core/reed-solomon",
|
||||
"nomos-da-core/kzg",
|
||||
"nodes/nomos-node",
|
||||
"simulations",
|
||||
"consensus-engine",
|
||||
|
17
nomos-da-core/kzg/Cargo.toml
Normal file
17
nomos-da-core/kzg/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "nomos-kzg"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
kzg = { git = "https://github.com/sifraitech/rust-kzg.git", package = "rust-kzg-blst", features = ["parallel"] }
|
||||
kzg_traits = { git = "https://github.com/sifraitech/rust-kzg.git", package = "kzg" }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.5.1"
|
||||
|
||||
[[bench]]
|
||||
name = "nomos_kzg"
|
||||
harness = false
|
37
nomos-da-core/kzg/benches/nomos_kzg.rs
Normal file
37
nomos-da-core/kzg/benches/nomos_kzg.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use kzg::{types::kzg_settings::FsKZGSettings, utils::generate_trusted_setup};
|
||||
use kzg_traits::{FFTSettings, KZGSettings};
|
||||
use nomos_kzg::Blob;
|
||||
|
||||
fn nomos_dynamic_vs_external(c: &mut Criterion) {
|
||||
let (g1s, g2s) = generate_trusted_setup(4096, [0; 32]);
|
||||
let fft_settings = kzg::types::fft_settings::FsFFTSettings::new(8).unwrap();
|
||||
let settings = FsKZGSettings::new(&g1s, &g2s, 4096, &fft_settings).unwrap();
|
||||
let blob = Blob::from_bytes(&[5; 4096 * 32]).unwrap();
|
||||
|
||||
let mut group = c.benchmark_group("KZG Commitment Benchmarks");
|
||||
|
||||
group.bench_function("nomos blob commitment", |b| {
|
||||
b.iter(|| {
|
||||
nomos_kzg::blob_to_kzg_commitment(
|
||||
black_box(&blob),
|
||||
black_box(&settings),
|
||||
black_box(4096),
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
group.bench_function("external blob commitment", |b| {
|
||||
b.iter(|| {
|
||||
kzg::eip_4844::blob_to_kzg_commitment_rust(
|
||||
black_box(&blob.inner()),
|
||||
black_box(&settings),
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(benches, nomos_dynamic_vs_external);
|
||||
criterion_main!(benches);
|
246
nomos-da-core/kzg/src/dynamic_kzg.rs
Normal file
246
nomos-da-core/kzg/src/dynamic_kzg.rs
Normal file
@ -0,0 +1,246 @@
|
||||
//! Custom variant of rust-kzg that supports dynamic sized blobs.
|
||||
//! https://github.com/sifraitech/rust-kzg
|
||||
//! Some types were changed to fit our API for comfort.
|
||||
//! Blob related constants were removed and we use a config based approach.
|
||||
|
||||
use crate::types::{Blob, Commitment, KzgSettings, Proof};
|
||||
use kzg::eip_4844::{hash_to_bls_field, verify_kzg_proof_rust};
|
||||
use kzg::kzg_proofs::g1_linear_combination;
|
||||
use kzg::types::fr::FsFr;
|
||||
use kzg::types::g1::FsG1;
|
||||
use kzg::types::kzg_settings::FsKZGSettings;
|
||||
use kzg::types::poly::FsPoly;
|
||||
use kzg_traits::eip_4844::{
|
||||
bytes_of_uint64, hash, CHALLENGE_INPUT_SIZE, FIAT_SHAMIR_PROTOCOL_DOMAIN,
|
||||
};
|
||||
use kzg_traits::{Fr, Poly, G1};
|
||||
|
||||
pub fn blob_to_kzg_commitment(
|
||||
blob: &Blob,
|
||||
s: &FsKZGSettings,
|
||||
field_elements_per_blob: usize,
|
||||
) -> FsG1 {
|
||||
let mut out = FsG1::default();
|
||||
g1_linear_combination(&mut out, &s.secret_g1, &blob.inner, field_elements_per_blob);
|
||||
out
|
||||
}
|
||||
|
||||
pub fn compute_blob_kzg_proof(
|
||||
blob: &Blob,
|
||||
commitment: &Commitment,
|
||||
settings: &KzgSettings,
|
||||
) -> Result<FsG1, String> {
|
||||
if !commitment.0.is_valid() {
|
||||
return Err("Invalid commitment".to_string());
|
||||
}
|
||||
|
||||
let evaluation_challenge_fr = compute_challenge(blob, &commitment.0, settings);
|
||||
let (proof, _) = compute_kzg_proof(blob, &evaluation_challenge_fr, settings);
|
||||
Ok(proof)
|
||||
}
|
||||
|
||||
pub fn verify_blob_kzg_proof(
|
||||
blob: &Blob,
|
||||
commitment: &Commitment,
|
||||
proof: &Proof,
|
||||
settings: &KzgSettings,
|
||||
) -> Result<bool, String> {
|
||||
if !commitment.0.is_valid() {
|
||||
return Err("Invalid commitment".to_string());
|
||||
}
|
||||
if !proof.0.is_valid() {
|
||||
return Err("Invalid proof".to_string());
|
||||
}
|
||||
|
||||
let polynomial = blob_to_polynomial(&blob.inner);
|
||||
let evaluation_challenge_fr = compute_challenge(blob, &commitment.0, settings);
|
||||
let y_fr = evaluate_polynomial_in_evaluation_form_rust(
|
||||
&polynomial,
|
||||
&evaluation_challenge_fr,
|
||||
&settings.settings,
|
||||
);
|
||||
verify_kzg_proof_rust(
|
||||
&commitment.0,
|
||||
&evaluation_challenge_fr,
|
||||
&y_fr,
|
||||
&proof.0,
|
||||
&settings.settings,
|
||||
)
|
||||
}
|
||||
|
||||
fn compute_challenge(blob: &Blob, commitment: &FsG1, settings: &KzgSettings) -> FsFr {
|
||||
let mut bytes: Vec<u8> = vec![0; CHALLENGE_INPUT_SIZE];
|
||||
|
||||
// Copy domain separator
|
||||
bytes[..16].copy_from_slice(&FIAT_SHAMIR_PROTOCOL_DOMAIN);
|
||||
bytes_of_uint64(&mut bytes[16..24], blob.len() as u64);
|
||||
// Set all other bytes of this 16-byte (little-endian) field to zero
|
||||
bytes_of_uint64(&mut bytes[24..32], 0);
|
||||
|
||||
// Copy blob
|
||||
for i in 0..blob.len() {
|
||||
let v = blob.inner[i].to_bytes();
|
||||
bytes[(32 + i * settings.bytes_per_field_element)
|
||||
..(32 + (i + 1) * settings.bytes_per_field_element)]
|
||||
.copy_from_slice(&v);
|
||||
}
|
||||
|
||||
// Copy commitment
|
||||
let v = commitment.to_bytes();
|
||||
for i in 0..v.len() {
|
||||
bytes[32 + settings.bytes_per_field_element * blob.len() + i] = v[i];
|
||||
}
|
||||
|
||||
// Now let's create the challenge!
|
||||
let eval_challenge = hash(&bytes);
|
||||
hash_to_bls_field(&eval_challenge)
|
||||
}
|
||||
|
||||
fn compute_kzg_proof(blob: &Blob, z: &FsFr, s: &KzgSettings) -> (FsG1, FsFr) {
|
||||
let polynomial = blob_to_polynomial(blob.inner.as_slice());
|
||||
let poly_len = polynomial.coeffs.len();
|
||||
let y = evaluate_polynomial_in_evaluation_form_rust(&polynomial, z, &s.settings);
|
||||
|
||||
let mut tmp: FsFr;
|
||||
let roots_of_unity: &Vec<FsFr> = &s.settings.fs.roots_of_unity;
|
||||
|
||||
let mut m: usize = 0;
|
||||
let mut q: FsPoly = FsPoly::new(poly_len).unwrap();
|
||||
|
||||
let mut inverses_in: Vec<FsFr> = vec![FsFr::default(); poly_len];
|
||||
let mut inverses: Vec<FsFr> = vec![FsFr::default(); poly_len];
|
||||
|
||||
for i in 0..poly_len {
|
||||
if z.equals(&roots_of_unity[i]) {
|
||||
// We are asked to compute a KZG proof inside the domain
|
||||
m = i + 1;
|
||||
inverses_in[i] = FsFr::one();
|
||||
continue;
|
||||
}
|
||||
// (p_i - y) / (ω_i - z)
|
||||
q.coeffs[i] = polynomial.coeffs[i].sub(&y);
|
||||
inverses_in[i] = roots_of_unity[i].sub(z);
|
||||
}
|
||||
|
||||
fr_batch_inv(&mut inverses, &inverses_in, poly_len);
|
||||
|
||||
for (i, inverse) in inverses.iter().enumerate().take(poly_len) {
|
||||
q.coeffs[i] = q.coeffs[i].mul(inverse);
|
||||
}
|
||||
|
||||
if m != 0 {
|
||||
// ω_{m-1} == z
|
||||
m -= 1;
|
||||
q.coeffs[m] = FsFr::zero();
|
||||
for i in 0..poly_len {
|
||||
if i == m {
|
||||
continue;
|
||||
}
|
||||
// Build denominator: z * (z - ω_i)
|
||||
tmp = z.sub(&roots_of_unity[i]);
|
||||
inverses_in[i] = tmp.mul(z);
|
||||
}
|
||||
|
||||
fr_batch_inv(&mut inverses, &inverses_in, poly_len);
|
||||
|
||||
for i in 0..poly_len {
|
||||
if i == m {
|
||||
continue;
|
||||
}
|
||||
// Build numerator: ω_i * (p_i - y)
|
||||
tmp = polynomial.coeffs[i].sub(&y);
|
||||
tmp = tmp.mul(&roots_of_unity[i]);
|
||||
// Do the division: (p_i - y) * ω_i / (z * (z - ω_i))
|
||||
tmp = tmp.mul(&inverses[i]);
|
||||
q.coeffs[m] = q.coeffs[m].add(&tmp);
|
||||
}
|
||||
}
|
||||
|
||||
let proof = g1_lincomb(&s.settings.secret_g1, &q.coeffs, poly_len);
|
||||
(proof, y)
|
||||
}
|
||||
|
||||
fn evaluate_polynomial_in_evaluation_form_rust(p: &FsPoly, x: &FsFr, s: &FsKZGSettings) -> FsFr {
|
||||
let poly_len = p.coeffs.len();
|
||||
let roots_of_unity: &Vec<FsFr> = &s.fs.roots_of_unity;
|
||||
let mut inverses_in: Vec<FsFr> = vec![FsFr::default(); poly_len];
|
||||
let mut inverses: Vec<FsFr> = vec![FsFr::default(); poly_len];
|
||||
|
||||
for i in 0..poly_len {
|
||||
if x.equals(&roots_of_unity[i]) {
|
||||
return p.get_coeff_at(i);
|
||||
}
|
||||
inverses_in[i] = x.sub(&roots_of_unity[i]);
|
||||
}
|
||||
|
||||
fr_batch_inv(&mut inverses, &inverses_in, poly_len);
|
||||
|
||||
let mut tmp: FsFr;
|
||||
let mut out = FsFr::zero();
|
||||
|
||||
for i in 0..poly_len {
|
||||
tmp = inverses[i].mul(&roots_of_unity[i]);
|
||||
tmp = tmp.mul(&p.coeffs[i]);
|
||||
out = out.add(&tmp);
|
||||
}
|
||||
|
||||
tmp = FsFr::from_u64(poly_len as u64);
|
||||
out = out.div(&tmp).unwrap();
|
||||
tmp = x.pow(poly_len);
|
||||
tmp = tmp.sub(&FsFr::one());
|
||||
out = out.mul(&tmp);
|
||||
out
|
||||
}
|
||||
|
||||
fn fr_batch_inv(out: &mut [FsFr], a: &[FsFr], len: usize) {
|
||||
assert!(len > 0);
|
||||
|
||||
let mut accumulator = FsFr::one();
|
||||
|
||||
for i in 0..len {
|
||||
out[i] = accumulator;
|
||||
accumulator = accumulator.mul(&a[i]);
|
||||
}
|
||||
|
||||
accumulator = accumulator.eucl_inverse();
|
||||
|
||||
for i in (0..len).rev() {
|
||||
out[i] = out[i].mul(&accumulator);
|
||||
accumulator = accumulator.mul(&a[i]);
|
||||
}
|
||||
}
|
||||
|
||||
fn g1_lincomb(points: &[FsG1], scalars: &[FsFr], length: usize) -> FsG1 {
|
||||
let mut out = FsG1::default();
|
||||
g1_linear_combination(&mut out, points, scalars, length);
|
||||
out
|
||||
}
|
||||
|
||||
fn blob_to_polynomial(blob: &[FsFr]) -> FsPoly {
|
||||
let mut p: FsPoly = FsPoly::new(blob.len()).unwrap();
|
||||
p.coeffs = blob.to_vec();
|
||||
p
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use kzg::eip_4844::blob_to_kzg_commitment_rust;
|
||||
use kzg::utils::generate_trusted_setup;
|
||||
use kzg_traits::{FFTSettings, KZGSettings};
|
||||
|
||||
#[test]
|
||||
fn test_blob_to_kzg_commitment() {
|
||||
let (g1s, g2s) = generate_trusted_setup(4096, [0; 32]);
|
||||
let fft_settings = kzg::types::fft_settings::FsFFTSettings::new(8).unwrap();
|
||||
let settings = FsKZGSettings::new(&g1s, &g2s, 4096, &fft_settings).unwrap();
|
||||
let kzg_settings = KzgSettings {
|
||||
settings,
|
||||
bytes_per_field_element: 32,
|
||||
};
|
||||
let blob = Blob::from_bytes(&[5; 4096 * 32], &kzg_settings).unwrap();
|
||||
let commitment = blob_to_kzg_commitment(&blob, &kzg_settings.settings, 4096);
|
||||
let commitment2 = blob_to_kzg_commitment_rust(&blob.inner, &kzg_settings.settings);
|
||||
assert_eq!(commitment, commitment2);
|
||||
}
|
||||
}
|
83
nomos-da-core/kzg/src/lib.rs
Normal file
83
nomos-da-core/kzg/src/lib.rs
Normal file
@ -0,0 +1,83 @@
|
||||
mod dynamic_kzg;
|
||||
mod types;
|
||||
|
||||
use crate::types::KzgSettings;
|
||||
pub use crate::types::{Blob, Commitment, Proof};
|
||||
pub use dynamic_kzg::{blob_to_kzg_commitment, compute_blob_kzg_proof, verify_blob_kzg_proof};
|
||||
use std::error::Error;
|
||||
|
||||
pub const BYTES_PER_PROOF: usize = 48;
|
||||
pub const BYTES_PER_COMMITMENT: usize = 48;
|
||||
|
||||
/// Compute a kzg commitment for the given data.
|
||||
/// It works for arbitrary data, but the data must be a multiple of **32 bytes**.
|
||||
/// The data is interpreted as a sequence of field elements. Each consisting of **32 bytes**.
|
||||
pub fn compute_commitment(
|
||||
data: &[u8],
|
||||
settings: &KzgSettings,
|
||||
) -> Result<Commitment, Box<dyn Error>> {
|
||||
let blob = Blob::from_bytes(data, settings)?;
|
||||
Ok(Commitment(blob_to_kzg_commitment(
|
||||
&blob,
|
||||
&settings.settings,
|
||||
data.len() / settings.bytes_per_field_element,
|
||||
)))
|
||||
}
|
||||
|
||||
/// Compute a kzg proof for each field element in the given data.
|
||||
/// It works for arbitrary data, but the data must be a multiple of **32 bytes**.
|
||||
/// The data is interpreted as a sequence of field elements. Each consisting of **32 bytes**.
|
||||
pub fn compute_proofs(
|
||||
data: &[u8],
|
||||
commitment: &Commitment,
|
||||
settings: &KzgSettings,
|
||||
) -> Result<Vec<Proof>, Box<dyn Error>> {
|
||||
let blobs = data
|
||||
.chunks(settings.bytes_per_field_element)
|
||||
.map(|b| Blob::from_bytes(b, settings));
|
||||
let mut res = Vec::new();
|
||||
for blob in blobs {
|
||||
let blob = blob?;
|
||||
res.push(Proof(compute_blob_kzg_proof(&blob, commitment, settings)?))
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Verify a kzg proof for the given blob.
|
||||
/// It works for arbitrary data, but the data must be a multiple of **32 bytes**.
|
||||
/// The data is interpreted as a sequence of field elements. Each consisting of **32 bytes**.
|
||||
pub fn verify_blob(
|
||||
blob: &[u8],
|
||||
proof: &Proof,
|
||||
commitment: &Commitment,
|
||||
settings: &KzgSettings,
|
||||
) -> Result<bool, Box<dyn Error>> {
|
||||
let blob = Blob::from_bytes(blob, settings)?;
|
||||
verify_blob_kzg_proof(&blob, commitment, proof, settings).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use kzg::types::kzg_settings::FsKZGSettings;
|
||||
use kzg::utils::generate_trusted_setup;
|
||||
use kzg_traits::{FFTSettings, KZGSettings};
|
||||
|
||||
#[test]
|
||||
fn test_compute_and_verify() -> Result<(), Box<dyn Error>> {
|
||||
let (g1s, g2s) = generate_trusted_setup(4096, [0; 32]);
|
||||
let fft_settings = kzg::types::fft_settings::FsFFTSettings::new(8).unwrap();
|
||||
let settings = FsKZGSettings::new(&g1s, &g2s, 4096, &fft_settings).unwrap();
|
||||
let kzg_settings = KzgSettings {
|
||||
settings,
|
||||
bytes_per_field_element: 32,
|
||||
};
|
||||
let blob = vec![0; 4096];
|
||||
let commitment = compute_commitment(&blob, &kzg_settings)?;
|
||||
let proofs = compute_proofs(&blob, &commitment, &kzg_settings)?;
|
||||
for proof in proofs {
|
||||
assert!(verify_blob(&blob, &proof, &commitment, &kzg_settings)?);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
63
nomos-da-core/kzg/src/types.rs
Normal file
63
nomos-da-core/kzg/src/types.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use crate::{BYTES_PER_COMMITMENT, BYTES_PER_PROOF};
|
||||
use kzg::types::fr::FsFr;
|
||||
use kzg::types::g1::FsG1;
|
||||
use kzg::types::kzg_settings::FsKZGSettings;
|
||||
use kzg_traits::{Fr, G1};
|
||||
use std::error::Error;
|
||||
|
||||
/// A wrapper around the KZG settings that also stores the number of bytes per field element.
|
||||
pub struct KzgSettings {
|
||||
pub settings: FsKZGSettings,
|
||||
pub bytes_per_field_element: usize,
|
||||
}
|
||||
|
||||
/// A KZG commitment.
|
||||
pub struct Commitment(pub(crate) FsG1);
|
||||
|
||||
/// A KZG proof.
|
||||
pub struct Proof(pub(crate) FsG1);
|
||||
|
||||
/// A blob of data.
|
||||
pub struct Blob {
|
||||
pub(crate) inner: Vec<FsFr>,
|
||||
}
|
||||
|
||||
impl Commitment {
|
||||
pub fn as_bytes_owned(&self) -> [u8; BYTES_PER_COMMITMENT] {
|
||||
self.0.to_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl Proof {
|
||||
pub fn as_bytes_owned(&self) -> [u8; BYTES_PER_PROOF] {
|
||||
self.0.to_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl Blob {
|
||||
pub fn from_bytes(data: &[u8], settings: &KzgSettings) -> Result<Self, Box<dyn Error>> {
|
||||
let mut inner = Vec::with_capacity(data.len() / settings.bytes_per_field_element);
|
||||
for chunk in data.chunks(settings.bytes_per_field_element) {
|
||||
if chunk.len() < settings.bytes_per_field_element {
|
||||
let mut padded_chunk = vec![0; settings.bytes_per_field_element];
|
||||
padded_chunk[..chunk.len()].copy_from_slice(chunk);
|
||||
inner.push(FsFr::from_bytes(&padded_chunk)?);
|
||||
} else {
|
||||
inner.push(FsFr::from_bytes(chunk)?);
|
||||
}
|
||||
}
|
||||
Ok(Self { inner })
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.is_empty()
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> Vec<FsFr> {
|
||||
self.inner.clone()
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user