-
Notifications
You must be signed in to change notification settings - Fork 26
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
Negate groth16 b (depends on #283) #287
Conversation
Note |
82cc580
to
afe76e8
Compare
e812c74
to
a3184f9
Compare
a3184f9
to
65e912f
Compare
0f25bce
to
524ae79
Compare
65e912f
to
13f5d4f
Compare
I have mixed feelings about this one. Obtaining
Before proceeding, do you have an estimate on the benefits of this "optimization"? I'd be keen to understand how much speedup/gas is saved to "trade this off" with the points above. In other words, what was the cost of:
(and what would the cost of similar operations be with BW6 params?) |
I'd need to check around to see if there is a trick for efficient multi-word mod, but it seems to me it could be very complex. Note that saving gas is not really the primary motivation in my mind. By removing this operation from the contract we avoid a lot of complex operations which have to be completely re-implemented for each curve. All other operations are done in precompiled contracts, so there is also a certain tidiness to avoiding doing this in solidity.
I think I understand the concern. My thinking here is that it should be possible for this to be done without any impact on the applications. Note that the difference is only in how the data is passed around by zecale (and the corresponding zecale contract simplification) - there should be no difference in the nested or wrapping circuit. Applications are free to generate and verify their own proofs in whatever form they wish. In any case we will need to provide library function / tools to generate the zecale In that sense, I was thinking of it as pretty much equivalent to changing whether we pass
In the trivial case, I'm sure the cost is negligible. Since optimization wasn't really the primary concern I was less concerned about gas savings, but I can imagine that this could get very complex for the multi-word case. Given the points above about considering this as being similar to an internal encoding, let me know if this makes sense in terms of your points. (Obviously it would still make sense to mention this in the docs as an implementation detail - I'm not arguing for leaving it out completely.) |
I see, I got you intention slightly wrong. Your focus really is on making things simpler - which weights a lot in the balance.
They are indeed, but as per my initial comment, I saw that the
I see, why not do this as a new precompile? (as we discussed off-line in the past). I mean, if the point is to avoid any trouble with the Zecale proofs verif (and avoid to manipulate multiple EVM words for this step), this is already making the assumption that the BLS12/BW6 curves arithmetic is available in the client (which makes the assumption of extra pre-compiled). Now, I see that this method has a drawback, which is that it contrasts with the "family of ECC" available set of precompiled (which do not propose a way to negate a point) but we can surely add this and document the client API changes somewhere. |
I should have made that clear in the description. Sorry about that.
Yes. The motivation behind this was for zecale and eventually for using other curves with zeth. What do you think about providing the contract code for verification (for BW6-761 and BLS12-377) as part of Zeth? In this case, applications should be essentially unaffected by this change. Unless I've missed something, everything else should be abstracted. For example, f you look at this commit in zecale clearmatics/zecale@ea4b989 which deals with this change, it only involves the proof verifier in the contract and related test code. Everything else should be hidden behind zeth calls and types (which we can tighten up to make more opaque). Would this be a workable solution? |
@@ -72,17 +72,21 @@ void groth16_api_handler<ppT>::extended_proof_to_proto( | |||
const extended_proof<ppT, groth16_api_handler<ppT>::snark> &ext_proof, | |||
zeth_proto::ExtendedProof *message) | |||
{ | |||
libsnark::r1cs_gg_ppzksnark_proof<ppT> proof_obj = ext_proof.get_proof(); | |||
// Note that the protobuf format exports -b (`minus_b`) to support a small | |||
// optimization in on-chain verification code. |
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.
Shall this one be rephrased to reflect that the proposed changes are mostly for simplicity matters?
Maybe something like: "// Note that the protobuf format exports -b (minus_b
) to simplify (and slightly optimize) the on-chain verification code."
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.
Additionally, could you make sure to document this on top of the function declaration in the hpp
file using Doxygen's syntax to make sure this shows up in the generated docs? (same for _from_proto
to explain that minus_b
is negated back to b
)
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.
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.
BTW, why not doing the negation on the python client in the proof_to_contract_parameters
? Wouldn't that save the need to modify the cpp API?
For now, the cpp API is modified because of something happening on-chain. That'll be quite odd for someone using libzeth
outside of a blockchain context to exchange -b
instead of b
. I think that modifying proof_to_contract_parameters
to:
- Negate b in the proof structure
- Convert to EVM words
would keep changes to the right scope. This needs some "curve specific" operations on the client - which are not supported though (for now the client pretty much "relays" information between components). Did you think about that @dtebbs ?
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.
BTW, why not doing the negation on the python client in the proof_to_contract_parameters?
Yes, I did consider that. I can see the point - we limit the scope of this change to the encoding made by the client for the verification code on the contract. The main reason I didn't do that here is because the client would then need to know which curve is being used, and the value of q
for that curve. Currently it does not have that information and just uses a single scheme that converts field elements and curve points to evm words.
We could do that. It would require some CURVE
config in the client, and the curve constants (or maybe the client could query the server to get q
, or to actually perform the negation) but it's definitely possible.
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.
(This would involve negating vk.delta
and one of vk.alpha
or vk.beta
. The contract would then contain a hard-coded $[-1]_2$
instead of $[1]_2$
in the verifier, or it could be passed a constructor parameter).
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.
One other thought: if we add an RPC method it could be GetProcessedVerificaitonKey
which returns the VK with extra data: -beta
, -delta
and -1
in G2.
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 main reason I didn't do that here is because the client would then need to know which curve is being used [...]
Exact. I think this would be quite a nice thing to have actually (some curve config on the client), here are the pros/cons I can think of (please add any that comes to mind)
Pros:
- By having access to some "remarkable params" (field characteristics etc), we can not only do the negation above, but also support some tests on the received arguments (type/"range" check received elements if needed - this btw needs to be systematically done on the contract to avoid any potential attacks leveraging the mismatch in the length of EVM words and field elements -> that's bad because this advocates to define partial functions (and an API with partial functions is.. well.. annoying to say the least), but that's a requirement for some minimal type safety on the contract)
- We keep consistent APIs and limit changes to the smallest scope possible
Cons:
- Needs to track the curve config on the client (which exacerbates the "configuration logistic" where several params need to be duplicated to cpp code, python code and solidity code -> this may be fixed later on by building some tooling/scripts (like a lightweight
configure
script to generate each components' config for us to lessen this issue - but out of scope now))
To this point btw, we can surely avoid such troubles by extending the server's API to have a "fetchServerConfig" endpoint that fetches the server configuration/protocol params from the server and configures the client as per the fetched data. That way the curve/pairing group name (i.e. ALT_BN128
, BLS12_377
etc) can be part of the received message, and upon reception of the init request
response the client can instantiate CurveParams
by selecting the right local curve configuration and proceed (i.e. as soon as the connection is established with the server/as soon as the client is started -> it fetches the server config).
This seems to be the right tradeoff. It keeps the client generic (works with any pairing group), while keeping things fairly simple. I can't think about any meaningful use-case where the client keeps processing proofs coming from several servers configured using different curves (so adding the "curve" to the extended_proof
object - which would solve against that - seems completely overkilled for instance).
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.
Oh, I just read your comment:
We rely on the prover server being available at deployment, so we could add an RPC method to get the curve parameters and the client could just use those for a one-shot transform. Proofs can then remain unchanged with b.
For some reason it didn't show up when I wrote my comment (maybe I forgot to refresh the page before commenting, not sure). Anyway, seems to align with the point I made at the end of my previous comment. That looks like a solution.
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.
Explained minus_b
in the doc comments for this class. Ideally it would be on the protobuf type declaration, but that may not be captured by doc generation so added on the class.
@@ -132,8 +132,10 @@ template<typename ppT> | |||
std::ostream &groth16_snark<ppT>::proof_write_json( | |||
const typename groth16_snark<ppT>::proof &proof, std::ostream &out_s) | |||
{ | |||
// JSON matches the protobuf format, where we export -b instead of b, to | |||
// support efficient verification in contracts. |
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. I'd be clear on the fact that these changes are mostly for simplicity matters (as per the main thread discussion)
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.
Added a note on the doc comments for this function.
As discussed off-line @dtebbs this PR LGTM overall (modulo the few comments above). I saw that you negate |
(After offline discussion).
We could also consider modifying this PR (or creating issues) in order to:
|
Change the groth16 proof protobuf data object to hold
minus_b
instead ofb
, so that this does not have to be done in the contract when evaluating the pairing.'This makes it much easier to support groth16 verification over more pairings.
Depends on #283