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

supernova implementation with naive opcode commitment for argumented circuit sequence. #204

Closed
wants to merge 93 commits into from

Conversation

hero78119
Copy link
Contributor

@hero78119 hero78119 commented Jul 16, 2023

Related to issue #146

This works majorly based on the initial works credits to @wyattbenno777, who initiatives a neat and clean skeleton then make further refactor possible! 🥇

To @wyattbenno777: there are many subtleties related to initial works on folk and motivate me to refactor it accordingly :) Your commit is cherry-pick and included/preserved in this PR. (Some of commit is modified due to touch CCS on PSE Nova repo, therefore I clean up to isolated supernova features). Feel free to comments/raise question and we can discuss here with more clear manner!

This implementation idea is mostly followed supernova paper https://eprint.iacr.org/2022/1758, with some modified details described as below.
Feature flagged under a supernova flag that is off by default, so it has no impact on other parts of the code or users of the Nova library.
Comments are welcome!

Implementation rationales

consolidate Φ(zi, wi) functionality into step_circuit/F circuit

In realistic ISA design, program counter can be set as arbitrary value by opcode itself.
Take RIV32 JAL opcode for example, pc_{i} will be added by imm offset to obtain pc_{i+1}.
In other words, pc_{i+1} manipulation makemore sense to be constrained by correspondence opcode implementation, i.e. step_circuit.
This PR make above feature possible, by enhancing synthesis function to pass pc_i along with z_i as input, and output corresponding pc_{i+1} and z_{i+1} in supernova, and pc_{i} will be assumed to be checked by step circuit, along with a memory commitment to prove memory[pc_{i}] matched with current opcode. A Read-only-Memory, ROM is introduced as a naive memory commitment demo. More detail of ROM will be elaboration below.

extract circuit index selector against program counter, execution sequence correctness

In supernova paper, one of implicit part is how to choose i+1 argumented F' circuit to fold previous relaxed r1cs and r1cs.
To assure next F' argumented circuit is invoked in pre-defined sequence, each F' circuit should constraint it order with each pc_i.

This implementation demo a naive prototype of RAM machine: a Read-Only-Memory, ROM for opcode sequence constraints. ROM is passed as public input z as a flattern vector, [<stepcircuit_1>, <stepcircuit_2>, …], then each step_circuit constraint rom[pc_{i}] == step_circuit_index. In the later works, it should be replaced by more advanced memory commitment, such as merkle root + merkle proofs, or other batched KZG commitment. This can be combined together as 4.4 Optimizations, p27 in supernova paper.

secondary circuit logic

For supernova secondary circuit, program counter just assign 0 for each folding step. There is no non-trivial logic in supernova secondary circuit.

Enhance debugging ability

This PR also introduces debugging friendly feature to is_sat and is_sat_relaxed function to point out failed constrain’s correspondence annotation. This improve a lot comparing with show true/false only.

Follow-up works (thought can be future PRs)

  • more documentation on supernova
  • introduce vector commitment, address RAM machine memory commitment and 4.4 Optimizations, p27 in supernova paper.

@hero78119
Copy link
Contributor Author

@microsoft-github-policy-service agree

@srinathsetty
Copy link
Collaborator

Thanks for the PR! Given this is a relatively big code contribution, it will take some time to review it.

@hero78119
Copy link
Contributor Author

hero78119 commented Jul 18, 2023

Thanks for the PR! Given this is a relatively big code contribution, it will take some time to review it.

Well noted and thanks for update!
Besides, added 2 more commits to optimise recursive-snark proof size from 2 x #running_r1cs (2 come from primary + secondary ) to #running_r1cs + 1. Primary circuit can just fold single running r1cs for each iteration, only secondary circuit need to fold respective strictly r1cs by argumented circuit index :)

Copy link
Contributor

@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 haven't looked at the circuit logic in detail yet and am for now just checkpointing in-line suggestions which I think would be helpful. heterogeneity is super exciting, thanks for opening a PR in that direction!

