Code Monkey home page Code Monkey logo

circom-ecdsa's Introduction

circom-ecdsa

Implementation of ECDSA operations in circom.

Project overview

This repository provides proof-of-concept implementations of ECDSA operations in circom. These implementations are for demonstration purposes only. These circuits are not audited, and this is not intended to be used as a library for production-grade applications.

Circuits can be found in circuits. scripts contains various utility scripts (most importantly, scripts for building a few example zkSNARKs using the ECDSA circuit primitives). test contains some unit tests for the circuits, mostly for witness generation.

Install dependencies

  • Run yarn at the top level to install npm dependencies (snarkjs and circomlib).
  • You'll also need circom version >= 2.0.2 on your system. Installation instructions here.
  • If you want to build the pubkeygen, eth_addr, and groupsig circuits, you'll need to download a Powers of Tau file with 2^20 constraints and copy it into the circuits subdirectory of the project, with the name pot20_final.ptau. We do not provide such a file in this repo due to its large size. You can download and copy Powers of Tau files from the Hermez trusted setup from this repository.
  • If you want to build the verify circuits, you'll also need a Powers of Tau file that can support at least 2^21 constraints (place it in the same directory as above with the same naming convention).

Building keys and witness generation files

We provide examples of four circuits using the ECDSA primitives implemented here:

  • pubkeygen: Prove knowledge of a private key corresponding to a ECDSA public key.
  • eth_addr: Prove knowledge of a private key corresponding to an Ethereum address.
  • groupsig: Prove knowledge of a private key corresponding to one of three Ethereum addresses, and attest to a specific message.
  • verify: Prove that a ECDSA verification ran properly on a provided signature and message. Note that this circuit does not verify that the public key itself is valid. This must be done separately by the user.

Run yarn build:pubkeygen, yarn build:eth_addr, yarn build:groupsig, yarn build:verify at the top level to compile each respective circuit and keys.

