Skip to content
Merged
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
95 changes: 44 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,59 @@

The Encrypted ERC-20 (eERC) is a protocol that enables efficient confidential token transfers on Avalanche blockchain. eERC does not require modification at the protocol level or off-chain actors and relies purely on zk-SNARKs and homomorphic encryption. It comes with various features such as:

- Confidential Transactions: Conceals the token balances of users and the amounts in each transaction.
- Supports large integers: Allows for the use of integers up to 2^128 bits.
- Client-side operations: Encryption, decryption and proof generation are conducted by the users from client side.
- Fully on-chain Nature: Operates entirely on-chain without the need for relayers or off-chain actors.
- Native compliance: Auditors can audit the transaction details.
- Confidential Transactions: Conceals the token balances of users and the amounts in each transaction.
- Supports large integers: Allows for the use of integers up to 2^128 bits.
- Client-side operations: Encryption, decryption and proof generation are conducted by the users from client side.
- Fully on-chain Nature: Operates entirely on-chain without the need for relayers or off-chain actors.
- Native compliance: Auditors can audit the transaction details.

## Overview

TODO
TODO

# File structure

- [contracts](#contracts) Smart contract source files for the eERC protocol.
- [scripts](#scripts) Utility and deployment scripts for contracts.
- [src](#src) TODO
- [tests](#tests) Test scripts and files of eERC protocol.
- [zk-SNARKs](#zk) Implementation of zero-knowledge proof components used by eERC.

- [contracts](#contracts) Smart contract source files for the eERC protocol.
- [scripts](#scripts) Utility and deployment scripts for contracts.
- [src](#src) TODO
- [tests](#tests) Test scripts and files of eERC protocol.
- [zk-SNARKs](#zk) Implementation of zero-knowledge proof components used by eERC.

## Getting Started

### Prerequisites

You need following dependencies for setup:

- `NodeJS >= v16.x `
- `Golang >= 1.20.x `
- `NodeJS >= v16.x `
- `Golang >= 1.20.x `

### Installation

1. Clone the repo
```sh
git clone https://github.com/ava-labs/EncryptedERC.git
```
```sh
git clone https://github.com/ava-labs/EncryptedERC.git
```
2. Install NPM packages

```sh
npm install
```
```sh
npm install
```

Note: This command will run a bash script to compile gnark's circuits, if this does not work:
In [zk](#zk) directory run the following command to build manually:
Note: This command will run a bash script to compile gnark's circuits, if this does not work:
In [zk](#zk) directory run the following command to build manually:

On x64:
On x64:

```sh
go build -o ../outputs/eerc20_zk_x64
```
```sh
go build -o ../outputs/eerc20_zk_x64
```

On arm64:
On arm64:

```sh
go build -o ../outputs/eerc20_zk
```
```sh
go build -o ../outputs/eerc20_zk
```

### Run Tests/Coverage

Expand All @@ -65,33 +64,27 @@ Contract tests:
npx hardhat coverage
```

Jest:

```
npm run test --coverage
```

## 📊 Performance Overview

### ⛽ On-Chain Gas Costs (Fuji Testnet)

| **Operation** | **Gas Cost** |
|----------------------|----------------|
| Register | 273,085 gas |
| Deposit | 556,273 gas |
| Withdraw | *TODO* |
| Private Burn | 646,666 gas |
| Private Mint | 677,304 gas |
| Private Transfer | 1,036,451 gas |
| Update Auditor | 103,753 gas |
| **Operation** | **Gas Cost** |
| ---------------- | ------------- |
| Register | 273,085 gas |
| Deposit | 556,273 gas |
| Withdraw | _TODO_ |
| Private Burn | 646,666 gas |
| Private Mint | 677,304 gas |
| Private Transfer | 1,036,451 gas |
| Update Auditor | 103,753 gas |

### ⏱️ Circuit Proving Times

Tested on a MacBook (M3 Pro CPU):

| **Operation** | **Proving Time** |
|----------------------|------------------|
| Registration | 71 ms |
| Private Mint | 359 ms |
| Private Burn | 360 ms |
| Private Transfer | 606 ms |
| **Operation** | **Proving Time** |
| ---------------- | ---------------- |
| Registration | 71 ms |
| Private Mint | 359 ms |
| Private Burn | 360 ms |
| Private Transfer | 606 ms |
22 changes: 2 additions & 20 deletions contracts/FeeERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ import {SimpleERC20} from "./SimpleERC20.sol";

/**
* @title FeeERC20
* @dev ERC20 token with a fee mechanism for testing TransferFailed error
* @dev ERC20 token with a fee mechanism for testing
*/
contract FeeERC20 is SimpleERC20 {
// Fee percentage (in basis points, 1 = 0.01%)
uint256 public feeRate;
// Fee collector address
address public feeCollector;

constructor(
Expand All @@ -24,22 +22,6 @@ contract FeeERC20 is SimpleERC20 {
feeCollector = feeCollectors;
}

/**
* @dev Set the fee rate
* @param feeRates New fee rate in basis points
*/
function setFeeRate(uint256 feeRates) external {
feeRate = feeRates;
}

/**
* @dev Set the fee collector
* @param feeCollectors New fee collector address
*/
function setFeeCollector(address feeCollectors) external {
feeCollector = feeCollectors;
}

/**
* @dev Override transferFrom to apply a fee
* @param sender The address to transfer from
Expand All @@ -55,7 +37,7 @@ contract FeeERC20 is SimpleERC20 {
address spender = _msgSender();

// Calculate fee
uint256 fee = (amount * feeRate) / 10000;
uint256 fee = (amount * feeRate) / 100;
uint256 amountAfterFee = amount - fee;

// Deduct allowance
Expand Down
2 changes: 1 addition & 1 deletion scripts/deploy-prod.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/dist/src/signer-with-address";
import { ethers } from "hardhat";
import { deployLibrary, deployVerifiers } from "../test/helpers";
import { deployLibrary } from "../test/helpers";
import {
EncryptedERC__factory,
Registrar__factory,
Expand Down
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const BASE_POINT_ORDER = 2736030358979909402780800718157159386076813972158567259200215660948447373041n;
export const BN254_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./jub";
export * from "./poseidon";
export * from "./constants";
1 change: 1 addition & 0 deletions src/jub/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./jub";
149 changes: 58 additions & 91 deletions src/jub/jub.ts
Original file line number Diff line number Diff line change
@@ -1,104 +1,71 @@
import {
Base8,
Fr,
type Point,
addPoint,
mulPointEscalar,
Base8,
Fr,
type Point,
addPoint,
mulPointEscalar,
} from "@zk-kit/baby-jubjub";
import {
formatPrivKeyForBabyJub,
genRandomBabyJubValue,
poseidonDecrypt,
poseidonEncrypt,
} from "maci-crypto";
import { randomBytes } from "crypto";

export const BASE_POINT_ORDER = 2736030358979909402780800718157159386076813972158567259200215660948447373041n;
export const BN254_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;


// el-gamal decryption
export const decryptPoint = (
privateKey: bigint,
c1: bigint[],
c2: bigint[],
): bigint[] => {
const privKey = formatPrivKeyForBabyJub(privateKey);

const c1x = mulPointEscalar(c1 as Point<bigint>, privKey);
const c1xInverse = [Fr.e(c1x[0] * -1n), c1x[1]];
return addPoint(c2 as Point<bigint>, c1xInverse as Point<bigint>);
};

// el-gamal encryption
import { formatPrivKeyForBabyJub, genRandomBabyJubValue } from "maci-crypto";
import { BASE_POINT_ORDER } from "../constants";

/**
* Implements El-Gamal encryption on BabyJubJub curve
* @param publicKey BabyJubJub public key
* @param point Point to encrypt
* @param random Randomness for the encryption
* @returns [c1,c2] - returns 2 different points as a ciphertext
*/
export const encryptPoint = (
publicKey: bigint[],
point: bigint[],
random = genRandomBabyJubValue(),
publicKey: bigint[],
point: bigint[],
random = genRandomBabyJubValue()
): [Point<bigint>, Point<bigint>] => {
const c1 = mulPointEscalar(Base8, random);
const pky = mulPointEscalar(publicKey as Point<bigint>, random);
const c2 = addPoint(point as Point<bigint>, pky);
const c1 = mulPointEscalar(Base8, random);
const pky = mulPointEscalar(publicKey as Point<bigint>, random);
const c2 = addPoint(point as Point<bigint>, pky);

return [c1, c2];
return [c1, c2];
};

/**
* Implements El-Gamal encryption on scalar message on BabyJubJub curve
* @param publicKey Public key to encrypt the message
* @param message Message to encrypt
* @param random Randomness for the encryption
* @returns { cipher: [c1,c2], random: bigint } - returns 2 different points as a ciphertext and the randomness used
*/
export const encryptMessage = (
publicKey: bigint[],
message: bigint,
random = genRandomBabyJubValue(),
publicKey: bigint[],
message: bigint,
random = genRandomBabyJubValue()
): { cipher: [bigint[], bigint[]]; random: bigint } => {
let encRandom = random;
if (encRandom >= BASE_POINT_ORDER) {
encRandom = genRandomBabyJubValue() / 100n;
}
const p = mulPointEscalar(Base8, message);

return {
cipher: encryptPoint(publicKey, p, encRandom),
random: encRandom,
};
let encRandom = random;
if (encRandom >= BASE_POINT_ORDER) {
encRandom = genRandomBabyJubValue() / 100n;
}
const p = mulPointEscalar(Base8, message);

return {
cipher: encryptPoint(publicKey, p, encRandom),
random: encRandom,
};
};

export const randomNonce = (): bigint => {
const bytes = randomBytes(16);
return BigInt("0x" + bytes.toString("hex")) + 1n;
};

export const processPoseidonEncryption = (
inputs: bigint[],
publicKey: bigint[],
) => {
const nonce = randomNonce();

let encRandom = genRandomBabyJubValue();
if (encRandom >= BASE_POINT_ORDER) {
encRandom = genRandomBabyJubValue() / 10n;
}

const poseidonEncryptionKey = mulPointEscalar(
publicKey as Point<bigint>,
encRandom,
);
const authKey = mulPointEscalar(Base8, encRandom);
const ciphertext = poseidonEncrypt(inputs, poseidonEncryptionKey, nonce);

return { ciphertext, nonce, encRandom, poseidonEncryptionKey, authKey };
};

export const processPoseidonDecryption = (
ciphertext: bigint[],
authKey: bigint[],
nonce: bigint,
privateKey: bigint,
length: number,
) => {
const sharedKey = mulPointEscalar(
authKey as Point<bigint>,
formatPrivKeyForBabyJub(privateKey),
);

const decrypted = poseidonDecrypt(ciphertext, sharedKey, nonce, length);
/**
* Implements El-Gamal decryption on BabyJubJub curve
* @param privateKey - Private key to decrypt the point
* @param c1 - First part of the cipher
* @param c2 - Second part of the cipher
* @returns Point - returns the decrypted point
*/
export const decryptPoint = (
privateKey: bigint,
c1: bigint[],
c2: bigint[]
): bigint[] => {
const privKey = formatPrivKeyForBabyJub(privateKey);

return decrypted.slice(0, length);
const c1x = mulPointEscalar(c1 as Point<bigint>, privKey);
const c1xInverse = [Fr.e(c1x[0] * -1n), c1x[1]];
return addPoint(c2 as Point<bigint>, c1xInverse as Point<bigint>);
};
Empty file removed src/poseidon/.gitkeep
Empty file.
1 change: 1 addition & 0 deletions src/poseidon/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./poseidon";
Loading
Loading