Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 97 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,110 @@

Implementation of ECDSA operations in circom.

## Set up for ASCS project goal
- Run `yarn` at the top level to install npm dependencies (`snarkjs` and `circomlib`).

- Download `circom` version `>= 2.0.2` on your system. Installation instructions [here](https://docs.circom.io/getting-started/installation/).

- Download ``ptau`` file with power 21 from the Hermez trusted setup from [this repository](https://github.com/iden3/snarkjs#7-prepare-phase-2) and copy it into the `circuits` subdirectory of the project, with the name `pot20_final.ptau`.

- Build key and wittness by running `yarn build:groupsig` at the top level. This build process will create `r1cs` and `wasm` files for witness generation, as well as a `zkey` file (proving and verifying keys) in a the folder `./build/groupsig`. If no `zkey` file was generated and you are on windows, then:
1. Install snarkjs gloablly like so: `npm install -g snarkjs`
2. Instead of `yarn build:groupsig`, run `cd ./scripts/groupsig && ./windows_build_groupsig.sh` in Git Bash terminal
3. Optional: move back to root folder (required for next step): `cd ../..`

- Test set up by running groupsig demo through `yarn groupsig-demo` at the top level and follow the instructions in your terminal. [Randomly generated](https://privatekeys.pw/keys/ethereum/random) valid inputs for demo:
1. private key: 0x3d87d34a290b124ad0b29b87053363d5dca57cd02650e4b1f4cc75e9c8275648 --> associated eth address: 0x68F3A3AfD9Cbf1cb27b5359b79B563A5E423115a
2. addr1: 0x0F2D3bF9ce11737566E5bcef7222Df31C0D90395
3. addr2: 0x46a8801DA492f6d2eADbd3ec30f4255c29aB656b
4. nonce: 6789

- For prototype: Build key and wittnesses for variable number of company admins by
1. setting `SIZES=(2 3 4)` in script: `./scripts/groupsig/build_circuits.sh` (default is 2 to 4)
2. executing the script by running ``cd ./scripts/groupsig && ./build_circuits.sh`` while root folder of repo
- For prototype demo run `yarn prototype-demo` at the top level and follow the instructions in your terminal. [Randomly generated](https://privatekeys.pw/keys/ethereum/random) valid inputs for demo:
1. private key: 0x3d87d34a290b124ad0b29b87053363d5dca57cd02650e4b1f4cc75e9c8275648 --> associated eth address: 0x68F3A3AfD9Cbf1cb27b5359b79B563A5E423115a
2. addr1: 0x0F2D3bF9ce11737566E5bcef7222Df31C0D90395
3. addr2: 0x46a8801DA492f6d2eADbd3ec30f4255c29aB656b
4. addr3: 0xC8a6ab61Cc685586F3399857A4e80a75327fE4C0
6. msg: bafybeicn7i3soqdgr7dwnrwytgq4zxy7a5jpkizrvhm5mv6bgjd32wm3q4
6. nonce: 6789

## Build and test final circuit for ASCS project goal
**note**: the final circuit is in `./scripts/groupsig/private_secure_variable_groupsig.circom`

1. executing the script by running ``cd ./scripts/groupsig && ./build_private_groupsig_circuits.sh`` while root folder of repo
2. test by running `node ./scripts/test_private_secure_variable_groupsig.js` while root folder of repo (note: the test script solely works for three group members which must be considered during the first step)
3. copy the `./build/groupsig/verifiers` folder and add the folder to `./packages/trust-anchor-did-ethr/contracts` of the [on-chain-ssi repo](https://github.com/ASCS-eV/on-chain-ssi/tree/main/packages/trust-anchor-did-ethr/contracts)

*note*: the final build script was used to generate the ``./build/groupsig/p_m_4`` folder which was added as `circom-zkp-generator` in `./packages/trust-anchor-did-ethr/test` folder of the [on-chain-ssi repo](https://github.com/ASCS-eV/on-chain-ssi/tree/main/packages/trust-anchor-did-ethr/contracts)`

## Requirements for ASCS project goal
1. Enhanced security:
1. replay attack: solved with nonce logic
2. domain seperation
2. Variable number of eth addresses as public input of circuit
3. Enhanced privacy by making the signature a private input of the circuit

## Security analysis
The upper requirements ensure enhanced security but other security matters must be consdiered and are divide in security issues for production and for the potential future of the project:

### Security issues for production
1. **Trusted Setup**
Circom with Groth16 requires a Phase 2 Trusted Setup. If the trust ceremony is not done correctly, or if the "Power of Tau" file is compromised (as it is the case for this prototype since we download a public ptau file), someone could generate fake proofs (forgeries) without knowing any private key.

2. **MiMic vs. ECDSA**
MiMC is used because it is cheap and easy in zero-knowledge circuits and sufficient for demonstrating private-key ownership. However, it does not provide a standard digital signature. For real-world Ethereum applications, MiMC should ideally be replaced with in-circuit ECDSA verification to ensure compatibility, interoperability, and strong cryptographic guarantees. However, in-circuit ECDSA verification comes at the cost of more circuit constraints and thus worse performance.

3. **Scalar Malleability**
- *Risk:* The curve order \\( n \\) is slightly smaller than the field size \\( 2^{256} \\). If the circuit does not enforce that the private key lies in the range \\( 1 \\leq d < n \\), a prover can submit values such as \\( d \\) and \\( d + n \\). Both values generate the same public key and Ethereum address. This allows multiple valid witnesses for the same identity, enabling proof malleability, replay attacks, and potential bypass of double-spending protections.
- *Recommendation:* Enforce a strict range check ensuring that the private key satisfies \\( 1 \\leq d < n \\), where \\( n \\) is the secp256k1 curve order. This should be implemented using multi-limb comparison circuits (e.g., `BigLessThan`) and zero-value checks to guarantee a unique canonical representation.

4. **Non-existent Points**
- *Risk:* Without explicitly verifying that a derived public key lies on the secp256k1 curve (i.e., satisfies \\( y^2 = x^3 + 7 \\)), a prover may use mathematically invalid points. These “fake” points can sometimes be mapped through the public-key-to-address hashing process to a valid Ethereum address, enabling identity forgery without possession of a legitimate private key.
- *Recommendation:* Enforce point-on-curve validation by constraining the public key coordinates to satisfy the elliptic curve equation. If public keys are derived internally, ensure that the scalar multiplication circuit includes built-in curve membership checks. Prefer using audited elliptic curve components that guarantee valid point generation.

5. **Edge Case Exploits**
- *Risk:* Special values such as `0`, `1`, or multiples of the curve order may trigger undefined or unintended behavior in elliptic curve arithmetic, including producing the point at infinity or degenerate keys. These edge cases can result in addresses that do not correspond to any real user but can still be claimed by a malicious prover.
- *Recommendation:* Explicitly forbid invalid scalar values by enforcing lower and upper bounds on private keys. Disallow zero and other degenerate values through dedicated zero-check constraints. Ensure that all elliptic curve operations are well-defined for the permitted input range.

### Potential future security issues
1. **Deterministic Signatures (MiMC Vulnerability)**
- *Risk*: The groupsig circuit uses mimc(msg, privkey). If you ever sign the same msg with the same privkey but a different nonce, you aren't leaking the key, but you are creating a linkable trail.
- *Recommendation*: Ensure your attestation always includes all unique context (like a domain_separator) to ensure signatures cannot be "imported" from one app to another.
- *Note*: In ASCS's use case, we can neglect this security issue (as of now) because the msg always contains the CID of the digital data asset uploaded to IPFS and to be published on the digital data asset marketplace. Therefore, the CID ensures domain seperation!
2. **Forgery via Public Input Manipulation**
- *Risk*: The groupsig circuit proves membership in a list of ETH addresses but an attacker could take your valid proof and simply change the list of ETH addresses to different addresses. If the verifier doesn't check the entire set of addresses against a trusted root (like a Merkle Root), the proof is useless.
- *Recommendation*: Instead of passing a lsit of ETH addresses, pass a Merkle Root as a public input and use a Merkle Proof (private input) to prove your address is in the set.
- *Note*: In ASCS's use case, we can neglect this security issue (as of now) because the verifier is an on-chain smart contract which checks if the ETH addresses are actually part of the same and correct company by considering the did:ethr-based on-chain company structre.
3. **The "Frozen Heart" (Input Aliasing)**
- *Risk*: Circom signals are in a prime field.If msg or nonce are larger than the field size ($p \approx 2^{254}$), they will wrap around (modulo $p$). An attacker could provide a msg that is OriginalMsg + p, and the circuit would produce the exact same attestation.
- *Recommendation*: Always constrain your public inputs to be within a specific bit range (e.g., 253 bits) if they are derived from external data like Ethereum hashes.
- *Note*: TODO: think about if this is an issue for the ASCS's use case?

## Potential optimizations
1. **Membership Proof Is O(m)**
The current circuit verifies group membership by computing the product \\((myAddr - addrs[0]) \\times (myAddr - addrs[1]) \\times \\dots \\times (myAddr - addrs[m-1])\\), which requires one multiplication per group member. This means that the proving cost and circuit size grow linearly with the number of addresses in the group. As the group becomes large, this approach quickly becomes impractical due to high constraint counts, slower proof generation, and higher verification costs. A more scalable alternative is to use a Merkle tree or cryptographic accumulator. In this design, all valid addresses are committed into a single root hash, and the prover supplies a Merkle proof showing that their address is included in the tree. This reduces the complexity from O(m) to O(log m), making it feasible to support thousands or millions of members. Merkle-based membership proofs are widely used in modern zero-knowledge systems and are well-supported by existing Circom libraries, making them a practical and secure upgrade.

2. **Private Key Inside Circuit**
The circuit directly includes the full Ethereum private key as a private witness and uses it both to derive the address and to generate the message attestation. While this is functionally correct, it significantly increases circuit complexity and proof generation time, because elliptic-curve operations and full key handling are expensive in zero-knowledge environments. It also increases the risk surface: if the prover’s environment is compromised, exposure of the witness leaks the actual Ethereum private key, which may control real funds. A better approach is to avoid embedding the long-term private key in the circuit and instead use derived or committed secrets. For example, the user can generate a random secret, commit to it on-chain, and link it to their address through a one-time signature or registration step. The circuit then proves knowledge of this secret rather than the main private key. Another option is to use nullifiers or hierarchical keys, where a dedicated ZK key is derived from the Ethereum key and used only for proofs. These approaches reduce computational cost, improve security isolation, and prevent direct exposure of high-value private keys inside zero-knowledge circuits.

3. **Improven Variable Group Size ZKP-Generation with Unified Circuit with Dynamic Group Padding**
Currently, the system generates separate WASM and ZKey pairs for every possible group size (`m`). This requires the `DIDMultisigController` to maintain a mapping of verifier contracts and forces the client to download specific proving keys based on the admin count. We propose moving to a **Unified Max-Size Circuit** (e.g., `MAX_M = 100`). Instead of a strict product check, the circuit will be updated to ignore "Null Addresses" (`0x0`). This is achieved by implementing a conditional product factor. The final constraint remains $\\prod factor_i = 0$, but it will only be satisfied if `myAddr` matches one of the **non-zero** entries in the provided array. Benefits are **Single Verifier:** Only one `Verifier.sol` needs to be deployed on-chain, **Simplified Client:** The frontend no longer needs to switch between different WASM/ZKey files, and **Privacy:** A fixed array size of 100 hides the *actual* number of admins in a group, providing better metadata private (e.g., a group of 2 looks identical to a group of 50 on-chain). However, the trade-offs are **Proof Generation Time:** Proving for 100 slots takes longer than proving for 2, and **Gas Cost:** The public input array sent to the `verifyProof` function will always be 100 elements long, increasing calldata gas costs.

---
---
---
# README OF CRICOM-ECSDSA

## Project overview

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

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

## Install dependencies

- Run `yarn` at the top level to install npm dependencies (`snarkjs` and `circomlib`).
- Run `yarn` at the top level to install npm dependencies (`snarkjs` and `circomlib`).
- You'll also need `circom` version `>= 2.0.2` on your system. Installation instructions [here](https://docs.circom.io/getting-started/installation/).
- If you want to build the `pubkeygen`, `eth_addr`, and `groupsig` circuits, you'll need to download a Powers of Tau file with `2^20` constraints and copy it into the `circuits` subdirectory of the project, with the name `pot20_final.ptau`. We do not provide such a file in this repo due to its large size. You can download and copy Powers of Tau files from the Hermez trusted setup from [this repository](https://github.com/iden3/snarkjs#7-prepare-phase-2).
- If you want to build the `verify` circuits, you'll also need a Powers of Tau file that can support at least `2^21` constraints (place it in the same directory as above with the same naming convention).
Expand Down
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
"scripts": {
"build:pubkeygen": "cd ./scripts/pubkeygen && ./build_pubkeygen.sh",
"build:groupsig": "cd ./scripts/groupsig && ./build_groupsig.sh",
"build-windows:groupsig": "cd ./scripts/groupsig && ./windows_build_groupsig.sh",
"build:verify": "cd ./scripts/verify && ./build_verify.sh",
"build:eth_addr": "cd ./scripts/eth_addr && ./build_eth_addr.sh",
"test": "NODE_OPTIONS=--max_old_space_size=56000 mocha -r ts-node/register 'test/**/*.ts'",
"groupsig-demo": "npx ts-node scripts/groupsign_cli.ts"
"groupsig-demo": "npx ts-node scripts/groupsign_cli.ts",
"prototype-demo": "npx ts-node scripts/variable_groupsign_cli.ts"
},
"repository": "git@github.com:0xPARC/circom-ecdsa.git",
"author": "0xPARC <hello@0xparc.org>",
Expand All @@ -29,5 +31,6 @@
"mocha": "^9.1.3",
"ts-node": "^10.4.0",
"typescript": "^4.5.4"
}
},
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
}
55 changes: 55 additions & 0 deletions scripts/groupsig/build_circuits.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/bin/bash
set -e # Exit on error

export MSYS_NO_PATHCONV=1
PHASE1=../../circuits/pot20_final.ptau
CIRCUIT_NAME=secure_variable_groupsig
BUILD_BASE=../../build/groupsig

# Check for Phase 1
if [ ! -f "$PHASE1" ]; then
echo "Error: Phase 1 ptau file not found at $PHASE1"
exit 1
fi

# Sizes we want to support
SIZES=(2 3 4)

for m in "${SIZES[@]}"
do
echo "-------------------------------------------"
echo "🔨 BUILDING FOR GROUP SIZE: m = $m"
echo "-------------------------------------------"

# Create a specific directory for this size
TARGET_DIR="$BUILD_BASE/m_$m"
mkdir -p "$TARGET_DIR"

# 1. Modify the circom file temporarily to set the group size
# This replaces the 'Main(64, 4, X)' line with the current size 'm'
sed -i "s/component main {public \[addrs, msg, nonce\]} = Main(64, 4, [0-9]*);/component main {public [addrs, msg, nonce]} = Main(64, 4, $m);/" "$CIRCUIT_NAME".circom

echo "**** COMPILING ****"
circom "$CIRCUIT_NAME".circom --r1cs --wasm --output "$TARGET_DIR"

echo "**** GENERATING ZKEY ****"
# Groth16 Setup
snarkjs groth16 setup "$TARGET_DIR/$CIRCUIT_NAME.r1cs" "$PHASE1" "$TARGET_DIR/temp_0.zkey"

# Quick Contribution (using 'test' as entropy)
echo "test" | snarkjs zkey contribute "$TARGET_DIR/temp_0.zkey" "$TARGET_DIR/temp_1.zkey" --name="Builder" -v -e="entropy"

# Final Beacon and ZKey
snarkjs zkey beacon "$TARGET_DIR/temp_1.zkey" "$TARGET_DIR/$CIRCUIT_NAME.zkey" 0102030405060708090a0b0c0d0e0f101112231415161718221a1b1c1d1e1f 10

echo "**** EXPORTING VKEY ****"
snarkjs zkey export verificationkey "$TARGET_DIR/$CIRCUIT_NAME.zkey" "$TARGET_DIR/vkey.json"

# Cleanup intermediate files to save space
rm "$TARGET_DIR/temp_0.zkey" "$TARGET_DIR/temp_1.zkey" "$TARGET_DIR/$CIRCUIT_NAME.r1cs"

echo "✅ Done for m=$m"
done

echo "-------------------------------------------"
echo "All builds complete in $BUILD_BASE"
Loading