Each of these will create a subdirectory inside a build directory at the top level (which will be created if it doesn't already exist). Inside this directory, the build process will create r1cs and wasm files for witness generation, as well as a zkey file (proving and verifying keys). Note that this process will take several minutes (see full benchmarks below). Building verify requires 56G of RAM.

This process will also generate and verify a proof for a dummy input in the respective scripts/[circuit_name] subdirectory, as a smoke test.

Circuits Description

The following circuits are implemented and can be found in circuits/ecdsa.circom.

  • ECDSAPrivToPub: Given a secp256k1 private key, outputs the corresponding public key by computing (private_key) * G where G is the base point of secp256k1.
  • ECDSAVerifyNoPubkeyCheck: Given a signature (r, s), a message hash, and a secp256k1 public key, it follows ecdsa verification algorithm to extract r' from s, message hash and public key, and then compares r' with r to see if the signaure is correct. The output result is 1 if r' and r are equal, 0 otherwise.

The 256-bits input and output are chunked and represented as k n-bits values where k is 4 and n is 64. Please see above examples for concrete usages.

WARNING: Beware that the input to the above circuits should be properly checked and guarded (Lies on the curve, not equal to zero, etc). The purpose of the above circuits is to serve as building blocks but not as stand alone circuits to deploy.

Benchmarks

All benchmarks were run on a 16-core 3.0GHz, 32G RAM machine (AWS c5.4xlarge instance).

pubkeygen eth_addr groupsig verify
Constraints 95444 247380 250938 1508136
Circuit compilation 21s 47s 48s 72s
Witness generation 11s 11s 12s 175s
Trusted setup phase 2 key generation 71s 94s 98s 841s
Trusted setup phase 2 contribution 9s 20s 19s 149s
Proving key size 62M 132M 134M 934M
Proving key verification 61s 81s 80s 738s
Proving time 3s 7s 6s 45s
Proof verification time 1s <1s 1s 1s

Testing

Run yarn test at the top level to run tests. Note that these tests only test correctness of witness generation. They do not check that circuits are properly constrained, i.e. that only valid witnesses satisfy the constraints. This is a much harder problem that we're currently working on!

Circuit unit tests are written in typescript, in the test directory using chai, mocha, and circom_tester. Running all tests takes about 1 hour on our 3.3GHz, 64G RAM test machine. To run a subset of the tests, use yarn test --grep [test_str] to run all tests whose description matches [test_str].

Groupsig CLI Demo

You can run a CLI demo of a zkSNARK-enabled group signature generator once you've built the groupsig keys. Simply run yarn groupsig-demo at the top level and follow the instructions in your terminal.

Acknowledgments

This project was built during 0xPARC's Applied ZK Learning Group #1.

We use a circom implementation of keccak from Vocdoni. We also use some circom utilities for converting an ECDSA public key to an Ethereum address implemented by lsankar4033, jefflau, and veronicaz41 for another ZK Learning Group project in the same cohort. We use an optimization for big integer multiplication from xJsnark.

circom-ecdsa's People

Contributors

antimatter15 avatar ecnerwala avatar gubsheep avatar tloinuy avatar xu3kev avatar yi-sun avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

circom-ecdsa's Issues

Few questions for pubkeygen

Hi, I'm a circom beginner and I have a few questions.

  1. for pubkeygen, if I have a test private key as: 2e1e48c14716af93ca8294875f23285ca7e3bf5804087a981480bdfc4f703a93, how do I change it in input.json when n=64, k=4?

  2. When I write in input.js :"0x2e1e48c14716af93", "0xca8294875f23285c", "0xa7e3bf5804087a98", "0x1480bdfc4f703a93"
    I get in public.json when n=64, k=4:
    "10774290833407669026",
    "9342151812079227025",
    "12927457736662185437",
    "28562608038875422",
    "8791524470153019700",
    "14993995613883526844",
    "10767226978053538038",
    "3669898547168484177".
    How do I know what public key I end up with?

Thank you very much for your answer!

Few questions about assert

I notice that there are many assert statements in this form:

assert(n == 86 && k == 3)

I wonder why should we need these assert, instead of just using const in the template?

Public Key Validation in the Circuit

Hi there!

I'm building a circuit to prove membership in some list of addresses for PSE's e2e-zk-ecdsa project, and I think I need public key validation inside the circuit. This is because we want to prove membership in arbitrary address sets, including ones where some addresses may have no transactions or signed messages which means the public key can't be recovered. This means we can't do public key validation on the set outside the circuit as you reccomend, so it has to be done in the circuit.

I don't think circom-ecdsa has public key validation yet, so I was planning on implementing it and I hoped you guys could validate my approach.
According to Johnson et al, you just need to make sure that:

  1. $\mathcal{Q} \neq \mathcal{O}$ (where $\mathcal{Q}$ is the public key, and $\mathcal{O}$ is the point at infinity).
  2. The coordinates of $\mathcal{Q}$ are in the field
  3. $\mathcal{Q}$ is on the curve
  4. $n\mathcal{Q} = \mathcal{O}$

I think Secp256k1PointOnCurve solves 2 and 3, and Secp256k1ScalarMult partially solves 4, but I'm not sure how to represent $\mathcal{O}$. My guess is that you represent it as (0,0) but I can't quite tell.

I was also considering writing an ecrecover circuit, but I realised that passing a public key as input to ECDSAVerifyNoPubkeyCheck basically does the same thing from the verifier's point of view, at least for set membership.

I'd be curious if you pick any holes in this. Thanks!

Witness not equal to expected output for some registers

Good Morrning!

I am testing out the BigMult template and get weird results some of the time.

I am trying to multiply 2 6000-bit values. For this I am using 48 registers of size 125 bits which I think should be enough since 125 * 48 = 6000. I also tried it with regsiters of size 126 bit, but get the same weird results as described below.

I am testing my circuits in a similar way as in the test_bigmult_22 test in this repository:

var test_bigmult_22 = function (x: [bigint, bigint, bigint]) {
const [a, b, product] = x;
var a_array: bigint[] = bigint_to_array(2, 2, a);
var b_array: bigint[] = bigint_to_array(2, 2, b);
var product_array: bigint[] = bigint_to_array(2, 4, product);
it('Testing a: ' + a + ' b: ' + b, async function() {
let witness = await circuit.calculateWitness({"a": a_array, "b": b_array});
expect(witness[1]).to.equal(product_array[0]);
expect(witness[2]).to.equal(product_array[1]);
expect(witness[3]).to.equal(product_array[2]);
expect(witness[4]).to.equal(product_array[3]);
await circuit.checkConstraints(witness);
});
}
test_cases.forEach(test_bigmult_22);
});

My test looks as follows:

  const REGISTER_BITS = 125;
  const REGISTERS = 48;

  const a = getDecimalOfXBits(6000); // get random decimal of 6000 bits
  const b = getDecimalOfXBits(6000); // get random decimal of 6000 bits

  const c = a * b; // compute the expected result

  var a_array: bigint[] = bigint_to_array(REGISTER_BITS, REGISTERS, a);
  var b_array: bigint[] = bigint_to_array(REGISTER_BITS, REGISTERS, b);
  var expectedOutput: bigint[] = bigint_to_array(
    REGISTER_BITS,
    2 * REGISTERS,
    c
  );

  it(`works`, async function () {
    expect(expectedOutput.length).to.deep.equal(2 * REGISTERS);

    let witness = await circuit.calculateWitness({ a: a_array, b: b_array });

    for (let index = 0; index < 2 * REGISTERS - 1; index++) {
      const isEqual = witness[index + 1] === expectedOutput[index];

      console.log(index, isEqual);
      if (!isEqual) {
        console.log(witness[index + 1], expectedOutput[index]);
      }
    }
  });

The current test does not expect() but just logs whether the witness and expected values are equal for debuggin purposes.

About half of the time, the witness and expected result is equal, so it prints true 96 times.

But some of the time, a couple of registers in the middle are not equal:

0 true
1 true
2 true
3 true
4 true
5 true
6 true
7 true
8 true
9 true
10 true
11 true
12 true
13 true
14 true
15 true
16 true
17 true
18 true
19 true
20 true
21 true
22 true
23 true
24 true
25 true
26 true
27 true
28 true
29 true
30 true
31 true
32 true
33 true
34 true
35 true
36 true
37 true
38 true
39 false
8453337267339809270303850396040759911n 19356679634532029726886917175741188712n
40 false
22525820911752817423614499513842353218n 37595729803002759020032115027698779948n
41 false
13725279342457917460520115904340169812n 17891845866515638600354664638496167753n
42 false
9973396228599699196098832908446363588n 20876738595791919652681899688146792401n
43 false
28857810453351758165157282191245103401n 1392423479484391828653071776130503699n
44 false
38924382843754459701501724032246917683n 11458995869887093364997513617132317994n
45 false
9321750410371435307892782652304237455n 24391659301621376904310398166160664198n
46 false
35958279378704421325565950779171961409n 8492892404837054989061740364057361719n
47 false
23796092187423824810490992486978617074n 38866001078673766406908608000835043817n
48 false
28278521425390209521251244548343473835n 813134451522843184747034133228874145n
49 false
1509344625494845894451540426632336053n 16579253516744787490869155940488762796n
50 false
15152982406989025347913809736513963650n 19319548931046746487748358470669961591n
51 false
33226092938100071450551448907838587400n 33226092938100071450551448907838587412n
52 true
53 true
54 true
55 true
56 true
57 true
58 true
59 true
60 true
61 true
62 true
63 true
64 true
65 true
66 true
67 true
68 true
69 true
70 true
71 true
72 true
73 true
74 true
75 true
76 true
77 true
78 true
79 true
80 true
81 true
82 true
83 true
84 true
85 true
86 true
87 true
88 true
89 true
90 true
91 true
92 true
93 true
94 true

As you can see register 51 is fairly equal, but the other not equal registers differ a lot. So I suspect some carry or overflow issue, but have not been able to figure it out.

Does anyone have any idea?

bigint_to_array

I am unable to reproduce (understand) the results of your test utility function:

function bigint_to_array(n: number, k: number, x: bigint) {
    let mod: bigint = 1n;
    for (var idx = 0; idx < n; idx++) {
        mod = mod * 2n;
    }

    let ret: bigint[] = [];
    var x_temp: bigint = x;
    for (var idx = 0; idx < k; idx++) {
        ret.push(x_temp % mod);
        x_temp = x_temp / mod;
    }
    return ret;
}

Running it on the x-coordinate of Gx gives:
[ 6481385041966929816n, 188021827762530521n, 6170039885052185351n, 8772561819708210092n]

But if I chop it into 64 bit chunks (order by LSB) I obtain:
['6481385041966929816 ', '376043655525061042 ', '12340079770104370702 ', '17545123639416420184 ']

So obviously your function does not split (as I expect it to). Can you help me and explain why you do x_temp % mod ? In what ring do you perform this operation?

Is BigInt arithmetic still needed for BLS12-381?

The original reason for requiring BigInt arithmetic was because the size of base field for the secp256k1 curve is 256 bits long, and the native field for snark circuits has an order that is 254 bits long ref. If one compiles their circom circuit using the BLS12-381 curve (supported by the compiler now) then the order of the base field is 381 bits long.

Does this mean that there is no reason to have the BigInt arithmetic anymore?

Furthermore, does the ECDSA verification algorithm work if one compiles with BLS12-381 as opposed to BN254? I would assume not be cause the original code relied on the automatic modulus arithmetic to the field prime for BN254.

Is there a way in eth_addr.circom the output of the address to be string instead of BigInt?

Hello, I managed to run eth_addr.circom, but the output is in BigInt. Is it possible or are there any template that transform the BigInt to String?

For example the address of a private key in eth_addr.circom template is: 388937196676651799135513985478953745031396740769, but i want it to be represented as: 0x44208c0dcb8f242aa4ac0ed13d14c0ef660a4ea1, or even checksum: 0x44208C0dcb8F242aa4ac0ed13d14C0Ef660A4Ea1

Is that feasible?

Thanks in advance

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.