src/supernova/mod.rs Outdated Show resolved Hide resolved
src/gadgets/utils.rs Outdated Show resolved Hide resolved
src/r1cs.rs Outdated Show resolved Hide resolved
src/r1cs.rs Outdated Show resolved Hide resolved
src/r1cs.rs Outdated Show resolved Hide resolved
src/gadgets/r1cs.rs Outdated Show resolved Hide resolved
src/supernova/circuit.rs Outdated Show resolved Hide resolved
Comment on lines 9 to 13
//! There are two Verification Circuits. The primary and the secondary.
//! Each of them is over a Pasta curve but
//! only the primary executes the next step of the computation.
//! We have two running instances. Each circuit takes as input 2 hashes: one for each
//! of the running instances. Each of these hashes is
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be great to rephrase this in order to refer to the l (!= 2) running instances you'd expect to find in the list.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed, all the supernova docs/comment are preliminary rephrased in latest commit.

Comment on lines +82 to +50
r1cs_shape_primary: R1CSShape<G1>,
ro_consts_secondary: ROConstants<G2>,
ro_consts_circuit_secondary: ROConstantsCircuit<G1>,
ck_secondary: Option<CommitmentKey<G2>>,
r1cs_shape_secondary: R1CSShape<G2>,
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems to be precisely the same struct at PublicParams. Would some sizing of the (R1CS) contents of the ROM be more appropriate than r1cs_shape_primary and r1cs_shape_secondary here?

Copy link
Contributor Author

@hero78119 hero78119 Jul 19, 2023

Choose a reason for hiding this comment

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

r1cs_shape_primary and r1cs_shape_secondary are not the same shape, since secondary circuit is just a trivial circuit and do nothing while primary circuit got the application logic. Not sure I understand correctly, would you kindly elaborate more on this :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Setting heterogeneity aside for a moment, the sizing of the public parameters depends on the shape of the circuit they are used for. In particular, you would want the CommitmentKey (in practice, a vector Pedersen) to offer enough generators for the size of the step circuit. How this is done is expanded upon in PR #203 .

With heterogeneity, I would expect that same dependency to still exist, but the circuit bookkeeping to be different. More specifically, I would expect the R1CSShape of the largest circuit among those in the ROM to drive the size requirements for the public parameters.

Copy link

@wyattbenno777 wyattbenno777 Aug 8, 2023

Choose a reason for hiding this comment

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

In this earlier version (shell) of SuperNova it was noted that the user would choose the largest circuit for the public parameters; They know the part of the ROM that is largest. I think the notes were possibly lost on this.. (or there is possibly a way to automate choosing?)

Copy link
Contributor

@huitseeker huitseeker Aug 9, 2023

Choose a reason for hiding this comment

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

This was hinted at in a prior comment.
Nova, as a library, is quite safe in sizing public parameters:

The approach of simply recommending the user to pass the largest circuit among those in the ROM here may be error-prone -- as I'm worried a user might miss the requirement. If there was a ROM API in the form of an indexed iterator of circuits (perhaps with a cached way to determine their shape), I suspect it would make automating this part of the setup possible.

Copy link
Contributor Author

@hero78119 hero78119 Aug 14, 2023

Choose a reason for hiding this comment

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

This was hinted at in a prior comment. Nova, as a library, is quite safe in sizing public parameters:

The approach of simply recommending the user to pass the largest circuit among those in the ROM here may be error-prone -- as I'm worried a user might miss the requirement. If there was a ROM API in the form of an indexed iterator of circuits (perhaps with a cached way to determine their shape), I suspect it would make automating this part of the setup possible.

For current version, the automating way to identify largest primary circuit is relying on user to implement logic to sizing the largest circuit to setup commitment key based on it. demo in Supernova test
I admitted the ideal way is to exploring way to hide it from user to be more error pruning. The challenges for moving this part inside framework is mostly due to the circuit generic Ca, Cb defined on Supernova RunningClaims to adopt heterogeneous of the primary circuit. I had explorer few way to fine tune this design but no satisfying version

In this earlier version (shell) of SuperNova it was noted that the user would choose the largest circuit for the public parameters; They know the part of the ROM that is largest. I think the notes were possibly lost on this.. (or there is possibly a way to automate choosing?)

haha, I clean up and re-design the largest in earlier version since IMO 1. this variable design and naming is ambiguously and confusing 2. it still shift the responsibility back to user.
For current design, I try to solve point 1, however point 2 still lefted open

src/supernova/mod.rs Outdated Show resolved Hide resolved
@hero78119 hero78119 force-pushed the supernova branch 4 times, most recently from 3f2b308 to 3ed8f19 Compare July 19, 2023 14:43
src/supernova/mod.rs Outdated Show resolved Hide resolved
@CPerezz
Copy link
Contributor

CPerezz commented Jul 20, 2023

Hmmm.

It's hard for me to see how this fits in inside of this lib instead of being a standalone crate.
We already have a bunch of code to maintain like the spartan re-implementation with SPARK that is "duplicated" from the original Spartan code.

SuperNova is actually a nice use-case to have nova-snark as a dependency. Which will help to also see API visibility concerns.
Not sure is the best choice to merge this here rather than have it as a standalone crate that depends on this.

@hero78119
Copy link
Contributor Author

hero78119 commented Jul 20, 2023

Hmmm.

It's hard for me to see how this fits in inside of this lib instead of being a standalone crate. We already have a bunch of code to maintain like the spartan re-implementation with SPARK that is "duplicated" from the original Spartan code.

SuperNova is actually a nice use-case to have nova-snark as a dependency. Which will help to also see API visibility concerns. Not sure is the best choice to merge this here rather than have it as a standalone crate that depends on this.

From my viewpoint, supernova is a generalized version of current Nova implementation (Nova is a special case for 1 augmented circuit version)
Feel like that, if SuperNova benchmark also show no much different comparing with Nova, Maybe it make sense to have Supernova as a standalone version, in separate PR maybe.

Would like to hear more opinions from author later :)

) as usize];

let mut recursive_snark = recursive_snark_option.unwrap_or_else(|| {
if augmented_circuit_index == OPCODE_0 {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This demo have some code duplication on opcode multiplexing, due to slightly hard on handling heterogeneous type of augment circuit. I try few options to beautify this part and no satisfied version.

If you have better suggestion kindly let me know, thanks

@wyattbenno777
Copy link

Hmmm.

It's hard for me to see how this fits in inside of this lib instead of being a standalone crate. We already have a bunch of code to maintain like the spartan re-implementation with SPARK that is "duplicated" from the original Spartan code.

SuperNova is actually a nice use-case to have nova-snark as a dependency. Which will help to also see API visibility concerns. Not sure is the best choice to merge this here rather than have it as a standalone crate that depends on this.

My opinion is that it would belong here as a 'feature'.
For many use-case the a'la carte benefits of SuperNova are needed.

Other things I would include:

  1. Different cycles of curves. Like @huitseeker is already working on.
    (in the future possibility to remove cycles for another method .)

  2. Different/future final SNARK wrappers.

A user would come and say: "I want a Nova proof of xyz, the verifier needs to work on xyz blockchain/environment",
and be able to configure that fairly easily.

My 2 cents.

@srinathsetty
Copy link
Collaborator

Hmmm.
It's hard for me to see how this fits in inside of this lib instead of being a standalone crate. We already have a bunch of code to maintain like the spartan re-implementation with SPARK that is "duplicated" from the original Spartan code.
SuperNova is actually a nice use-case to have nova-snark as a dependency. Which will help to also see API visibility concerns. Not sure is the best choice to merge this here rather than have it as a standalone crate that depends on this.

From my viewpoint, supernova is a generalized version of current Nova implementation (Nova is a special case for 1 augmented circuit version) Feel like that, if SuperNova benchmark also show no much different comparing with Nova, Maybe it make sense to have Supernova as a standalone version, in separate PR maybe.

Would like to hear more opinions from author later :)

@CPerezz, regarding a stand-alone crate with nova-snark as a dependency, wouldn't it need access to several internal methods of Nova (e.g., nifs.rs), which is not currently exposed? This would also require nova to move to being a workspace right? Or, is there a different and better approach to multiple packages?

In general, I do like the layering approach, but I will have to look through the new code to see what code will need to be exposed for the layering to work.

@hero78119
Copy link
Contributor Author

hero78119 commented Jul 21, 2023

Updated:
Optimize supernova RecursiveSNARK prove/verify step, circuit input,... to pass by reference almost everywhere, around 10% improvement based on benchmark.

Also run benchmark recursive-snark for Nova vs Supernova-1-augmented-circuit version
In summary, in low constraints case no much different, on high constraints case Supernova is around 5-10% slower. However on largest constraints 1038757 supernova is 10% fast.

Benchmark is on windows-subsystem-linux so perf will be variant. Will be better to run benchmark on other neutral server to get more accurate result.

src/traits/circuit_supernova.rs Outdated Show resolved Hide resolved
src/supernova/circuit.rs Outdated Show resolved Hide resolved
src/supernova/mod.rs Outdated Show resolved Hide resolved
@hero78119 hero78119 force-pushed the supernova branch 2 times, most recently from 9482a6f to 6d76659 Compare July 23, 2023 05:36
@hero78119
Copy link
Contributor Author

hero78119 commented Jul 23, 2023

Updated: fixed a soundness in supernova implementation.

More detail: in Nova, digest value of publicparam is constraint to be the same for each folding round, since there is uniform IVC.

In Supernova Non-Uniform IVC, there will be > 1 augmented circuit, each will have different primary circuit R1CS shape, and in each round of folding, different augmented circuit will be invoked and fold to get {i+1} running instance. Soundness is introduced in this implementation as digest_{i}/digest_{i+1} be witness separately, which imply that it's possible to fill different digest value under SAME augmented index in the intermediated folding rounds.

In latest commit 6d76659, this is fixed by have a unified_digest = compute_digest([pp1, pp2, ...]) and removing digest for each augmented circuit. It equivalently to require filling same unified_digest for each round of folding no matter which augmented circuit is invoked

@srinathsetty
Copy link
Collaborator

Thanks @hero78119 for your prompt updates to the PR! I will review the PR more carefully and provide inputs as necessary.

Copy link

@dajuguan dajuguan left a comment

Choose a reason for hiding this comment

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

In Supernova's paper the verifier need to check every related instance-witness pair (U, W) satisfys relaxed R1CS constrains. However, it seems only the claimed pair is checked here.

// check the satisfiability of the provided `circuit_index` instance
let default_instance = RelaxedR1CSInstance::default(ck_primary, &pp.r1cs_shape_primary);
let default_witness = RelaxedR1CSWitness::default(&pp.r1cs_shape_primary);
let (res_r_primary, (res_r_secondary, res_l_secondary)) = rayon::join(

Choose a reason for hiding this comment

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

In Supernova's paper the verifier need to check every related instance-witness pair (U, W) satisfys relaxed R1CS constrains. However, it seems only the claimed pair is checked here.

Copy link

@wyattbenno777 wyattbenno777 Aug 14, 2023

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

Thanks for your explanation.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@wyattbenno777 I'm confused a bit by your explanation. In SuperNova instantiated over a cycle of curves, if there are \ell different step circuits, we will have \ell running instances (on the first curve), a running instance (on the second curve), and a fresh instance (second curve). We need to check that all \ell + 2 instances are satisfying. In the case of Nova, \ell = 1, so we have 3 instances verified in the main branch.

Choose a reason for hiding this comment

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

In code, we are giving a PrimaryCircuit and a TrivialCircuit to a RunningInstance object. It runs with these as many times as mentioned in the ROM (there is an instance being folded in at each step). In the case of Nova the user is still specifying a primary and a trivial circuit. So should I be explaining this as 3 things to be verified for each opcode? (Primary, Trivial, Thing to be folded on second curve) or as the things the user specifies (Primary and Trivial circuit)?

Choose a reason for hiding this comment

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

Extra info for any future reader: pg. 15 https://eprint.iacr.org/2022/1758.pdf

Copy link
Contributor Author

@hero78119 hero78119 Aug 15, 2023

Choose a reason for hiding this comment

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

They are all verified in their own step. opcode 1 runs x times and is verified, then opcode 2. If the ROM is something like [1,2,1,1] it is run and verified for each step with the code you commented. At the very end a valid SuperNova proof would have both 1 and 2 be valid.

https://github.com/microsoft/Nova/blob/ea14cabf116b6be34fee112737e00a64d8c81f7c/src/supernova/mod.rs#L1017C1-L1018C102

More addon. I think opcode 1 runs x times is indeed a bit confusing haha, because it sounds like we can run opcode 1 x times first then follow by opcode 2 runs y times, ...., this mindset can explain well on Uniform-IVC Nova, but on Supernova, non-uniform with sequence need to be emphasised.

Giving example of sequence [1, 2, 1, 1], Supernova assure

  • i=0, augmented circuit 1 is correctly invoked
  • i=1, augmented circuit 2 is correctly invoked
  • i=2, augmented circuit 1 ...
  • i=3, augmented circuit 1 ...

and supernova need to capture soundness from the maliculous prover giving another sequence [1, 1, 1, 2]. Since order matters, and y1 != y2, for y1=F_1(F_1(F_2(F_1(x)))), y2=F_2(F_1(F_1(F_1(x)))).

Giving there are N opcodes on 2 cycles curve, there will be N running instances (on second curve) + 1 running instance (on first curve) + 1 fresh instance (second curve)

In each round of folding for the sake of efficiency, we can just verify One running instances (on second curve) + 1 running instance (on first curve) + 1 fresh instance (second curve). The One is the opcode being folded in this round. Of course we can verify N running instance (on second curve) all together, but it's not nessesary (even from security perspective) since they will be verified on their own round.

Actually, invoking verify on RecursiveSNARK (here verify is not the same as NIVC verify circuit!) at each round is more for demo purpose. In production I think we can just update RecursiveSNARK and have a final SNARK prove via CompressSNARK is enough (Terminlogies borrow from nova implementation, not paper)

Choose a reason for hiding this comment

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

This is a very well thought out answer!

*CycleNova will change this if/when implemented 😊

@hero78119
Copy link
Contributor Author

Update:

  1. resolve conflicts with main branch
  2. fix typo according to reviews
  3. modify to fit ShapeCS/TestShapeCS usage

Comment on lines 387 to 389
// Now reduce to one is safe, because only 1 of them != G::Zero based on the previous step
// TODO optimize this part to have raw linear-combination on variables to achieve less constraints
let U_to_fold = U?.iter().enumerate().try_fold(empty_U, |agg, (i, U)| {
Copy link
Contributor

@porcuquine porcuquine Aug 16, 2023

Choose a reason for hiding this comment

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

The logic presented here makes me nervous because you are relying on every element of the unselected instances being zero for the selection logic. This is a picky coupling making the correctness of this code very dependent on invariants elsewhere in the code base, when those might not actually be guaranteed.

In fact, as far as I can tell, the empty_U returned from AllocatedRelaxedInstance::alloc(…, None, …) is not actually constrained to be entirely composed of zeros. Therefore, although the defined behavior of the prover's code does do that, nothing guarantees that it will — so a witness containing other values could also be chosen. This means the prover could supply alternate unconstrained values such that U_to_fold can be chosen freely (even though the Nova code base does not choose such values). That seems to violate the intent and requirements of this sub-circuit.

I think the problem would not exist (at least in this form) if you implemented a version of AllocatedRelaxedInstance::alloc that guarantees that what it returns is constrained to be all zeros.

To be very explicit, look at the code:

You can see here, for example, that when inst is None, then Allocated::Point::alloc is being called with None.

    let W = AllocatedPoint::alloc(
      cs.namespace(|| "allocate W"),
      inst
        .get()
        .map_or(None, |inst| Some(inst.comm_W.to_coordinates())),
    )?;

Then in AllocatedPoint::alloc we see that an input of None allocates the 'default infinity point' (including zero for the x and y coordinates), but does not constrain those allocations.


You could also use something like Lurk's multicase gadget, to abstract the selection logic and not entangle it with ad-hoc scalar/curve arithmetic. Although that might be too heavy to pull into Nova just for this purpose, I still believe such non-trivial control logic should be abstracted so it can be reviewed and audited in isolation. It's too easy for an assumption to slip through when having to 'rebuild' understanding of clever constraints embedded in a context where assumptions are hard to check. I think that's actually what happened here, but I needed to check it over more carefully.

For clarity, I'll repeat what I think happened:

  • the computation of U_to_fold depends on all but one of the allocated instances in U being zero (from the perspective of addition) in each of its components.
  • all non-matching (unselected) instances are indeed constrained to be equal to empty_U.
  • empty_U does indeed contain 'zero values', but these are unconstrained.
  • therefore the invariant required to make the calculation of U_to_fold valid does not actually hold.

See if that makes sense to you, and if not, please let me know what I missed.

Copy link
Contributor Author

@hero78119 hero78119 Aug 16, 2023

Choose a reason for hiding this comment

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

Hi, Thanks for catch up this and point out that!

Yes this is an under-constraints, due to there is no constraint for the empty value declaration.
I refered your idea and reviewed again of this logic.
Then I realized a better way to solve it is just rewrite this logic to avoid empty allocation, instead just iterate through the list to get the selected value when index match.

More detail in pseudo code

a := the array
index := target index
res0 = a[0] // default value is first element
res1 := index == 1 ? a[1]:res0
res2 := index == 2 ? a[2]:res1 
res3 := index == 3 ? a[3]:res2 
...
return resN

In particular, this ad-hoc N-reduce-1 optimisation is only applicable here , giving assumption that prover have no choice but need to honestly choose correct witness last_augmented_circuit_index to hint the fold(fresh instance, U[last_augmented_circuit_index]) in NIFS verify circuit, otherwise error will be catch up in the final CompressSNARK check.
Besides, This selection logic also abstract to util. However, by default it will return first element which is not general for other usecase, so just make it dedicated to supernova crate.

Updated with a938f29
Thanks again for the review :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I think this repeated conditional works. Given the size of the values (15 field elements for the allocated relax instance, I think), a multicase will be cheaper at about 6 clauses (i.e. 6 running instances). We don't need to worry about that now, but I think we can create an ergonomic and secure solution for this in general. I'll show my work later, perhaps elsewhere, so we can work through the details. For now, I think doing as you suggest would fairly simply solve the soundness issue.

Copy link
Contributor Author

@hero78119 hero78119 Aug 17, 2023

Choose a reason for hiding this comment

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

Issue follow-up:

Updated a commit 4475884 to fix another relevant soundness issue

Background: there is existing and related soundness cause by last_augmented_circuit_index usage, once malicious prover set it outside range [0, #num_opcode). More explicitly, It will cause no-matching of running instance selection in the code, which means NO UPDATES of running instances U[..]_i+1.

The solution I adopted is to propagate the processed last_augmented_circuit_index through whole circuit. The mindset behind is we already have ad-hoc logic to set last_augmented_circuit_index=0 once outside range. Then circuit need to use this new variable to assure U[last_augmented_circuit_index]_i+1 updated accordingly.
I expected this simple solution just fit well in last_augmented_circuit_index usecase.

Along with new fix, I also document more on this part

// NOTE `last_augmented_circuit_index` is aux without any constraint.
// Reason is prover can only produce valid running instance by folding u into proper U_i[last_augmented_circuit_index]
// However, there is crucial pre-asumption: `last_augmented_circuit_index` must within range [0, num_augmented_circuits)
// otherwise there will be a soundness error, such that maliculous prover can choose out of range last_augmented_circuit_index.
// The soundness error depends on how we process out-of-range condition.
//
// there are 2 possible solution
// 1. range check `last_augmented_circuit_index`
// 2. if last_augmented_circuit_index out of range, then by default select index 0
//
// For current version we choose 2, due to its simplicify and fit well in last_augmented_circuit_index use case.
// Recap, the only way to pass running instance check is folding u into respective U_i[last_augmented_circuit_index]
// So, a circuit implementing to set out-of-range last_augmented_circuit_index to index 0 is fine.
// The illegal running instances will be propogate to later phase and finally captured with "high" probability on the basis of Nova IVC security.
//
// Although above "informal" analysis implies there is no `malleability` on statement (malleability refer `NARK.8 Malleability of Nova’s IVC` https://eprint.iacr.org/2023/969.pdf )
// We need to carefully check whether it lead to other vulnerability.
cc @srinathsetty if you can give more inputs :)

src/supernova/circuit.rs Outdated Show resolved Hide resolved
src/supernova/circuit.rs Outdated Show resolved Hide resolved
@porcuquine
Copy link
Contributor

Hi all, just a quick note, which I've discussed with @hero78119 and @srinathsetty already. Because this work is going to still require further work on the right interfaces and general polishing, we'd like to move it to our (Lurk Lab's) Arecibo Nova fork. That will let us iterate on design, and especially simplify using from lurk-rs.

This PR and valuable discussion will remain open so @srinathsetty can review the crypto and circuits, with the goal of merging to Nova under a feature flag. Meanwhile, we will continue active review and development of this work in arecibo. Once better answers for some of the unknowns/questions posed by the current work have been incubated, the plan is to bring those back as PRs to Nova.

@srinathsetty
Copy link
Collaborator

@hero78119 To reiterate, we intend to merge this PR here in Microsoft/Nova once comments are addressed. I'll do a detailed audit of the circuit code and the entire PR soon. We will merge this code under an optional feature flag "super".

@porcuquine It likely makes sense to wait for this merge to happen before further work elsewhere, to avoid duplication of efforts.

@hero78119
Copy link
Contributor Author

Hi all, just a quick note, which I've discussed with @hero78119 and @srinathsetty already. Because this work is going to still require further work on the right interfaces and general polishing, we'd like to move it to our (Lurk Lab's) Arecibo Nova fork. That will let us iterate on design, and especially simplify using from lurk-rs.

This PR and valuable discussion will remain open so @srinathsetty can review the crypto and circuits, with the goal of merging to Nova under a feature flag. Meanwhile, we will continue active review and development of this work in arecibo. Once better answers for some of the unknowns/questions posed by the current work have been incubated, the plan is to bring those back as PRs to Nova.

Yes I have already discussed with @porcuquine and @huitseeker, it makes sense to incubating and polishing more on interface for applications, and (Lurk Lab's) Arecibo is nice candidate to explore more on it.
Another application I am exploring is integrate Supernova to Powdr and ongoing PR as first folding-based backend.
With more applications it will help to judging APIs, supported usage patterns etc :)

@hero78119 To reiterate, we intend to merge this PR here in Microsoft/Nova once comments are addressed. I'll do a detailed audit of the circuit code and the entire PR soon. We will merge this code under an optional feature flag "super".

@porcuquine It likely makes sense to wait for this merge to happen before further work elsewhere, to avoid duplication of efforts.

Yes it sounds great to me. (+ cc co-author @wyattbenno777 )

@hero78119
Copy link
Contributor Author

hero78119 commented Aug 17, 2023

Updated: Optimize supernova RecursiveSNARK prove/verify step, circuit input,... to pass by reference almost everywhere, around 10% improvement based on benchmark.

Also run benchmark recursive-snark for Nova vs Supernova-1-augmented-circuit version In summary, in low constraints case no much different, on high constraints case Supernova is around 5-10% slower. However on largest constraints 1038757 supernova is 10% fast.

Benchmark is on windows-subsystem-linux so perf will be variant. Will be better to run benchmark on other neutral server to get more accurate result.

Share benchmark result on latest commit vs old commit

drawing

(y-axis millisecond)

Summary

  • old commit vs new commit compare
  • recent few optimisation on Nova: optimise memory/dependency/trait interface ... optimise both Nova/Supernova prove/verify quite a lot, especially on largest constraints 1038757 seems > 4x fast.
  • supernova vs nova no significant performance difference after recently few optimisation

@porcuquine
Copy link
Contributor

@porcuquine It likely makes sense to wait for this merge to happen before further work elsewhere, to avoid duplication of efforts.

We don't mind a little duplication, and bringing this in sooner will let us start iterating on things we need to bring into existence for our own purposes. We'll make sure any outcomes of your review are incorporated.

@srinathsetty
Copy link
Collaborator

Based on the offline discussion, @porcuquine and team will submit a new PR that has been improved in their fork of Nova. We will review the PR once it is stable and submitted.

@hero78119
Copy link
Contributor Author

Another review ongoing PR argumentcomputer/arecibo#48 which is to unlock the full power of Nova to support read-write lookup in cheap cost, leveraging offline-memory-check tricks mentioned in various paper. It's related to this SuperNova PR where the ROM based on this to achieve constant size of Nova state instead of linear to ROM size

@srinathsetty
Copy link
Collaborator

We will close this PR given alternate plans to get this back.

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.

7 participants