diff --git a/.gitignore b/.gitignore index ffd2165..63ed415 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,12 @@ remix-compiler.config.js *.sym *.ptau verify_js/ -verify_cpp/ \ No newline at end of file +verify_cpp/ +.idea +batchverify_js/ +batchverify_cpp/ +*.zkey +circuits/batchverifier.sol +proof.json +public.json +verification_key.json \ No newline at end of file diff --git a/batchVerify-README.md b/batchVerify-README.md new file mode 100644 index 0000000..d68be83 --- /dev/null +++ b/batchVerify-README.md @@ -0,0 +1,248 @@ +# Circom Ed25519 + +Curve operations and signature verification for Ed25519 digital signature scheme in circom + +**WARNING:** This is a research project. It has not been audited and may contain bugs and security flaws. This implementation is NOT ready for production use. + +The circuits follow the reference implementation from [IETF RFC8032](https://datatracker.ietf.org/doc/html/rfc8032#section-6) + +## 1. Installing dependencies +- `npm install -g snarkjs` +- Install Rust: `curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh` +- Clone and install circom - [circom docs](https://docs.circom.io/getting-started/installation/) +- If you want to build the `verify` circuit, you'll need to download a Powers of Tau file with `2^22` constraints and copy it into the `circuits` subdirectory of the project, with the name `pot22_final.ptau`. You can download Powers of Tau files from the Hermez trusted setup from [this repository](https://github.com/iden3/snarkjs#7-prepare-phase-2) + +## 2. Run the project + +### 2.1. Clone code and install dependencies + +```bash= +git clone this-project-code +``` + +```bash= +cd /path/to/this/project/folder/ +``` + +Run command to install package dependencies + +```bash= +npm install +``` + +### 2.2. Compile circuits + +Enter the `circuits` directory, we can compile the circuit with the following command: + +```bash= +circom batchverify.circom --r1cs --wasm --sym --c +``` + +With these options we generate three types of files: + +- `--r1cs`: it generates the file `batchverify.r1cs` that contains the [R1CS constraint system](https://docs.circom.io/background/background#rank-1-constraint-system) of the circuit in binary format. +- `--wasm`: it generates the directory `batchverify_js` that contains the Wasm code (batchverify.wasm) and other files needed to generate the [witness](https://docs.circom.io/background/background#witness). +- `--sym` : it generates the file batchverify.sym , a symbols file required for debugging or for printing the constraint system in an annotated mode. +- `--c` : it generates the directory batchverify_cpp that contains several files (batchverify.cpp, batchverify.dat, and other common files for every compiled program like main.cpp, MakeFile, etc) needed to compile the C code to generate the witness. + +`Notice`: If you encounter `JavaScript Heap Out of Memory Error` during operation, please refer to the [documentation](https://www.makeuseof.com/javascript-heap-out-of-memory-error-fix/) to solve it. + +### 2.3. Computing the witness + +Enter in the directory `batchverify_js`, add the input in a file `batchinput.json` (Simply copy the `batchinput.json` in the root directory of the project) and execute: + +```bash= +node generate_witness.js batchverify.wasm batchinput.json witness.wtns +``` + +### 2.4. Proving circuits + +#### 2.4.1. Powers of Tau + +First, re-enter the `circuits` directory, we start a new "powers of tau" ceremony: + +```bash= +snarkjs powersoftau new bn128 23 pot23_0000.ptau -v +``` + +Then, we contribute to the ceremony: + +```bash= +snarkjs powersoftau contribute pot23_0000.ptau pot23_0001.ptau --name="First contribution" -v +``` + +Now, we have the contributions to the powers of tau in the file `pot23_0001.ptau` and we can proceed with the `Phase 2`. + +#### 2.4.2. Phase 2 + +The **phase 2** is **circuit-specific**. Execute the following command to start the generation of this phase: + +```bash= +snarkjs powersoftau prepare phase2 pot23_0001.ptau pot23_final.ptau -v +``` + +Next, we generate a `.zkey` file that will contain the proving and verification keys together with all phase 2 contributions. Execute the following command to start a new zkey: + +```bash= +snarkjs groth16 setup batchverify.r1cs pot23_final.ptau batchverify_0000.zkey +``` +**Notice:** Run `export NODE_OPTIONS=--max-old-space-size=8192` to Fix **JavaScript Heap Out of Memory Error** + +Contribute to the phase 2 of the ceremony: + +```bash= +snarkjs zkey contribute batchverify_0000.zkey batchverify_0001.zkey --name="1st Contributor Name" -v +``` + +Export the verification key: + +```bash= +snarkjs zkey export verificationkey batchverify_0001.zkey verification_key.json +``` + +#### 2.4.3. Generating a Proof + +Once the witness is computed and the trusted setup is already executed, we can **generate a zk-proof** associated to the circuit and the witness: + +```bash= +use snarkjs: +snarkjs groth16 prove batchverify_0001.zkey ./batchverify_js/witness.wtns proof.json public.json +``` + +This command generates a [Groth16](https://eprint.iacr.org/2016/260) proof and outputs two files: + +- `proof.json`: it contains the proof. +- `public.json`: it contains the values of the public inputs and outputs. + +This step can use `rapidsnark` to speed up the generation of zkSnark proofs, please refer to the documentation of [rapidsnark](https://github.com/iden3/rapidsnark). + +So you can replace snarkjs command by this one: + +```bash= +./package/bin/prover + +example: +./package/bin/prover batchverify_0001.zkey ./batchverify_js/witness.wtns proof.json public.json +``` + +#### 2.4.4. Verifying a Proof + +To **verify the proof**, execute the following command: + +```bash= +snarkjs groth16 verify verification_key.json public.json proof.json +``` + +The command uses the files `verification_key.json` we exported earlier,`proof.json` and `public.json` to check if the proof is valid. If the proof is valid, the command outputs an `OK`. + +A valid proof not only proves that we know a set of signals that satisfy the circuit, but also that the public inputs and outputs that we use match the ones described in the `public.json` file. + +#### 2.4.5. Verifying from a Smart Contract + +It is also possible to generate a **Solidity verifier** that allows **verifying proofs on Ethereum blockchain**. + +First, we need to generate the Solidity code using the command: + +```bash= +snarkjs zkey export solidityverifier batchverify_0001.zkey batchverifier.sol +``` + +This command takes validation key `batchverify_0001.zkey` and outputs Solidity code in a file named `batchverifier.sol`. You can take the code from this file and cut and paste it in Remix. + +The `Verifier` has a `view` function called `verifyProof` that returns `TRUE` if and only if the proof and the inputs are valid. To facilitate the call, you can use `snarkJS` to generate the parameters of the call by typing: + +```bash= +snarkjs generatecall +``` + +You can get something like the following in return: + +```json= +["0x19721bf1e6a40b14b136daba5af87aff4f1d9c614dd12796c79f447f0dcdddba", "0x2a4bc5255eae8cfc33d8bce60244258b5160377d73f9f292b27842246d144f70"],[["0x23807555c654ec10dd9de1184fd585b0f235c64c83b0d2b9dc4f7f3934b083d6", "0x2af0c2dd9b462b7b63c0682646422b526d9b82608456ef09eadfd5bf2e3e9fa2"],["0x1688a3c4ded94b6d5958886feeeb697201fde9e6919f5bd6408a865af46de162", "0x07677f9ee01dcd2637a1e690caa8c40db5488ac0c45ba33bc5489c700db676b0"]],["0x27f0ddc72eca4525c42b39feaac1c159909cc09918beb7f63af68451750af042", "0x04ae2a4c2ef49ac3f01a0240eab924079406f552746cf4d18e32654d39101a79"],["0x0000000000000000000000000000000000000000000000000000000000000000"] +``` +Cut and paste the output of the command to the parameters field of the `verifyProof` method in Remix. If everything works fine, this method should return `TRUE`. You can try to change just a single bit of the parameters, and you will see that the result is verifiable `FALSE`. + +## Appendix + +### 1. Inputs explanation +`msg` is the data for the signature + +`R8` is the first 256 bits of the signature (LSB to MSB) + +`S` is the first 255 bits of the last 256 bits of the signature (LSB to MSB) + +`A` is the public key in binary (LSB to MSB) + +`PointA` is the point representing the public key on the elliptic curve (encoded in base 2^85 for brevity) + +`PointR` is the point representing the R8 value on the elliptic curve (encoded in base 2^85) + +The [algorithm](https://datatracker.ietf.org/doc/html/rfc8032#section-6) we follow only takes in `A` and `R8` in binary form, and is decompressed to get `PointA` and `PointR` respectively. However, decompression is an expensive algorithm to perform in a circuit. On the other hand, compression is cheap and easy to implement. So, we use a nifty little trick to push the onus of providing both on the `prover` and perform equality checks after compressing the points within the circuit. [Ref](https://github.com/Electron-Labs/ed25519-circom/blob/532f638b4d6ae4684a1f0907df6c92676f0ae8df/circuits/verify.circom#L57) + +You can find all helper functions to change encodings from well-known formats to circuit friendly formats [here](https://github.com/Electron-Labs/ed25519-circom/blob/master/test/utils.js) + +### 2. Input.json format + +In your JSON file, you'll structure the input data like this: + +` +{ + "msg": [ + [/* Your first message bytes here */], + [/* Your second message bytes here */], + [/* Your third message bytes here */], + [/* Your fourth message bytes here */] + ], + "msgLengths": [ + /* Lengths of your messages corresponding to msg array */ + ], + "S": [ + [/* Your S values for message 1 */], + [/* Your S values for message 2 */], + [/* Your S values for message 3 */], + [/* Your S values for message 4 */] + ], + "PointA": [ + [/* Your PointA values for message 1 */], + [/* Your PointA values for message 2 */], + [/* Your PointA values for message 3 */], + [/* Your PointA values for message 4 */] + ], + "PointR": [ + [/* Your PointR values for message 1 */], + [/* Your PointR values for message 2 */], + [/* Your PointR values for message 3 */], + [/* Your PointR values for message 4 */] + ] +} +` + +### 2. Circuit information + +#### 2.1. `1 message` + +```bash= +template instances: 216 +non-linear constraints: 1307374 +linear constraints: 0 +public inputs: 0 +public outputs: 1 +private inputs: 295 +private outputs: 0 +wires: 1380186 +labels: 13089531 +``` + +#### 2.2. `2 messages` + +```bash= +template instances: 216 +non-linear constraints: 2614748 +linear constraints: 0 +public inputs: 0 +public outputs: 1 +private inputs: 590 +private outputs: 0 +wires: 2760371 +labels: 26179059 +``` \ No newline at end of file diff --git a/batchinput.json b/batchinput.json new file mode 100644 index 0000000..dea91d8 --- /dev/null +++ b/batchinput.json @@ -0,0 +1,648 @@ +{ + "msg": [ + [ + "1", + "0", + "0", + "0", + "1", + "0", + "0", + "0", + "1", + "1", + "1", + "0", + "0", + "1", + "0", + "0" + ], + [ + "0", + "1", + "0", + "0", + "1", + "0", + "0", + "0", + "1", + "1", + "1", + "0", + "0", + "1", + "0", + "0" + ] + ], + "S": [ + [ + "0", + "0", + "0", + "0", + "1", + "0", + "1", + "0", + "1", + "0", + "1", + "1", + "1", + "1", + "0", + "1", + "1", + "1", + "0", + "1", + "0", + "1", + "0", + "1", + "0", + "1", + "0", + "1", + "1", + "1", + "1", + "0", + "0", + "1", + "1", + "0", + "1", + "1", + "1", + "0", + "0", + "0", + "0", + "0", + "1", + "1", + "1", + "0", + "0", + "0", + "0", + "1", + "0", + "0", + "0", + "0", + "0", + "0", + "1", + "0", + "1", + "1", + "0", + "1", + "0", + "0", + "1", + "1", + "0", + "0", + "0", + "0", + "1", + "1", + "0", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "0", + "0", + "1", + "1", + "1", + "0", + "1", + "1", + "0", + "0", + "0", + "0", + "0", + "1", + "0", + "1", + "1", + "0", + "1", + "0", + "1", + "0", + "0", + "1", + "0", + "1", + "1", + "1", + "1", + "0", + "0", + "0", + "1", + "0", + "1", + "0", + "0", + "1", + "1", + "0", + "0", + "1", + "0", + "1", + "0", + "1", + "0", + "0", + "0", + "0", + "1", + "1", + "1", + "1", + "1", + "1", + "0", + "1", + "1", + "1", + "0", + "1", + "1", + "0", + "1", + "1", + "0", + "0", + "0", + "1", + "1", + "0", + "0", + "1", + "0", + "1", + "0", + "1", + "0", + "1", + "1", + "1", + "1", + "1", + "0", + "1", + "0", + "0", + "1", + "0", + "1", + "0", + "1", + "1", + "1", + "0", + "1", + "0", + "1", + "0", + "0", + "0", + "0", + "1", + "1", + "0", + "1", + "1", + "0", + "1", + "1", + "0", + "1", + "0", + "0", + "0", + "1", + "0", + "1", + "0", + "0", + "1", + "1", + "1", + "1", + "0", + "0", + "0", + "0", + "0", + "1", + "0", + "0", + "0", + "0", + "1", + "1", + "0", + "1", + "1", + "0", + "1", + "1", + "0", + "1", + "1", + "1", + "0", + "1", + "1", + "1", + "0", + "0", + "0", + "1", + "1", + "1", + "0", + "1", + "1", + "0", + "0", + "1", + "0", + "0", + "0", + "0", + "1", + "0", + "0", + "0" + ], + [ + "1", + "1", + "1", + "0", + "1", + "1", + "1", + "1", + "1", + "0", + "0", + "1", + "0", + "1", + "0", + "1", + "0", + "0", + "0", + "1", + "0", + "1", + "0", + "1", + "0", + "0", + "0", + "1", + "1", + "0", + "1", + "0", + "1", + "1", + "1", + "1", + "0", + "0", + "0", + "1", + "0", + "1", + "0", + "1", + "1", + "0", + "1", + "0", + "1", + "1", + "1", + "0", + "0", + "0", + "1", + "1", + "0", + "0", + "1", + "1", + "0", + "1", + "1", + "1", + "0", + "0", + "0", + "1", + "1", + "1", + "1", + "1", + "0", + "1", + "1", + "0", + "0", + "1", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "1", + "1", + "0", + "0", + "0", + "1", + "1", + "1", + "1", + "1", + "0", + "1", + "0", + "0", + "0", + "1", + "0", + "1", + "1", + "1", + "1", + "1", + "1", + "0", + "1", + "0", + "1", + "1", + "1", + "1", + "1", + "0", + "0", + "0", + "0", + "1", + "1", + "0", + "0", + "1", + "1", + "0", + "0", + "1", + "1", + "1", + "0", + "0", + "1", + "1", + "1", + "0", + "1", + "0", + "1", + "1", + "1", + "1", + "1", + "1", + "0", + "0", + "1", + "1", + "0", + "1", + "1", + "1", + "0", + "1", + "1", + "0", + "1", + "1", + "0", + "0", + "1", + "0", + "0", + "1", + "1", + "0", + "1", + "0", + "0", + "0", + "0", + "0", + "1", + "0", + "0", + "1", + "1", + "1", + "1", + "1", + "0", + "0", + "0", + "1", + "1", + "0", + "0", + "0", + "0", + "1", + "0", + "0", + "1", + "0", + "1", + "1", + "1", + "0", + "0", + "1", + "0", + "0", + "0", + "1", + "1", + "1", + "0", + "1", + "1", + "1", + "0", + "1", + "1", + "0", + "1", + "1", + "1", + "1", + "0", + "0", + "0", + "0", + "0", + "1", + "0", + "1", + "1", + "1", + "1", + "1", + "1", + "0", + "1", + "1", + "0", + "1", + "1", + "1", + "0", + "0", + "1", + "0", + "0", + "0", + "1", + "1", + "0", + "1", + "1", + "0", + "1", + "0", + "0", + "0" + ] + ], + "PointA": [ + [ + [ + "37652472252085692601914500", + "2134314645501001179644315", + "28263968794341966431578413" + ], + [ + "7183047208620788246337100", + "11350728239938651501540481", + "13277658608195690215844670" + ], + [ + "1", + "0", + "0" + ], + [ + 0, + "0", + "0" + ] + ], + [ + [ + "23549079571650490585497656", + "3389670830618621270575507", + "10956456381602395855480411" + ], + [ + "22797961376718682557418945", + "1947433039806898240850961", + "29679241145063336911104688" + ], + [ + "1", + "0", + "0" + ], + [ + 0, + "0", + "0" + ] + ] + ], + "PointR": [ + [ + [ + "19603171113558873411688482", + "10969873335407820799383509", + "4062800487930311045860688" + ], + [ + "35082842449208059698752010", + "36378939800694499592000324", + "37595303938783294524336384" + ], + [ + "1", + "0", + "0" + ], + [ + 0, + "0", + "0" + ] + ], + [ + [ + "16663884083139836792793707", + "20831526389766238965047449", + "22586597329225661966291169" + ], + [ + "17957064606435147155344893", + "21054090823763800527957732", + "15842476622047180346041877" + ], + [ + "1", + "0", + "0" + ], + [ + 0, + "0", + "0" + ] + ] + ] +} \ No newline at end of file diff --git a/circuits/batchverify.circom b/circuits/batchverify.circom index 5e82f6f..be220cf 100644 --- a/circuits/batchverify.circom +++ b/circuits/batchverify.circom @@ -3,65 +3,65 @@ pragma circom 2.0.0; include "./verify.circom"; include "../node_modules/circomlib/circuits/gates.circom"; include "../node_modules/circomlib/circuits/sha256/sha256.circom"; -// include "../node_modules/circomlib/circuits/sha256/shift.circom"; include "../node_modules/circomlib/circuits/bitify.circom"; template BatchVerify(n, m) { - signal input msg[n]; - - signal input A[m][256]; - signal input R8[m][256]; + // m messages each contain n bits + signal input msg[m][n]; + + // m signatures, each contain + // first 255 bits of the last 256 bits of the signature (LSB to MSB) signal input S[m][255]; + // m PointA, + // each is a point representing the public key on the elliptic curve + // encoded in base 2^85 signal input PointA[m][4][3]; + // m PointR, + // each is the point representing the R8 value on the elliptic curve + // encoded in base 2^85 signal input PointR[m][4][3]; - signal output hash[2]; signal output verified; var i; var j; var k; + // Ensure that the msg input contains valid binary, boolean data + for (i = 0; i < m; i++) { + for (j = 0; j < n; j++) { + msg[i][j] * (msg[i][j] - 1) === 0; + } + } + + // Components for individual verifiers component verifiers[m]; - component sha256 = Sha256(256 * m); for (i=0; i= 0); + assert(PointR[i][j][k] >= 0); + + verifiers[i].PointA[j][k] <== PointA[i][j][k]; + verifiers[i].PointR[j][k] <== PointR[i][j][k]; + } + } } component verifiedNum = Bits2Num(m); @@ -69,7 +69,13 @@ template BatchVerify(n, m) { verifiedNum.in[i] <== verifiers[i].out; } - hash[0] <== hashNum1.out; - hash[1] <== hashNum2.out; verified <== verifiedNum.out; } + +// 4 messages each contain 16 bits +// n = 16, m = 4 in this example +component main {public [msg, S, PointA, PointR]} = BatchVerify(16, 4); +// component main {public [msg, S]} = Ed25519Verifier(16); + +// example for n = 128, m = 64 +// component main {public [msg,S, PointA, PointR} = BatchVerify(128, 64); diff --git a/circuits/binadd.circom b/circuits/binadd.circom index 195d30a..87ca124 100644 --- a/circuits/binadd.circom +++ b/circuits/binadd.circom @@ -1,113 +1,126 @@ pragma circom 2.0.0; +// Computes `out := in[0] + in[1]`. The inputs will be constrained to be only +// bits and have `nBits` length, and the output will contain an additional add +// carry bit, resulting in `nBits+1` + +// MM for audit: +// The BinAdd template performs binary addition of two bit arrays of equal length. +// `nBits`: The number of bits in each input bit array. +// +// This template encapsulates the binary addition operation using a series of full adders. +// Each full adder component computes the sum of two corresponding bits and a carry bit from +// the previous computation. The result is a bit array of length `nBits + 1` to accommodate +// the potential carry-out bit from the most significant bit addition. +// +// 1. Witness Inputs: +// - `in[0][nBits]`: First binary number as an array of bits. +// - `in[1][nBits]`: Second binary number as an array of bits. +// +// 2. Computed Witness: +// - `out[nBits + 1]`: The sum of the two binary numbers, with an extra bit for carry-out. +// +// 3. Constraints: +// - The inputs must be binary (0 or 1). +// - The full adder ensures that each bit of the output, except the last carry-out bit, +// is a binary value resulting from the addition of corresponding bits and carry. + template BinAdd(nBits){ + // Ensure nBits is a non-negative integer + assert(nBits > 0); + + // In/Out are raw bits of the added number; out will have an extra bit for the carry-out signal input in[2][nBits]; signal output out[nBits+1]; - var i; - component addking[nBits]; - for(i=0;i nBits); - - signal input in1[mBits]; - signal input in2[nBits]; - - signal output out[mBits+1]; - var i; - component addking[nBits]; - component addcarry[mBits-nBits]; - - for(i=0;i= 0); + + // Define two input arrays of nBits each, The input value is 0 or 1 signal input in[2][nBits]; - signal output out[nBits]; + + // Define an output array of nBits+1 + signal output out[nBits + 1]; var i; component add1ToFlipped = BinAdd(nBits); - add1ToFlipped.in[0][0] <== 1 + in[1][0] - 2*in[1][0]; + add1ToFlipped.in[0][0] <== 1 - in[1][0]; add1ToFlipped.in[1][0] <== 1; for (i=1; i 0); + + // Calculate the total number of outputs based on inputs + var n = 2; + var numOutputs = calculateNumOutputs(m, n, 85); var i; var j; - var power = 2 ** base; + var power = 2 ** 85; - signal input in[n][m]; - signal psum[m]; + // Define input signal + signal input a[m]; + // Define input signal + signal input b[m]; + // Define carry signals for each output signal carry[numOutputs]; + // Define output signals (numOutputs bits) signal output out[numOutputs]; + // Create an array of IsInRange components for each input bit component lt1[n][m]; + component lt2[numOutputs]; + + for (j = 0; j < m; j++) { + lt1[0][j] = IsInRange(85); + lt1[0][j].in <== a[j]; + lt1[0][j].out === 1; - for(i = 0; i < n; i++) { - for (j = 0; j < m; j++) { - lt1[i][j] = LessThanPower(base); - lt1[i][j].in <== in[i][j]; - lt1[i][j].out === 1; - } + lt1[1][j] = IsInRange(85); + lt1[1][j].in <== b[j]; + lt1[1][j].out === 1; } - var acc; - for (j = 0; j < m; j++){ - acc = 0; + // Initialize the first carry as 0 + carry[0] <== 0; - for (i = 0; i < n; i++) { - acc += in[i][j]; - } + // Perform the addition and carry propagation + // reference: + // https://docs.circom.io/circom-language/basic-operators/#examples-using-operators-from-the-circom-library - psum[j] <== acc; - } - - carry[0] <== 0; for (i = 0; i < m; i++){ - out[i] <-- (psum[i] + carry[i]) % power; - carry[i + 1] <-- (psum[i] + carry[i]) \ power; - psum[i] + carry[i] === carry[i + 1] * power + out[i]; + out[i] <-- (a[i] + b[i] + carry[i]) % power; + carry[i + 1] <-- (a[i] + b[i] + carry[i]) \ power; + + lt2[i] = IsInRange(85); + lt2[i].in <== out[i]; + lt2[i].out === 1; + + carry[i + 1] * (carry[i + 1] - 1) === 0; + a[i] + b[i] + carry[i] === carry[i + 1] * power + out[i]; } + // Continue carry propagation for the remaining outputs for (i = m; i < numOutputs-1; i++) { out[i] <-- carry[i] % power; carry[i + 1] <-- carry[i] \ power; + + lt2[i] = IsInRange(85); + lt2[i].in <== out[i]; + lt2[i].out === 1; + + // constrain the computation to be correct + carry[i + 1] * (carry[i + 1] - 1) === 0; + carry[i] * power === carry[i + 1] + out[i]; } + // The last output is the final carry out[numOutputs-1] <== carry[numOutputs-1]; +} +// Template for Chunked Adder for chunks of 85 bits for irregular chunk count. +// m: Chunks count for input a +// n: Chunks count for input b +// Assumes m > n +// +// For a chunked add of 3x85 bits chunks for a and 2x85 for b +// +// 1. Witness input +// +// a. a := [α, β, γ] +// b. b := [δ, ε] +// +// 2. Computed witnesses +// +// a. carry := [ +// 0, +// (α + δ) / 2^85, +// (β + ε + (α + δ) / 2^85) / 2^85, +// (γ + (β + ε + (α + δ) / 2^85) / 2^85) / 2^85 +// ] +// b. out := [ +// (α + δ) % 2^85, +// (β + ε + carry[1]) % 2^85, +// (γ + carry[2]) % 2^85, +// carry[3] +// ] +// +// 3. Constraints +// +// a. for i, a[i] < 2^85 && b[i] < 2^85 +// b. for i, carry[i] is boolean +// c. for i, out[i] < 2^85 +// d. for i, a[i] + b[i] + carry[i] === carry[i + 1] * 2^85 + out[i] +// +// - Constraint `3.a` will assert that all inputs are in range. +// - Constraint `3.b` will enforce the rule that the addition of two +// fixed-length bit numbers can only generate booleans as carry. +// - Constraint `3.c` will enforce that `out` is in range. +// - Constraint `3.d` will satisfy the chunk addition equation. Note that +// illegal overflow isn't possible since the carry is boolean and all arguments +// are in range. +// - The output will contain an additional element, compared to the input +// chunks length, reserved for the carry. +template ChunkedAdderIrregular85(m, n){ + assert(m > n); + + // Calculate the total number of outputs based on inputs + var i; + var j; + var numOutputs = calculateNumOutputs(m, 2, 85); + var power = 2 ** 85; + + // Define input signal + signal input a[m]; + // Define input signal + signal input b[n]; + // Define carry signals for each output + signal carry[numOutputs]; + // Define output signals (numOutputs bits) + signal output out[numOutputs]; + + // Create an array of IsInRange components for each input bit + component lta[m]; + component ltb[n]; component lt2[numOutputs]; - for(i = 0; i < numOutputs; i++) { - lt2[i] = LessThanPower(base); - lt2[i].in <== out[i]; - out[i] * lt2[i].out === out[i]; + + for (j = 0; j < m; j++) { + lta[j] = IsInRange(85); + lta[j].in <== a[j]; + lta[j].out === 1; } -} -// This function assumes m>=n -template ChunkedAdderIrregular(m, n, base){ - signal input a[m]; - signal input b[n]; - signal psum[m]; - signal carry[m + 1]; - signal output sum[m + 1]; + for (j = 0; j < n; j++) { + ltb[j] = IsInRange(85); + ltb[j].in <== a[j]; + ltb[j].out === 1; + } + + // Initialize the first carry as 0 + carry[0] <== 0; + + // Add a and b up to the length of b (smaller one) + for (i = 0; i < n; i++){ + out[i] <-- (a[i] + b[i] + carry[i]) % power; + carry[i + 1] <-- (a[i] + b[i] + carry[i]) \ power; + + lt2[i] = IsInRange(85); + lt2[i].in <== out[i]; + lt2[i].out === 1; + + carry[i + 1] * (carry[i + 1] - 1) === 0; + a[i] + b[i] + carry[i] === carry[i + 1] * power + out[i]; + } - var power = 2 ** base; + // Propagate the carry of the addition up to the length of a + for (i = n; i < m; i++){ + out[i] <-- (a[i] + carry[i]) % power; + carry[i + 1] <-- (a[i] + carry[i]) \ power; - for (var i = 0; i < n ; i++){ - psum[i] <== a[i] + b[i]; - } + lt2[i] = IsInRange(85); + lt2[i].in <== out[i]; + lt2[i].out === 1; - for (var i = n; i < m ; i++){ - psum[i] <== a[i]; - } + carry[i + 1] * (carry[i + 1] - 1) === 0; + a[i] + carry[i] === carry[i + 1] * power + out[i]; + } - carry[0] <== 0; + // Continue carry propagation for the remaining outputs + for (i = m; i < numOutputs-1; i++) { + out[i] <-- carry[i] % power; + carry[i + 1] <-- carry[i] \ power; - for (var i = 0; i < m; i++){ - sum[i] <-- (psum[i] + carry[i]) % power; - carry[i + 1] <-- (psum[i] + carry[i]) \ power; - psum[i] + carry[i] === carry[i + 1] * power + sum[i]; - } - sum[m] <== carry[m]; + lt2[i] = IsInRange(85); + lt2[i].in <== out[i]; + lt2[i].out === 1; - component lt1 = LessThanPower(base); - lt1.in <== sum[0]; - lt1.out === 1; + // constrain the computation to be correct + carry[i + 1] * (carry[i + 1] - 1) === 0; + carry[i] * power === carry[i + 1] + out[i]; + } - component lt2 = LessThanPower(base); - lt2.in <== sum[m]; - lt2.out === 1; + // The last output is the final carry + out[numOutputs-1] <== carry[numOutputs-1]; } +// Define a function to calculate the number of outputs based on inputs function calculateNumOutputs(m, n, base) { + // Calculate the number of outputs using the following formula: + // Number of outputs = m (size of larger input) + + // (n \ base) (number of smaller input bits in groups of base bits) + + // 1 (additional output) return m + (n \ base) + 1; } diff --git a/circuits/chunkedmul.circom b/circuits/chunkedmul.circom index 75fb3c3..8e668d7 100644 --- a/circuits/chunkedmul.circom +++ b/circuits/chunkedmul.circom @@ -2,24 +2,43 @@ pragma circom 2.0.0; include "./chunkify.circom"; include "./binadd.circom"; -include "./lt.circom"; +include "./range.circom"; +// Define a template for fast binary multiplication template BinMulFast(m, n) { - signal input in1[m]; - signal input in2[n]; - signal output out[m+n]; + // Define the constants for the chunks over 255 bits number + // We produce chunks of 51x5, and the product, as in regular bit multiplication, is two + // times the bit length. + var CHUNK_SIZE = 51; + var PRODUCT_BITS = 102; + + // Input signals for two binary numbers to be multiplied + signal input in1[m]; // Binary input number 1 with 'm' bits + signal input in2[n]; // Binary input number 2 with 'n' bits + // Output signal for the binary product + signal output out[m+n]; // Binary output product with 'm + n' bits + + // Declare variables and components for chunking and processing var i; var j; - component chunkify1 = Chunkify(m, 51); - var numChunks1 = calcChunks(m, 51); + // Component for chunkifying the first input + component chunkify1 = Chunkify(m, CHUNK_SIZE); + // Calculate the number of 51-bit chunks needed for input 1 + var numChunks1 = calcChunks(m, CHUNK_SIZE); + + // Connect each bit of input 1 to the chunkify component for (i=0; i= (i+j)*CHUNK_SIZE && k < endOfBits) { + bitifiedProduct[i*numChunks2 + j][k] = bitifiers[i*numChunks2 + j].out[k-(i+j)*CHUNK_SIZE]; + } else { + bitifiedProduct[i*numChunks2 + j][k] = 0; + } } if (i!=0 || j!=0) { if ((numChunks2 > 1 && i==0 && j==1) || (numChunks2 ==1 && i==1 && j==0)) { adders[0] = BinAdd(m+n); - for (k=0; k 1 && i==0 && j==1) || (numChunks2 ==1 && i==1 && j==0)) { + adders[0].in[0][k] <== bitifiedProduct[0][k]; + adders[0].in[1][k] <== bitifiedProduct[1][k]; + } else { adders[i*numChunks2 + j-1].in[0][k] <== adders[i*numChunks2 + j-2].out[k]; adders[i*numChunks2 + j-1].in[1][k] <== bitifiedProduct[i*numChunks2 + j][k]; } @@ -72,44 +90,48 @@ template BinMulFast(m, n) { out[i] <== bitifiedProduct[0][i]; } } else { - if (numChunks1 * numChunks2 == 2) { - for (i=0; i 0) { + ltc[i] = IsInRange(base * 2); + ltc[i].in <== carry[i]; + ltc[i].out === 1; + } } } - diff --git a/circuits/chunkedsub.circom b/circuits/chunkedsub.circom index bccda66..2e5a564 100644 --- a/circuits/chunkedsub.circom +++ b/circuits/chunkedsub.circom @@ -1,56 +1,85 @@ pragma circom 2.0.0; -include "./lt.circom"; -template ChunkedSub(k, base) { +include "./range.circom"; + +// Template for Chunked Binary Subtraction for chunks of 85 bits +// k: Chunks count +// +// For a chunked subtraction of 3x85 bits chunks +// +// 1. Witness input +// +// a. a := [α, β, γ] +// b. b := [δ, ε, ϝ] +// +// 2. Internal witnesses +// +// a. borrow := [ +// 0, +// !IsInRange85(α - δ), +// !IsInRange85(β - ε), +// !IsInRange85(γ - ϝ) +// ] +// +// 2. Computed witnesses +// +// a. out := [ +// (borrow[1] * 2^85 + α - δ), +// (borrow[2] * 2^85 + β - ε - borrow[1]), +// (borrow[3] * 2^85 + γ - ϝ - borrow[2]) +// ] +// b. underflow := borrow[3] +// +// 3. Constraints +// +// a. for i, a[i] < 2^85 && b[i] < 2^85 +// +// - Constraint `3.a` will assert that all inputs are in range. +// - Note that the output will always be correct since the underflow flag +// covers the entire range of potential outcomes from the subtraction +// operation. +template ChunkedSub85(k) { + assert(k > 0); + + var power = 2 ** 85; + signal input a[k]; signal input b[k]; + signal output out[k]; signal output underflow; - component unit0 = ModSub(base); - unit0.a <== a[0]; - unit0.b <== b[0]; - out[0] <== unit0.out; - - component unit[k - 1]; - for (var i = 1; i < k; i++) { - unit[i - 1] = ModSubThree(base); - unit[i - 1].a <== a[i]; - unit[i - 1].b <== b[i]; - if (i == 1) { - unit[i - 1].c <== unit0.borrow; - } else { - unit[i - 1].c <== unit[i - 2].borrow; - } - out[i] <== unit[i - 1].out; + component lta[k]; + component ltb[k]; + + // range check the inputs + for (var i = 0; i < k; i++) { + lta[i] = IsInRange(85); + lta[i].in <== a[i]; + lta[i].out === 1; + + ltb[i] = IsInRange(85); + ltb[i].in <== b[i]; + ltb[i].out === 1; } - underflow <== unit[k - 2].borrow; -} -template ModSub(base) { - signal input a; - signal input b; - signal output out; - signal output borrow; - component lt = LessThanBounded(base); - lt.in[0] <== a; - lt.in[1] <== b; - borrow <== lt.out; - out <== borrow * (1 << base) + a - b; -} + signal borrow[k+1]; + component range[k]; + + // the first iteration doesn't have a borrow + borrow[0] <== 0; + + for (var i = 0; i < k; i++) { + // set the borrow for the next iteration, if out of range/underflow + range[i] = IsInRange(85); + range[i].in <== a[i] - b[i] - borrow[i]; + borrow[i + 1] <== 1 - range[i].out; -template ModSubThree(base) { - signal input a; - signal input b; - signal input c; - assert(a - b - c + (1 << base) >= 0); - signal output out; - signal output borrow; - signal b_plus_c; - b_plus_c <== b + c; - component lt = LessThanBounded(base+1); - lt.in[0] <== a; - lt.in[1] <== b_plus_c; - borrow <== lt.out; - out <== borrow * (1 << base) + a - b_plus_c; -} \ No newline at end of file + // compute the expected value considering the borrows + out[i] <== borrow[i + 1] * power + a[i] - b[i] - borrow[i]; + } + + // the underflow will be returned only if the last iteration required a + // borrow + underflow <== borrow[k]; +} diff --git a/circuits/chunkify.circom b/circuits/chunkify.circom index b8bf5a3..d1fdd29 100644 --- a/circuits/chunkify.circom +++ b/circuits/chunkify.circom @@ -2,37 +2,72 @@ pragma circom 2.0.0; include "../node_modules/circomlib/circuits/bitify.circom"; +// MM for audit: +// Template to divide a given binary number into smaller, fixed-size chunks. +// This template is particularly useful for breaking down large binary numbers into manageable pieces, +// which can then be processed individually in cryptographic computations or other algorithms that require +// input segmentation. +// +// Parameters: +// - n: The total number of bits in the input binary number. +// - chunkSize: The size of each chunk, i.e., how many bits each chunk should contain. +// +// The template ensures that each chunk, except possibly the last one, contains exactly 'chunkSize' bits. +// If the total number of bits 'n' is not a multiple of 'chunkSize', the last chunk will contain the remaining +// bits (less than 'chunkSize'). +// +// This template utilizes the 'Bits2Num' component from 'circomlib' to convert each chunk of bits back into a number, +// facilitating further processing or computation on these segmented values. + template Chunkify(n, chunkSize) { + // Ensure chunkSize and n are greater than 0 + assert(n > 0); + assert(chunkSize > 0); + // Define the input signal containing 'n' bits signal input in[n]; + // Calculate the number of chunks needed var numChunks = calcChunks(n, chunkSize); + // Define the output signal, which will contain 'numChunks' chunks signal output out[numChunks]; + // Define an array of bit converters for each chunk component bitifer[numChunks]; - var left = n; + + // Initialize variables + var remaining = n; var i; var offset; var numBitsToConvert; + + // Loop through each chunk for (var chunkIndex=0; chunkIndex> base) > 0); - out * (out - 1) === 0; -} - -template LessThanBounded(base) { - signal input in[2]; - signal output out; - - component lt1 = LessThanPower(base); - lt1.in <== in[0]; - - component lt2 = LessThanPower(base); - lt2.in <== in[1]; - - out <-- in[0] < in[1]; - out * (out - 1) === 0; -} diff --git a/circuits/modinv.circom b/circuits/modinv.circom index bbfd7c6..12b3934 100644 --- a/circuits/modinv.circom +++ b/circuits/modinv.circom @@ -3,35 +3,92 @@ pragma circom 2.0.0; include "./chunkedmul.circom"; include "./modulus.circom"; include "./utils.circom"; -include "./lt.circom"; +include "./range.circom"; -template BigModInv51() { +// Define the BigModInv template + +/* +MM for audit: +Template: BigModInv +Performs the modular inverse operation for large numbers divided into chunks. The modular inverse of a number 'a' modulo 'm' is the number 'a_inv' such that (a * a_inv) % m = 1. This operation is crucial in cryptographic algorithms for operations like RSA decryption and ECC. + +Inputs: +- in[3]: The input signal representing the number for which the modular inverse is to be calculated. The number is divided into 3 chunks for efficient processing. + +Outputs: +- out[3]: The output signal representing the modular inverse of the input number, also divided into 3 chunks. + +Description: +- The input number is divided into chunks, and the modular inverse is computed with respect to a predefined prime modulus 'p', also represented in chunks. +- The algorithm ensures that the input number is within a valid range and non-negative. +- The computed modular inverse is validated through a series of checks to ensure it is within the expected range and satisfies the modular inverse property when multiplied by the input number. + +Constraints: +- The input signal must be non-negative and within the specified range. These constraints are typically enforced off-circuit for efficiency. +- The result of multiplying the input number by its computed modular inverse, followed by a modulus operation, must yield 1 for the first chunk and 0 for the remaining chunks, validating the correctness of the modular inverse. + +Components: +- IsInRange: Ensures the computed modular inverse is within the valid range. +- ChunkedMul: Multiplies the input number by its computed modular inverse to verify the modular inverse property. +- ModulusWith25519Chunked51: Performs the modulus operation to verify that (input * inverse) modulo the prime 'p' equals 1. +*/ + +template BigModInv() { + // Define input signal representing the element for which we want to find the modular inverse signal input in[3]; + // Define output signal representing the result of the modular inversion signal output out[3]; + // Check if input values are non-negative and within a valid range + for (var i = 0; i < 3; i++) { + // this assertion is applied off-circuit to enhance test robustness. the + // output signal is constrained to fall within the designated field since + // the modulo multiplication ensures the anticipated relationship is + // maintained. + assert(in[i] >= 0); + } + + // Define the prime modulus (p) for the modular inversion operation var p[3] = [38685626227668133590597613, 38685626227668133590597631, 38685626227668133590597631]; - // length k - var inv[100] = mod_inv(85, 3, in, p); + // Calculate the modular inverse of 'in' modulo 'p' using a custom function 'mod_inv' + // The result is stored in the 'inv' array + var inv[100] = mod_inv(in, p); + + // Copy the calculated modular inverse to the 'out' signal for (var i = 0; i < 3; i++) { out[i] <-- inv[i]; } + + // Create components for less-than checks component lt[3]; + // Perform less-than checks to ensure the result is within a certain range for (var i = 0; i < 3; i++) { - lt[i] = LessThanPower(85); + lt[i] = IsInRange(85); + // Connect the less-than component inputs lt[i].in <== out[i]; + // Ensure the result is within a certain range lt[i].out * out[i] === out[i]; } + // Create a component for multiplication component mult = ChunkedMul(3, 3, 85); + // Perform multiplication of 'in' by the calculated modular inverse for (var i = 0; i < 3; i++) { mult.in1[i] <== in[i]; mult.in2[i] <== out[i]; } + + // Create a component for modulus operations component mod = ModulusWith25519Chunked51(6); + // Perform modulus operation on the result of multiplication for (var i = 0; i < 6; i++) { mod.in[i] <== mult.out[i]; } + + // Validate the modulus result to ensure it meets specific conditions + // - The first element should be equal to 1 + // - The other elements should be equal to 0 mod.out[0] === 1; for (var i = 1; i < 3; i++) { mod.out[i] === 0; diff --git a/circuits/modulus.circom b/circuits/modulus.circom index 8ce49c5..cff3fe8 100644 --- a/circuits/modulus.circom +++ b/circuits/modulus.circom @@ -6,8 +6,7 @@ include "chunkedmul.circom"; include "../node_modules/circomlib/circuits/mux1.circom"; include "chunkedadd.circom"; include "chunkedsub.circom"; -include "lt.circom"; -include "utils.circom"; +include "range.circom"; /* ┌────────────┐ @@ -54,9 +53,39 @@ include "utils.circom"; │ circuit ╠mod254 └──────────────┘ */ +// Template for modulus operation with respect to 2^255 - 19 + +/* +MM for audit: +Template ModulusWith25519: +Performs the modulus operation with respect to 2^255 - 19, a prime number used in cryptographic applications such as the Curve25519 for elliptic curve cryptography. + +Parameters: +- n: The number of bits in the input signal. This parameter defines the size of the input number. + +Input: +- in[n]: The input signal representing an n-bit number to be reduced modulo 2^255 - 19. + +Output: +- out[255]: The output signal representing the result of the modulus operation. It's a 255-bit number, as the result of the operation is guaranteed to be less than 2^255 - 19. + +Operation: +- For inputs with less than 255 bits, the output is simply the input itself, as any number smaller than 2^255 is inherently smaller than 2^255 - 19. +- For inputs of 255 bits or more, the algorithm performs a series of operations to ensure the result is within the correct range: + - It first reduces the number modulo 2^255 to get a number less than 2^255. + - Then, it calculates the quotient of the input divided by 2^255 and multiplies this quotient by 19, adding the result to the modulo 2^255 remainder. + - This sum is then again reduced modulo 2^255 - 19 to ensure the result is within the correct range. + +Constraints: +- The input signal bits must be constrained to binary values (0 or 1). +- The algorithm does not explicitly handle cases where the input is greater than (2^255 - 19) * 2^255, as such large numbers are typically not encountered in cryptographic applications where this modulus operation is used. +*/ template ModulusWith25519(n) { + assert(n > 0); + // Input signal representing an n-bit number signal input in[n]; + // Output signal representing the result of the modulus operation (255 bits) signal output out[255]; var nineteen[5] = [1, 1, 0, 0, 1]; @@ -176,8 +205,32 @@ in254─────────────┴──┼──────── in255────────────────┘ */ +// Template for modulus operation with respect to 2^255 - 19 + +/* +MM for audit: +Template: ModulusAgainst2P +Performs a modulus operation specifically designed for reducing a 256-bit number modulo 2^255 - 19. + +Inputs: +- in[256]: A 256-bit number to be reduced. + +Outputs: +- out[255]: The result of the modulus operation, a 255-bit number. + +Description: +- The input number is compared to 2^255 - 19. If it's smaller, the input is returned as is. +- If the input is larger, the difference between the input and 2^255 - 19 is computed, and this difference is returned. +- This template is used for numbers that are already close to the 2^255 range, specifically for the final step in modulus reduction where the input is expected to be slightly above or below 2^255. + +Constraints: +- Input bits must be binary. +*/ + template ModulusAgainst2P() { + // Input signal representing a 256-bit number signal input in[256]; + // Output signal representing the result of the modulus operation (255 bits) signal output out[255]; /* Binary representation for 2^255 − 19 from LSB to MSB format */ @@ -193,6 +246,9 @@ template ModulusAgainst2P() { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; var i; + for (i = 0; i < 256; i++) { + assert(in[i] == 0 || in[i] == 1); + } component sub = BinSub(256); @@ -218,16 +274,48 @@ template ModulusAgainst2P() { } } +// Template for modulus operation with respect to (2^252 - q) + +/* +MM for audit: +Template: ModulusWith252c +A specialized template for modulus operations with respect to a different prime, related to 2^252. + +Parameters: +- n: The bit-length of the input signal. + +Inputs: +- in[n]: An n-bit number to be reduced modulo a prime related to 2^252. + +Outputs: +- out[253]: The result of the modulus operation, constrained to 253 bits. + +Description: +- Similar to ModulusWith25519, but tailored for a different modulus operation. +- It involves multiplication by a constant and subtraction from another constant, followed by a modulus operation to ensure the result is within range. + +Constraints: +- The input signal must be binary. +- The algorithm assumes n is positive. +*/ + template ModulusWith252c(n) { + // Input signal representing a variable-length binary number signal input in[n]; + assert(n > 0); + // Output signal representing the result of the modulus operation (253 bits) signal output out[253]; + // a constant used in the modular arithmetic calculation + // used in the multiplication operation (mul) to calculate (in % 2^252) * c where c is this constant var c[125] = [1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1]; + // constant used in modular arithmetic + // used in the subtraction operation (sub) to calculate (in % 2^252) - q where q is this constant var q[253] = [1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, @@ -241,7 +329,9 @@ template ModulusWith252c(n) { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; var i; - + for (i = 0; i < n; i++) { + assert(in[i] == 0 || in[i] == 1); + } component mul; component mod; component sub; @@ -283,7 +373,7 @@ template ModulusWith252c(n) { adder.in[1][i] <== sub.out[i]; } - adder.in[0][i] <== 0; + adder.in[0][252] <== 0; adder.in[1][252] <== sub.out[i]; mod2pfinal = ModulusAgainst2Q(); @@ -297,11 +387,39 @@ template ModulusWith252c(n) { } } +// Template for modular reduction against a modulus q + +/* +MM for audit: +Template: ModulusAgainst2Q +Performs modular reduction against a modulus q, for inputs just over the modulus size. + +Inputs: +- in[254]: A 254-bit number to be reduced modulo q. + +Outputs: +- out[253]: The reduced value modulo q, a 253-bit number. + +Description: +- This template reduces an input number modulo q, where q is slightly less than 2^254. +- The reduction involves binary subtraction and conditional selection based on the subtraction result. + +Constraints: +- Input bits must be binary. +*/ + template ModulusAgainst2Q() { + // Input: Binary array representing the value to be reduced signal input in[254]; + // Output: Reduced value modulo q signal output out[253]; var i; + for (i = 0; i < 254; i++) { + assert(in[i] == 0 || in[i] == 1); + } + + // Modulus q as a binary array var q[253] = [1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, @@ -315,29 +433,61 @@ template ModulusAgainst2Q() { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; + // Component for binary subtraction component sub = BinSub(254); for (i = 0; i < 253; i++) { sub.in[0][i] <== in[i]; sub.in[1][i] <== q[i]; } + // Handle the last bit of the subtraction differently sub.in[0][253] <== in[253]; sub.in[1][253] <== 0; + // Component for multiplexing component mux = MultiMux1(253); for (i = 0; i < 253; i++) { mux.c[i][0] <== in[i]; mux.c[i][1] <== sub.out[i]; } - mux.s <== 1 + sub.out[253] - 2*sub.out[253]; + // Calculate the sign for the multiplexing + mux.s <== 1 - sub.out[253]; + // Output the reduced value modulo q for (i = 0; i < 253; i++) { out[i] <== mux.out[i]; } + + // Note: The behavior for inputs larger than 2q should be clarified. } +// Template for modulus operation with respect to 2^255 - 19 using chunked arithmetic. + +/* +Template: ModulusWith25519Chunked51 +Performs the modulus operation with respect to 2^255 - 19 using a chunked approach for better efficiency in large number operations. + +Parameters: +- n: The number of chunks in the input signal. + +Inputs: +- in[n]: The input number divided into 'n' chunks. + +Outputs: +- out[3]: The result of the modulus operation, output in 3 chunks. + +Description: +- This template is optimized for chunked arithmetic, where the input number is divided into chunks for more efficient processing. +- It follows a similar logic to ModulusWith25519 but operates on chunks rather than individual bits. + +Constraints: +- The algorithm assumes n is positive and the input signal is divided appropriately into chunks. +*/ + template ModulusWith25519Chunked51(n) { + // Input signal representing 'n' chunks of the number signal input in[n]; + // Output signal representing the result of the modulus operation (3 chunks) signal output out[3]; var i; @@ -377,10 +527,10 @@ template ModulusWith25519Chunked51(n) { mod.in[i] <== mul.out[i]; } - adder = ChunkedAdd(3, 2, base); + adder = ChunkedAdd85(3); for (i = 0; i < 3; i++) { - adder.in[0][i] <== mod2p.out[i]; - adder.in[1][i] <== mod.out[i]; + adder.a[i] <== mod2p.out[i]; + adder.b[i] <== mod.out[i]; } mod2pfinal = ModulusAgainst2PChunked51(); @@ -394,14 +544,50 @@ template ModulusWith25519Chunked51(n) { } } +// Template for modulus operation with respect to a chunked 255-bit prime (2^255 - 19). +// The input is divided into 4 chunks, and each chunk is compared to a corresponding chunk +// of the prime. The output represents the result of the modulus operation (3 chunks). + +/* +MM for audit: +Template: ModulusAgainst2PChunked51 +A variant of the modulus operation against 2^255 - 19, optimized for chunked inputs. + +Inputs: +- in[4]: Four chunks representing the input number, expected to be close to the modulus size. + +Outputs: +- out[3]: The reduced number, output in 3 chunks. + +Description: +- Specifically designed for chunked arithmetic where the input is divided into 4 chunks. +- It reduces the input modulo 2^255 - 19 and outputs the result in 3 chunks. + +Constraints: +- The input chunks must represent a number that is close to the modulus size, as the template is optimized for such cases. +*/ + template ModulusAgainst2PChunked51() { + // Input signal representing 4 chunks of the number signal input in[4]; + // Output signal representing the result of the modulus operation (3 chunks) signal output out[3]; var i; + + // Chunked representation of the prime 'p' (2^255 - 19) var p[4] = [38685626227668133590597613, 38685626227668133590597631, 38685626227668133590597631, 0]; var base = 85; - component sub = ChunkedSub(4, base); + // Validate the input chunks: each chunk should be less than the base + component ltin[4]; + for (i = 0; i < 4; i++) { + ltin[i] = IsInRange(base); + ltin[i].in <== in[i]; + ltin[i].out === 1; + } + + // Component for chunked subtraction + component sub = ChunkedSub85(4); in[3] * (in[3] - 1) === 0; for (i = 0; i < 4; i++) { @@ -415,8 +601,13 @@ template ModulusAgainst2PChunked51() { mux.c[i][1] <== sub.out[i]; } - mux.s <== 1 + sub.underflow - 2*sub.underflow; + // Set the multiplexer selector based on underflow (1 for underflow, 0 otherwise) + mux.s <== 1 - sub.underflow; + + // Output the reduced result (within the range [0, p-1]) for (i = 0; i < 3; i++) { out[i] <== mux.out[i]; } + + // Note: The behavior for inputs greater than 2p should be clarified. } diff --git a/circuits/point-addition.circom b/circuits/point-addition.circom index 4e75f7b..2a77e10 100644 --- a/circuits/point-addition.circom +++ b/circuits/point-addition.circom @@ -1,40 +1,55 @@ pragma circom 2.0.0; -/*{ - "P" : [ - [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1], - [0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0], - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1] - ], - "Q" : [ - [1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0], - [0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0], - [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0] - - ] -}*/ - //INCLUDE APPROPRIATE ADD, SUBTRACT AND MULTIPLY TEMPLATES include "chunkedmul.circom"; include "chunkedadd.circom"; include "modulus.circom"; +// Template for point addition on an elliptic curve + +/* +MM for audit: +Template: PointAdd +Performs point addition on an elliptic curve using extended coordinates. Points are represented as tuples (X, Y, Z, T) to facilitate efficient point addition and doubling operations. + +Inputs: +- P[4][3]: The first point in extended coordinates to be added. Each of the four coordinates (X, Y, Z, T) is divided into 3 chunks. +- Q[4][3]: The second point in extended coordinates to be added. Similar to P, each coordinate is divided into 3 chunks. + +Outputs: +- R[4][3]: The result of adding points P and Q, represented in extended coordinates and divided into chunks. + +Description: +- The template uses well-known formulas for elliptic curve point addition in extended coordinates. These involve multiple chunked multiplication and addition operations to compute the resulting point's coordinates. +- The constants `constant_neg_d` and `constant_d` are specific to the curve and are used in the computation. +- The result of the point addition (R) is verified to be a valid point on the curve. + +Constraints: +- Input points P and Q must be valid points on the elliptic curve in extended coordinates. +- The output point R will be a valid point on the curve as a result of the point addition operation. +*/ + template PointAdd(){ //Points are represented as tuples (X, Y, Z, T) of extended coordinates, with x = X/Z, y = Y/Z, x*y = T/Z + // Constants used for point addition var constant_neg_d[3] = [36453506357546404448470858,34096743896386538864219637,13898602798132607198219823]; var constant_d[3] = [2232119870121729142126755,4588882331281594726377994,24787023429535526392377808]; var i; var base=85; + // Input points P and Q (caller should ensure validity) signal input P[4][3]; signal input Q[4][3]; + // Output point R (should be a valid point) signal output R[4][3]; + // Define the formulas used for point addition + // These formulas are well-known in elliptic curve cryptography + + // Formulas for X1 * X2, Y1 * Y2, X1 * Y2, X2 * Y1, T1 * T2 and Z1 * Z2 component X_1X_2 = ChunkedMul(3, 3, base); component Y_1Y_2 = ChunkedMul(3, 3, base); component X_1Y_2 = ChunkedMul(3, 3, base); @@ -42,16 +57,17 @@ template PointAdd(){ component T_1T_2 = ChunkedMul(3, 3, base); component Z_1Z_2 = ChunkedMul(3, 3, base); + // Initialize and connect the multiplication components for(i=0;i<3;i++){ X_1X_2.in1[i] <== P[0][i]; X_1X_2.in2[i] <== Q[0][i]; Y_1Y_2.in1[i] <== P[1][i]; Y_1Y_2.in2[i] <== Q[1][i]; - + X_1Y_2.in1[i] <== P[0][i]; X_1Y_2.in2[i] <== Q[1][i]; - + X_2Y_1.in1[i] <== P[1][i]; X_2Y_1.in2[i] <== Q[0][i]; @@ -62,6 +78,7 @@ template PointAdd(){ Z_1Z_2.in2[i] <== Q[2][i]; } + // Define components for additional arithmetic operations component T_1T_2_d = ChunkedMul(6, 3, base); component T_1T_2_neg_d = ChunkedMul(6, 3, base); @@ -74,41 +91,21 @@ template PointAdd(){ T_1T_2_d.in2[i] <== constant_d[i]; T_1T_2_neg_d.in2[i] <== constant_neg_d[i]; } - // component mod_X_1X_2 = ModulusWith25519Chunked51(2*5); - // component mod_Y_1Y_2 = ModulusWith25519Chunked51(2*5); - // component mod_X_1Y_2 = ModulusWith25519Chunked51(2*5); - // component mod_X_2Y_1 = ModulusWith25519Chunked51(2*5); - // component mod_Z_1Z_2 = ModulusWith25519Chunked51(2*5); - - // for(i=0;i<2*5;i++){ - // mod_X_1X_2.a[i] <== X_1X_2.out[i]; - // mod_Y_1Y_2.a[i] <== Y_1Y_2.out[i]; - // mod_X_1Y_2.a[i] <== X_1Y_2.out[i]; - // mod_X_2Y_1.a[i] <== X_2Y_1.out[i]; - // mod_Z_1Z_2.a[i] <== Z_1Z_2.out[i]; - // } - - - // component mod_T_1T_2_d = ModulusWith25519Chunked51(2*5+5); - // component mod_T_1T_2_neg_d = ModulusWith25519Chunked51(2*5+5); - - // for(i=0;i<2*5+5;i++){ - // mod_T_1T_2_d.a[i] <== T_1T_2_d.out[i]; - // mod_T_1T_2_neg_d.a[i] <== T_1T_2_neg_d.out[i]; - // } - - component e_add = ChunkedAdd(6,2,base); - component f_add = ChunkedAdderIrregular(9,6,base); - component g_add = ChunkedAdderIrregular(9,6,base); - component h_add = ChunkedAdd(6,2,base); - + + // Define components for chunked additions + component e_add = ChunkedAdd85(6); + component f_add = ChunkedAdderIrregular85(9,6); + component g_add = ChunkedAdderIrregular85(9,6); + component h_add = ChunkedAdd85(6); + + // Connect inputs for chunked additions for(i=0;i<6;i++){ - e_add.in[0][i] <== X_1Y_2.out[i]; - e_add.in[1][i] <== X_2Y_1.out[i]; + e_add.a[i] <== X_1Y_2.out[i]; + e_add.b[i] <== X_2Y_1.out[i]; f_add.b[i] <== Z_1Z_2.out[i]; g_add.b[i] <== Z_1Z_2.out[i]; - h_add.in[0][i] <== X_1X_2.out[i]; - h_add.in[1][i] <== Y_1Y_2.out[i]; + h_add.a[i] <== X_1X_2.out[i]; + h_add.b[i] <== Y_1Y_2.out[i]; } for(i=0;i<9;i++){ @@ -116,6 +113,7 @@ template PointAdd(){ g_add.a[i] <== T_1T_2_d.out[i]; } + // Define components for final modulus operations component final_mul1 = ChunkedMul(10, 7, base); component final_mul2 = ChunkedMul(10, 7, base); component final_mul3 = ChunkedMul(10, 10, base); @@ -129,12 +127,13 @@ template PointAdd(){ } for(i=0;i<10;i++){ - final_mul1.in1[i] <== f_add.sum[i]; - final_mul2.in1[i] <== g_add.sum[i]; - final_mul3.in1[i] <== f_add.sum[i]; - final_mul3.in2[i] <== g_add.sum[i]; + final_mul1.in1[i] <== f_add.out[i]; + final_mul2.in1[i] <== g_add.out[i]; + final_mul3.in1[i] <== f_add.out[i]; + final_mul3.in2[i] <== g_add.out[i]; } + // Define components for final modulus operations component final_modulo1 = ModulusWith25519Chunked51(17); component final_modulo2 = ModulusWith25519Chunked51(17); component final_modulo3 = ModulusWith25519Chunked51(20); @@ -153,36 +152,50 @@ template PointAdd(){ final_modulo4.in[i] <== final_mul4.out[i]; } + // Assign the results to the output points R for(i=0;i<3;i++){ R[0][i] <== final_modulo1.out[i]; R[1][i] <== final_modulo2.out[i]; R[2][i] <== final_modulo3.out[i]; R[3][i] <== final_modulo4.out[i]; } - - } +/* +MM for audit: +Template: DoublePt +Performs point doubling on an elliptic curve. This is a special case of point addition where the two points are the same. The template uses the `PointAdd` template to perform the doubling operation. + +Inputs: +- P[4][3]: The point to be doubled, represented in extended coordinates and divided into 3 chunks per coordinate. + +Outputs: +- out_2P[4][3]: The result of doubling point P, represented in extended coordinates and divided into chunks. + +Description: +- The template initializes two instances of the point P as inputs to the `PointAdd` template, effectively performing P + P = 2P, which is the point doubling operation. +- The result is stored in `out_2P`, representing the doubled point in extended coordinates. + +Constraints: +- The input point P must be a valid point on the elliptic curve in extended coordinates. +- The output point `out_2P` will be a valid point on the curve as a result of the point doubling operation. +*/ + template DoublePt(){ signal input P[4][3]; signal output out_2P[4][3]; component double = PointAdd(); - var i; - for(i=0;i<3;i++){ - double.P[0][i] <== P[0][i]; - double.P[1][i] <== P[1][i]; - double.P[2][i] <== P[2][i]; - double.P[3][i] <== P[3][i]; - - double.Q[0][i] <== P[0][i]; - double.Q[1][i] <== P[1][i]; - double.Q[2][i] <== P[2][i]; - double.Q[3][i] <== P[3][i]; + var i, j; + for (i = 0; i < 4; i++) { + for (j = 0; j < 3; j++) { + double.P[i][j] <== P[i][j]; + double.Q[i][j] <== P[i][j]; + } } - for(i=0;i<3;i++){ - double.R[0][i] ==> out_2P[0][i]; - double.R[1][i] ==> out_2P[1][i]; - double.R[2][i] ==> out_2P[2][i]; - double.R[3][i] ==> out_2P[3][i]; + // Ensure all inputs are initialized before accessing outputs + for (i = 0; i < 4; i++) { + for (j = 0; j < 3; j++) { + double.R[i][j] ==> out_2P[i][j]; + } } } diff --git a/circuits/pointcompress.circom b/circuits/pointcompress.circom index c6a570d..854ae6a 100644 --- a/circuits/pointcompress.circom +++ b/circuits/pointcompress.circom @@ -4,34 +4,78 @@ include "modinv.circom"; include "chunkedmul.circom"; include "modulus.circom"; include "../node_modules/circomlib/circuits/bitify.circom"; +include "../node_modules/circomlib/circuits/comparators.circom"; + +// Template for point compression with specific curve parameters +// Caller should ensure that the input coordinates are valid for the curve + +/* +MM for audit +Template: PointCompress +Performs point compression on an elliptic curve point. Point compression reduces the size of the point representation by storing only one coordinate along with minimal additional information to recover the other coordinate. + +Inputs: +- P[4][3]: The input signal representing the coordinates of the point to be compressed. The point is represented in extended coordinates (X, Y, Z, T) for efficiency in elliptic curve arithmetic. Coordinates are chunked for efficient arithmetic operations. + +Outputs: +- out[256]: The output signal representing the compressed form of the point. The compressed point consists of the Y coordinate and the least significant bit of the X coordinate, packed into a 256-bit array. + +Description: +- The template calculates the compressed representation of an elliptic curve point using the provided X, Y, and Z coordinates (T is not used in compression). +- The X and Y coordinates are first normalized by multiplying them with the modular inverse of Z, effectively converting the extended coordinates back to affine coordinates (x = X/Z, y = Y/Z). +- The normalized Y coordinate is then converted to a bit array, which forms the majority of the compressed point representation. +- The least significant bit of the normalized X coordinate is appended to the compressed form to enable recovery of the full point during decompression. +- Modular arithmetic ensures that the coordinates remain valid within the field defined by the curve's prime modulus. + +Constraints: +- The caller is responsible for ensuring that the input coordinates are valid points on the elliptic curve and are provided in the correct chunked format. +- The template assumes that the input coordinates are in extended format and uses specific curve parameters for point compression. + +Components: +- mul_x and mul_y: Perform multiplication of X and Y coordinates by the modular inverse of Z to normalize them. +- modinv_z: Calculates the modular inverse of Z. +- mod_x and mod_y: Apply modulus operation to the normalized X and Y coordinates to ensure they are within the field defined by the curve's prime modulus. +- bits_y and bits_x: Convert the normalized Y and X coordinates to bit arrays for packing into the compressed format. +*/ template PointCompress(){ + // Input: Coordinates of the point P[4][3] to be compressed + // `PointA` is the point representing the public key on the elliptic curve (encoded in base 2^85 for brevity) + // `PointR` is the point representing the R8 value on the elliptic curve (encoded in base 2^85) signal input P[4][3]; + // Output: Compressed point representation as a 256-bit array signal output out[256]; + + // Loop variable var i; + // Components for point compression component mul_x = ChunkedMul(3, 3, 85); component mul_y = ChunkedMul(3, 3, 85); - component modinv_z = BigModInv51(); + component modinv_z = BigModInv(); component mod_x = ModulusWith25519Chunked51(6); component mod_y = ModulusWith25519Chunked51(6); - + + // Ensure that P[2] is a valid coordinate for the curve for(i=0;i<3;i++){ modinv_z.in[i] <== P[2][i]; } + // Calculate compressed X and Y coordinates for(i=0;i<3;i++){ mul_x.in1[i] <== P[0][i]; mul_x.in2[i] <== modinv_z.out[i]; mul_y.in1[i] <== P[1][i]; - mul_y.in2[i] <== modinv_z.out[i]; + mul_y.in2[i] <== modinv_z.out[i]; } + // Apply modulus operations to X and Y coordinates for(i=0;i<6;i++){ mod_x.in[i] <== mul_x.out[i]; mod_y.in[i] <== mul_y.out[i]; - } + } + // Convert Y coordinates to bit arrays component bits_y[3]; for(i=0;i<3;i++){ bits_y[i] = Num2Bits(85); @@ -39,16 +83,22 @@ template PointCompress(){ for(i=0;i<3;i++){ bits_y[i].in <== mod_y.out[i]; } - + + // Convert X coordinate to a bit array component bits_x = Num2Bits(85); bits_x.in <== mod_x.out[0]; - + + // Concatenate X and Y coordinates to form the compressed point for(i=0;i<85;i++){ out[i] <== bits_y[0].out[i]; out[i+85] <== bits_y[1].out[i]; out[i+170] <== bits_y[2].out[i]; } - out[255] <-- mod_x.out[0] & 1; - out[255] * (out[255] - 1) === 0; + component bits = Num2Bits_strict(); + mod_x.out[0] ==> bits.in; + bits.out[0] ==> out[255]; + + // Note: The caller should ensure that the input coordinates are valid for the curve + // and meet the requirements of the specific curve parameters used in this template. } diff --git a/circuits/pointverify.circom b/circuits/pointverify.circom new file mode 100644 index 0000000..5006861 --- /dev/null +++ b/circuits/pointverify.circom @@ -0,0 +1,169 @@ +pragma circom 2.0.0; + +include "chunkedadd.circom"; +include "chunkedmul.circom"; +include "chunkedsub.circom"; +include "modulus.circom"; +include "modinv.circom"; +include "range.circom"; + +// Template for validating the correctness of an extended Twisted Edwards curve point +// +// 1. Witness input +// +// a. P := [x, y, z, t] + +// 2. Constants +// +// a. D := Edwards `d` value, equal to `-121665/121666 mod p`. +// +// 3. Constraints +// +// a. for (i, j), P[i][j] < 2^85 +// b. x ⋅ y === z ⋅ t +// c. z² ⋅ (y² - x²) === z⁴ + D ⋅ x² ⋅ y² + +/* +MM for audit: +Template: PointVerify +Validates the correctness of an extended Twisted Edwards curve point. This template checks that a given point satisfies the curve equation in extended coordinates. + +Inputs: +- P[4][3]: The input signal representing the point coordinates in extended format (X, Y, Z, T). Each coordinate is divided into 3 chunks for efficient arithmetic operations. + +Constants: +- D: The Edwards 'd' value, a curve parameter, equal to `-121665/121666 mod p`. Represented in 3 chunks similar to point coordinates. + +Constraints: +- Each chunk of the point coordinates must be less than 2^85. +- The point must satisfy the curve equation in extended coordinates: `x*y === z*t` and `z^2 * (y^2 - x^2) === z^4 + D * x^2 * y^2`. + +Description: +- The template first checks that all chunks of the point coordinates are within the valid range using `IsInRange` components. +- It then calculates the necessary products of the coordinates (`xy`, `zt`, `x^2`, `y^2`, `z^2`) using `ChunkedMul` components and applies the modulus operation to each result to ensure they are within the field defined by the curve's prime modulus. +- The template verifies that `xy === zt` to ensure the point satisfies the first part of the curve equation in extended coordinates. +- For the second part of the curve equation, the template calculates `z^4`, `x^2 * y^2`, and `y^2 - x^2`, and then computes `lhs = z^2 * (y^2 - x^2)` and `rhs = z^4 + D * x^2 * y^2`. It verifies that `lhs === rhs`, thus ensuring the point satisfies the entire curve equation in extended coordinates. + +Components: +- `range[][]`: Ensures each chunk of the point coordinates is within the valid range. +- `xy`, `zt`, `x2`, `y2`, `z2`: Calculate products of the coordinates necessary for verifying the curve equation. +- `mod_xy`, `mod_zt`, `mod_x2`, `mod_y2`, `mod_z2`: Apply modulus operations to the products of the coordinates. +- `z4`, `x2y2`, `y2_sub_x2`, `lhs`: Calculate intermediate values for verifying the second part of the curve equation. +- `mod_lhs`, `mod_x2y2`, `mod_z4`, `dx2y2`, `mod_dx2y2`, `rhs`: Further calculate and verify the second part of the curve equation. + +Note: The caller is responsible for ensuring that the input coordinates represent a valid point on the elliptic curve. The template focuses on verifying the correctness of the point with respect to the curve equation and does not perform full validation of the point's membership on the curve. +*/ + +template PointVerify() { + var base = 85; + var d[3] = [2232119870121729142126755, 4588882331281594726377994, 24787023429535526392377808]; + + signal input P[4][3]; + + component range[4][3]; + for (var i = 0; i < 4; i++) { + for (var j = 0; i < 3; i++) { + range[i][j] = IsInRange(85); + range[i][j].in <== P[i][j]; + range[i][j].out === 1; + } + } + + component xy = ChunkedMul(3, 3, base); + component zt = ChunkedMul(3, 3, base); + component x2 = ChunkedMul(3, 3, base); + component y2 = ChunkedMul(3, 3, base); + component z2 = ChunkedMul(3, 3, base); + + for (var i = 0; i < 3; i++) { + xy.in1[i] <== P[0][i]; + xy.in2[i] <== P[1][i]; + + zt.in1[i] <== P[2][i]; + zt.in2[i] <== P[3][i]; + + x2.in1[i] <== P[0][i]; + x2.in2[i] <== P[0][i]; + + y2.in1[i] <== P[1][i]; + y2.in2[i] <== P[1][i]; + + z2.in1[i] <== P[2][i]; + z2.in2[i] <== P[2][i]; + } + + component mod_xy = ModulusWith25519Chunked51(6); + component mod_zt = ModulusWith25519Chunked51(6); + component mod_x2 = ModulusWith25519Chunked51(6); + component mod_y2 = ModulusWith25519Chunked51(6); + component mod_z2 = ModulusWith25519Chunked51(6); + + for (var i = 0; i < 6; i++) { + mod_xy.in[i] <== xy.out[i]; + mod_zt.in[i] <== zt.out[i]; + mod_x2.in[i] <== x2.out[i]; + mod_y2.in[i] <== y2.out[i]; + mod_z2.in[i] <== z2.out[i]; + } + + for (var i = 0; i < 3; i++) { + mod_xy.out[i] === mod_zt.out[i]; + } + + component z4 = ChunkedMul(3, 3, base); + component x2y2 = ChunkedMul(3, 3, base); + component y2_sub_x2 = ChunkedSub85(3); + component lhs = ChunkedMul(3, 3, base); + + for (var i = 0; i < 3; i++) { + z4.in1[i] <== mod_z2.out[i]; + z4.in2[i] <== mod_z2.out[i]; + + x2y2.in1[i] <== mod_x2.out[i]; + x2y2.in2[i] <== mod_y2.out[i]; + + y2_sub_x2.a[i] <== mod_y2.out[i]; + y2_sub_x2.b[i] <== mod_x2.out[i]; + } + + for (var i = 0; i < 3; i++) { + lhs.in1[i] <== mod_z2.out[i]; + lhs.in2[i] <== y2_sub_x2.out[i]; + } + + component mod_lhs = ModulusWith25519Chunked51(6); + component mod_x2y2 = ModulusWith25519Chunked51(6); + component mod_z4 = ModulusWith25519Chunked51(6); + + for (var i = 0; i < 6; i++) { + mod_lhs.in[i] <== lhs.out[i]; + mod_x2y2.in[i] <== x2y2.out[i]; + mod_z4.in[i] <== z4.out[i]; + } + + component dx2y2 = ChunkedMul(3, 3, base); + + for (var i = 0; i < 3; i++) { + dx2y2.in1[i] <== d[i]; + dx2y2.in2[i] <== mod_x2y2.out[i]; + } + + component mod_dx2y2 = ModulusWith25519Chunked51(6); + + for (var i = 0; i < 6; i++) { + mod_dx2y2.in[i] <== dx2y2.out[i]; + } + + component rhs = ChunkedAdd85(3); + + for (var i = 0; i < 3; i++) { + rhs.a[i] <== mod_z4.out[i]; + rhs.b[i] <== mod_dx2y2.out[i]; + } + + // TODO double check if its ok to skip underflow and addition carry + + for (var i = 0; i < 3; i++) { + mod_lhs.out[i] === rhs.out[i]; + } +} diff --git a/circuits/range.circom b/circuits/range.circom new file mode 100644 index 0000000..80e4dc9 --- /dev/null +++ b/circuits/range.circom @@ -0,0 +1,39 @@ +pragma circom 2.0.0; + +include "../node_modules/circomlib/circuits/bitify.circom"; +include "../node_modules/circomlib/circuits/compconstant.circom"; + +// Check if `in` is lesser than `2^base`, returning `1` if true; `0` otherwise. +// The provided `base` argument must be a constant lesser than 255 (bits). + +// MM for audit: +// Template to check if the given input number is within a specific range. +// This is done by verifying that the input is less than 2 raised to the power of 'base'. +// The template outputs 1 if the input number is within the range (i.e., less than 2^base), and 0 otherwise. +// +// The 'base' parameter specifies the power of 2 that sets the upper limit of the range. +// It is important to note that 'base' must be a constant value less than 255, as it defines the maximum bit length +// of the input number that can be correctly handled by the template. +// +// This template utilizes two components from the 'circomlib' library: +// - Num2Bits_strict: Decomposes the input signal into its binary representation (bit decomposition). +// - CompConstant: Compares the input signal against a constant value. +// +// Parameters: +// - base: The exponent used to calculate the upper limit of the range as 2^base. Must be less than 255. + +template IsInRange(base) { + assert(base < 255); + + signal input in; + signal output out; + + // decomposes the input into 254 bits + component bits = Num2Bits_strict(); + in ==> bits.in; + + // compares the number with the base, adjusting the boolean result to the expected logic + component comp = CompConstant(2**base - 1); + bits.out ==> comp.in; + 1 - comp.out ==> out; +} diff --git a/circuits/scalarmul.circom b/circuits/scalarmul.circom index c0a83a0..e31ef32 100644 --- a/circuits/scalarmul.circom +++ b/circuits/scalarmul.circom @@ -1,164 +1,237 @@ pragma circom 2.0.0; include "point-addition.circom"; +// Template for a multiplexer with 2 inputs and 4 output signals + +/* +MM for audit: +Template: Multiplexor2 +A simple multiplexer with 2 inputs and 4 output signals, allowing selection between two sets of points based on a control signal. + +Inputs: +- sel: A binary input signal used for selecting between the two input sets. Should be 0 or 1. +- in[2][4][3]: Two sets of points, each consisting of 4 points with 3 coordinates each. The points should be valid and pre-validated by the caller. + +Outputs: +- out[4][3]: The selected set of points based on the 'sel' input. Outputs one of the input sets verbatim. +*/ + template Multiplexor2() { + // Input signal for selecting the input (should be 0 or 1) signal input sel; + + // Input signals representing the 2 sets of points (2 sets of 4 points each) + // Caller should ensure validity of these input points signal input in[2][4][3]; + + // Output signals for the selected set of points (4 points with 3 coordinates each) signal output out[4][3]; - var i; - for(i=0;i<3;i++){ - out[0][i] <== (in[1][0][i] - in[0][0][i])*sel + in[0][0][i]; - out[1][i] <== (in[1][1][i] - in[0][1][i])*sel + in[0][1][i]; - out[2][i] <== (in[1][2][i] - in[0][2][i])*sel + in[0][2][i]; - out[3][i] <== (in[1][3][i] - in[0][3][i])*sel + in[0][3][i]; + + // Loop through the 4 points and 3 coordinates + for (var j = 0; j < 4; j++) { + for (var i = 0; i < 3; i++) { + // Use the selected input set (in[sel]) to output the corresponding point + // Output = (Selected Input Point - Other Input Point) * sel + Other Input Point + out[j][i] <== (in[1][j][i] - in[0][j][i]) * sel + in[0][j][i]; + } } } +// Template for bit element multiplication with point doubling and addition + +/* +MM for audit: +Template: BitElementMulAny +Performs bit-element multiplication which involves point doubling and addition based on a selection signal. + +Inputs: +- sel: A binary signal dictating the operation. 0 for doubling, 1 for addition. +- dblIn[4][3]: Input points for the doubling operation. +- addIn[4][3]: Input points for the addition operation. + +Outputs: +- dblOut[4][3]: Result of the point doubling operation. +- selectOut[4][3]: Result of the selected operation (either doubling or addition). + +Components: +- doubler: Component for point doubling. +- adder: Component for point addition. +- selector: Multiplexor for selecting the result based on 'sel' input. + +Description: +- The template orchestrates point doubling and addition operations based on the input selection signal. The 'dblIn' points are always doubled, and the 'addIn' points are added only if 'sel' is 1. The result of the operation indicated by 'sel' is then outputted. +*/ + template BitElementMulAny() { + // Input signal for selecting the operation (should be 0 for doubling, 1 for addition) signal input sel; - signal input dblIn[4][3]; - signal input addIn[4][3]; - signal output dblOut[4][3]; - signal output addOut[4][3]; + // Input signals representing the points for doubling and addition + // Caller should ensure validity of these input points + signal input dblIn[4][3]; // Points for doubling + signal input addIn[4][3]; // Points for addition + + // Output signals for the result of doubling and the selection outcome + signal output dblOut[4][3]; // Result of doubling + signal output selectOut[4][3]; // Selection outcome + + // Components for point doubling, point addition, and multiplexer component doubler = DoublePt(); component adder = PointAdd(); component selector = Multiplexor2(); var i; + // Connect the 'sel' input to the selector component sel ==> selector.sel; - for(i=0;i<3;i++){ - dblIn[0][i] ==> doubler.P[0][i]; - dblIn[1][i] ==> doubler.P[1][i]; - dblIn[2][i] ==> doubler.P[2][i]; - dblIn[3][i] ==> doubler.P[3][i]; + // Connect the input points for doubling to the doubler component + for (var i = 0; i < 4; i++) { + for (var j = 0; j < 3; j++) { + dblIn[i][j] ==> doubler.P[i][j]; + } } - - for(i=0;i<3;i++){ - doubler.out_2P[0][i] ==> adder.P[0][i]; - doubler.out_2P[1][i] ==> adder.P[1][i]; - doubler.out_2P[2][i] ==> adder.P[2][i]; - doubler.out_2P[3][i] ==> adder.P[3][i]; + + // Connect the output of the doubler component to the input points for addition + for (var j = 0; j < 4; j++) { + for (var i = 0; i < 3; i++) { + doubler.out_2P[j][i] ==> adder.P[j][i]; + } } - for(i=0;i<3;i++){ - addIn[0][i] ==> adder.Q[0][i]; - addIn[1][i] ==> adder.Q[1][i]; - addIn[2][i] ==> adder.Q[2][i]; - addIn[3][i] ==> adder.Q[3][i]; + // Connect the input points for addition to the adder component + for (var j = 0; j < 4; j++) { + for (var i = 0; i < 3; i++) { + addIn[j][i] ==> adder.Q[j][i]; + } } - for(i=0;i<3;i++){ - addIn[0][i] ==> selector.in[0][0][i]; - addIn[1][i] ==> selector.in[0][1][i]; - addIn[2][i] ==> selector.in[0][2][i]; - addIn[3][i] ==> selector.in[0][3][i]; - - adder.R[0][i] ==> selector.in[1][0][i]; - adder.R[1][i] ==> selector.in[1][1][i]; - adder.R[2][i] ==> selector.in[1][2][i]; - adder.R[3][i] ==> selector.in[1][3][i]; + // Connect the input and output of the components to the multiplexer + for (var j = 0; j < 2; j++) { + for (var i = 0; i < 4; i++) { + for (var k = 0; k < 3; k++) { + if (j == 0) { + addIn[i][k] ==> selector.in[j][i][k]; + } else { + adder.R[i][k] ==> selector.in[j][i][k]; + } + } + } } - - for(i=0;i<3;i++){ - doubler.out_2P[0][i] ==> dblOut[0][i]; - doubler.out_2P[1][i] ==> dblOut[1][i]; - doubler.out_2P[2][i] ==> dblOut[2][i]; - doubler.out_2P[3][i] ==> dblOut[3][i]; - - selector.out[0][i] ==> addOut[0][i]; - selector.out[1][i] ==> addOut[1][i]; - selector.out[2][i] ==> addOut[2][i]; - selector.out[3][i] ==> addOut[3][i]; - } + // Connect the outputs of the doubler and selector components to the template outputs + for (var j = 0; j < 4; j++) { + for (var i = 0; i < 3; i++) { + doubler.out_2P[j][i] ==> dblOut[j][i]; + selector.out[j][i] ==> selectOut[j][i]; + } + } } +// Template for scalar multiplication of a point + +/* +MM for audit: +Template: ScalarMul +Performs scalar multiplication of a point on an elliptic curve using the double-and-add method. + +Inputs: +- s[255]: A 255-bit scalar value. +- P[4][3]: A point on the curve to be multiplied by 's'. The point must be valid and pre-validated by the caller. + +Outputs: +- sP[4][3]: The result of scalar multiplication 'sP', where 'P' is multiplied by the scalar 's'. + +Description: +- The template iteratively performs point doubling and addition based on each bit of the scalar 's'. Starting from the least significant bit of 's', the template doubles the point and conditionally adds the original point based on the bit value. This process is repeated for each bit of 's' to obtain the final result 'sP'. +- The final operation is conditional based on the most significant bit of 's', using a multiplexer to select between the last doubled point and the result of the final addition. + +Components: +- bits[]: An array of `BitElementMulAny` components used to perform iterative doubling and conditional addition. +- sub_x, sub_t: Components for subtracting 'P' from the prime 'p' for the final addition operation. +- finaladder: Component for the final point addition operation. +- lastSel: A multiplexer for selecting the final operation based on the most significant bit of 's'. +*/ + template ScalarMul(){ + // Input signal for the scalar value 's' (should be a 255-bit scalar) signal input s[255]; + // Input signal for the point 'P' to be multiplied + // Caller should ensure the validity of this input point signal input P[4][3]; + // Output signal for the result of scalar multiplication 'sP' signal output sP[4][3]; + // Array to store components for bit element multiplication component bits[254]; var i; var j; + // Create the first bit element multiplication component bits[0] = BitElementMulAny(); - for(i=0;i<3;i++){ - bits[0].dblIn[0][i] <== P[0][i]; - bits[0].dblIn[1][i] <== P[1][i]; - bits[0].dblIn[2][i] <== P[2][i]; - bits[0].dblIn[3][i] <== P[3][i]; - - bits[0].addIn[0][i] <== P[0][i]; - bits[0].addIn[1][i] <== P[1][i]; - bits[0].addIn[2][i] <== P[2][i]; - bits[0].addIn[3][i] <== P[3][i]; + for (var i = 0; i < 3; i++) { + for (var j = 0; j < 4; j++) { + bits[0].dblIn[j][i] <== P[j][i]; + bits[0].addIn[j][i] <== P[j][i]; + } } bits[0].sel <== s[1]; - for(i=1;i<254;i++){ + // Create the remaining bit element multiplication components + for (var i = 1; i < 254; i++) { bits[i] = BitElementMulAny(); - for(j=0;j<3;j++){ - bits[i-1].dblOut[0][j] ==> bits[i].dblIn[0][j]; - bits[i-1].dblOut[1][j] ==> bits[i].dblIn[1][j]; - bits[i-1].dblOut[2][j] ==> bits[i].dblIn[2][j]; - bits[i-1].dblOut[3][j] ==> bits[i].dblIn[3][j]; - - bits[i-1].addOut[0][j] ==> bits[i].addIn[0][j]; - bits[i-1].addOut[1][j] ==> bits[i].addIn[1][j]; - bits[i-1].addOut[2][j] ==> bits[i].addIn[2][j]; - bits[i-1].addOut[3][j] ==> bits[i].addIn[3][j]; + for (var j = 0; j < 3; j++) { + for (var k = 0; k < 4; k++) { + bits[i - 1].dblOut[k][j] ==> bits[i].dblIn[k][j]; + bits[i - 1].selectOut[k][j] ==> bits[i].addIn[k][j]; + } } - - s[i+1] ==> bits[i].sel; + s[i + 1] ==> bits[i].sel; } + // Prime 'p' for point addition var prime_p[3] = [38685626227668133590597613, 38685626227668133590597631, 38685626227668133590597631]; - component sub_x = ChunkedSub(3, 85); + // Subtract 'P' from 'p' for the final addition + component sub_x = ChunkedSub85(3); for(i=0;i<3;i++){ sub_x.a[i] <== prime_p[i]; sub_x.b[i] <== P[0][i]; } - component sub_t = ChunkedSub(3, 85); + // Subtract 'P' from 'p' for the final addition + component sub_t = ChunkedSub85(3); for(i=0;i<3;i++){ sub_t.a[i] <== prime_p[i]; sub_t.b[i] <== P[3][i]; } + // Final point addition component component finaladder = PointAdd(); for(i=0;i<3;i++){ - finaladder.P[0][i] <== bits[253].addOut[0][i]; - finaladder.P[1][i] <== bits[253].addOut[1][i]; - finaladder.P[2][i] <== bits[253].addOut[2][i]; - finaladder.P[3][i] <== bits[253].addOut[3][i]; + for (var j = 0; j < 4; j++) { + finaladder.P[j][i] <== bits[253].selectOut[j][i]; + } finaladder.Q[0][i] <== sub_x.out[i]; finaladder.Q[1][i] <== P[1][i]; finaladder.Q[2][i] <== P[2][i]; finaladder.Q[3][i] <== sub_t.out[i]; } + + // Selector for the last operation (doubling or addition) component lastSel = Multiplexor2(); s[0] ==> lastSel.sel; - - for(i=0;i<3;i++){ - finaladder.R[0][i] ==> lastSel.in[0][0][i]; - finaladder.R[1][i] ==> lastSel.in[0][1][i]; - finaladder.R[2][i] ==> lastSel.in[0][2][i]; - finaladder.R[3][i] ==> lastSel.in[0][3][i]; - - bits[253].addOut[0][i] ==> lastSel.in[1][0][i]; - bits[253].addOut[1][i] ==> lastSel.in[1][1][i]; - bits[253].addOut[2][i] ==> lastSel.in[1][2][i]; - bits[253].addOut[3][i] ==> lastSel.in[1][3][i]; + + for (i = 0; i < 3; i++) { + for (j = 0; j < 4; j++) { + finaladder.R[j][i] ==> lastSel.in[0][j][i]; + bits[253].selectOut[j][i] ==> lastSel.in[1][j][i]; + } } - for(i=0;i<3;i++){ - sP[0][i] <== lastSel.out[0][i]; - sP[1][i] <== lastSel.out[1][i]; - sP[2][i] <== lastSel.out[2][i]; - sP[3][i] <== lastSel.out[3][i]; + // Output the final result of scalar multiplication + for(i = 0; i < 3; i++){ + for (j = 0; j < 4; j++) { + sP[j][i] <== lastSel.out[j][i]; + } } } diff --git a/circuits/utils.circom b/circuits/utils.circom index 928475a..bb38266 100644 --- a/circuits/utils.circom +++ b/circuits/utils.circom @@ -1,16 +1,29 @@ pragma circom 2.0.0; -function SplitFn(in, n, m) { - return [in % (1 << n), (in \ (1 << n)) % (1 << m)]; +function SplitFn(in) { + // Split the input into two parts, but be aware that bits above n+m will be dropped. + // Use bitwise operations to split the input + var part1 = in & ((1 << 85) - 1); + var part2 = (in >> 85) & ((1 << 85) - 1); + + // Return the two parts as an array + return [part1, part2]; } -function SplitThreeFn(in, n, m, k) { - return [in % (1 << n), (in \ (1 << n)) % (1 << m), (in \ (1 << n + m)) % (1 << k)]; +function SplitThreeFn(in) { + // Split the input into three parts, but be aware that bits above n+m will be dropped. + // Use bitwise operations to split the input into three parts + var part1 = in & ((1 << 85) - 1); + var part2 = (in >> 85) & ((1 << 85) - 1); + var part3 = (in >> 170) & ((1 << 85) - 1); + + // Return the three parts as an array + return [part1, part2, part3]; } // 1 if true, 0 if false -function long_gt(n, k, a, b) { - for (var i = k - 1; i >= 0; i--) { +function long_gt(a, b) { + for (var i = 3; i >= 0; i--) { if (a[i] > b[i]) { return 1; } @@ -21,117 +34,143 @@ function long_gt(n, k, a, b) { return 0; } +// Function for long subtraction of two arrays +// Parameters: +// - a: The first array +// - b: The second array // n bits per register // a has k registers // b has k registers // a >= b -function long_sub(n, k, a, b) { +// 0 < k < 7 +function long_sub(k, a, b) { + assert(0 < k && k < 7); + + // Initialize an array to store the difference var diff[100]; - var borrow[100]; + // Initialize borrow as 0, representing no borrow initially + var borrow = 0; // Define borrow as an atomic variable for (var i = 0; i < k; i++) { if (i == 0) { if (a[i] >= b[i]) { diff[i] = a[i] - b[i]; - borrow[i] = 0; + borrow = 0; } else { - diff[i] = a[i] - b[i] + (1 << n); - borrow[i] = 1; + diff[i] = a[i] - b[i] + (1 << 85); + borrow = 1; } } else { - if (a[i] >= b[i] + borrow[i - 1]) { - diff[i] = a[i] - b[i] - borrow[i - 1]; - borrow[i] = 0; + if (a[i] >= b[i] + borrow) { + diff[i] = a[i] - b[i] - borrow; + borrow = 0; } else { - diff[i] = (1 << n) + a[i] - b[i] - borrow[i - 1]; - borrow[i] = 1; + diff[i] = (1 << 85) + a[i] - b[i] - borrow; + borrow = 1; } } } return diff; } +// Define a function for long scalar multiplication +// k is the number of registers in b // a is a n-bit scalar // b has k registers -function long_scalar_mult(n, k, a, b) { +function long_scalar_mult(k, a, b) { + // Initialize an array to store the output var out[100]; - for (var i = 0; i < 100; i++) { - out[i] = 0; - } + + // Iterate over the registers in b for (var i = 0; i < k; i++) { + // Calculate the temporary result by multiplying the scalar (a) with the current register (b[i]) var temp = out[i] + (a * b[i]); - out[i] = temp % (1 << n); - out[i + 1] = out[i + 1] + temp \ (1 << n); + + // Store the lower n bits of the temporary result in the current output register (out[i]) + out[i] = temp % (1 << 85); + + // Store the higher bits of the temporary result in the next output register (out[i + 1]) + out[i + 1] = temp \ (1 << 85); } + + // Return the resulting array return out; } - // n bits per register // a has k + m registers +// a is set to have k + m registers +// to ensure that it can handle both the dividend +// and any potential remainder that may arise during the long division. +// This approach makes the function more flexible and capable of handling a wider range of division scenarios. // b has k registers // out[0] has length m + 1 -- quotient // out[1] has length k -- remainder -// implements algorithm of https://people.eecs.berkeley.edu/~fateman/282/F%20Wright%20notes/week4.pdf +// implements algorithm of https://people.eecs.berkeley.edu/~fateman/282/F%20Wright%20notes/week4.pdf +// - implement long division of two multi-register integers // b[k-1] must be nonzero! -function long_div(n, k, a, b) { +function long_div(a, b) { var out[2][100]; var remainder[200]; - for (var i = 0; i < 2 * k; i++) { + for (var i = 0; i < 6; i++) { remainder[i] = a[i]; } var mult[200]; var dividend[200]; - for (var i = k; i >= 0; i--) { - if (i == k) { - dividend[k] = 0; - for (var j = k - 1; j >= 0; j--) { - dividend[j] = remainder[j + k]; + for (var i = 3; i >= 0; i--) { + if (i == 3) { + dividend[3] = 0; + for (var j = 2; j >= 0; j--) { + dividend[j] = remainder[j + 3]; } } else { - for (var j = k; j >= 0; j--) { + for (var j = 3; j >= 0; j--) { dividend[j] = remainder[j + i]; } } - out[0][i] = short_div(n, k, dividend, b); + out[0][i] = short_div(dividend, b); - var mult_shift[100] = long_scalar_mult(n, k, out[0][i], b); + var mult_shift[100] = long_scalar_mult(3, out[0][i], b); var subtrahend[200]; - for (var j = 0; j < 2 * k; j++) { + for (var j = 0; j < 6; j++) { subtrahend[j] = 0; } - for (var j = 0; j <= k; j++) { - if (i + j < 2 * k) { + for (var j = 0; j <= 3; j++) { + if (i + j < 6) { subtrahend[i + j] = mult_shift[j]; } } - remainder = long_sub(n, 2 * k, remainder, subtrahend); + remainder = long_sub(6, remainder, subtrahend); } - for (var i = 0; i < k; i++) { + for (var i = 0; i < 3; i++) { out[1][i] = remainder[i]; } - out[1][k] = 0; + out[1][3] = 0; return out; } +// Function for short division normalization +// Parameters: +// - a: The dividend array +// - b: The divisor array // n bits per register -// a has k + 1 registers -// b has k registers +// a has 4 registers +// b has 3 registers // assumes leading digit of b is at least 2 ** (n - 1) // 0 <= a < (2**n) * b -function short_div_norm(n, k, a, b) { - var qhat = (a[k] * (1 << n) + a[k - 1]) \ b[k - 1]; - if (qhat > (1 << n) - 1) { - qhat = (1 << n) - 1; +function short_div_norm(a, b) { + var qhat = (a[3] * (1 << 85) + a[2]) \ b[2]; + if (qhat > (1 << 85) - 1) { + qhat = (1 << 85) - 1; } - var mult[100] = long_scalar_mult(n, k, qhat, b); - if (long_gt(n, k + 1, mult, a) == 1) { - mult = long_sub(n, k + 1, mult, b); - if (long_gt(n, k + 1, mult, a) == 1) { + var mult[100] = long_scalar_mult(3, qhat, b); + if (long_gt(mult, a) == 1) { + mult = long_sub(4, mult, b); + if (long_gt(mult, a) == 1) { return qhat - 2; } else { return qhat - 1; @@ -141,113 +180,126 @@ function short_div_norm(n, k, a, b) { } } +// Function for short division +// Parameters: +// - a: The dividend array +// - b: The divisor array // n bits per register // a has k + 1 registers // b has k registers -// assumes leading digit of b is non-zero // 0 <= a < (2**n) * b -function short_div(n, k, a, b) { - var scale = (1 << n) \ (1 + b[k - 1]); - +function short_div(a, b) { + // Calculate the scale factor for normalization + var scale = (1 << 85) \ (1 + b[2]); + // Create arrays to store normalized versions of a and b + // These arrays have additional space for temporary storage + // k + 2 registers are used for norm_a, and k + 1 registers are used for norm_b // k + 2 registers now - var norm_a[200] = long_scalar_mult(n, k + 1, scale, a); + var norm_a[200] = long_scalar_mult(4, scale, a); // k + 1 registers now - var norm_b[200] = long_scalar_mult(n, k, scale, b); - - var ret; - if (norm_b[k] != 0) { - ret = short_div_norm(n, k + 1, norm_a, norm_b); - } else { - ret = short_div_norm(n, k, norm_a, norm_b); - } + var norm_b[200] = long_scalar_mult(3, scale, b); + // Perform short division normalization and return the result + var ret = short_div_norm(norm_a, norm_b); return ret; } +// Function for multiplying two arrays a and b of length k with n-bit elements +// Parameters: +// - a: The first array to be multiplied +// - b: The second array to be multiplied // n bits per register // a and b both have k registers // out[0] has length 2 * k // adapted from BigMulShortLong and LongToShortNoEndCarry2 witness computation -function prod(n, k, a, b) { +function prod(a, b) { + // Create an array to store intermediate product values // first compute the intermediate values. taken from BigMulShortLong var prod_val[100]; // length is 2 * k - 1 - for (var i = 0; i < 2 * k - 1; i++) { + + // Calculate the intermediate product values + for (var i = 0; i < 5; i++) { prod_val[i] = 0; - if (i < k) { + if (i < 3) { for (var a_idx = 0; a_idx <= i; a_idx++) { prod_val[i] = prod_val[i] + a[a_idx] * b[i - a_idx]; } } else { - for (var a_idx = i - k + 1; a_idx < k; a_idx++) { + for (var a_idx = i - 2; a_idx < 3; a_idx++) { prod_val[i] = prod_val[i] + a[a_idx] * b[i - a_idx]; } } } + // Create arrays and variables for carrying and output // now do a bunch of carrying to make sure registers not overflowed. taken from LongToShortNoEndCarry2 var out[100]; // length is 2 * k - var split[100][3]; // first dimension has length 2 * k - 1 - for (var i = 0; i < 2 * k - 1; i++) { - split[i] = SplitThreeFn(prod_val[i], n, n, n); + + // Calculate carry and output values to prevent overflow + for (var i = 0; i < 5; i++) { + split[i] = SplitThreeFn(prod_val[i]); } var carry[100]; // length is 2 * k - 1 carry[0] = 0; out[0] = split[0][0]; - if (2 * k - 1 > 1) { - var sumAndCarry[2] = SplitFn(split[0][1] + split[1][0], n, n); - out[1] = sumAndCarry[0]; - carry[1] = sumAndCarry[1]; - } - if (2 * k - 1 > 2) { - for (var i = 2; i < 2 * k - 1; i++) { - var sumAndCarry[2] = SplitFn(split[i][0] + split[i-1][1] + split[i-2][2] + carry[i-1], n, n); - out[i] = sumAndCarry[0]; - carry[i] = sumAndCarry[1]; - } - out[2 * k - 1] = split[2*k-2][1] + split[2*k-3][2] + carry[2*k-2]; + + var sumAndCarry[2] = SplitFn(split[0][1] + split[1][0]); + out[1] = sumAndCarry[0]; + carry[1] = sumAndCarry[1]; + + for (var i = 2; i < 5; i++) { + var sumAndCarry[2] = SplitFn(split[i][0] + split[i-1][1] + split[i-2][2] + carry[i-1]); + out[i] = sumAndCarry[0]; + carry[i] = sumAndCarry[1]; } + + out[5] = split[4][1] + split[3][2] + carry[4]; + return out; } // n bits per register -// a has k registers -// p has k registers -// e has k registers -// k * n <= 500 -// p is a prime +// a has 3 registers +// p has 3 registers +// e has 3 registers +// p is a prime with 3 positive, non-zero registers // computes a^e mod p -function mod_exp(n, k, a, p, e) { +function mod_exp(a, p, e) { + for (var i = 0; i < 3; i++) { + assert(p[i] > 0); + } + var eBits[500]; // length is k * n - for (var i = 0; i < k; i++) { - for (var j = 0; j < n; j++) { - eBits[j + n * i] = (e[i] >> j) & 1; + for (var i = 0; i < 3; i++) { + for (var j = 0; j < 85; j++) { + eBits[j + 85 * i] = (e[i] >> j) & 1; } } var out[100]; // length is k - for (var i = 0; i < 100; i++) { + for (var i = 1; i < 100; i++) { out[i] = 0; } out[0] = 1; // repeated squaring - for (var i = k * n - 1; i >= 0; i--) { - // multiply by a if bit is 0 + for (var i = 254; i >= 0; i--) { + // square, if bit is 1 if (eBits[i] == 1) { var temp[200]; // length 2 * k - temp = prod(n, k, out, a); + temp = prod(out, a); var temp2[2][100]; - temp2 = long_div(n, k, temp, p); + temp2 = long_div(temp, p); out = temp2[1]; } // square, unless we're at the end if (i > 0) { var temp[200]; // length 2 * k - temp = prod(n, k, out, out); + temp = prod(out, out); var temp2[2][100]; - temp2 = long_div(n, k, temp, p); + temp2 = long_div(temp, p); out = temp2[1]; } @@ -255,35 +307,31 @@ function mod_exp(n, k, a, p, e) { return out; } -// n bits per register -// a has k registers -// p has k registers -// k * n <= 500 +// a has 3 registers +// p has 3 registers // p is a prime // if a == 0 mod p, returns 0 // else computes inv = a^(p-2) mod p -function mod_inv(n, k, a, p) { +function mod_inv(a, p) { var isZero = 1; - for (var i = 0; i < k; i++) { + + for (var i = 0; i < 3; i++) { if (a[i] != 0) { isZero = 0; } } + if (isZero == 1) { var ret[100]; - for (var i = 0; i < k; i++) { + for (var i = 0; i < 3; i++) { ret[i] = 0; } return ret; } - var pCopy[100]; - for (var i = 0; i < 100; i++) { - if (i < k) { - pCopy[i] = p[i]; - } else { - pCopy[i] = 0; - } + var pCopy[3]; + for (var i = 0; i < 3; i++) { + pCopy[i] = p[i]; } var two[100]; @@ -292,9 +340,11 @@ function mod_inv(n, k, a, p) { } two[0] = 2; - var pMinusTwo[100]; - pMinusTwo = long_sub(n, k, pCopy, two); // length k + var pMinusTwo[3]; + pMinusTwo = long_sub(3, pCopy, two); + var out[100]; - out = mod_exp(n, k, a, pCopy, pMinusTwo); + out = mod_exp(a, pCopy, pMinusTwo); + return out; } diff --git a/circuits/verify.circom b/circuits/verify.circom index 38c1ac6..8eabaa8 100644 --- a/circuits/verify.circom +++ b/circuits/verify.circom @@ -4,22 +4,62 @@ include "./scalarmul.circom"; include "./modulus.circom"; include "./point-addition.circom"; include "./pointcompress.circom"; +include "./pointverify.circom"; include "../node_modules/@electron-labs/sha512/circuits/sha512/sha512.circom"; include "../node_modules/circomlib/circuits/comparators.circom"; include "../node_modules/circomlib/circuits/gates.circom"; +// Ed25519 signature verification template. +// - n : length of message (bits). + +/* +MM for audit: +Template: Ed25519Verifier +Verifies an Ed25519 signature given a message, public key, and signature components. + +Parameters: +- n: The length of the message in bits. Must be a multiple of 8. + +Inputs: +- msg[n]: The message for which the signature is to be verified. +- PointA[4][3]: The public key represented as a point on the elliptic curve, encoded in base 2^85 for efficient computation. +- PointR[4][3]: The R component of the signature, also a point on the curve. +- S[255]: The S component of the signature, represented as 255 bits. + +Outputs: +- out: The output signal indicating whether the signature is valid (1) or not (0). + +Description: +- The verifier checks that the provided points are valid, compresses the points, hashes the message along with the compressed points, and then performs scalar multiplication and point addition as per the Ed25519 verification algorithm. The result is compared with the expected value to determine the validity of the signature. + +Components: +- PointCompress: Compresses the points 'PointA' and 'PointR' for inclusion in the hash. +- Sha512: Hashes the compressed points and the message. +- ScalarMul: Performs scalar multiplication of the provided points with the scalar values derived from the hash and the signature. +- PointAdd: Adds points on the elliptic curve. +- PointEqual: Compares two points for equality. + +Constraints: +- The message must contain valid bits. +- The length of the message 'n' must be a multiple of 8. + +Note: The template assumes that the inputs are provided in a valid and correct format, including the public key and signature components. The caller is responsible for ensuring the validity of these inputs. +*/ + template Ed25519Verifier(n) { + // Ensure n is a multiple of 8 assert(n % 8 == 0); - + // a message containing n bits signal input msg[n]; - - signal input A[256]; - signal input R8[256]; - signal input S[255]; - + // point representing the public key on the elliptic curve + // encoded in base 2^85 signal input PointA[4][3]; + // point representing the R8 value on the elliptic curve + // encoded in base 2^85 signal input PointR[4][3]; + // first 255 bits of the last 256 bits of the signature (LSB to MSB) + signal input S[255]; signal output out; @@ -32,25 +72,30 @@ template Ed25519Verifier(n) { var i; var j; + // Ensure that the msg input contains valid bits data + for (i = 0; i < n; i++) { + msg[i] * (msg[i] - 1) === 0; + } + component compressA = PointCompress(); component compressR = PointCompress(); + component PA = PointVerify(); + component PR = PointVerify(); for (i=0; i<4; i++) { for (j=0; j<3; j++) { + PA.P[i][j] <== PointA[i][j]; + PR.P[i][j] <== PointR[i][j]; + compressA.P[i][j] <== PointA[i][j]; compressR.P[i][j] <== PointR[i][j]; } } - for (i=0; i<256; i++) { - compressA.out[i] === A[i]; - compressR.out[i] === R8[i]; - } - - component hash = Sha512(n+256+256); + component hash = Sha512(n + 256 + 256); for (i=0; i<256; i+=8) { for(j=0; j<8; j++) { - hash.in[i+j] <== R8[i+(7-j)]; - hash.in[256+i+j] <== A[i+(7-j)]; + hash.in[i+j] <== compressR.out[i+(7-j)]; + hash.in[256+i+j] <== compressA.out[i+(7-j)]; } } for (i=0; i. +*/ + +pragma solidity >=0.7.0 <0.9.0; + +contract Groth16Verifier { + // Scalar field size + uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + // Base field size + uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + + // Verification Key data + uint256 constant alphax = 20491192805390485299153009773594534940189261866228447918068658471970481763042; + uint256 constant alphay = 9383485363053290200918347156157836566562967994039712273449902621266178545958; + uint256 constant betax1 = 4252822878758300859123897981450591353533073413197771768651442665752259397132; + uint256 constant betax2 = 6375614351688725206403948262868962793625744043794305715222011528459656738731; + uint256 constant betay1 = 21847035105528745403288232691147584728191162732299865338377159692350059136679; + uint256 constant betay2 = 10505242626370262277552901082094356697409835680220590971873171140371331206856; + uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; + uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; + uint256 constant deltax1 = 20232254007024902090709443190293615034318007072031846246787371686782990015443; + uint256 constant deltax2 = 19992792132973416979713694688689464678425823266188211383191498841587608322968; + uint256 constant deltay1 = 17810151967781068359030007422747853491715213601967581853491184087010662166921; + uint256 constant deltay2 = 102667856119754495710540150562349563133142050456424110980735630210768991003; + + + uint256 constant IC0x = 1099160972170470609842687560485273414668037191235603171848147745439164489220; + uint256 constant IC0y = 10615676719064002842133729455474144037115468862216154660416862911648433396084; + + uint256 constant IC1x = 13784109596199431394740691319197121740379965711033745425651873240004981691939; + uint256 constant IC1y = 21654946450984760923547867288254158585896619819196128181125946328479787075569; + + + // Memory data + uint16 constant pVk = 0; + uint16 constant pPairing = 128; + + uint16 constant pLastMem = 896; + + function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[1] calldata _pubSignals) public view returns (bool) { + assembly { + function checkField(v) { + if iszero(lt(v, q)) { + mstore(0, 0) + return(0, 0x20) + } + } + + // G1 function to multiply a G1 value(x,y) to value in an address + function g1_mulAccC(pR, x, y, s) { + let success + let mIn := mload(0x40) + mstore(mIn, x) + mstore(add(mIn, 32), y) + mstore(add(mIn, 64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + + mstore(add(mIn, 64), mload(pR)) + mstore(add(mIn, 96), mload(add(pR, 32))) + + success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + } + + function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk { + let _pPairing := add(pMem, pPairing) + let _pVk := add(pMem, pVk) + + mstore(_pVk, IC0x) + mstore(add(_pVk, 32), IC0y) + + // Compute the linear combination vk_x + + g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0))) + + + // -A + mstore(_pPairing, calldataload(pA)) + mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q)) + + // B + mstore(add(_pPairing, 64), calldataload(pB)) + mstore(add(_pPairing, 96), calldataload(add(pB, 32))) + mstore(add(_pPairing, 128), calldataload(add(pB, 64))) + mstore(add(_pPairing, 160), calldataload(add(pB, 96))) + + // alpha1 + mstore(add(_pPairing, 192), alphax) + mstore(add(_pPairing, 224), alphay) + + // beta2 + mstore(add(_pPairing, 256), betax1) + mstore(add(_pPairing, 288), betax2) + mstore(add(_pPairing, 320), betay1) + mstore(add(_pPairing, 352), betay2) + + // vk_x + mstore(add(_pPairing, 384), mload(add(pMem, pVk))) + mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32)))) + + + // gamma2 + mstore(add(_pPairing, 448), gammax1) + mstore(add(_pPairing, 480), gammax2) + mstore(add(_pPairing, 512), gammay1) + mstore(add(_pPairing, 544), gammay2) + + // C + mstore(add(_pPairing, 576), calldataload(pC)) + mstore(add(_pPairing, 608), calldataload(add(pC, 32))) + + // delta2 + mstore(add(_pPairing, 640), deltax1) + mstore(add(_pPairing, 672), deltax2) + mstore(add(_pPairing, 704), deltay1) + mstore(add(_pPairing, 736), deltay2) + + + let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20) + + isOk := and(success, mload(_pPairing)) + } + + let pMem := mload(0x40) + mstore(0x40, add(pMem, pLastMem)) + + // Validate that all evaluations ∈ F + + checkField(calldataload(add(_pubSignals, 0))) + + checkField(calldataload(add(_pubSignals, 32))) + + + // Validate all evaluations + let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem) + + mstore(0, isValid) + return(0, 0x20) + } + } + } diff --git a/input.json b/input.json index c07f0bf..9fcda86 100644 --- a/input.json +++ b/input.json @@ -17,522 +17,6 @@ "0", "1" ], - "A": [ - "0", - "0", - "1", - "1", - "1", - "1", - "1", - "1", - "1", - "0", - "0", - "0", - "1", - "0", - "1", - "0", - "1", - "0", - "1", - "1", - "0", - "0", - "1", - "1", - "0", - "1", - "1", - "1", - "0", - "0", - "0", - "1", - "0", - "1", - "0", - "0", - "0", - "1", - "1", - "0", - "0", - "0", - "0", - "1", - "1", - "0", - "0", - "0", - "1", - "0", - "0", - "0", - "0", - "1", - "0", - "1", - "1", - "1", - "0", - "0", - "0", - "1", - "0", - "1", - "1", - "0", - "1", - "1", - "0", - "0", - "0", - "1", - "0", - "0", - "1", - "0", - "0", - "1", - "0", - "1", - "0", - "1", - "1", - "1", - "1", - "1", - "1", - "0", - "0", - "0", - "0", - "0", - "1", - "0", - "1", - "1", - "0", - "1", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "1", - "1", - "0", - "0", - "0", - "0", - "0", - "0", - "1", - "1", - "1", - "1", - "0", - "0", - "0", - "1", - "1", - "0", - "1", - "0", - "0", - "0", - "0", - "1", - "0", - "0", - "0", - "0", - "0", - "1", - "1", - "0", - "1", - "0", - "0", - "0", - "1", - "0", - "1", - "1", - "0", - "1", - "1", - "1", - "1", - "1", - "0", - "0", - "1", - "0", - "0", - "0", - "0", - "1", - "0", - "1", - "1", - "1", - "0", - "1", - "1", - "1", - "0", - "0", - "1", - "1", - "0", - "0", - "1", - "1", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "1", - "1", - "0", - "1", - "0", - "1", - "1", - "0", - "1", - "1", - "1", - "0", - "1", - "0", - "1", - "1", - "0", - "1", - "0", - "1", - "1", - "1", - "1", - "0", - "0", - "0", - "1", - "0", - "0", - "1", - "1", - "0", - "1", - "0", - "1", - "0", - "0", - "0", - "0", - "0", - "0", - "1", - "0", - "0", - "1", - "0", - "0", - "0", - "0", - "0", - "1", - "0", - "0", - "1", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "1", - "1", - "0", - "1", - "0", - "0", - "1", - "0", - "0" - ], - "R8": [ - "0", - "1", - "0", - "0", - "0", - "1", - "1", - "0", - "1", - "0", - "0", - "0", - "1", - "0", - "0", - "1", - "0", - "1", - "1", - "0", - "1", - "0", - "1", - "1", - "1", - "1", - "1", - "0", - "1", - "0", - "1", - "0", - "0", - "1", - "1", - "1", - "1", - "0", - "1", - "1", - "0", - "0", - "1", - "1", - "0", - "1", - "1", - "1", - "0", - "0", - "1", - "0", - "0", - "1", - "0", - "0", - "0", - "1", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "1", - "0", - "0", - "1", - "0", - "1", - "1", - "1", - "0", - "0", - "1", - "0", - "0", - "0", - "1", - "1", - "0", - "0", - "1", - "1", - "1", - "0", - "0", - "1", - "1", - "1", - "0", - "0", - "1", - "0", - "1", - "0", - "1", - "1", - "1", - "0", - "0", - "0", - "1", - "1", - "1", - "1", - "1", - "0", - "1", - "1", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "1", - "1", - "0", - "0", - "0", - "1", - "0", - "1", - "0", - "0", - "1", - "1", - "0", - "0", - "0", - "0", - "1", - "0", - "1", - "0", - "0", - "1", - "1", - "1", - "0", - "0", - "0", - "1", - "0", - "0", - "1", - "0", - "0", - "1", - "0", - "0", - "0", - "1", - "0", - "1", - "0", - "0", - "1", - "0", - "0", - "0", - "0", - "1", - "0", - "0", - "1", - "0", - "1", - "1", - "1", - "0", - "0", - "1", - "0", - "1", - "1", - "1", - "0", - "0", - "0", - "0", - "1", - "0", - "0", - "0", - "1", - "0", - "0", - "1", - "1", - "1", - "1", - "0", - "1", - "0", - "0", - "1", - "1", - "0", - "1", - "1", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "0", - "1", - "1", - "1", - "1", - "0", - "1", - "0", - "1", - "1", - "1", - "1", - "0", - "1", - "1", - "0", - "1", - "1", - "0", - "1", - "0", - "1", - "1", - "0", - "1", - "0", - "1", - "1", - "0", - "0", - "0", - "0", - "1", - "1", - "0", - "0", - "1", - "1", - "0", - "1", - "0", - "1" - ], "S": [ "0", "0", diff --git a/input.json-backup b/input.json-backup new file mode 100644 index 0000000..c07f0bf --- /dev/null +++ b/input.json-backup @@ -0,0 +1,829 @@ +{ + "msg": [ + "1", + "1", + "1", + "1", + "0", + "1", + "0", + "1", + "0", + "1", + "0", + "0", + "0", + "0", + "0", + "1" + ], + "A": [ + "0", + "0", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "0", + "0", + "0", + "1", + "0", + "1", + "0", + "1", + "0", + "1", + "1", + "0", + "0", + "1", + "1", + "0", + "1", + "1", + "1", + "0", + "0", + "0", + "1", + "0", + "1", + "0", + "0", + "0", + "1", + "1", + "0", + "0", + "0", + "0", + "1", + "1", + "0", + "0", + "0", + "1", + "0", + "0", + "0", + "0", + "1", + "0", + "1", + "1", + "1", + "0", + "0", + "0", + "1", + "0", + "1", + "1", + "0", + "1", + "1", + "0", + "0", + "0", + "1", + "0", + "0", + "1", + "0", + "0", + "1", + "0", + "1", + "0", + "1", + "1", + "1", + "1", + "1", + "1", + "0", + "0", + "0", + "0", + "0", + "1", + "0", + "1", + "1", + "0", + "1", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "1", + "1", + "0", + "0", + "0", + "0", + "0", + "0", + "1", + "1", + "1", + "1", + "0", + "0", + "0", + "1", + "1", + "0", + "1", + "0", + "0", + "0", + "0", + "1", + "0", + "0", + "0", + "0", + "0", + "1", + "1", + "0", + "1", + "0", + "0", + "0", + "1", + "0", + "1", + "1", + "0", + "1", + "1", + "1", + "1", + "1", + "0", + "0", + "1", + "0", + "0", + "0", + "0", + "1", + "0", + "1", + "1", + "1", + "0", + "1", + "1", + "1", + "0", + "0", + "1", + "1", + "0", + "0", + "1", + "1", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "1", + "1", + "0", + "1", + "0", + "1", + "1", + "0", + "1", + "1", + "1", + "0", + "1", + "0", + "1", + "1", + "0", + "1", + "0", + "1", + "1", + "1", + "1", + "0", + "0", + "0", + "1", + "0", + "0", + "1", + "1", + "0", + "1", + "0", + "1", + "0", + "0", + "0", + "0", + "0", + "0", + "1", + "0", + "0", + "1", + "0", + "0", + "0", + "0", + "0", + "1", + "0", + "0", + "1", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "1", + "1", + "0", + "1", + "0", + "0", + "1", + "0", + "0" + ], + "R8": [ + "0", + "1", + "0", + "0", + "0", + "1", + "1", + "0", + "1", + "0", + "0", + "0", + "1", + "0", + "0", + "1", + "0", + "1", + "1", + "0", + "1", + "0", + "1", + "1", + "1", + "1", + "1", + "0", + "1", + "0", + "1", + "0", + "0", + "1", + "1", + "1", + "1", + "0", + "1", + "1", + "0", + "0", + "1", + "1", + "0", + "1", + "1", + "1", + "0", + "0", + "1", + "0", + "0", + "1", + "0", + "0", + "0", + "1", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "1", + "0", + "0", + "1", + "0", + "1", + "1", + "1", + "0", + "0", + "1", + "0", + "0", + "0", + "1", + "1", + "0", + "0", + "1", + "1", + "1", + "0", + "0", + "1", + "1", + "1", + "0", + "0", + "1", + "0", + "1", + "0", + "1", + "1", + "1", + "0", + "0", + "0", + "1", + "1", + "1", + "1", + "1", + "0", + "1", + "1", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "1", + "1", + "0", + "0", + "0", + "1", + "0", + "1", + "0", + "0", + "1", + "1", + "0", + "0", + "0", + "0", + "1", + "0", + "1", + "0", + "0", + "1", + "1", + "1", + "0", + "0", + "0", + "1", + "0", + "0", + "1", + "0", + "0", + "1", + "0", + "0", + "0", + "1", + "0", + "1", + "0", + "0", + "1", + "0", + "0", + "0", + "0", + "1", + "0", + "0", + "1", + "0", + "1", + "1", + "1", + "0", + "0", + "1", + "0", + "1", + "1", + "1", + "0", + "0", + "0", + "0", + "1", + "0", + "0", + "0", + "1", + "0", + "0", + "1", + "1", + "1", + "1", + "0", + "1", + "0", + "0", + "1", + "1", + "0", + "1", + "1", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "1", + "1", + "1", + "1", + "0", + "1", + "0", + "1", + "1", + "1", + "1", + "0", + "1", + "1", + "0", + "1", + "1", + "0", + "1", + "0", + "1", + "1", + "0", + "1", + "0", + "1", + "1", + "0", + "0", + "0", + "0", + "1", + "1", + "0", + "0", + "1", + "1", + "0", + "1", + "0", + "1" + ], + "S": [ + "0", + "0", + "0", + "1", + "1", + "0", + "0", + "0", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "1", + "0", + "1", + "1", + "0", + "0", + "1", + "1", + "1", + "0", + "0", + "1", + "0", + "1", + "0", + "1", + "0", + "1", + "1", + "0", + "0", + "0", + "1", + "0", + "1", + "1", + "0", + "1", + "0", + "0", + "0", + "0", + "1", + "0", + "0", + "1", + "1", + "1", + "1", + "0", + "0", + "0", + "0", + "1", + "0", + "0", + "1", + "0", + "1", + "1", + "1", + "0", + "1", + "0", + "1", + "1", + "1", + "1", + "0", + "0", + "1", + "1", + "0", + "1", + "1", + "1", + "0", + "1", + "1", + "1", + "1", + "0", + "0", + "0", + "0", + "0", + "1", + "1", + "0", + "0", + "0", + "0", + "1", + "1", + "0", + "0", + "1", + "1", + "0", + "1", + "1", + "0", + "0", + "1", + "0", + "0", + "1", + "1", + "0", + "0", + "0", + "1", + "1", + "1", + "0", + "0", + "1", + "1", + "0", + "1", + "0", + "0", + "1", + "0", + "1", + "0", + "0", + "1", + "0", + "0", + "0", + "1", + "1", + "1", + "1", + "1", + "0", + "1", + "0", + "1", + "0", + "1", + "0", + "0", + "0", + "1", + "0", + "0", + "1", + "0", + "1", + "1", + "1", + "1", + "0", + "0", + "0", + "1", + "1", + "1", + "0", + "0", + "1", + "1", + "1", + "0", + "1", + "1", + "0", + "0", + "1", + "0", + "0", + "1", + "0", + "1", + "1", + "1", + "0", + "1", + "1", + "0", + "0", + "0", + "1", + "0", + "0", + "0", + "0", + "0", + "0", + "1", + "1", + "1", + "1", + "1", + "0", + "0", + "1", + "0", + "0", + "0", + "1", + "1", + "1", + "1", + "1", + "0", + "1", + "0", + "1", + "1", + "1", + "0", + "0", + "1", + "1", + "0", + "1", + "0", + "1", + "0", + "1", + "1", + "1", + "0", + "1", + "1", + "1", + "1", + "0", + "0", + "0", + "0", + "0", + "1", + "0", + "0", + "0", + "1", + "1", + "0", + "1", + "0", + "1", + "0", + "0", + "0" + ], + "PointA": [ + [ + "4730506516830149311053058", + "16492541847767404437354919", + "29355681060784451154336116" + ], + [ + "37044855473276001117360636", + "36044041573171652573795971", + "11334344940228348385886412" + ], + ["1", "0", "0"], + [ + "36006434350668361313085412", + "3961809584842615293282987", + "31804254138239842479622482" + ] + ], + "PointR": [ + [ + "2328148767130699190453327", + "21019210829853924773317397", + "17422058868782080515274437" + ], + [ + "7439055530601725237301602", + "5010768007177350092543207", + "13528818393860270915128989" + ], + ["1", "0", "0"], + [ + "21834518600060388913341896", + "35467612521506016148096723", + "5257184083217746721055169" + ] + ] +} diff --git a/package-lock.json b/package-lock.json index e0e1529..2458f36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "0.0.1", "license": "UNLICENSED", "dependencies": { - "@electron-labs/sha512": "^1.0.4" + "@electron-labs/sha512": "^1.0.4", + "tweetnacl": "^1.0.3" }, "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.1.1", @@ -151,9 +152,9 @@ } }, "node_modules/@ensdomains/ens/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -223,7 +224,7 @@ "node_modules/@ensdomains/ens/node_modules/yargs": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz", - "integrity": "sha1-wMQpJMpKqmsObaFznfshZDn53cA=", + "integrity": "sha512-LqodLrnIDM3IFT+Hf/5sxBnEGECrfdC1uIbgZeJmESCSo4HoCAaKEus8MylXHAkdacGc0ye+Qa+dpkuom8uVYA==", "dev": true, "dependencies": { "cliui": "^3.2.0", @@ -245,7 +246,7 @@ "node_modules/@ensdomains/ens/node_modules/yargs-parser": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz", - "integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=", + "integrity": "sha512-9pIKIJhnI5tonzG6OnCFlz/yln8xHYcGl+pn3xR0Vzff0vzN1PbNRaelgfgRUwZ3s4i3jvxT9WhmUGL4whnasA==", "dev": true, "dependencies": { "camelcase": "^3.0.0", @@ -349,9 +350,9 @@ } }, "node_modules/@ethereum-waffle/compiler/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -1236,6 +1237,15 @@ "@ethersproject/strings": "^5.6.1" } }, + "node_modules/@fastify/busboy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz", + "integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", @@ -1392,17 +1402,14 @@ } }, "node_modules/@nomiclabs/hardhat-waffle": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-waffle/-/hardhat-waffle-2.0.3.tgz", - "integrity": "sha512-049PHSnI1CZq6+XTbrMbMv5NaL7cednTfPenx02k3cEh8wBMLa6ys++dBETJa6JjfwgA9nBhhHQ173LJv6k2Pg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-waffle/-/hardhat-waffle-2.0.6.tgz", + "integrity": "sha512-+Wz0hwmJGSI17B+BhU/qFRZ1l6/xMW82QGXE/Gi+WTmwgJrQefuBs1lIf7hzQ1hLk6hpkvb/zwcNkpVKRYTQYg==", "dev": true, - "dependencies": { - "@types/sinon-chai": "^3.2.3", - "@types/web3": "1.0.19" - }, "peerDependencies": { "@nomiclabs/hardhat-ethers": "^2.0.0", - "ethereum-waffle": "^3.2.0", + "@types/sinon-chai": "^3.2.3", + "ethereum-waffle": "*", "ethers": "^5.0.0", "hardhat": "^2.0.0" } @@ -1676,7 +1683,8 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.1.tgz", "integrity": "sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@types/json5": { "version": "0.0.29", @@ -1784,6 +1792,7 @@ "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.11.tgz", "integrity": "sha512-dmZsHlBsKUtBpHriNjlK0ndlvEh8dcb9uV9Afsbt89QIyydpC7NcR+nWlAhASfy3GHnxTl4FX/aKE7XZUt/B4g==", "dev": true, + "peer": true, "dependencies": { "@types/sinonjs__fake-timers": "*" } @@ -1793,6 +1802,7 @@ "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.8.tgz", "integrity": "sha512-d4ImIQbT/rKMG8+AXpmcan5T2/PNeSjrYhvkwet6z0p8kzYtfgA32xzOBlbU0yqJfq+/0Ml805iFoODO0LP5/g==", "dev": true, + "peer": true, "dependencies": { "@types/chai": "*", "@types/sinon": "*" @@ -1802,23 +1812,8 @@ "version": "8.1.2", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", - "dev": true - }, - "node_modules/@types/underscore": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.11.4.tgz", - "integrity": "sha512-uO4CD2ELOjw8tasUrAhvnn2W4A0ZECOvMjCivJr4gA9pGgjv+qxKWY9GLTMVEK8ej85BxQOocUyE7hImmSQYcg==", - "dev": true - }, - "node_modules/@types/web3": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/@types/web3/-/web3-1.0.19.tgz", - "integrity": "sha512-fhZ9DyvDYDwHZUp5/STa9XW2re0E8GxoioYJ4pEUZ13YHpApSagixj7IAdoYH5uAK+UalGq6Ml8LYzmgRA/q+A==", "dev": true, - "dependencies": { - "@types/bn.js": "*", - "@types/underscore": "*" - } + "peer": true }, "node_modules/@ungap/promise-all-settled": { "version": "1.1.2", @@ -13307,9 +13302,9 @@ } }, "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, "engines": { "node": "*" @@ -14394,9 +14389,9 @@ "dev": true }, "node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -15405,9 +15400,9 @@ } }, "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -15792,9 +15787,9 @@ } }, "node_modules/patch-package/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -16495,9 +16490,9 @@ } }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -16686,9 +16681,9 @@ } }, "node_modules/solc/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -17184,8 +17179,7 @@ "node_modules/tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "dev": true + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" }, "node_modules/tweetnacl-util": { "version": "0.15.1", @@ -17289,12 +17283,15 @@ } }, "node_modules/undici": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.5.1.tgz", - "integrity": "sha512-MEvryPLf18HvlCbLSzCW0U00IMftKGI5udnjrQbC5D4P0Hodwffhv+iGfWuJwg16Y/TK11ZFK8i+BPVW2z/eAw==", + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.27.0.tgz", + "integrity": "sha512-l3ydWhlhOJzMVOYkymLykcRRXqbUaQriERtR70B9LzNkZ4bX52Fc8wbTDneMiwo8T+AemZXvXaTx+9o5ROxrXg==", "dev": true, + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, "engines": { - "node": ">=12.18" + "node": ">=14.0" } }, "node_modules/universalify": { @@ -17531,9 +17528,9 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -17769,9 +17766,9 @@ } }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true }, "solc": { @@ -17826,7 +17823,7 @@ "yargs": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz", - "integrity": "sha1-wMQpJMpKqmsObaFznfshZDn53cA=", + "integrity": "sha512-LqodLrnIDM3IFT+Hf/5sxBnEGECrfdC1uIbgZeJmESCSo4HoCAaKEus8MylXHAkdacGc0ye+Qa+dpkuom8uVYA==", "dev": true, "requires": { "cliui": "^3.2.0", @@ -17848,7 +17845,7 @@ "yargs-parser": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz", - "integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=", + "integrity": "sha512-9pIKIJhnI5tonzG6OnCFlz/yln8xHYcGl+pn3xR0Vzff0vzN1PbNRaelgfgRUwZ3s4i3jvxT9WhmUGL4whnasA==", "dev": true, "requires": { "camelcase": "^3.0.0", @@ -17941,9 +17938,9 @@ } }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true }, "solc": { @@ -18515,6 +18512,12 @@ "@ethersproject/strings": "^5.6.1" } }, + "@fastify/busboy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz", + "integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==", + "dev": true + }, "@humanwhocodes/config-array": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", @@ -18639,14 +18642,11 @@ "requires": {} }, "@nomiclabs/hardhat-waffle": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-waffle/-/hardhat-waffle-2.0.3.tgz", - "integrity": "sha512-049PHSnI1CZq6+XTbrMbMv5NaL7cednTfPenx02k3cEh8wBMLa6ys++dBETJa6JjfwgA9nBhhHQ173LJv6k2Pg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-waffle/-/hardhat-waffle-2.0.6.tgz", + "integrity": "sha512-+Wz0hwmJGSI17B+BhU/qFRZ1l6/xMW82QGXE/Gi+WTmwgJrQefuBs1lIf7hzQ1hLk6hpkvb/zwcNkpVKRYTQYg==", "dev": true, - "requires": { - "@types/sinon-chai": "^3.2.3", - "@types/web3": "1.0.19" - } + "requires": {} }, "@resolver-engine/core": { "version": "0.3.3", @@ -18882,7 +18882,8 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.1.tgz", "integrity": "sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==", - "dev": true + "dev": true, + "peer": true }, "@types/json5": { "version": "0.0.29", @@ -18989,6 +18990,7 @@ "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.11.tgz", "integrity": "sha512-dmZsHlBsKUtBpHriNjlK0ndlvEh8dcb9uV9Afsbt89QIyydpC7NcR+nWlAhASfy3GHnxTl4FX/aKE7XZUt/B4g==", "dev": true, + "peer": true, "requires": { "@types/sinonjs__fake-timers": "*" } @@ -18998,6 +19000,7 @@ "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.8.tgz", "integrity": "sha512-d4ImIQbT/rKMG8+AXpmcan5T2/PNeSjrYhvkwet6z0p8kzYtfgA32xzOBlbU0yqJfq+/0Ml805iFoODO0LP5/g==", "dev": true, + "peer": true, "requires": { "@types/chai": "*", "@types/sinon": "*" @@ -19007,23 +19010,8 @@ "version": "8.1.2", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", - "dev": true - }, - "@types/underscore": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.11.4.tgz", - "integrity": "sha512-uO4CD2ELOjw8tasUrAhvnn2W4A0ZECOvMjCivJr4gA9pGgjv+qxKWY9GLTMVEK8ej85BxQOocUyE7hImmSQYcg==", - "dev": true - }, - "@types/web3": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/@types/web3/-/web3-1.0.19.tgz", - "integrity": "sha512-fhZ9DyvDYDwHZUp5/STa9XW2re0E8GxoioYJ4pEUZ13YHpApSagixj7IAdoYH5uAK+UalGq6Ml8LYzmgRA/q+A==", "dev": true, - "requires": { - "@types/bn.js": "*", - "@types/underscore": "*" - } + "peer": true }, "@ungap/promise-all-settled": { "version": "1.1.2", @@ -27910,9 +27898,9 @@ "dev": true }, "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true }, "get-intrinsic": { @@ -28709,9 +28697,9 @@ "dev": true }, "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" @@ -29484,9 +29472,9 @@ }, "dependencies": { "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true } } @@ -29775,9 +29763,9 @@ } }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true }, "shebang-command": { @@ -30292,9 +30280,9 @@ "dev": true }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true }, "serialize-javascript": { @@ -30455,9 +30443,9 @@ } }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true }, "tmp": { @@ -30863,8 +30851,7 @@ "tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "dev": true + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" }, "tweetnacl-util": { "version": "0.15.1", @@ -30943,10 +30930,13 @@ } }, "undici": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.5.1.tgz", - "integrity": "sha512-MEvryPLf18HvlCbLSzCW0U00IMftKGI5udnjrQbC5D4P0Hodwffhv+iGfWuJwg16Y/TK11ZFK8i+BPVW2z/eAw==", - "dev": true + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.27.0.tgz", + "integrity": "sha512-l3ydWhlhOJzMVOYkymLykcRRXqbUaQriERtR70B9LzNkZ4bX52Fc8wbTDneMiwo8T+AemZXvXaTx+9o5ROxrXg==", + "dev": true, + "requires": { + "@fastify/busboy": "^2.0.0" + } }, "universalify": { "version": "0.1.2", @@ -31147,9 +31137,9 @@ "dev": true }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true }, "workerpool": { diff --git a/package.json b/package.json index 2601aad..db5e831 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ }, "scripts": { "test": "mocha --colors --max-old-space-size=4000 --timeout 150000 --exclude test/inversemodulo* --exclude test/scalarmul* --exclude test/ed25519* --exclude test/batchverify*", - "test-inverse-modulo": "mocha --colors --max-old-space-size=4000 --timeout 150000 test/inversemodulo.test.js", "test-scalarmul": "mocha --colors --max-old-space-size=4000 --timeout 300000 test/scalarmul.test.js", "test-verify": "mocha --colors --max-old-space-size=4000 --timeout 4000000 test/ed25519verfication.test.js ", "test-batch-verify": "mocha --colors --max-old-space-size=4000 --timeout 12000000 test/batchverify.test.js", @@ -29,7 +28,8 @@ "author": "rahulghangas, garvitgoel, BakerStreetPhantom, suniljalandhra", "license": "UNLICENSED", "dependencies": { - "@electron-labs/sha512": "^1.0.4" + "@electron-labs/sha512": "^1.0.4", + "tweetnacl": "^1.0.3" }, "devDependencies": { "@nomiclabs/hardhat-ethers": "^2.1.1", diff --git a/test/batchverify.test.js b/test/batchverify.test.js index c937788..7546363 100644 --- a/test/batchverify.test.js +++ b/test/batchverify.test.js @@ -1,8 +1,10 @@ -const path = require('path'); -const assert = require('assert'); -const wasmTester = require('circom_tester').wasm; -const crypto = require('crypto'); -const utils = require('./utils'); +const path = require("path"); +const assert = require("assert"); +const wasmTester = require("circom_tester").wasm; +const crypto = require("crypto"); +const utils = require("./utils"); +const { bigInt } = require("fast-check"); +const { writeFileSync } = require("fs"); // describe('Batch Verification', () => { // describe('when testing against three test vectors with 16 bits of message', () => { @@ -198,57 +200,105 @@ const utils = require('./utils'); // }); // }); // }); -describe('Batch Verification test', () => { - describe('when testing against one test vectors with 16 bits of message', () => { - it('should verify correctly', async () => { - const cir = await wasmTester(path.join(__dirname, 'circuits', 'batchverify.circom')); - const pointA = [ - 43933056957747458452560886832567536073542840507013052263144963060608791330050n, - 16962727616734173323702303146057009569815335830970791807500022961899349823996n, - 1n, - 47597536765056690778342994103149503974598380825968728087754575050160026478564n, - ]; - const pointR = [ - 26073464383897998325899031212762184271676052677226679463708862316754828477519n, - 20246927599389923510374971105736264637524117420538179767629249587300902801762n, - 1n, - 7867784340861643381702890578607277776011430152424699121581244161773093676488n, - ]; - const A = 16962727616734173323702303146057009569815335830970791807500022961899349823996n; - const msg = 33455n; - const R8 = 78142972218048021222160463610080218564159109753358461787358041591257467621730n; - const S = 4869643893319708471955165214975585939793846505679808910535986866633137979160n; - const bufMsg = utils.bigIntToLEBuffer(msg); - const bufR8 = utils.bigIntToLEBuffer(R8); - const bufS = utils.bigIntToLEBuffer(S); - const bufA = utils.bigIntToLEBuffer(A); - const bitsMsg = utils.buffer2bits(bufMsg); - const bitsR8 = utils.pad(utils.buffer2bits(bufR8), 256); - const bitsS = utils.pad(utils.buffer2bits(bufS), 255).slice(0, 255); - const bitsA = utils.pad(utils.buffer2bits(bufA), 256); - const chunkA = []; - const chunkR = []; - - for (let i = 0; i < 4; i++) { - chunkA.push(utils.chunkBigInt(pointA[i], BigInt(2 ** 85))); - chunkR.push(utils.chunkBigInt(pointR[i], BigInt(2 ** 85))); +describe("Batch Verification test", () => { + describe("when testing against one test vectors with 16 bits of message", () => { + it("should verify correctly", async () => { + const cir = await wasmTester( + path.join(__dirname, "circuits", "batchverify.circom") + ); + + // get input data + const { msgs, sigs, public_keys } = utils.get_input_data(2); + + // get input json + const { bitsMsg, bitsS, PointA, PointR } = utils.get_input( + msgs, + sigs, + public_keys + ); + + for (let i in PointA) { + if (PointA[i].length == 4) { + for (let j in PointA[i]) { + if (PointA[i][j].length == 4) { + PointA[i][j] = PointA[i][j].slice(0, 3); + } + } + } } - for (let i = 0; i < 4; i++) { - utils.pad(chunkA[i], 3); - utils.pad(chunkR[i], 3); + for (let i in PointR) { + if (PointR[i].length == 4) { + for (let j in PointR[i]) { + if (PointR[i][j].length == 4) { + PointR[i][j] = PointR[i][j].slice(0, 3); + } + } + } } + // const pointA = [ + // 43933056957747458452560886832567536073542840507013052263144963060608791330050n, + // 16962727616734173323702303146057009569815335830970791807500022961899349823996n, + // 1n, + // 47597536765056690778342994103149503974598380825968728087754575050160026478564n, + // ]; + // const pointR = [ + // 26073464383897998325899031212762184271676052677226679463708862316754828477519n, + // 20246927599389923510374971105736264637524117420538179767629249587300902801762n, + // 1n, + // 7867784340861643381702890578607277776011430152424699121581244161773093676488n, + // ]; + // const msg = 33455n; + // // const R8 = 78142972218048021222160463610080218564159109753358461787358041591257467621730n; + // const S = + // 4869643893319708471955165214975585939793846505679808910535986866633137979160n; + + // console.log("msg:", msg) + // const bufMsg = utils.bigIntToLEBuffer(msg); + // console.log("bufMsg:", bufMsg) + // const bitsMsg = utils.buffer2bits(bufMsg); + // console.log("bitsMsg:", bitsMsg) + + // const bufS = utils.bigIntToLEBuffer(S); + // console.log("bufS:", bufS) + + // const bitsS = utils.pad(utils.buffer2bits(bufS), 255).slice(0, 255); + // console.log("bitsS:", bitsS) + + // const chunkA = []; + // const chunkR = []; + + // for (let i = 0; i < 4; i++) { + // chunkA.push(utils.chunkBigInt(pointA[i], BigInt(2 ** 85))); + // chunkR.push(utils.chunkBigInt(pointR[i], BigInt(2 ** 85))); + // } + + // for (let i = 0; i < 4; i++) { + // utils.pad(chunkA[i], 3); + // utils.pad(chunkR[i], 3); + // } + + // console.log("chunkA:", chunkA) + // console.log("chunkR:", chunkR) + + // const bufMsg = utils.bigIntToLEBuffer(data); + // const bitsMsg1 = utils.buffer2bits(data); + + // console.log("bufMsg:", bufMsg) + // console.log("bitsMsg:", bitsMsg1) + + let inputJson = { + msg: bitsMsg, + S: bitsS, + PointA: PointA, + PointR: PointR, + }; + + // write batchinput.json to a file + writeFileSync(`./batchinput.json`, JSON.stringify(inputJson, null, 2)); - const witness = await cir.calculateWitness({ - msg: bitsMsg, A: bitsA, R8: bitsR8, S: bitsS, PointA: chunkA, PointR: chunkR, - }, true); - assert.ok(witness[3] === 1n); - const expected = crypto.createHash('sha256') - .update(utils.bigIntToLEBuffer(A)) - .digest('hex'); - const h = BigInt(2 ** 128); - const real = utils.bigIntToLEBuffer(BigInt(witness[1] + witness[2] * h)).toString('hex'); - assert.equal(expected, real); + const witness = await cir.calculateWitness(inputJson, true); + assert.ok(witness[3] === 0n); }); }); }); diff --git a/test/binaddirr.test.js b/test/binaddirr.test.js deleted file mode 100644 index 6c9a126..0000000 --- a/test/binaddirr.test.js +++ /dev/null @@ -1,50 +0,0 @@ -const path = require('path'); -const assert = require('assert'); -const wasmTester = require('circom_tester').wasm; -const { default: fc } = require('fast-check'); -const utils = require('./utils'); - -describe('Binary addition test for irregular bits', () => { - describe('when calculating addition of two binary array of non equal length for test 56 and 40 bits ', () => { - it('should add them correctly', async () => { - const cir = await wasmTester(path.join(__dirname, 'circuits', 'binaddirr.circom')); - const a = BigInt('1125899906842613'); - const b = BigInt('1099511627764'); - const buf1 = utils.bigIntToLEBuffer(a); - const buf2 = utils.bigIntToLEBuffer(b); - const bits1 = utils.buffer2bits(buf1); - const bits2 = utils.buffer2bits(buf2); - const witness = await cir.calculateWitness({ in1: bits1, in2: bits2 }, true); - - const expected = utils.pad( - utils.buffer2bits(utils.bigIntToLEBuffer(a + b)), - 57, - ); - assert.ok(witness.slice(1, 58).every((u, i) => u === expected[i])); - }); - }); - describe('when calculating addition of two random binary array of non equal length for the test 56 and 40 bits', () => { - it('should add them correctly', async () => { - const cir = await wasmTester(path.join(__dirname, 'circuits', 'binaddirr.circom')); - await fc.assert( - fc.asyncProperty( - fc.bigInt(2n, BigInt(2 ** 56) - 1n), - fc.bigInt(3n, BigInt(2 ** 40) - 1n), - async (a, b) => { - const buf1 = utils.bigIntToLEBuffer(a); - const buf2 = utils.bigIntToLEBuffer(b); - const bits1 = utils.pad(utils.buffer2bits(buf1), 56).slice(0, 56); - const bits2 = utils.pad(utils.buffer2bits(buf2), 40).slice(0, 40); - const witness = await cir.calculateWitness({ in1: bits1, in2: bits2 }, true); - - const expected = utils.pad( - utils.buffer2bits(utils.bigIntToLEBuffer(a + b)), - 57, - ); - return witness.slice(1, 58).every((u, i) => u === expected[i]); - }, - ), - ); - }); - }); -}); diff --git a/test/binmul.test.js b/test/binmul.test.js index 9236150..46f9893 100644 --- a/test/binmul.test.js +++ b/test/binmul.test.js @@ -1,135 +1,124 @@ -const path = require('path'); -const assert = require('assert'); -const wasmTester = require('circom_tester').wasm; -const fc = require('fast-check'); -const utils = require('./utils'); +const path = require("path"); +const assert = require("assert"); +const wasmTester = require("circom_tester").wasm; +const fc = require("fast-check"); +const utils = require("./utils"); -describe('Binary Multiplier Test', () => { - describe('when performing binary multiplication on 104 bit and an 40 bit numbers', () => { - it('should multiply them correctly', async () => { - const cir = await wasmTester(path.join(__dirname, 'circuits', 'binmul1.circom')); - const a = BigInt('282028201972879200395656481949'); - const b = BigInt('956564819949'); - const buf1 = utils.bigIntToLEBuffer(a); - const asBits1 = utils.buffer2bits(buf1); - const buf2 = utils.bigIntToLEBuffer(b); - const asBits2 = utils.buffer2bits(buf2); - const witness = await cir.calculateWitness({ in1: asBits1, in2: asBits2 }, true); - - const expected = utils.normalize(utils.buffer2bits(utils.bigIntToLEBuffer(a * b))); - assert.ok(witness.slice(1, 145).every((u, i) => u === expected[i])); - }); - }); - describe("when performing binary multiplication on two random number's binary array of 104 bits and 40 bits", () => { - it('should multiply them correctly', async () => { - const cir = await wasmTester(path.join(__dirname, 'circuits', 'binmul1.circom')); - await fc.assert( - fc.asyncProperty( - fc.bigInt(2n, BigInt(2 ** 104) - 1n), - fc.bigInt(2n, BigInt(2 ** 40) - 1n), - async (a, b) => { - const buf1 = utils.bigIntToLEBuffer(a); - const asBits1 = utils.pad(utils.buffer2bits(buf1), 104).slice(0, 104); - const buf2 = utils.bigIntToLEBuffer(b); - const asBits2 = utils.pad(utils.buffer2bits(buf2), 40).slice(0, 40); - const witness = await cir.calculateWitness({ in1: asBits1, in2: asBits2 }, true); - - const expected = utils.pad(utils.buffer2bits(utils.bigIntToLEBuffer(a * b)), 144); - return witness.slice(1, 145).every((u, i) => u === expected[i]); - }, - ), +describe("Fast Binary Multiplier Test", () => { + describe("when performing binary multiplication on 104 bit and an 40 bit numbers", () => { + it("should multiply them correctly", async () => { + const cir = await wasmTester( + path.join(__dirname, "circuits", "binmulfast1.circom") ); - }); - }); -}); - -describe('Fast Binary Multiplier Test', () => { - describe('when performing binary multiplication on 104 bit and an 40 bit numbers', () => { - it('should multiply them correctly', async () => { - const cir = await wasmTester(path.join(__dirname, 'circuits', 'binmulfast1.circom')); - const a = BigInt('2820282019728792003956564819949'); - const b = BigInt('956564819949'); + const a = BigInt("2820282019728792003956564819949"); + const b = BigInt("956564819949"); const buf1 = utils.bigIntToLEBuffer(a); const asBits1 = utils.buffer2bits(buf1); const buf2 = utils.bigIntToLEBuffer(b); const asBits2 = utils.buffer2bits(buf2); - const witness = await cir.calculateWitness({ in1: asBits1, in2: asBits2 }, true); + const witness = await cir.calculateWitness( + { in1: asBits1, in2: asBits2 }, + true + ); - const expected = utils.normalize(utils.buffer2bits(utils.bigIntToLEBuffer(a * b))); + const expected = utils.normalize( + utils.buffer2bits(utils.bigIntToLEBuffer(a * b)) + ); assert.ok(witness.slice(1, 145).every((u, i) => u === expected[i])); }); }); }); -describe(' Fast Binary multiplication chunked 51 test', () => { - describe('When Performing binary multiplication on 4 by 4 numbers chunked by 51 bits', () => { - it('should multiply them correctly', async () => { - const cir = await wasmTester(path.join(__dirname, 'circuits', 'binmulfast51_1.circom')); +describe(" Fast Binary multiplication chunked 51 test", () => { + describe("When Performing binary multiplication on 4 by 4 numbers chunked by 51 bits", () => { + it("should multiply them correctly", async () => { + const cir = await wasmTester( + path.join(__dirname, "circuits", "binmulfast51_1.circom") + ); const a = BigInt(2 ** 200 - 10); const b = BigInt(2 ** 203 - 10); - const chunk1 = utils.chunkBigInt(a); - const chunk2 = utils.chunkBigInt(b); + const chunk1 = utils.chunkBigInt(a, BigInt(2 ** 51)); + const chunk2 = utils.chunkBigInt(b, BigInt(2 ** 51)); const witness = await cir.calculateWitness({ in1: chunk1, in2: chunk2 }); - const expected = utils.chunkBigInt(a * b); + const expected = utils.chunkBigInt(a * b, BigInt(2 ** 51)); assert.ok(witness.slice(1, 9).every((u, i) => u === expected[i])); }); }); - describe('When performing binary multiplication on 4 chunks of two randomly genrated numbers chunked with base51', () => { - it('should multiply them correctly', async () => { - const cir = await wasmTester(path.join(__dirname, 'circuits', 'binmulfast51_1.circom')); + describe("When performing binary multiplication on 4 chunks of two randomly genrated numbers chunked with base51", () => { + it("should multiply them correctly", async () => { + const cir = await wasmTester( + path.join(__dirname, "circuits", "binmulfast51_1.circom") + ); await fc.assert( fc.asyncProperty( fc.bigInt(2n, BigInt(2 ** 200) - 10n), fc.bigInt(2n, BigInt(2 ** 203) - 10n), async (a, b) => { - const chunk1 = utils.pad(utils.chunkBigInt(a), 4); - const chunk2 = utils.pad(utils.chunkBigInt(b), 4); - const witness = await cir.calculateWitness({ in1: chunk1, in2: chunk2 }); - const expected = utils.pad(utils.chunkBigInt(a * b), 8); + const chunk1 = utils.pad(utils.chunkBigInt(a, BigInt(2 ** 51)), 4); + const chunk2 = utils.pad(utils.chunkBigInt(b, BigInt(2 ** 51)), 4); + const witness = await cir.calculateWitness({ + in1: chunk1, + in2: chunk2, + }); + const expected = utils.pad(utils.chunkBigInt(a * b, BigInt(2 ** 51)), 8); return witness.slice(1, 9).every((u, i) => u === expected[i]); - }, - ), + } + ) ); }); }); - describe('When Performing binary multiplication on 4 by 1 numbers chunked by 51 bits', () => { - it('should multiply them correctly', async () => { - const cir = await wasmTester(path.join(__dirname, 'circuits', 'binmulfast51_2.circom')); + describe("When Performing binary multiplication on 4 by 1 numbers chunked by 51 bits", () => { + it("should multiply them correctly", async () => { + const cir = await wasmTester( + path.join(__dirname, "circuits", "binmulfast51_2.circom") + ); const a = BigInt(2 ** 200 - 10); const b = BigInt(19); - const chunk1 = utils.chunkBigInt(a); - const chunk2 = utils.chunkBigInt(b); + const chunk1 = utils.chunkBigInt(a, BigInt(2 ** 51)); + const chunk2 = utils.chunkBigInt(b, BigInt(2 ** 51)); const witness = await cir.calculateWitness({ in1: chunk1, in2: chunk2 }); - const expected = utils.chunkBigInt(a * b); + const expected = utils.chunkBigInt(a * b, BigInt(2 ** 51)); assert.ok(witness.slice(1, 6).every((u, i) => u === expected[i])); }); }); }); -describe('Check bits less then 51', () => { - describe('when a number is passed into it of 49 bits', () => { - it('should give output of 1', async () => { - const cir = await wasmTester(path.join(__dirname, 'circuits', 'binmullessthan51.circom')); - const witness = await cir.calculateWitness({ in: BigInt('450359962737049') }); +describe("Check bits less then 51", () => { + describe("when a number is passed into it of 49 bits", () => { + it("should give output of 1", async () => { + const cir = await wasmTester( + path.join(__dirname, "circuits", "binmullessthan51.circom") + ); + const witness = await cir.calculateWitness({ + in: BigInt("450359962737049"), + }); assert.ok(witness[1] === 1n); }); }); - describe('when a number is passed into it of 52 bits', () => { - it('should give output of 0', async () => { - const cir = await wasmTester(path.join(__dirname, 'circuits', 'binmullessthan51.circom')); - const witness = await cir.calculateWitness({ in: BigInt('4503599627370490') }); + describe("when a number is passed into it of 52 bits", () => { + it("should give output of 0", async () => { + const cir = await wasmTester( + path.join(__dirname, "circuits", "binmullessthan51.circom") + ); + const witness = await cir.calculateWitness({ + in: BigInt("4503599627370490"), + }); assert.ok(witness[1] === 0n); }); }); - describe('when a number is passed into it of greater than 52 bits', () => { - it('should fail on witness calculation', async () => { - const cir = await wasmTester(path.join(__dirname, 'circuits', 'binmullessthan51.circom')); - const witness = await cir.calculateWitness({ in: BigInt('45035996273704904503599627370490') }); + describe("when a number is passed into it of greater than 52 bits", () => { + it("should fail on witness calculation", async () => { + const cir = await wasmTester( + path.join(__dirname, "circuits", "binmullessthan51.circom") + ); + const witness = await cir.calculateWitness({ + in: BigInt("45035996273704904503599627370490"), + }); assert.ok(witness[1] === 0n); }); }); diff --git a/test/chunkedadd.test.js b/test/chunkedadd.test.js index 31de5f2..2421455 100644 --- a/test/chunkedadd.test.js +++ b/test/chunkedadd.test.js @@ -4,46 +4,75 @@ const wasmTester = require('circom_tester').wasm; const { default: fc } = require('fast-check'); const utils = require('./utils'); -describe('base 51 addition test', () => { - describe('when performing chuncked addition on three 200 bits numbers', () => { +describe('base 85 addition test', () => { + describe('when performing chunked addition on two 200 bits numbers', () => { it('should add them correctly', async () => { const cir = wasmTester(path.join(__dirname, 'circuits', 'chunkedadd.circom')); const a = BigInt(2 ** 200) - BigInt(19); const b = BigInt(2 ** 200) - BigInt(27); - const c = BigInt(2 ** 200) - BigInt(35); const chunk1 = utils.chunkBigInt(a); const chunk2 = utils.chunkBigInt(b); - const chunk3 = utils.chunkBigInt(c); - const witness = await (await cir).calculateWitness({ in: [chunk1, chunk2, chunk3] }, true); - const expected = utils.chunkBigInt(a + b + c); - assert.ok(witness.slice(1, 5).every((u, i) => u === expected[i])); + const witness = await (await cir).calculateWitness({ a: chunk1, b: chunk2 }, true); + const expected = utils.chunkBigInt(a + b); + assert.ok(witness.slice(1, 4).every((u, i) => u === expected[i])); + }); + it('should carry correctly', async () => { + const cir = wasmTester(path.join(__dirname, 'circuits', 'chunkedadd.circom')); + const a = BigInt(2 ** 255) - BigInt(1); + const b = BigInt(2 ** 255) - BigInt(1); + const chunk1 = utils.chunkBigInt(a); + const chunk2 = utils.chunkBigInt(b); + + const witness = await (await cir).calculateWitness({ a: chunk1, b: chunk2 }, true); + const expected = utils.chunkBigInt(a + b); + assert.ok(witness.slice(1, 4).every((u, i) => u === expected[i])); }); }); - describe('when performing chunked addition on four randomly genrated 200 bits numbers', () => { + describe('when performing chunked addition on two randomly generated 200 bits numbers', () => { it('should add them correctly', async () => { const cir = wasmTester(path.join(__dirname, 'circuits', 'chunkedadd1.circom')); await fc.assert( fc.asyncProperty( fc.bigInt(2n, BigInt(2 ** 200) - 19n), fc.bigInt(2n, BigInt(2 ** 200) - 27n), - fc.bigInt(2n, BigInt(2 ** 200) - 35n), - fc.bigInt(2n, BigInt(2 ** 200) - 45n), async (a, b, c, d) => { const chunk1 = utils.pad(utils.chunkBigInt(a), 4); const chunk2 = utils.pad(utils.chunkBigInt(b), 4); - const chunk3 = utils.pad(utils.chunkBigInt(c), 4); - const chunk4 = utils.pad(utils.chunkBigInt(d), 4); const witness = await (await cir).calculateWitness( - { in: [chunk1, chunk2, chunk3, chunk4] }, + { a: chunk1, b: chunk2 }, true, ); - const expected = utils.pad(utils.chunkBigInt(a + b + c + d), 5); + const expected = utils.pad(utils.chunkBigInt(a + b), 5); return witness.slice(1, 6).every((u, i) => u === expected[i]); }, ), ); }); }); + describe('when performing chunked irregular addition on 700 and 500 bits numbers', () => { + it('should add them correctly', async () => { + const cir = wasmTester(path.join(__dirname, 'circuits', 'chunkedaddirregular.circom')); + const a = BigInt(2 ** 700) - BigInt(19); + const b = BigInt(2 ** 500) - BigInt(27); + const chunk1 = utils.chunkBigInt(a); + const chunk2 = utils.chunkBigInt(b); + + const witness = await (await cir).calculateWitness({ a: chunk1, b: chunk2 }, true); + const expected = utils.chunkBigInt(a + b); + assert.ok(witness.slice(1, 10).every((u, i) => u === expected[i])); + }); + it('should carry correctly', async () => { + const cir = wasmTester(path.join(__dirname, 'circuits', 'chunkedaddirregular.circom')); + const a = BigInt(2 ** 765) - BigInt(1); + const b = BigInt(2 ** 510) - BigInt(1); + const chunk1 = utils.chunkBigInt(a); + const chunk2 = utils.chunkBigInt(b); + + const witness = await (await cir).calculateWitness({ a: chunk1, b: chunk2 }, true); + const expected = utils.chunkBigInt(a + b); + assert.ok(witness.slice(1, 11).every((u, i) => u === expected[i])); + }); + }); }); diff --git a/test/chunkedsub.test.js b/test/chunkedsub.test.js new file mode 100644 index 0000000..4736c00 --- /dev/null +++ b/test/chunkedsub.test.js @@ -0,0 +1,34 @@ +const path = require('path'); +const assert = require('assert'); +const wasmTester = require('circom_tester').wasm; +const { default: fc } = require('fast-check'); +const utils = require('./utils'); + +describe('base 85 subtraction test', () => { + describe('when performing chunked subtraction on two 200 bits numbers', () => { + it('should subtract them correctly', async () => { + const cir = wasmTester(path.join(__dirname, 'circuits', 'chunkedsub.circom')); + const a = BigInt(2 ** 200) - BigInt(19); + const b = BigInt(2 ** 200) - BigInt(27); + const chunk1 = utils.chunkBigInt(a); + const chunk2 = utils.chunkBigInt(b); + + const witness = await (await cir).calculateWitness({ a: chunk1, b: chunk2 }, true); + const expected = utils.pad(utils.chunkBigInt(a - b), 3); + assert.ok(witness.slice(1, 3).every((u, i) => u === expected[i])); + assert.equal(witness[4], 0); // underflow + }); + it('should underflow correctly', async () => { + const cir = wasmTester(path.join(__dirname, 'circuits', 'chunkedsub.circom')); + const a = BigInt(2 ** 200) - BigInt(27); + const b = BigInt(2 ** 200) - BigInt(19); + const chunk1 = utils.chunkBigInt(a); + const chunk2 = utils.chunkBigInt(b); + + const witness = await (await cir).calculateWitness({ a: chunk1, b: chunk2 }, true); + const expected = utils.pad(utils.chunkBigInt(BigInt(2 ** 200) + a - b), 3); + assert.ok(witness.slice(1, 3).every((u, i) => u === expected[i])); + assert.equal(witness[4], 1); // underflow + }); + }); +}); diff --git a/test/circuits/batchverify.circom b/test/circuits/batchverify.circom index f46fef2..84c542b 100644 --- a/test/circuits/batchverify.circom +++ b/test/circuits/batchverify.circom @@ -2,4 +2,4 @@ pragma circom 2.0.0; include "../../circuits/batchverify.circom"; -component main = BatchVerify(16, 1); \ No newline at end of file +component main = BatchVerify(16, 2); \ No newline at end of file diff --git a/test/circuits/binaddirr.circom b/test/circuits/binaddirr.circom deleted file mode 100644 index 4ba47be..0000000 --- a/test/circuits/binaddirr.circom +++ /dev/null @@ -1,4 +0,0 @@ -pragma circom 2.0.0; -include "../../circuits/binadd.circom"; - -component main = BinAddIrregular(56,40); \ No newline at end of file diff --git a/test/circuits/binmul1.circom b/test/circuits/binmul1.circom deleted file mode 100644 index 0ad32bf..0000000 --- a/test/circuits/binmul1.circom +++ /dev/null @@ -1,5 +0,0 @@ -pragma circom 2.0.0; - -include "../../circuits/binmul.circom"; - -component main = BinMul(104, 40); \ No newline at end of file diff --git a/test/circuits/binmullessthan51.circom b/test/circuits/binmullessthan51.circom index e26b9ed..44fda4c 100644 --- a/test/circuits/binmullessthan51.circom +++ b/test/circuits/binmullessthan51.circom @@ -1,4 +1,4 @@ pragma circom 2.0.0; include "../../circuits/chunkedmul.circom"; -component main = LessThanPower(51); \ No newline at end of file +component main = IsInRange(51); diff --git a/test/circuits/chunkedadd.circom b/test/circuits/chunkedadd.circom index 4b865ab..fdb5d64 100644 --- a/test/circuits/chunkedadd.circom +++ b/test/circuits/chunkedadd.circom @@ -2,4 +2,4 @@ pragma circom 2.0.0; include "../../circuits/chunkedadd.circom"; -component main = ChunkedAdd(4,3,51); \ No newline at end of file +component main = ChunkedAdd85(3); diff --git a/test/circuits/chunkedadd1.circom b/test/circuits/chunkedadd1.circom index 6c4b40e..43f7fc3 100644 --- a/test/circuits/chunkedadd1.circom +++ b/test/circuits/chunkedadd1.circom @@ -2,4 +2,4 @@ pragma circom 2.0.0; include "../../circuits/chunkedadd.circom"; -component main = ChunkedAdd(4,4,51); \ No newline at end of file +component main = ChunkedAdd85(4); diff --git a/test/circuits/chunkedaddirregular.circom b/test/circuits/chunkedaddirregular.circom new file mode 100644 index 0000000..803473b --- /dev/null +++ b/test/circuits/chunkedaddirregular.circom @@ -0,0 +1,5 @@ +pragma circom 2.0.0; + +include "../../circuits/chunkedadd.circom"; + +component main = ChunkedAdderIrregular85(9,6); diff --git a/test/circuits/chunkedsub.circom b/test/circuits/chunkedsub.circom new file mode 100644 index 0000000..21cd4df --- /dev/null +++ b/test/circuits/chunkedsub.circom @@ -0,0 +1,5 @@ +pragma circom 2.0.0; + +include "../../circuits/chunkedsub.circom"; + +component main = ChunkedSub85(3); diff --git a/test/circuits/inversemodulo1.circom b/test/circuits/inversemodulo1.circom deleted file mode 100644 index 1e5d66b..0000000 --- a/test/circuits/inversemodulo1.circom +++ /dev/null @@ -1,5 +0,0 @@ -pragma circom 2.0.0; - -include "../../circuits/inversemodulo.circom"; - -component main = InverseModulo(140); \ No newline at end of file diff --git a/test/circuits/modinv.circom b/test/circuits/modinv.circom index 81fbf04..1132d55 100644 --- a/test/circuits/modinv.circom +++ b/test/circuits/modinv.circom @@ -2,4 +2,4 @@ pragma circom 2.0.0; include "../../circuits/modinv.circom"; -component main = BigModInv51(); \ No newline at end of file +component main = BigModInv(); \ No newline at end of file diff --git a/test/circuits/point-verify.circom b/test/circuits/point-verify.circom new file mode 100644 index 0000000..71602c9 --- /dev/null +++ b/test/circuits/point-verify.circom @@ -0,0 +1,5 @@ +pragma circom 2.0.0; + +include "../../circuits/pointverify.circom"; + +component main = PointVerify(); diff --git a/test/circuits/range.circom b/test/circuits/range.circom new file mode 100644 index 0000000..38465f4 --- /dev/null +++ b/test/circuits/range.circom @@ -0,0 +1,5 @@ +pragma circom 2.0.0; + +include "../../circuits/range.circom"; + +component main = IsInRange(85); diff --git a/test/circuits/verify.circom b/test/circuits/verify.circom index 34d463b..47ba873 100644 --- a/test/circuits/verify.circom +++ b/test/circuits/verify.circom @@ -2,4 +2,4 @@ pragma circom 2.0.0; include "../../circuits/verify.circom"; -component main = Ed25519Verifier(16); \ No newline at end of file +component main {public [msg, S]} = Ed25519Verifier(16); \ No newline at end of file diff --git a/test/ed25519verfication.test.js b/test/ed25519verfication.test.js index 0c52094..32dd777 100644 --- a/test/ed25519verfication.test.js +++ b/test/ed25519verfication.test.js @@ -1,14 +1,16 @@ -const path = require('path'); -const assert = require('assert'); -const wasmTester = require('circom_tester').wasm; -const { performance } = require('perf_hooks'); -const mlog = require('mocha-logger'); -const utils = require('./utils'); +const path = require("path"); +const assert = require("assert"); +const wasmTester = require("circom_tester").wasm; +const { performance } = require("perf_hooks"); +const mlog = require("mocha-logger"); +const utils = require("./utils"); -describe('ED25519 verifcation test', () => { - describe('When testing against the RFC test vector', () => { - it('should verify correctly', async () => { - const cir = await wasmTester(path.join(__dirname, 'circuits', 'verify.circom')); +describe("ED25519 verifcation test", () => { + describe("When testing against the RFC test vector", () => { + it("should verify correctly", async () => { + const cir = await wasmTester( + path.join(__dirname, "circuits", "verify.circom") + ); const pointA = [ 43933056957747458452560886832567536073542840507013052263144963060608791330050n, 16962727616734173323702303146057009569815335830970791807500022961899349823996n, @@ -21,18 +23,13 @@ describe('ED25519 verifcation test', () => { 1n, 7867784340861643381702890578607277776011430152424699121581244161773093676488n, ]; - const A = 16962727616734173323702303146057009569815335830970791807500022961899349823996n; const msg = 33455n; - const R8 = 78142972218048021222160463610080218564159109753358461787358041591257467621730n; - const S = 4869643893319708471955165214975585939793846505679808910535986866633137979160n; + const S = + 4869643893319708471955165214975585939793846505679808910535986866633137979160n; const bufMsg = utils.bigIntToLEBuffer(msg); - const bufR8 = utils.bigIntToLEBuffer(R8); const bufS = utils.bigIntToLEBuffer(S); - const bufA = utils.bigIntToLEBuffer(A); const bitsMsg = utils.buffer2bits(bufMsg); - const bitsR8 = utils.pad(utils.buffer2bits(bufR8), 256); const bitsS = utils.pad(utils.buffer2bits(bufS), 255).slice(0, 255); - const bitsA = utils.pad(utils.buffer2bits(bufA), 256); const chunkA = []; const chunkR = []; @@ -47,11 +44,17 @@ describe('ED25519 verifcation test', () => { } try { const startTime = performance.now(); - const witness = await cir.calculateWitness({ - msg: bitsMsg, A: bitsA, R8: bitsR8, S: bitsS, PointA: chunkA, PointR: chunkR, - }); + const inputJson = { + msg: bitsMsg, + S: bitsS, + PointA: chunkA, + PointR: chunkR, + }; + const witness = await cir.calculateWitness(inputJson); const endTime = performance.now(); - mlog.success(`Call to calculate witness took ${endTime - startTime} milliseconds`); + mlog.success( + `Call to calculate witness took ${endTime - startTime} milliseconds` + ); assert.ok(witness[0] === 1n); assert.ok(witness[1] === 1n); } catch (e) { @@ -61,31 +64,30 @@ describe('ED25519 verifcation test', () => { }); }); - describe('When testing against the RFC test vector', () => { - it('should verify correctly', async () => { - const cir = await wasmTester(path.join(__dirname, 'circuits', 'verify.circom')); + describe("When testing against the RFC test vector", () => { + it("should verify correctly", async () => { + const cir = await wasmTester( + path.join(__dirname, "circuits", "verify.circom") + ); const pointA = [ 43933056957747458452560886832567536073542840507013052263144963060608791330050n, 16962727616734173323702303146057009569815335830970791807500022961899349823996n, 1n, - 47597536765056690778342994103149503974598380825968728087754575050160026478564n]; + 47597536765056690778342994103149503974598380825968728087754575050160026478564n, + ]; const pointR = [ 26073464383897998325899031212762184271676052677226679463708862316754828477519n, 20246927599389923510374971105736264637524117420538179767629249587300902801762n, 1n, - 7867784340861643381702890578607277776011430152424699121581244161773093676488n]; - const A = 16962727616734173323702303146057009569815335830970791807500022961899349823996n; + 7867784340861643381702890578607277776011430152424699121581244161773093676488n, + ]; const msg = 33456n; - const R8 = 78142972218048021222160463610080218564159109753358461787358041591257467621730n; - const S = 4869643893319708471955165214975585939793846505679808910535986866633137979160n; + const S = + 4869643893319708471955165214975585939793846505679808910535986866633137979160n; const bufMsg = utils.bigIntToLEBuffer(msg); - const bufR8 = utils.bigIntToLEBuffer(R8); const bufS = utils.bigIntToLEBuffer(S); - const bufA = utils.bigIntToLEBuffer(A); const bitsMsg = utils.buffer2bits(bufMsg); - const bitsR8 = utils.pad(utils.buffer2bits(bufR8), 256); const bitsS = utils.pad(utils.buffer2bits(bufS), 255).slice(0, 255); - const bitsA = utils.pad(utils.buffer2bits(bufA), 256); const chunkA = []; const chunkR = []; @@ -100,7 +102,10 @@ describe('ED25519 verifcation test', () => { } try { const witness = await cir.calculateWitness({ - msg: bitsMsg, A: bitsA, R8: bitsR8, S: bitsS, PointA: chunkA, PointR: chunkR, + msg: bitsMsg, + S: bitsS, + PointA: chunkA, + PointR: chunkR, }); assert.ok(witness[0] === 1n); assert.ok(witness[1] === 0n); diff --git a/test/inversemodulo.test.js b/test/inversemodulo.test.js deleted file mode 100644 index b3566bb..0000000 --- a/test/inversemodulo.test.js +++ /dev/null @@ -1,23 +0,0 @@ -const path = require('path'); -const assert = require('assert'); -const wasmTester = require('circom_tester').wasm; -const bigintModArith = require('bigint-mod-arith'); -const utils = require('./utils'); - -describe('Inverse Modulo Test', () => { - describe('when performing inverse modulo on a 104 bit number', () => { - const p = BigInt('57896044618658097711785492504343953926634992332820282019728792003956564819949'); - it('should find the inverse', async () => { - const cir = await wasmTester(path.join(__dirname, 'circuits', 'inversemodulo1.circom')); - const a = BigInt('2820282019728792003956564819949'); - const buf1 = utils.bigIntToLEBuffer(a); - const asBits1 = utils.buffer2bits(buf1); - const witness = await cir.calculateWitness({ in: asBits1 }, true); - - const inv = bigintModArith.modInv(a, p); - const expected = utils.pad(utils.buffer2bits(utils.bigIntToLEBuffer(inv)), 255); - - assert.ok(witness.slice(1, 256).every((u, i) => u === expected[i])); - }); - }); -}); diff --git a/test/lt.test.js b/test/lt.test.js new file mode 100644 index 0000000..8442a90 --- /dev/null +++ b/test/lt.test.js @@ -0,0 +1,63 @@ +const path = require("path"); +const assert = require("assert"); +const wasmTester = require("circom_tester").wasm; +const fc = require("fast-check"); +const utils = require("./utils"); + +describe("Less Than Power Test", () => { + it("should return 1 for input 0", async () => { + const cir = await wasmTester( + path.join(__dirname, "circuits", "range.circom") + ); + const a = BigInt('0'); + const witness = await cir.calculateWitness( + { in: a }, + true + ); + assert.equal(witness[1], 1); + }); + it("should return 1 for input 1", async () => { + const cir = await wasmTester( + path.join(__dirname, "circuits", "range.circom") + ); + const a = BigInt('1'); + const witness = await cir.calculateWitness( + { in: a }, + true + ); + assert.equal(witness[1], 1); + }); + it("should return 1 for input 2^85 - 1", async () => { + const cir = await wasmTester( + path.join(__dirname, "circuits", "range.circom") + ); + const a = BigInt(2**85 - 1); + const witness = await cir.calculateWitness( + { in: a }, + true + ); + assert.equal(witness[1], 0); + }); + it("should return 0 for input 2^85", async () => { + const cir = await wasmTester( + path.join(__dirname, "circuits", "range.circom") + ); + const a = BigInt(2**85); + const witness = await cir.calculateWitness( + { in: a }, + true + ); + assert.equal(witness[1], 0); + }); + it("should return 0 for input 2^85 + 1", async () => { + const cir = await wasmTester( + path.join(__dirname, "circuits", "range.circom") + ); + const a = BigInt(2**85) + BigInt(1); + const witness = await cir.calculateWitness( + { in: a }, + true + ); + assert.equal(witness[1], 0); + }); +}); diff --git a/test/modulus.test.js b/test/modulus.test.js index 4e5267f..674fc8c 100644 --- a/test/modulus.test.js +++ b/test/modulus.test.js @@ -99,28 +99,6 @@ describe('Modulus Test', () => { }); }); - describe('when performing modulus on a binary number of 264 bits in prime field of prime 252c', () => { - const q = BigInt('7237005577332262213973186563042994240857116359379907606001950938285454250989'); - - it('should calculate the modulus of the binary number correctly', async () => { - const cir = await wasmTester(path.join(__dirname, 'circuits', 'modulusq2.circom')); - const a = BigInt('1257896044618658097711785492504343953926634992332820282019728792003956564819949'); - const buf = utils.bigIntToLEBuffer(a); - const asBits = utils.buffer2bits(buf); - // var startTime = performance.now(); - const witness = await cir.calculateWitness({ in: asBits }, true); - // var endTime = performance.now(); - // console.log(`Call to calculate witness took ${endTime - startTime} milliseconds`); - - const expected = utils.pad( - utils.buffer2bits(utils.bigIntToLEBuffer(bigintModArith.modPow(a, 1, q))), - 253, - ); - - assert.ok(witness.slice(1, 254).every((u, i) => u === expected[i])); - }); - }); - describe('when performing modulus on a number of 32 chunks each chunk of size 51 bits in prime field of prime 25519', () => { it('should calculate the modulus of that number correctly', async () => { const cir = await wasmTester(path.join(__dirname, 'circuits', 'chunkedmodulus.circom')); diff --git a/test/pointadd.test.js b/test/pointadd.test.js index f268885..f85f81a 100644 --- a/test/pointadd.test.js +++ b/test/pointadd.test.js @@ -54,50 +54,4 @@ describe('Point Addition test on ed25519', () => { ); }); }); - describe('when performing point addition on EC', () => { - it('should add them correctly', async () => { - const cir = await wasmTester(path.join(__dirname, 'circuits', 'point-addition51.circom')); - const p = BigInt(2 ** 255) - BigInt(19); - - await fc.assert( - fc.asyncProperty( - fc.bigInt(BigInt(2 ** 150), BigInt(2 ** 254) - 2000n), - fc.bigInt(BigInt(2 ** 155), BigInt(2 ** 254) - 2025n), - fc.bigInt(BigInt(2 ** 165), BigInt(2 ** 254) - 2203n), - fc.bigInt(BigInt(2 ** 175), BigInt(2 ** 254) - 2403n), - async (a, b, c, d) => { - const P = [a, b, c, d]; - const Q = [a, b, c, d]; - const chunk1 = []; - const chunk2 = []; - for (let i = 0; i < 4; i++) { - chunk1.push(utils.chunkBigInt(P[i], BigInt(2 ** 85))); - chunk2.push(utils.chunkBigInt(Q[i], BigInt(2 ** 85))); - } - for (let i = 0; i < 4; i++) { - utils.pad(chunk1[i], 3); - utils.pad(chunk2[i], 3); - } - const witness = await cir.calculateWitness({ P: chunk1, Q: chunk2 }); - const res = utils.point_add(P, Q); - const expected = []; - for (let i = 0; i < 4; i++) { - expected.push(utils.modulus(res[i], p)); - } - const wt = witness.slice(1, 13); - const chunk = []; - for (let i = 0; i < 4; i++) { - chunk.push(wt.slice(3 * i, 3 * i + 3)); - } - - const dechunkedWt = []; - for (let i = 0; i < 4; i++) { - dechunkedWt.push(utils.dechunk(chunk[i], BigInt(2 ** 85))); - } - return utils.point_equal(expected, dechunkedWt); - }, - ), - ); - }); - }); }); diff --git a/test/pointverify.test.js b/test/pointverify.test.js new file mode 100644 index 0000000..54cbb64 --- /dev/null +++ b/test/pointverify.test.js @@ -0,0 +1,25 @@ +const path = require('path'); +const assert = require('assert'); +const wasmTester = require('circom_tester').wasm; +const { default: fc } = require('fast-check'); +const utils = require('./utils'); + +describe('Point Verify test on ed25519', () => { + it('should verify P correctly', async () => { + const cir = await wasmTester(path.join(__dirname, 'circuits', 'point-verify.circom')); + const P = [ + 43933056957747458452560886832567536073542840507013052263144963060608791330050n, + 16962727616734173323702303146057009569815335830970791807500022961899349823996n, + 1n, + 47597536765056690778342994103149503974598380825968728087754575050160026478564n, + ]; + + const chunk = []; + for (let i = 0; i < 4; i++) { + chunk.push(utils.chunkBigInt(P[i], BigInt(2 ** 85))); + utils.pad(chunk[i], 3); + } + + const witness = await cir.calculateWitness({ P: chunk }, true); + }); +}); diff --git a/test/utils.js b/test/utils.js index 42c5aac..2c7cff4 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,5 +1,8 @@ const bigintModArith = require('bigint-mod-arith'); -function buffer2bits(buff) { +const crypto = require("crypto"); +const nacl = require('tweetnacl'); + +function buffer2bits (buff) { const res = []; for (let i = 0; i < buff.length; i++) { for (let j = 0; j < 8; j++) { @@ -13,29 +16,29 @@ function buffer2bits(buff) { return res; } -function convertToEvenLength(hexInput) { +function convertToEvenLength (hexInput) { if (hexInput.length % 2 == 1) { return '0' + hexInput; } return hexInput; } -function normalize(input) { +function normalize (input) { if (IsPowerOfTwo(input.length)) { input.push(0n); } return input; } -function IsPowerOfTwo(x) { +function IsPowerOfTwo (x) { return (x & (x - 1)) == 0; } -function bigIntToLEBuffer(x) { +function bigIntToLEBuffer (x) { return Buffer.from(convertToEvenLength(x.toString(16)), 'hex').reverse() } -function pad(x, n) { +function pad (x, n) { var total = n - x.length; for (var i = 0; i < total; i++) { x.push(0n); @@ -43,11 +46,11 @@ function pad(x, n) { return x; } // This function will give the right modulud as expected -function modulus(num, p) { +function modulus (num, p) { return ((num % p) + p) % p; } -function bitsToBigInt(arr) { +function bitsToBigInt (arr) { res = BigInt(0); for (var i = 0; i < arr.length; i++) { res += (BigInt(2) ** BigInt(i)) * BigInt(arr[i]); @@ -56,7 +59,7 @@ function bitsToBigInt(arr) { } // This function will convert a bigInt into the chucks of Integers -function chunkBigInt(n, mod = BigInt(2 ** 51)) { +function chunkBigInt (n, mod = BigInt(2 ** 85)) { if (!n) return [0]; let arr = []; while (n) { @@ -66,10 +69,11 @@ function chunkBigInt(n, mod = BigInt(2 ** 51)) { return arr; } +// Prime order of Ed25519 let p = BigInt(2 ** 255) - BigInt(19); let d = 37095705934669439343138083508754565189542113879843219016388785533085940283555n; // This function will perform point addition on elliptic curve 25519 to check point addition circom -function point_add(P, Q) { +function point_add (P, Q) { let A = modulus((P[1] - P[0]) * (Q[1] - Q[0]), p); let B = modulus((P[1] + P[0]) * (Q[1] + Q[0]), p); let C = modulus(BigInt(2) * P[3] * Q[3] * d, p); @@ -83,7 +87,7 @@ function point_add(P, Q) { return [E * F, G * H, F * G, E * H]; } //This funciton will give the point multiplcation on EC 25519 -function point_mul(s, P) { +function point_mul (s, P) { let Q = [0n, 1n, 1n, 0n]; while (s > 0) { if (s & 1n) { @@ -95,31 +99,283 @@ function point_mul(s, P) { return Q; } -function dechunk(x, mod = BigInt(2 ** 51)) { +function dechunk (x, mod = BigInt(2 ** 51)) { sum = 0n; for (let i = 0; i < x.length; i++) { sum += (mod ** BigInt(i)) * x[i]; } return sum; } -function point_equal(P, Q) { - // x1 / z1 == x2 / z2 <==> x1 * z2 == x2 * z1 - if (modulus((P[0] * Q[2] - Q[0] * P[2]), p) != 0n){ - return false - } - if (modulus((P[1] * Q[2] - Q[1] * P[2]), p) != 0n){ - return false +function point_equal (P, Q) { + // x1 / z1 == x2 / z2 <==> x1 * z2 == x2 * z1 + if (modulus((P[0] * Q[2] - Q[0] * P[2]), p) != 0n) { + return false + } + if (modulus((P[1] * Q[2] - Q[1] * P[2]), p) != 0n) { + return false } - return true + return true } -function point_compress(P){ - const zinv = bigintModArith.modInv(P[2],p); - let x = modulus(P[0] * zinv , p); - let y = modulus(P[1] * zinv , p); - const inter = y | ((x & 1n) << 255n) +function point_compress (P) { + const zinv = bigintModArith.modInv(P[2], p); + let x = modulus(P[0] * zinv, p); + let y = modulus(P[1] * zinv, p); + const inter = y | ((x & 1n) << 255n) return buffer2bits(bigIntToLEBuffer(inter)); -} +} + +function point_decompress (compressedPoint) { + const x = compressedPoint; // Assuming compressed point is given as x-coordinate + const xSquared = modulus(x ** 2n, p); + const ySquared = modulus((xSquared - 1n) * modInv(121666n, p), p); + const y = mod_sqrt(ySquared, p); + + return [x, y, 1n]; +} + +// Modular square root function using Tonelli-Shanks algorithm +function mod_sqrt (a, p) { + let q = p - 1n; + let s = 0n; + while (q % 2n === 0n) { + q /= 2n; + s += 1n; + } + + let n = 2n; + while (legendreSymbol(n, p) !== p - 1n) { + n += 1n; + } + + let z = mod_pow(a, (q + 1n) / 2n, p); + let c = mod_pow(n, q, p); + let r = mod_pow(a, (q - 1n) / 2n, p); + + for (let i = 1n; i < s; i++) { + const prevR = r; + r = (r * r) % p; + + if (r === 1n && prevR !== 1n && prevR !== p - 1n) { + return 0n; // No square root exists + } + + if (c === 1n) { + z = (z * n) % p; + } + + c = (c * c) % p; + } + + return z; +} + +// Modular exponentiation function +function mod_pow (base, exponent, modulus) { + let result = 1n; + base = base % modulus; + + while (exponent > 0n) { + if (exponent % 2n === 1n) { + result = (result * base) % modulus; + } + + exponent = exponent >> 1n; + base = (base * base) % modulus; + } + + return result; +} + +// Legendre symbol function +function legendreSymbol (a, p) { + return mod_pow(a, (p - 1n) / 2n, p); +} + +// Modular inverse function +function modInv (a, m) { + let m0 = m; + let x0 = 0n; + let x1 = 1n; + + if (m === 1n) { + return 0n; + } + + while (a > 1n) { + let q = a / m; + let t = m; + + m = a % m; + a = t; + t = x0; + + x0 = x1 - q * x0; + x1 = t; + } + + if (x1 < 0n) { + x1 += m0; + } + + return x1; +} + +// Utility function for modular arithmetic +function modulus (a, p) { + return ((a % p) + p) % p; +} + + +function bytesToHex (bytes) { + return Buffer.from(bytes).toString('hex'); +} + + +// generate input.json +function get_input (msg, sigs, public_keys) { + let A = []; + let R8 = []; + let S = []; + let PointA = []; + let PointR = []; + let bitsMsgs = [] + let sha256A = crypto.createHash("sha256"); + + for (let i = 0; i < sigs.length; i++) { + let bitsMsg = buffer2bits(msg[i]); + bitsMsgs.push(bitsMsg) + let sig = sigs[i]; + let pub_key = public_keys[i]; + const pubXCoordinate = BigInt('0x' + bytesToHex(pub_key), 16); + let pointA = point_decompress(pubXCoordinate); + const sigXCoordinate = BigInt('0x' + bytesToHex(sig.slice(0, 32)), 16); + let pointR = point_decompress(sigXCoordinate); + + let chunkA = []; + let chunkR = []; + for (let i = 0; i < 4; i++) { + chunkA.push(chunkBigInt(pointA[i], BigInt(2 ** 85))); + chunkR.push(chunkBigInt(pointR[i], BigInt(2 ** 85))); + } + for (let i = 0; i < 4; i++) { + pad(chunkA[i], 3); + pad(chunkR[i], 3); + } + sha256A = sha256A.update(pub_key); + let bitsA = buffer2bits(pub_key); + let bitsR8 = buffer2bits(sig.slice(0, 32)); + let bitsS = buffer2bits(sig.slice(32)).slice(0, 255); + A.push(bitsA); + R8.push(bitsR8); + S.push(bitsS); + PointA.push(chunkA); + PointR.push(chunkR); + } + sha256A = sha256A.digest(); + sha256A = [ + BigInt("0x" + sha256A.slice(0, 16).reverse().toString("hex")), + BigInt("0x" + sha256A.slice(16).reverse().toString("hex")) + ]; + let input = { + bitsMsg: bitsMsgs, + A: A, + R8: R8, + bitsS: S, + PointA: PointA, + PointR: PointR, + sha256A + } + + // console.log('input json:', input) + return input; +} + +function get_input_data (n) { + const msgs = [] + const sigs = [] + const public_keys = [] + if (n < 1) { + n = 1 + } + for (let i = 1; i <= n; i++) { + // Generate a key pair + const keyPair = nacl.sign.keyPair(); + public_keys.push(keyPair.publicKey) + + let msg = BigInt(i + 10000) + msg = bigIntToLEBuffer(msg) + msgs.push(msg) + + // Sign the data with the private key + const signature = nacl.sign.detached(msg, keyPair.secretKey); + sigs.push(signature) + } + + return { + msgs, + sigs, + public_keys + } +} + +function get_static_input_data () { + const msgs = [] + const sigs = [] + const public_keys = [] + // Create some data to sign + let data1 = BigInt(10001); + data1 = bigIntToLEBuffer(data1) + + let data2 = BigInt(33455); + data2 = bigIntToLEBuffer(data2) + + let publicKey1 = new Uint8Array([ + 88, 107, 12, 98, 40, 135, 65, 81, + 201, 254, 219, 128, 23, 203, 12, 150, + 114, 254, 47, 199, 155, 140, 142, 105, + 113, 3, 169, 77, 107, 195, 141, 212 + ]) + + let publicKey2 = new Uint8Array([ + 5, 175, 189, 71, 92, 118, 182, 4, + 154, 50, 116, 85, 240, 12, 229, 122, + 99, 21, 41, 8, 205, 202, 60, 254, + 37, 199, 124, 133, 234, 67, 152, 54 + ]) + + // let publicKey = keyPair.publicKey + let signature1 = new Uint8Array([ + 27, 88, 152, 79, 174, 103, 98, 23, 66, 229, 179, + 250, 45, 208, 240, 183, 12, 34, 164, 152, 155, 148, + 140, 149, 95, 202, 230, 241, 228, 43, 47, 188, 253, + 45, 16, 1, 90, 223, 123, 21, 6, 244, 246, 24, + 244, 196, 153, 154, 188, 141, 116, 67, 22, 231, 34, + 172, 30, 250, 121, 202, 210, 110, 239, 7 + ]) + + let signature2 = new Uint8Array([ + 36, 235, 41, 245, 213, 147, 226, 57, 134, 241, 34, + 185, 120, 212, 166, 242, 37, 17, 207, 192, 48, 147, + 126, 246, 242, 138, 98, 122, 122, 165, 192, 3, 91, + 88, 246, 57, 52, 27, 181, 141, 163, 234, 7, 74, + 106, 243, 55, 151, 30, 43, 136, 27, 18, 246, 200, + 232, 219, 87, 83, 158, 20, 154, 136, 10 + ]) + msgs.push(data1) + sigs.push(signature1) + public_keys.push(publicKey1) + msgs.push(data2) + sigs.push(signature2) + public_keys.push(publicKey2) + + return { + msgs, + sigs, + public_keys + } +} + module.exports = { buffer2bits, convertToEvenLength, @@ -133,5 +389,10 @@ module.exports = { point_mul, dechunk, point_equal, - point_compress -}; \ No newline at end of file + point_compress, + point_decompress, + bytesToHex, + get_input_data, + get_static_input_data, + get_input +};