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

Support for Hyrax polynomial commitment scheme #1

Open
wants to merge 24 commits into
base: main
Choose a base branch
from

Conversation

srinathsetty
Copy link
Contributor

No description provided.

Copy link

@huitseeker huitseeker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a few inline comments, please lmk if I can help upstream this to Nova!

src/provider/hyrax_pc.rs Outdated Show resolved Hide resolved
Comment on lines +234 to +251
fn commit(ck: &Self::CommitmentKey, v: &[G::Scalar]) -> Self::Commitment {
let poly = MultilinearPolynomial::new(v.to_vec());
let n = poly.len();
let ell = poly.get_num_vars();
assert_eq!(n, (2usize).pow(ell as u32));

let (left_num_vars, right_num_vars) = EqPolynomial::<G::Scalar>::compute_factored_lens(ell);
let L_size = (2usize).pow(left_num_vars as u32);
let R_size = (2usize).pow(right_num_vars as u32);
assert_eq!(L_size * R_size, n);

let comm = (0..L_size)
.collect::<Vec<usize>>()
.into_par_iter()
.map(|i| {
PedersenCommitmentEngine::commit(&ck.ck, &poly.get_Z()[R_size * i..R_size * (i + 1)])
})
.collect();

HyraxCommitment {
comm,
is_default: false,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Rather than a boolean flag is_default, I think what you want to employ in the HyraxCommitment is a std::cell::OnceCell.
This OnceCell represents a slot that you can fill "only once" (with any value you want), after which it becomes immutable. This should allow you to implement logic for commitment and operations thereof without having to rely on error management, or flag-checking logic.

Comment on lines +175 to +207
macro_rules! define_add_variants {
(G = $g:path, LHS = $lhs:ty, RHS = $rhs:ty, Output = $out:ty) => {
impl<'b, G: $g> Add<&'b $rhs> for $lhs {
type Output = $out;
fn add(self, rhs: &'b $rhs) -> $out {
&self + rhs
}
}

impl<'a, G: $g> Add<$rhs> for &'a $lhs {
type Output = $out;
fn add(self, rhs: $rhs) -> $out {
self + &rhs
}
}

impl<G: $g> Add<$rhs> for $lhs {
type Output = $out;
fn add(self, rhs: $rhs) -> $out {
&self + &rhs
}
}
};
}

macro_rules! define_add_assign_variants {
(G = $g:path, LHS = $lhs:ty, RHS = $rhs:ty) => {
impl<G: $g> AddAssign<$rhs> for $lhs {
fn add_assign(&mut self, rhs: $rhs) {
*self += &rhs;
}
}
};
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using forward_ref or even, if need be, forward_ref_generic. This is used in the compiler itself

Comment on lines +260 to +268
impl<G: Group> TranscriptReprTrait<G> for HyraxCommitment<G> {
fn to_transcript_bytes(&self) -> Vec<u8> {
let mut v = Vec::new();
v.append(&mut b"poly_commitment_begin".to_vec());

for c in &self.comm {
v.append(&mut c.to_transcript_bytes());
}

v.append(&mut b"poly_commitment_end".to_vec());
v
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The complex mutable logic of append requires an allocation for the rightmost argument, one that's not needed here since we perform it on the fly. Consider the following:

impl<G: Group> TranscriptReprTrait<G> for HyraxCommitment<G> {
    fn to_transcript_bytes(&self) -> Vec<u8> {
        let mut v = Vec::new();
        
        v.extend_from_slice(b"poly_commitment_begin");
        
        for c in &self.comm {
            v.extend_from_slice(&c.to_transcript_bytes());
        }
        
        v.extend_from_slice(b"poly_commitment_end");

        v
    }
}

}

impl<G: Group> TranscriptReprTrait<G> for HyraxCompressedCommitment<G> {
fn to_transcript_bytes(&self) -> Vec<u8> {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above for a more idiomatic formulation.

type EvaluationArgument = HyraxEvaluationArgument<G>;

fn setup(
ck: &<Self::CE as CommitmentEngineTrait<G>>::CommitmentKey,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ck will be copied in all instances of calls to this function, so it's best to take it by value and let the caller figure out how to come up with that copy (C-CALLER-CONTROL)


impl<G: Group> CommitmentKeyExtTrait<G> for CommitmentKey<G> {
type CE = CommitmentEngine<G>;
/// Reinterprets the commitments as a commitment key

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be really good to have a line of comments or two explaining the re-interpretation and a justification for why it makes sense (naturally, this wasn't introduced in your PR, but relating the CommitmentKeyExtTrait to homomorphic properties is non-trivial enough to deserve a comment IMHO).

Comment on lines +96 to +102
(0..R_size)
.map(|i| {
(0..L_size)
.map(|j| L[j] * self.Z[j * R_size + i])
.fold(Scalar::ZERO, |x, y| x + y)
})
.collect()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A.k.a.:

    let mut res = Vec::with_capacity(R_size);
    for i in 0..R_size {
        let sum = (0..L_size)
            .map(|j| L[j] * self.Z[j * R_size + i])
            .sum();
        res.push(sum);
    }
    res

src/traits/commitment.rs Show resolved Hide resolved
) -> Result<Self::EvaluationArgument, SpartanError> {
transcript.absorb(b"poly_com", comm);

let poly_m = MultilinearPolynomial::<G::Scalar>::new(poly.to_vec());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: if you're going to need to copy the full vector, you probably want to be explicit about it and make argument poly: Vec<G::Scalar>. (C-CALLER-CONTROL)

Take this with a grain of salt: MultilinearPolynomial in general is a bit unwieldy and would probably benefit from a larger refactoring based on Iterator APIs

Comment on lines +355 to +369
let W = {
let mut W = self.W.clone();
W.extend(vec![G::Scalar::ZERO; S.num_vars - W.len()]);
W
};
Copy link

@huitseeker huitseeker Aug 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This is more performant written as:

let W = {
    assert!(self.W.len() <= S.num_vars());
    let mut W = vec![G::Scalar::Zero; S.num_vars];
    W[...self.W.len()].copy_from_slice(&self.W);
};

because it avoids the possible allocation involved in extending from self.W.len() to S.num_vars()

@sga001
Copy link

sga001 commented Aug 9, 2023

I left a few inline comments, please lmk if I can help upstream this to Nova!

Thanks for the comments. This code is unlikely to be useful in Nova, though, as it will make the folding and the folding verifier circuit (which runs on each step) more complicated and expensive.

@huitseeker
Copy link

@sga001 The performance point is well-taken, and I indeed see in it reason to not have Hyrax as the default PCS for Nova. However, I think having a hiding PCS may open up a different range of trust assumptions between the (possibly several) prover(s) involved in the folding.

@sga001
Copy link

sga001 commented Aug 9, 2023

@sga001 The performance point is well-taken, and I indeed see in it reason to not have Hyrax as the default PCS for Nova. However, I think having a hiding PCS may open up a different range of trust assumptions between the (possibly several) prover(s) involved in the folding.

I'm not sure I understand the scenario you have in mind, but integrating Hyrax into Nova is non-trivial.
Another thing I want to make sure you know is that the Nova code base and this Spartan2 codebase are not zero-knowledge. Similarly, this specific Hyrax PC is not hiding.

Making them ZK is a bit of work (but doable and something we will do at some point); making Hyrax PC hiding is trivial though (just add blind).

@srinathsetty
Copy link
Contributor Author

srinathsetty commented Aug 9, 2023

@sga001 The performance point is well-taken, and I indeed see in it reason to not have Hyrax as the default PCS for Nova. However, I think having a hiding PCS may open up a different range of trust assumptions between the (possibly several) prover(s) involved in the folding.

I think there may be some confusion here. I understand we eventually need to make Nova proofs (compressed or otherwise) zero-knowledge: microsoft/Nova#174. For compressed proofs, Hyrax provides a zero-knowledge transformation, but it does not require using Hyrax-PC. The current Nova code uses Spartan with IPA-PC to compress IVC proofs, one can apply zk transformation to that directly, which we plan to do at some point.

@huitseeker
Copy link

That makes sense, thanks for the clarification, @srinathsetty

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants