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/mempool",
|
||||||
"nomos-services/http",
|
"nomos-services/http",
|
||||||
"nomos-da-core/reed-solomon",
|
"nomos-da-core/reed-solomon",
|
||||||
|
"nomos-da-core/kzg",
|
||||||
"nodes/nomos-node",
|
"nodes/nomos-node",
|
||||||
"simulations",
|
"simulations",
|
||||||
"consensus-engine",
|
"consensus-engine",
|
||||||
|
|
|
@ -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
|
|
@ -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);
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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…
Reference in New Issue