-
Notifications
You must be signed in to change notification settings - Fork 16
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
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this 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!
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, |
There was a problem hiding this comment.
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.
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; | ||
} | ||
} | ||
}; | ||
} |
There was a problem hiding this comment.
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
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 | ||
} | ||
} |
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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.
src/provider/hyrax_pc.rs
Outdated
type EvaluationArgument = HyraxEvaluationArgument<G>; | ||
|
||
fn setup( | ||
ck: &<Self::CE as CommitmentEngineTrait<G>>::CommitmentKey, |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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).
(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() |
There was a problem hiding this comment.
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
) -> Result<Self::EvaluationArgument, SpartanError> { | ||
transcript.absorb(b"poly_com", comm); | ||
|
||
let poly_m = MultilinearPolynomial::<G::Scalar>::new(poly.to_vec()); |
There was a problem hiding this comment.
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
let W = { | ||
let mut W = self.W.clone(); | ||
W.extend(vec![G::Scalar::ZERO; S.num_vars - W.len()]); | ||
W | ||
}; |
There was a problem hiding this comment.
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()
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. |
@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. 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). |
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. |
That makes sense, thanks for the clarification, @srinathsetty |
8a1e812
to
883d537
Compare
6fb4ef2
to
f657684
Compare
aef6168
to
63f5636
Compare
No description provided.