Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: halo2-lib-js unit tests #42

Closed
wants to merge 2 commits into from
Closed
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
8 changes: 4 additions & 4 deletions cli/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "halo2-wasm-cli",
"version": "0.1.0",
"description": "Halo2 Javascript library",
"name": "@axiom-crypto/halo2-wasm-cli",
"version": "0.1.1",
"description": "halo2-wasm cli tool",
"main": "index.js",
"scripts": {
"build": "rm -rf dist && tsc && node ./scripts/postTsc && chmod +x ./dist/index.js"
Expand All @@ -18,7 +18,7 @@
"author": "Intrinsic Technologies",
"license": "ISC",
"dependencies": {
"@axiom-crypto/halo2-wasm": "^0.2.3",
"@axiom-crypto/halo2-wasm": "^0.2.7",
"commander": "^11.1.0",
"typescript": "^5.2.2"
},
Expand Down
8 changes: 4 additions & 4 deletions cli/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions cli/scripts/postTsc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

const fs = require('fs');
const packageJson = require('../package.json');
const halo2WasmPackageJson = require('../../halo2-wasm/package.json');
const { execSync } = require('child_process');

// Copies a modified version of package.json to the /dist folder
Expand All @@ -11,7 +10,6 @@ function copyPackageJson() {
delete packageJsonCopy.scripts;
delete packageJsonCopy.devDependencies;
delete packageJsonCopy.publishConfig;
packageJsonCopy.dependencies['@axiom-crypto/halo2-wasm'] = halo2WasmPackageJson.version;
packageJsonCopy.bin = {
'halo2-wasm': './index.js',
};
Expand Down
1 change: 1 addition & 0 deletions halo2-lib-js/tests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
circuits
116 changes: 116 additions & 0 deletions halo2-lib-js/tests/gate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { Halo2Lib } from "../src/halo2lib";
import { writeCircuitToFile } from "./utils";


const buildGateTest = (inputs: number[], func: string) => {
return `
//@ts-ignore
export const circuit = async (halo2Lib: Halo2Lib, inputs: null) => {
const {witness, ${func}} = halo2Lib;
${func}(${inputs.map((input) => `witness(${input})`).join(", ")});
}`;
}

const buildGateTestFn = (name: string, fn: (halo2Lib: Halo2Lib, inputs: null) => void) => {
return `//@ts-ignore\nexport const circuit = async ${fn.toString()}`;
}

const gateTest = (inputs: number[], func: string) => {
const circuit = buildGateTest(inputs, func);
writeCircuitToFile(circuit, `${func}.gate.ts`);
}

const gateTestFn = (name: string, fn: (halo2Lib: Halo2Lib, inputs: null) => void) => {
const circuit = buildGateTestFn(name, fn);
writeCircuitToFile(circuit, `${name}.gate.ts`);
}

gateTest([15, 10], "add");
gateTest([15, 10], "sub");
gateTest([15], "neg");
gateTest([15, 10], "mul");
gateTest([15, 10, 10], "mulAdd");
gateTest([15, 10], "mulNot");
gateTest([1], "assertBit");
gateTest([1, 1], "and");
gateTest([1, 0], "or");
gateTest([1], "not");
gateTest([100], "dec");
gateTest([1, 0, 1], "orAnd");
gateTest([0], "isZero");
gateTest([9, 5], "isEqual");
gateTest([10, 5, 0], "select");

gateTestFn("assertIsConst", (halo2Lib, _) => {
const { witness, assertIsConst } = halo2Lib;
assertIsConst(witness(15), 15);
})

gateTestFn("innerProduct", (halo2Lib, _) => {
const { witness, innerProduct } = halo2Lib;
const inputs = [15, 10, 5].map(witness);
innerProduct(inputs, inputs)
})

gateTestFn("powVar", (halo2Lib, _) => {
const { witness, pow } = halo2Lib;
pow(witness(15), witness(10), "4")
})

gateTestFn("sum", (halo2Lib, _) => {
const { witness, sum } = halo2Lib;
sum([witness(15), witness(10), witness(5)])
})

gateTestFn("bitsToIndicator", (halo2Lib, _) => {
const { witness, bitsToIndicator } = halo2Lib;
bitsToIndicator([1, 0, 0, 1, 0].map(witness))
})

gateTestFn("idxToIndicator", (halo2Lib, _) => {
const { witness, idxToIndicator } = halo2Lib;
idxToIndicator(witness(8), 12)
})

gateTestFn("selectByIndicator", (halo2Lib, _) => {
const { witness, selectByIndicator } = halo2Lib;
const input = ["1", "2", "3", "4"].map(witness);
const indicator = ["0", "0", "1", "0"].map(witness);
selectByIndicator(input, indicator)
})

gateTestFn("selectFromIdx", (halo2Lib, _) => {
const { witness, selectFromIdx } = halo2Lib;
const input = ["1", "2", "3", "4"].map(witness);
selectFromIdx(input, witness(2))
})

gateTestFn("numToBits", (halo2Lib, _) => {
const { witness, numToBits } = halo2Lib;
numToBits(witness(15), 5)
})

gateTestFn("constrainEqual", (halo2Lib, _) => {
const { witness, checkEqual } = halo2Lib;
checkEqual(witness(15), witness(15))
})

gateTestFn("constant", (halo2Lib, _) => {
const { constant } = halo2Lib;
constant(15)
})

gateTestFn("witness", (halo2Lib, _) => {
const { witness } = halo2Lib;
witness(15)
})

gateTestFn("poseidon", (halo2Lib, _) => {
const { witness, poseidon } = halo2Lib;
poseidon(...["90", "50", "12", "12"].map(witness))
})

gateTestFn("makePublic", (halo2Lib, _) => {
const { witness, makePublic } = halo2Lib;
makePublic(witness(10))
})
37 changes: 37 additions & 0 deletions halo2-lib-js/tests/range.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Halo2Lib } from "../src/halo2lib";
import { writeCircuitToFile } from "./utils";

const buildGateTestFn = (fn: (halo2Lib: Halo2Lib, inputs: null) => void) => {
return `//@ts-ignore\nexport const circuit = async ${fn.toString()}`;
}

const rangeTest = (name: string, fn: (halo2Lib: Halo2Lib, inputs: null) => void) => {
const circuit = buildGateTestFn(fn);
writeCircuitToFile(circuit, `${name}.range.ts`);
}

rangeTest("rangeCheck", (halo2Lib, _) => {
const { witness, rangeCheck } = halo2Lib;
rangeCheck(witness(15141), 128)
})

rangeTest("checkLessThan", (halo2Lib, _) => {
const { witness, checkLessThan } = halo2Lib;
checkLessThan(witness(10), witness(15), "8")
})

rangeTest("isLessThan", (halo2Lib, _) => {
const { witness, isLessThan } = halo2Lib;
isLessThan(witness(10), witness(15), "8")
})

rangeTest("divModVar", (halo2Lib, _) => {
const { witness, div } = halo2Lib;
div(witness(90), witness(50), "12", "12")
});

rangeTest("divModVar.1", (halo2Lib, _) => {
const { witness, mod } = halo2Lib;
mod(witness(90), witness(50), "12", "12")
});

31 changes: 31 additions & 0 deletions halo2-lib-js/tests/test_vk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import os
import subprocess
import re
import sys

def camel_to_snake(name):
str1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', str1).lower()

script_dir = os.path.dirname(os.path.abspath(__file__))
circuit_dir = os.path.join(script_dir, 'circuits')
vk_dir = os.path.join(script_dir, 'data')

def test_vk(filename):
file_path = os.path.join(circuit_dir, filename)
if os.path.isfile(file_path):
test_name = camel_to_snake(filename.split('.')[0])
test_type = filename.split('.')[-2]
rust_test = "tests::" + test_type + "::test_" + test_name
print("Testing " + test_name)
subprocess.run(['halo2-wasm', 'keygen', file_path], stdout = subprocess.DEVNULL)
subprocess.run(['cargo', 'test', rust_test, "--quiet", "--", "--exact"], stdout = subprocess.DEVNULL)
subprocess.run(['diff', './data/vk.bin', '../halo2-wasm/vk.bin'])



if len(sys.argv) > 1:
test_vk(sys.argv[1] + ".ts")
else:
for filename in os.listdir(circuit_dir):
test_vk(filename)
5 changes: 5 additions & 0 deletions halo2-lib-js/tests/test_vk.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
rm -rf tests/circuits
mkdir tests/circuits
pnpm ts-node tests/range.ts
pnpm ts-node tests/gate.ts
python3 tests/test_vk.py
23 changes: 23 additions & 0 deletions halo2-lib-js/tests/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { existsSync, mkdirSync, writeFileSync } from 'fs';
import path from "path";

const config = {
k: 10,
numAdvice: 20,
numLookupAdvice: 3,
numInstance: 1,
numLookupBits: 9,
numVirtualInstance: 1,
}

export const writeCircuitToFile = (circuit: string, relativePath: string) => {
const configStr = `export const config = ${JSON.stringify(config, null, 4)}\n`;
const inputStr = `export const inputs = {}\n`;
const filePath = path.resolve(__dirname, "./circuits", relativePath);
const folderPath = path.dirname(filePath);
if (!existsSync(folderPath)) {
mkdirSync(folderPath, { recursive: true });
}
writeFileSync(filePath, configStr + inputStr + circuit);
console.log(`Wrote circuit to ${filePath}`);
}
3 changes: 1 addition & 2 deletions halo2-lib-js/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"module": "commonjs",
"target": "es2020",
"moduleResolution": "node",
"rootDir": "src",
"baseUrl": "src",
"outDir": "dist",
"lib": ["esnext", "DOM"],
Expand All @@ -25,5 +24,5 @@
},
"compileOnSave": false,
"exclude": ["node_modules", "dist"],
"include": ["src"]
"include": ["src", "tests"]
}
14 changes: 13 additions & 1 deletion halo2-wasm/src/tests/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,23 @@ range_test!(
}
);

//performs range check also since that's what halo2-lib-js does as well
//and `check_less_than` assumes that the inputs are already range checked
range_test!(
test_check_less_than,
("10", "15", 8),
|ctx: &mut Context<Fr>, chip: &RangeChip<Fr>, inputs: (&str, &str, usize)| {
let a = ctx.load_witness(Fr::from_str_vartime(inputs.0).unwrap());
let b = ctx.load_witness(Fr::from_str_vartime(inputs.1).unwrap());
chip.range_check(ctx, a, inputs.2);
chip.range_check(ctx, b, inputs.2);
chip.check_less_than(ctx, a, b, inputs.2);
},
|ctx: &mut Halo2LibWasm, inputs: (&str, &str, usize)| {
let a = ctx.witness(inputs.0);
let b = ctx.witness(inputs.0);
let b = ctx.witness(inputs.1);
ctx.range_check(a, &inputs.2.to_string());
ctx.range_check(b, &inputs.2.to_string());
ctx.check_less_than(a, b, &inputs.2.to_string());
}
);
Expand All @@ -64,17 +70,23 @@ range_test!(
}
);

//performs range check also since that's what halo2-lib-js does as well
//and `is_less_than` assumes that the inputs are already range checked
range_test!(
test_is_less_than,
&["10", "15", "8"],
|ctx: &mut Context<Fr>, chip: &RangeChip<Fr>, inputs: &[&str]| {
let a = ctx.load_witness(Fr::from_str_vartime(inputs[0]).unwrap());
let b = ctx.load_witness(Fr::from_str_vartime(inputs[1]).unwrap());
chip.range_check(ctx, a, inputs[2].parse().unwrap());
chip.range_check(ctx, b, inputs[2].parse().unwrap());
chip.is_less_than(ctx, a, b, inputs[2].parse().unwrap());
},
|ctx: &mut Halo2LibWasm, inputs: &[&str]| {
let a = ctx.witness(inputs[0]);
let b = ctx.witness(inputs[1]);
ctx.range_check(a, inputs[2]);
ctx.range_check(b, inputs[2]);
ctx.is_less_than(a, b, inputs[2]);
}
);
Expand Down