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

Negate groth16 b (depends on #283) #287

Merged
merged 5 commits into from
Oct 2, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions client/tests/test_zksnark.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@
"0x010bd3c06ed5aeb1a7b0653ba63f413b27ba7fd1b77cb4a403fb15f9fb8735abda93a3c78ad05afd111ea68d016cf99e", # noqa
"0x00255a73b1247dcfd62171b29ddbd271cdb7e98b78912ddf6bfe4723cd229f414f9a47cecd0fec7fb74bf13b22a7395b" # noqa
],
"b": [
"minus_b": [
[
"0x01ada9239a53b094ae15473baaa3649afb46d5330f36f8590df668167dd02aaf0a18602ce42654c3d857c4e5e454ca28", # noqa
"0x00938ce5525864aa135674b048bb68adadfabca2a4cea43ea13b19cacec1ae171986009e916f729a085c04cbe22c4127" # noqa
Expand Down Expand Up @@ -278,7 +278,7 @@
int("0000000000000000000000000000000000255a73b1247dcfd62171b29ddbd271", 16), # noqa
int("cdb7e98b78912ddf6bfe4723cd229f414f9a47cecd0fec7fb74bf13b22a7395b", 16), # noqa
],
# "b":
# "minus_b":
[
int("0000000000000000000000000000000001ada9239a53b094ae15473baaa3649a", 16), # noqa
int("fb46d5330f36f8590df668167dd02aaf0a18602ce42654c3d857c4e5e454ca28", 16), # noqa
Expand All @@ -304,7 +304,7 @@
"0x00b42fc65c4178e23c5ea46791b63f13e01057d957d097d2a7b1b99b921b3db0b519b21bd21f9d5209420de0d39e6ceebcf40df23e8f3dfb3544e3f221687a254f935e7e4eafbded993af4464cf7ca8da374b2cbcc6003fb47bc590dd8eaadc2", # noqa
"0x001f63f85f5e96168363e1c3733094347b9d7d0cbb2b762c65c12b52fe92e126b1f884d331d7b8740dccb383d7565eeb625fc43598bd371801153e0a690e1881f84849653fce01034cb571b78232b5e7aab22f0b3ee089c0b907de8a52628a92" # noqa
],
"b": [
"minus_b": [
"0x00bfb5be9eb134d7118ab1f759b5a801dda03315108848082a6815dab0c88fe253429d65b7b03a7983a6ee353f0f9687de39888afe4fcb106900a10cee2c4c42d6efa2ee7cdc8d82b052fa8e0f79786d2a4847a25d9ca9026a106de6c73c8d18", # noqa
"0x00b9f29ad8d2107e760fa728a897b26b673e3b099e56e7c2bdfe0194cd02f8aff4b799f6f8d07f6e3b7dfc000e02eda978e1993a57337b5e2f2e9e3f024ef30367887ed23cca57cc33d8bfafdfb4c914e085870621cf02bb380b80387162fb40" # noqa
],
Expand Down Expand Up @@ -332,7 +332,7 @@
int("b1f884d331d7b8740dccb383d7565eeb625fc43598bd371801153e0a690e1881", 16), # noqa
int("f84849653fce01034cb571b78232b5e7aab22f0b3ee089c0b907de8a52628a92", 16), # noqa
],
# "b":
# "minus_b":
[
int("00bfb5be9eb134d7118ab1f759b5a801dda03315108848082a6815dab0c88fe2", 16), # noqa
int("53429d65b7b03a7983a6ee353f0f9687de39888afe4fcb106900a10cee2c4c42", 16), # noqa
Expand All @@ -358,7 +358,7 @@
"0xbd3c06ed5aeb1a7b0653ba63f413b27ba7fd1b77cb4a403fb15f9fb8735abda9", # noqa
"0x55a73b1247dcfd62171b29ddbd271cdb7e98b78912ddf6bfe4723cd229f414f9" # noqa
],
"b": [
"minus_b": [
[
"0xda9239a53b094ae15473baaa3649afb46d5330f36f8590df668167dd02aaf0a1", # noqa
"0x38ce5525864aa135674b048bb68adadfabca2a4cea43ea13b19cacec1ae17198" # noqa
Expand All @@ -385,7 +385,7 @@
int("0xbd3c06ed5aeb1a7b0653ba63f413b27ba7fd1b77cb4a403fb15f9fb8735abda9", 16), # noqa
int("0x55a73b1247dcfd62171b29ddbd271cdb7e98b78912ddf6bfe4723cd229f414f9", 16), # noqa
],
# "b":
# "minus_b":
[
int("0xda9239a53b094ae15473baaa3649afb46d5330f36f8590df668167dd02aaf0a1", 16), # noqa
int("0x38ce5525864aa135674b048bb68adadfabca2a4cea43ea13b19cacec1ae17198", 16), # noqa
Expand Down
6 changes: 3 additions & 3 deletions client/zeth/core/zksnark.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def proof_from_proto(
return {
"proof": {
"a": group_point_g1_from_proto(proof.a),
"b": group_point_g2_from_proto(proof.b),
"minus_b": group_point_g2_from_proto(proof.minus_b),
"c": group_point_g1_from_proto(proof.c),
},
"inputs": json.loads(proof.inputs),
Expand All @@ -150,7 +150,7 @@ def proof_to_proto(
proof_proto = extproof_proto.groth16_extended_proof \
# pylint: disable=no-member
group_point_g1_to_proto(proof["a"], proof_proto.a)
group_point_g2_to_proto(proof["b"], proof_proto.b)
group_point_g2_to_proto(proof["minus_b"], proof_proto.minus_b)
group_point_g1_to_proto(proof["c"], proof_proto.c)
proof_proto.inputs = json.dumps(extproof["inputs"])
return extproof_proto
Expand All @@ -164,7 +164,7 @@ def proof_to_contract_parameters(extproof: GenericProof) -> List[List[int]]:
proof = extproof["proof"]
return [
hex_list_to_uint256_list(proof["a"]),
hex_list_to_uint256_list(proof["b"]),
hex_list_to_uint256_list(proof["minus_b"]),
hex_list_to_uint256_list(proof["c"]),
]

Expand Down
15 changes: 12 additions & 3 deletions client/zeth/helper/eth_gen_address.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from zeth.cli.constants import ETH_ADDRESS_DEFAULT, ETH_PRIVATE_KEY_FILE_DEFAULT
from zeth.cli.utils import write_eth_private_key, write_eth_address
from zeth.core.utils import eth_address_from_private_key
from click import command, option
from click import command, option, ClickException
from web3 import Web3 # type: ignore
import os
import sys
Expand All @@ -17,9 +17,11 @@
@command()
@option("--eth-addr-file", help="Address output filename")
@option("--eth-private-key-file", help="Private key output filename")
@option("--use-private-key", help="Use existing private key")
def eth_gen_address(
eth_addr_file: Optional[str],
eth_private_key_file: Optional[str]) -> None:
eth_private_key_file: Optional[str],
use_private_key: Optional[str]) -> None:
"""
Locally generate a new Ethereum private key and address file, and write
them to the current directory.
Expand All @@ -30,7 +32,14 @@ def eth_gen_address(
eth_addr_file = eth_addr_file or ETH_ADDRESS_DEFAULT
eth_private_key_file = eth_private_key_file or ETH_PRIVATE_KEY_FILE_DEFAULT

eth_private_key = gen_eth_private_key()
if use_private_key:
eth_private_key = bytes.fromhex(use_private_key)
else:
eth_private_key = gen_eth_private_key()

if len(eth_private_key) != 32:
raise ClickException("invalid private key length")

write_eth_private_key(eth_private_key, eth_private_key_file)
eth_address = eth_address_from_private_key(eth_private_key)
write_eth_address(eth_address, eth_addr_file)
Expand Down
32 changes: 18 additions & 14 deletions libzeth/snarks/groth16/groth16_api_handler.tcc
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ void groth16_api_handler<ppT>::verification_key_to_proto(
const typename groth16_api_handler<ppT>::snark::verification_key &vk,
zeth_proto::VerificationKey *message)
{
zeth_proto::HexPointBaseGroup1Affine *a =
zeth_proto::HexPointBaseGroup1Affine *alpha =
new zeth_proto::HexPointBaseGroup1Affine();
zeth_proto::HexPointBaseGroup2Affine *b =
zeth_proto::HexPointBaseGroup2Affine *beta =
new zeth_proto::HexPointBaseGroup2Affine();
zeth_proto::HexPointBaseGroup2Affine *d =
zeth_proto::HexPointBaseGroup2Affine *delta =
new zeth_proto::HexPointBaseGroup2Affine();

a->CopyFrom(point_g1_affine_to_proto<ppT>(vk.alpha_g1));
b->CopyFrom(point_g2_affine_to_proto<ppT>(vk.beta_g2));
d->CopyFrom(point_g2_affine_to_proto<ppT>(vk.delta_g2));
alpha->CopyFrom(point_g1_affine_to_proto<ppT>(vk.alpha_g1));
beta->CopyFrom(point_g2_affine_to_proto<ppT>(vk.beta_g2));
delta->CopyFrom(point_g2_affine_to_proto<ppT>(vk.delta_g2));

std::string abc_json_str = accumulation_vector_to_json<ppT>(vk.ABC_g1);

Expand All @@ -37,9 +37,9 @@ void groth16_api_handler<ppT>::verification_key_to_proto(
zeth_proto::VerificationKeyGROTH16 *grpc_verification_key_groth16 =
message->mutable_groth16_verification_key();

grpc_verification_key_groth16->set_allocated_alpha_g1(a);
grpc_verification_key_groth16->set_allocated_beta_g2(b);
grpc_verification_key_groth16->set_allocated_delta_g2(d);
grpc_verification_key_groth16->set_allocated_alpha_g1(alpha);
grpc_verification_key_groth16->set_allocated_beta_g2(beta);
grpc_verification_key_groth16->set_allocated_delta_g2(delta);
grpc_verification_key_groth16->set_abc_g1(abc_json_str);
}

Expand Down Expand Up @@ -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.
Copy link
Contributor

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."

Copy link
Contributor

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)

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor

@AntoineRondelet AntoineRondelet Oct 2, 2020

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:

  1. Negate b in the proof structure
  2. 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 ?

Copy link
Contributor Author

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.

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 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).

Copy link
Contributor Author

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.

Copy link
Contributor

@AntoineRondelet AntoineRondelet Oct 2, 2020

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).

Copy link
Contributor

@AntoineRondelet AntoineRondelet Oct 2, 2020

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.

Copy link
Contributor Author

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.


const libsnark::r1cs_gg_ppzksnark_proof<ppT> &proof_obj =
ext_proof.get_proof();

zeth_proto::HexPointBaseGroup1Affine *a =
new zeth_proto::HexPointBaseGroup1Affine();
zeth_proto::HexPointBaseGroup2Affine *b =
zeth_proto::HexPointBaseGroup2Affine *minus_b =
new zeth_proto::HexPointBaseGroup2Affine();
zeth_proto::HexPointBaseGroup1Affine *c =
new zeth_proto::HexPointBaseGroup1Affine();

a->CopyFrom(point_g1_affine_to_proto<ppT>(proof_obj.g_A));
b->CopyFrom(point_g2_affine_to_proto<ppT>(proof_obj.g_B));
minus_b->CopyFrom(point_g2_affine_to_proto<ppT>(-proof_obj.g_B));
c->CopyFrom(point_g1_affine_to_proto<ppT>(proof_obj.g_C));

std::stringstream ss;
Expand All @@ -95,7 +99,7 @@ void groth16_api_handler<ppT>::extended_proof_to_proto(
message->mutable_groth16_extended_proof();

grpc_extended_groth16_proof_obj->set_allocated_a(a);
grpc_extended_groth16_proof_obj->set_allocated_b(b);
grpc_extended_groth16_proof_obj->set_allocated_minus_b(minus_b);
grpc_extended_groth16_proof_obj->set_allocated_c(c);
grpc_extended_groth16_proof_obj->set_inputs(ss.str());
}
Expand All @@ -107,7 +111,7 @@ libzeth::extended_proof<ppT, groth16_snark<ppT>> groth16_api_handler<
const zeth_proto::ExtendedProofGROTH16 &e_proof =
ext_proof.groth16_extended_proof();
libff::G1<ppT> a = point_g1_affine_from_proto<ppT>(e_proof.a());
libff::G2<ppT> b = point_g2_affine_from_proto<ppT>(e_proof.b());
libff::G2<ppT> b = -point_g2_affine_from_proto<ppT>(e_proof.minus_b());
libff::G1<ppT> c = point_g1_affine_from_proto<ppT>(e_proof.c());

std::vector<libff::Fr<ppT>> inputs;
Expand Down
4 changes: 3 additions & 1 deletion libzeth/snarks/groth16/groth16_snark.tcc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Contributor

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)

Copy link
Contributor Author

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.

out_s << "{\n \"a\": " << point_affine_to_json(proof.g_A)
<< ",\n \"b\": " << point_affine_to_json(proof.g_B)
<< ",\n \"minus_b\": " << point_affine_to_json(-proof.g_B)
<< ",\n \"c\": " << point_affine_to_json(proof.g_C) << "\n}";
return out_s;
}
Expand Down
8 changes: 8 additions & 0 deletions libzeth/tests/core/ec_operation_data_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ template<typename ppT> void operation_test_data()
field_element_write_json(fr_minus_2, std::cout);

std::cout << "\n G1:";
std::cout << "\n 1: ";
point_affine_write_json(g1_1, std::cout);
std::cout << "\n -1: ";
point_affine_write_json(-g1_1, std::cout);
std::cout << "\n 2: ";
point_affine_write_json(g1_2, std::cout);
std::cout << "\n 3: ";
Expand All @@ -63,6 +67,10 @@ template<typename ppT> void operation_test_data()
point_affine_write_json(g1_minus_8, std::cout);

std::cout << "\n G2:";
std::cout << "\n 1: ";
point_affine_write_json(g2_1, std::cout);
std::cout << "\n -1: ";
point_affine_write_json(-g2_1, std::cout);
std::cout << "\n 4: ";
point_affine_write_json(g2_4, std::cout);
std::cout << "\n 8: ";
Expand Down
2 changes: 1 addition & 1 deletion proto/zeth/api/groth16_messages.proto
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ message VerificationKeyGROTH16 {

message ExtendedProofGROTH16 {
HexPointBaseGroup1Affine a = 1;
HexPointBaseGroup2Affine b = 2;
HexPointBaseGroup2Affine minus_b = 2;
HexPointBaseGroup1Affine c = 3;
string inputs = 4;
}
32 changes: 15 additions & 17 deletions zeth_contracts/contracts/Groth16Mixer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ contract Groth16Mixer is BaseMixer {
// Pairing.G1Point A;
uint256 A_X;
uint256 A_Y;
// Pairing.G2Point B;
uint256 B_X0;
uint256 B_X1;
uint256 B_Y0;
uint256 B_Y1;
// Pairing.G2Point minus_B;
uint256 minus_B_X0;
uint256 minus_B_X1;
uint256 minus_B_Y0;
uint256 minus_B_Y1;
// Pairing.G1Point C;
uint256 C_X;
uint256 C_Y;
Expand Down Expand Up @@ -68,7 +68,7 @@ contract Groth16Mixer is BaseMixer {
// This function mixes coins and executes payments in zero knowledge.
function mix(
uint256[2] memory a,
uint256[4] memory b,
uint256[4] memory minus_b,
uint256[2] memory c,
uint256[4] memory vk,
uint256 sigma,
Expand All @@ -91,7 +91,7 @@ contract Groth16Mixer is BaseMixer {
ciphertexts[0],
ciphertexts[1],
a,
b,
minus_b,
c,
input
));
Expand All @@ -103,7 +103,7 @@ contract Groth16Mixer is BaseMixer {

// 2.b Verify the proof
require(
verifyTx(a, b, c, input),
verifyTx(a, minus_b, c, input),
"Invalid proof: Unable to verify the proof correctly"
);

Expand Down Expand Up @@ -302,11 +302,9 @@ contract Groth16Mixer is BaseMixer {
mstore(add(pad, 0x140), sload(add(verifyKey_slot, 4)))
mstore(add(pad, 0x160), sload(add(verifyKey_slot, 5)))

// Write negate(Proof.A) and Proof.B from offset 0x180.
// Write Proof.A and Proof.minus_B from offset 0x180.
mstore(add(pad, 0x180), mload(proof))
let q := 21888242871839275222246405745257275088696311157297823662689037894645226208583
let proof_A_y := mload(add(proof, 0x20))
mstore(add(pad, 0x1a0), sub(q, mod(proof_A_y, q)))
mstore(add(pad, 0x1a0), mload(add(proof, 0x20)))
mstore(add(pad, 0x1c0), mload(add(proof, 0x40)))
mstore(add(pad, 0x1e0), mload(add(proof, 0x60)))
mstore(add(pad, 0x200), mload(add(proof, 0x80)))
Expand All @@ -331,7 +329,7 @@ contract Groth16Mixer is BaseMixer {

function verifyTx(
uint256[2] memory a,
uint256[4] memory b,
uint256[4] memory minus_b,
uint256[2] memory c,
uint256[nbInputs] memory primaryInputs)
internal
Expand All @@ -343,10 +341,10 @@ contract Groth16Mixer is BaseMixer {
Proof memory proof;
proof.A_X = a[0];
proof.A_Y = a[1];
proof.B_X0 = b[0];
proof.B_X1 = b[1];
proof.B_Y0 = b[2];
proof.B_Y1 = b[3];
proof.minus_B_X0 = minus_b[0];
proof.minus_B_X1 = minus_b[1];
proof.minus_B_Y0 = minus_b[2];
proof.minus_B_Y1 = minus_b[3];
proof.C_X = c[0];
proof.C_Y = c[1];

Expand Down