diff --git a/.eslintrc.json b/.eslintrc.json index 8adee7a..875f9eb 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -32,7 +32,8 @@ "dist", ".vscode", ".github", - "jest.config.js" + "jest.config.js", + "examples" ], "overrides": [ { diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9b9b242..eaa2d0c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,6 +4,16 @@ on: push: branches: - main + paths: + # Source files + - 'src/**' + - 'tests/**' + # Configurations + - 'circomkit.json' + - 'hardhat.config.ts' + - 'jest.config.js' + # workflow itself + - '.github/workflows/tests.yml' jobs: test: diff --git a/README.md b/README.md index 3b7e0fc..ad2504a 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ You can see available commands with: npx circomkit help ``` -You can check out examples at the [circomkit-examples](https://github.com/erhant/circomkit-examples) repository. +You can check out examples at the [circomkit-examples](https://github.com/erhant/circomkit-examples) repository, or within the [examples](./examples/) directory here. ### Command Line Interface diff --git a/examples/bun-sha256/.gitignore b/examples/bun-sha256/.gitignore new file mode 100644 index 0000000..6b58145 --- /dev/null +++ b/examples/bun-sha256/.gitignore @@ -0,0 +1,122 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and *not* Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# builds +build +dist + +# circuit-specific powers of tau are ignored +*.ptau +# universal ptaus not ignored +!ptau/* +# temporary ptaus are ignored +tmp.ptau + +# is this still a thing lol +.DS_Store + +# ignore auto generated test circuits +circuits/test +ptau diff --git a/examples/bun-sha256/.vscode/extensions.json b/examples/bun-sha256/.vscode/extensions.json new file mode 100644 index 0000000..5fc8eb4 --- /dev/null +++ b/examples/bun-sha256/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["iden3.circom", "oven.bun-vscode"] +} diff --git a/examples/bun-sha256/.vscode/settings.json b/examples/bun-sha256/.vscode/settings.json new file mode 100644 index 0000000..963048a --- /dev/null +++ b/examples/bun-sha256/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "jestrunner.jestCommand": "bun test" +} diff --git a/examples/bun-sha256/README.md b/examples/bun-sha256/README.md new file mode 100644 index 0000000..e3b49bc --- /dev/null +++ b/examples/bun-sha256/README.md @@ -0,0 +1,37 @@ +# Circomkit with Bun + +In this example, we use [Bun](https://bun.sh/) with the [SHA256](https://en.wikipedia.org/wiki/SHA-2) circuit. + +## Installation + +Simply do: + +```sh +bun install +``` + +## Usage + +You can see an example within [`src/index.ts`](./src/index.ts). Run it with: + +```sh +bun start +``` + +For the CLI, you can test the entire flow as follows: + +1. Compile circuit: `bunx circomkit compile sha256_32` +2. Prove with default input: `bunx circomkit prove sha256_32 default` +3. Verify the proof: `bunx circomkit verify sha256_32 default` +4. Create contract: `bunx circomkit contract sha256_32` +5. Create calldata: `bunx circomkit calldata sha256_32 default` + +Notice that we use `bunx` instead of `npx` to use Bun! + +## Testing + +Run tests with: + +```sh +bun test +``` diff --git a/examples/bun-sha256/bun.lockb b/examples/bun-sha256/bun.lockb new file mode 100755 index 0000000..8caf48c Binary files /dev/null and b/examples/bun-sha256/bun.lockb differ diff --git a/examples/bun-sha256/circomkit.json b/examples/bun-sha256/circomkit.json new file mode 100644 index 0000000..ded4944 --- /dev/null +++ b/examples/bun-sha256/circomkit.json @@ -0,0 +1,6 @@ +{ + "version": "2.1.2", + "protocol": "groth16", + "curve": "bn128", + "verbose": true +} diff --git a/examples/bun-sha256/circuits.json b/examples/bun-sha256/circuits.json new file mode 100644 index 0000000..9c5678d --- /dev/null +++ b/examples/bun-sha256/circuits.json @@ -0,0 +1,7 @@ +{ + "sha256_32": { + "file": "sha256", + "template": "Sha256Bytes", + "params": [32] + } +} diff --git a/examples/bun-sha256/circuits/main/sha256_32.circom b/examples/bun-sha256/circuits/main/sha256_32.circom new file mode 100644 index 0000000..480112e --- /dev/null +++ b/examples/bun-sha256/circuits/main/sha256_32.circom @@ -0,0 +1,6 @@ +// auto-generated by circomkit +pragma circom 2.1.2; + +include "../sha256.circom"; + +component main = Sha256Bytes(32); diff --git a/examples/bun-sha256/circuits/sha256.circom b/examples/bun-sha256/circuits/sha256.circom new file mode 100644 index 0000000..155b872 --- /dev/null +++ b/examples/bun-sha256/circuits/sha256.circom @@ -0,0 +1,42 @@ +pragma circom 2.0.0; + +include "circomlib/circuits/sha256/sha256.circom"; +include "circomlib/circuits/bitify.circom"; + +/** + * Wrapper around SHA256 to support bytes as input instead of bits + * @param N The number of input bytes + * @input in The input bytes + * @output out The SHA256 output of the n input bytes, in bytes + * + * SOURCE: https://github.com/celer-network/zk-benchmark/blob/main/circom/circuits/sha256/sha256_bytes.circom + */ +template Sha256Bytes(N) { + signal input in[N]; + signal output out[32]; + + // convert input bytes to bits + component byte_to_bits[N]; + for (var i = 0; i < N; i++) { + byte_to_bits[i] = Num2Bits(8); + byte_to_bits[i].in <== in[i]; + } + + // sha256 over bits + component sha256 = Sha256(N*8); + for (var i = 0; i < N; i++) { + for (var j = 0; j < 8; j++) { + sha256.in[i*8+j] <== byte_to_bits[i].out[7-j]; + } + } + + // convert output bytes to bits + component bits_to_bytes[32]; + for (var i = 0; i < 32; i++) { + bits_to_bytes[i] = Bits2Num(8); + for (var j = 0; j < 8; j++) { + bits_to_bytes[i].in[7-j] <== sha256.out[i*8+j]; + } + out[i] <== bits_to_bytes[i].out; + } +} diff --git a/examples/bun-sha256/inputs/sha256_32/default.json b/examples/bun-sha256/inputs/sha256_32/default.json new file mode 100644 index 0000000..8c463ad --- /dev/null +++ b/examples/bun-sha256/inputs/sha256_32/default.json @@ -0,0 +1,6 @@ +{ + "in": [ + 116, 111, 100, 97, 121, 32, 105, 115, 32, 97, 32, 103, 111, 111, 100, 32, 100, 97, 121, 44, 32, 110, 111, 116, 32, + 101, 118, 101, 114, 121, 100, 97 + ] +} diff --git a/examples/bun-sha256/package.json b/examples/bun-sha256/package.json new file mode 100644 index 0000000..16ca2d2 --- /dev/null +++ b/examples/bun-sha256/package.json @@ -0,0 +1,19 @@ +{ + "description": "Circomkit examples", + "scripts": { + "start": "bun run ./src/index.ts", + "test": "bun test" + }, + "dependencies": { + "circomkit": "^0.3.0", + "circomlib": "^2.0.5" + }, + "devDependencies": { + "@types/bun": "latest", + "typescript": "^5.1.3" + }, + "prettier": { + "printWidth": 120, + "singleQuote": true + } +} diff --git a/examples/bun-sha256/src/index.ts b/examples/bun-sha256/src/index.ts new file mode 100644 index 0000000..399ae17 --- /dev/null +++ b/examples/bun-sha256/src/index.ts @@ -0,0 +1,37 @@ +import { createHash } from 'crypto'; +import { Circomkit } from 'circomkit'; + +const PREIMAGE = Buffer.from('bunsbunsbunsbunsbuns'); +const PREIMAGE_BYTES = PREIMAGE.toJSON().data; + +// digest and its byte array +const DIGEST = createHash('sha256').update(new Uint8Array(PREIMAGE)).digest('hex'); +const DIGEST_BYTES = Buffer.from(DIGEST, 'hex').toJSON().data; + +const circomkit = new Circomkit({ + inspect: false, +}); +const circuitName = `sha256_${PREIMAGE_BYTES.length}`; + +console.info('Building circuit...'); +const buildPath = await circomkit.compile(circuitName, { + file: 'sha256', + template: 'Sha256Bytes', + params: [PREIMAGE_BYTES.length], +}); +console.info(`Compiled circuit to ${buildPath}`); + +console.info('Creating a witness...'); +const witnessPath = await circomkit.witness(circuitName, 'bunsbuns', { + in: PREIMAGE_BYTES, +}); +console.info(`Witness created at ${witnessPath}`); + +// https://github.com/oven-sh/bun/issues/11005 +// https://github.com/iden3/snarkjs/pull/490 +console.log('Cant prove with Bun yet!'); +// console.info('Running prover...'); +// const proofPath = await circomkit.prove(circuitName, 'bunsbuns', { +// in: PREIMAGE_BYTES, +// }); +// console.info('Done!'); diff --git a/examples/bun-sha256/tests/sha256.test.ts b/examples/bun-sha256/tests/sha256.test.ts new file mode 100644 index 0000000..a4c4f45 --- /dev/null +++ b/examples/bun-sha256/tests/sha256.test.ts @@ -0,0 +1,54 @@ +import { describe, it, beforeAll } from 'bun:test'; +import { WitnessTester } from 'circomkit'; +import { createHash } from 'crypto'; +import { Circomkit } from 'circomkit'; + +const circomkit = new Circomkit({ verbose: false }); + +describe('sha256', () => { + let circuit: WitnessTester<['in'], ['out']>; + + // number of bytes for the sha256 input + const NUM_BYTES = 36; + + // preimage and its byte array + const PREIMAGE = Buffer.from('today is a good day, not everyday is'); + const PREIMAGE_BYTES = PREIMAGE.toJSON().data; + + // digest and its byte array + const DIGEST = createHash('sha256').update(new Uint8Array(PREIMAGE)).digest('hex'); + const DIGEST_BYTES = Buffer.from(DIGEST, 'hex').toJSON().data; + + // circuit signals + const INPUT = { in: PREIMAGE_BYTES }; + const OUTPUT = { out: DIGEST_BYTES }; + + beforeAll(async () => { + circuit = await circomkit.WitnessTester(`sha256_${NUM_BYTES}`, { + file: 'sha256', + template: 'Sha256Bytes', + params: [NUM_BYTES], + }); + }); + + it('should compute hash correctly', async () => { + await circuit.expectPass(INPUT, OUTPUT); + }); + + it('should pass on correct witness', async () => { + const witness = await circuit.calculateWitness(INPUT); + await circuit.expectConstraintPass(witness); + }); + + it('should fail on fake witness', async () => { + const witness = await circuit.calculateWitness(INPUT); + const badWitness = await circuit.editWitness(witness, { + 'main.sha256.sha256compression[0].sigmaPlus[38].sigma0.out[1]': BigInt(1234), + 'main.sha256.sha256compression[0].sigmaPlus[38].sigma0.out[2]': BigInt(1234), + 'main.sha256.sha256compression[0].sigmaPlus[38].sigma0.out[3]': BigInt(1234), + 'main.sha256.sha256compression[0].sigmaPlus[38].sigma0.out[4]': BigInt(1234), + 'main.sha256.sha256compression[0].sigmaPlus[38].sigma0.out[5]': BigInt(1234), + }); + await circuit.expectConstraintFail(badWitness); + }); +}); diff --git a/examples/bun-sha256/tsconfig.json b/examples/bun-sha256/tsconfig.json new file mode 100644 index 0000000..fd521c7 --- /dev/null +++ b/examples/bun-sha256/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}