mirror of
https://github.com/chancehudson/circom-stark.git
synced 2026-05-10 03:00:51 -04:00
feat: witness solver on alt_bn128 circuit with optimization enabled
This commit is contained in:
9
package-lock.json
generated
9
package-lock.json
generated
@@ -10,7 +10,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"rstark": "../rstark/pkg",
|
||||
"starkstark": "vimwitch/starkstark#main"
|
||||
"starkstark": "chancehudson/starkstark#main"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ava": "^5.3.1",
|
||||
@@ -1390,8 +1390,7 @@
|
||||
},
|
||||
"node_modules/starkstark": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "git+ssh://git@github.com/vimwitch/starkstark.git#683bf06ef15882a67f86a26a33efedc5055df885",
|
||||
"license": "ISC",
|
||||
"resolved": "git+ssh://git@github.com/chancehudson/starkstark.git#63b80fa5d061670f387a6494f1f648bc29aab3cf",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.3.1",
|
||||
"randomf": "^0.0.3"
|
||||
@@ -2452,8 +2451,8 @@
|
||||
}
|
||||
},
|
||||
"starkstark": {
|
||||
"version": "git+ssh://git@github.com/vimwitch/starkstark.git#683bf06ef15882a67f86a26a33efedc5055df885",
|
||||
"from": "starkstark@vimwitch/starkstark#main",
|
||||
"version": "git+ssh://git@github.com/chancehudson/starkstark.git#63b80fa5d061670f387a6494f1f648bc29aab3cf",
|
||||
"from": "starkstark@chancehudson/starkstark#main",
|
||||
"requires": {
|
||||
"@noble/hashes": "^1.3.1",
|
||||
"randomf": "^0.0.3"
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "ava --timeout 200000"
|
||||
"test": "ava ./test/exampleR1cs.test.mjs --timeout 200000"
|
||||
},
|
||||
"dependencies": {
|
||||
"rstark": "../rstark/pkg",
|
||||
"starkstark": "vimwitch/starkstark#main"
|
||||
"starkstark": "chancehudson/starkstark#main"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
import { ScalarField } from 'starkstark/src/ScalarField.mjs'
|
||||
import { R1CS } from '../src/r1csParser.mjs'
|
||||
import { buildWitness } from './witnessBuilder.mjs'
|
||||
import { ScalarField } from "starkstark/src/ScalarField.mjs";
|
||||
import { R1CS } from "../src/r1csParser.mjs";
|
||||
import { buildWitness } from "./witnessBuilder.mjs";
|
||||
|
||||
export async function compileR1cs(buffer, input = [], memoryOverride) {
|
||||
const r = new R1CS(buffer)
|
||||
const {
|
||||
prime,
|
||||
constraints,
|
||||
nOutputs,
|
||||
nPubInputs,
|
||||
nPrvInputs,
|
||||
nVars
|
||||
} = r.data
|
||||
const baseField = new ScalarField(prime)
|
||||
const memory = memoryOverride ? memoryOverride : (await buildWitness(r.data, input))
|
||||
const negOne = baseField.neg(1n)
|
||||
const r = new R1CS(buffer);
|
||||
const { prime, constraints, nOutputs, nPubInputs, nPrvInputs, nVars } =
|
||||
r.data;
|
||||
const baseField = new ScalarField(prime);
|
||||
const memory = memoryOverride
|
||||
? memoryOverride
|
||||
: await buildWitness(r.data, input);
|
||||
return;
|
||||
const negOne = baseField.neg(1n);
|
||||
// order of variables
|
||||
// ONE, outputs, pub inputs, prv inputs
|
||||
// for all entries in the r1cs we must prove that ab - c = 0
|
||||
@@ -23,30 +20,30 @@ export async function compileR1cs(buffer, input = [], memoryOverride) {
|
||||
|
||||
// variables are laid out in memory from 0-(nVars-1)
|
||||
// after that are scratch0, scratch1, scratch2, scratch3
|
||||
const scratch0 = `0x${nVars.toString(16)}`
|
||||
const scratch1 = `0x${(nVars+1).toString(16)}`
|
||||
const scratch2 = `0x${(nVars+2).toString(16)}`
|
||||
const scratch3 = `0x${(nVars+3).toString(16)}`
|
||||
const scratch0 = `0x${nVars.toString(16)}`;
|
||||
const scratch1 = `0x${(nVars + 1).toString(16)}`;
|
||||
const scratch2 = `0x${(nVars + 2).toString(16)}`;
|
||||
const scratch3 = `0x${(nVars + 3).toString(16)}`;
|
||||
|
||||
const asm = []
|
||||
const asm = [];
|
||||
for (const [i, v] of Object.entries(memory)) {
|
||||
asm.push(`set 0x${i.toString(16)} ${v}`)
|
||||
asm.push(`set 0x${i.toString(16)} ${v}`);
|
||||
}
|
||||
for (const [a, b, c] of constraints) {
|
||||
// each are objects
|
||||
asm.push(...sum(scratch0, scratch1, a, negOne))
|
||||
asm.push(...sum(scratch0, scratch2, b, negOne))
|
||||
asm.push(...sum(scratch0, scratch3, c, negOne))
|
||||
asm.push(...sum(scratch0, scratch1, a, negOne));
|
||||
asm.push(...sum(scratch0, scratch2, b, negOne));
|
||||
asm.push(...sum(scratch0, scratch3, c, negOne));
|
||||
|
||||
asm.push(`mul ${scratch0} ${scratch1} ${scratch2}`)
|
||||
asm.push(`set ${scratch1} 0`)
|
||||
asm.push(`neg ${scratch3} ${scratch3}`)
|
||||
asm.push(`add ${scratch0} ${scratch0} ${scratch3}`)
|
||||
asm.push(`eq ${scratch0} ${scratch1}`)
|
||||
asm.push(`mul ${scratch0} ${scratch1} ${scratch2}`);
|
||||
asm.push(`set ${scratch1} 0`);
|
||||
asm.push(`neg ${scratch3} ${scratch3}`);
|
||||
asm.push(`add ${scratch0} ${scratch0} ${scratch3}`);
|
||||
asm.push(`eq ${scratch0} ${scratch1}`);
|
||||
}
|
||||
console.log(`Proving ${asm.length} steps with ${memory.length} memory slots`)
|
||||
await new Promise(r => setTimeout(r, 100))
|
||||
return asm.join('\n')
|
||||
console.log(`Proving ${asm.length} steps with ${memory.length} memory slots`);
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
return asm.join("\n");
|
||||
}
|
||||
|
||||
// mulsum - multiply 2 numbers and add them to a third register
|
||||
@@ -54,21 +51,20 @@ export async function compileR1cs(buffer, input = [], memoryOverride) {
|
||||
|
||||
function sum(scratch0, scratch, map, negOne) {
|
||||
if (Object.keys(map).length === 0) {
|
||||
return [`set ${scratch} 0`]
|
||||
return [`set ${scratch} 0`];
|
||||
}
|
||||
const out = []
|
||||
out.push(`set ${scratch} 0`)
|
||||
const out = [];
|
||||
out.push(`set ${scratch} 0`);
|
||||
for (let x = 0; x < Object.keys(map).length; x++) {
|
||||
const key = Object.keys(map)[x]
|
||||
const val = map[key]
|
||||
const key = Object.keys(map)[x];
|
||||
const val = map[key];
|
||||
if (val !== negOne) {
|
||||
out.push(`set ${scratch0} ${val}`)
|
||||
out.push(`mul ${scratch0} ${scratch0} ${key}`)
|
||||
out.push(`set ${scratch0} ${val}`);
|
||||
out.push(`mul ${scratch0} ${scratch0} ${key}`);
|
||||
} else {
|
||||
out.push(`neg ${scratch0} ${key}`)
|
||||
out.push(`neg ${scratch0} ${key}`);
|
||||
}
|
||||
out.push(`add ${scratch} ${scratch} ${scratch0}`)
|
||||
out.push(`add ${scratch} ${scratch} ${scratch0}`);
|
||||
}
|
||||
return out
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
import { R1CS } from './r1csParser.mjs'
|
||||
import { buildWitness } from './witnessBuilder.mjs'
|
||||
import { ScalarField } from 'starkstark/src/ScalarField.mjs'
|
||||
import { MultiPolynomial } from 'starkstark/src/MultiPolynomial.mjs'
|
||||
import { R1CS } from "./r1csParser.mjs";
|
||||
import { buildWitness } from "./witnessBuilder.mjs";
|
||||
import { ScalarField } from "starkstark/src/ScalarField.mjs";
|
||||
import { MultiPolynomial } from "starkstark/src/MultiPolynomial.mjs";
|
||||
|
||||
// export const field = new ScalarField(18446744069414584321n, 2717n);
|
||||
|
||||
export const field = new ScalarField(
|
||||
18446744069414584321n,
|
||||
2717n
|
||||
)
|
||||
BigInt(
|
||||
"21888242871839275222246405745257275088548364400416034343698204186575808495617",
|
||||
),
|
||||
1n,
|
||||
);
|
||||
|
||||
export function compile(r1csBuffer, input = [], memoryOverride) {
|
||||
const { data } = new R1CS(r1csBuffer)
|
||||
const {
|
||||
prime,
|
||||
constraints,
|
||||
nOutputs,
|
||||
nPubInputs,
|
||||
nPrvInputs,
|
||||
nVars
|
||||
} = data
|
||||
const { data } = new R1CS(r1csBuffer);
|
||||
const { prime, constraints, nOutputs, nPubInputs, nPrvInputs, nVars } = data;
|
||||
|
||||
if (prime !== field.p) {
|
||||
throw new Error(`r1cs prime does not match expected value. Got ${prime} expected ${field.p}`)
|
||||
throw new Error(
|
||||
`r1cs prime does not match expected value. Got ${prime} expected ${field.p}`,
|
||||
);
|
||||
}
|
||||
|
||||
const registerCount = nVars
|
||||
const memory = memoryOverride ?? buildWitness(data, input)
|
||||
const registerCount = nVars;
|
||||
const memory = memoryOverride ?? buildWitness(data, input);
|
||||
|
||||
// for all r1cs constraints make stark constraints
|
||||
// using signals in the trace memory
|
||||
@@ -32,48 +31,61 @@ export function compile(r1csBuffer, input = [], memoryOverride) {
|
||||
// the prover just supplies the signals as input
|
||||
// in a single row
|
||||
//
|
||||
const variables = Array(1+2*registerCount)
|
||||
const variables = Array(1 + 2 * registerCount)
|
||||
.fill()
|
||||
.map((_, i) => new MultiPolynomial(field).term({ coef: 1n, exps: { [i]: 1n }}))
|
||||
const cycleIndex = variables[0]
|
||||
const prevState = variables.slice(1, registerCount+1)
|
||||
const nextState = variables.slice(1+registerCount)
|
||||
.map((_, i) =>
|
||||
new MultiPolynomial(field).term({ coef: 1n, exps: { [i]: 1n } }),
|
||||
);
|
||||
const cycleIndex = variables[0];
|
||||
const prevState = variables.slice(1, registerCount + 1);
|
||||
const nextState = variables.slice(1 + registerCount);
|
||||
|
||||
const zero = new MultiPolynomial(field).term({ coef: 0n, exps: { [0]: 0n }})
|
||||
const one = new MultiPolynomial(field).term({ coef: 1n, exps: { [0]: 0n }})
|
||||
const zero = new MultiPolynomial(field).term({ coef: 0n, exps: { [0]: 0n } });
|
||||
const one = new MultiPolynomial(field).term({ coef: 1n, exps: { [0]: 0n } });
|
||||
|
||||
const starkConstraints = []
|
||||
const starkConstraints = [];
|
||||
|
||||
// TODO: form a single constraint using a RLC
|
||||
// need to sample the input to get the randomness
|
||||
for (const [a, b, c] of constraints) {
|
||||
const aPoly = zero.copy()
|
||||
const aPoly = zero.copy();
|
||||
for (const [key, value] of Object.entries(a)) {
|
||||
const coef = new MultiPolynomial(field).term({ coef: value, exps: { [0]: 0n }})
|
||||
aPoly.add(one.copy().mul(prevState[+key]).mul(coef))
|
||||
const coef = new MultiPolynomial(field).term({
|
||||
coef: value,
|
||||
exps: { [0]: 0n },
|
||||
});
|
||||
aPoly.add(one.copy().mul(prevState[+key]).mul(coef));
|
||||
}
|
||||
const bPoly = zero.copy()
|
||||
const bPoly = zero.copy();
|
||||
for (const [key, value] of Object.entries(b)) {
|
||||
const coef = new MultiPolynomial(field).term({ coef: value, exps: { [0]: 0n }})
|
||||
bPoly.add(one.copy().mul(prevState[+key]).mul(coef))
|
||||
const coef = new MultiPolynomial(field).term({
|
||||
coef: value,
|
||||
exps: { [0]: 0n },
|
||||
});
|
||||
bPoly.add(one.copy().mul(prevState[+key]).mul(coef));
|
||||
}
|
||||
const cPoly = zero.copy()
|
||||
const cPoly = zero.copy();
|
||||
for (const [key, value] of Object.entries(c)) {
|
||||
const coef = new MultiPolynomial(field).term({ coef: value, exps: { [0]: 0n }})
|
||||
cPoly.add(one.copy().mul(prevState[+key]).mul(coef))
|
||||
const coef = new MultiPolynomial(field).term({
|
||||
coef: value,
|
||||
exps: { [0]: 0n },
|
||||
});
|
||||
cPoly.add(one.copy().mul(prevState[+key]).mul(coef));
|
||||
}
|
||||
|
||||
starkConstraints.push(aPoly.copy().mul(bPoly).sub(cPoly))
|
||||
starkConstraints.push(aPoly.copy().mul(bPoly).sub(cPoly));
|
||||
}
|
||||
|
||||
// where in the memory the public inputs start
|
||||
const pubInputsOffset = 1 + nOutputs
|
||||
const pubInputsOffset = 1 + nOutputs;
|
||||
return {
|
||||
constraints: starkConstraints,
|
||||
boundary: memory.slice(pubInputsOffset, pubInputsOffset+nPubInputs).map((val, i) => [1, i+pubInputsOffset, val]),
|
||||
boundary: memory
|
||||
.slice(pubInputsOffset, pubInputsOffset + nPubInputs)
|
||||
.map((val, i) => [1, i + pubInputsOffset, val]),
|
||||
trace: [memory, memory],
|
||||
program: {
|
||||
registerCount,
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ScalarField } from 'starkstark/src/ScalarField.mjs'
|
||||
import { MultiPolynomial } from 'starkstark/src/MultiPolynomial.mjs'
|
||||
import { Polynomial } from 'starkstark/src/Polynomial.mjs'
|
||||
import { ScalarField } from "starkstark/src/ScalarField.mjs";
|
||||
import { MultiPolynomial } from "starkstark/src/MultiPolynomial.mjs";
|
||||
import { Polynomial } from "starkstark/src/Polynomial.mjs";
|
||||
|
||||
// data: the parsed R1CS data
|
||||
// input: the private and public input values, in that order
|
||||
@@ -11,144 +11,143 @@ import { Polynomial } from 'starkstark/src/Polynomial.mjs'
|
||||
// TODO: exploit structure of the constraint to solve more efficiently
|
||||
// when possible
|
||||
export function buildWitness(data, input = []) {
|
||||
const baseField = new ScalarField(data.prime)
|
||||
const {
|
||||
nOutputs,
|
||||
nPubInputs,
|
||||
nPrvInputs,
|
||||
nVars
|
||||
} = data
|
||||
const vars = Array(nVars).fill(null)
|
||||
vars[0] = 1n
|
||||
const baseField = new ScalarField(data.prime);
|
||||
const { nOutputs, nPubInputs, nPrvInputs, nVars } = data;
|
||||
// console.log(data);
|
||||
const vars = Array(nVars).fill(null);
|
||||
vars[0] = 1n;
|
||||
for (let x = 0; x < nPubInputs + nPrvInputs; x++) {
|
||||
vars[1+nOutputs+x] = input[x]
|
||||
vars[1 + nOutputs + x] = input[x];
|
||||
}
|
||||
const constraints = []
|
||||
const constraints = [];
|
||||
// turn the raw constraints into multivariate polynomials
|
||||
for (const constraint of data.constraints) {
|
||||
const [a, b, c] = constraint
|
||||
const polyA = new MultiPolynomial(baseField)
|
||||
const [a, b, c] = constraint;
|
||||
const polyA = new MultiPolynomial(baseField);
|
||||
for (const [key, val] of Object.entries(a)) {
|
||||
polyA.term({ coef: val, exps: { [Number(key)]: 1n } })
|
||||
polyA.term({ coef: val, exps: { [Number(key)]: 1n } });
|
||||
}
|
||||
const polyB = new MultiPolynomial(baseField)
|
||||
const polyB = new MultiPolynomial(baseField);
|
||||
for (const [key, val] of Object.entries(b)) {
|
||||
polyB.term({ coef: val, exps: { [Number(key)]: 1n } })
|
||||
polyB.term({ coef: val, exps: { [Number(key)]: 1n } });
|
||||
}
|
||||
const polyC = new MultiPolynomial(baseField)
|
||||
const polyC = new MultiPolynomial(baseField);
|
||||
for (const [key, val] of Object.entries(c)) {
|
||||
polyC.term({ coef: val, exps: { [Number(key)]: 1n } })
|
||||
polyC.term({ coef: val, exps: { [Number(key)]: 1n } });
|
||||
}
|
||||
const finalPoly = polyA.copy().mul(polyB).sub(polyC)
|
||||
finalPoly.originalConstraint = constraint
|
||||
constraints.push(finalPoly)
|
||||
const finalPoly = polyA.copy().mul(polyB).sub(polyC);
|
||||
finalPoly.originalConstraint = constraint;
|
||||
constraints.push(finalPoly);
|
||||
}
|
||||
// build a map of what constraints need what variables
|
||||
for (const [i, constraint] of Object.entries(constraints)) {
|
||||
const unknownVars = []
|
||||
const unknownVarsMap = {}
|
||||
constraint.vars = []
|
||||
const [a, b, c] = constraint.originalConstraint
|
||||
const unknownVars = [];
|
||||
const unknownVarsMap = {};
|
||||
constraint.vars = [];
|
||||
const [a, b, c] = constraint.originalConstraint;
|
||||
for (const obj of [a, b, c].flat()) {
|
||||
for (const key of Object.keys(obj)) {
|
||||
constraint.vars[+key] = true
|
||||
constraint.vars[+key] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
const allConstraints = [...constraints]
|
||||
const allConstraints = [...constraints];
|
||||
// iterate over the set of constraints
|
||||
// look for constraints that have only 1 unknown
|
||||
// and solve for that unknown
|
||||
// then iterate again
|
||||
// repeat until all variables are known
|
||||
while (vars.indexOf(null) !== -1) {
|
||||
const solved = []
|
||||
const solved = [];
|
||||
// determine what variables are unknown. If there is not
|
||||
// exactly 1 unknown then skip
|
||||
for (const [key, constraint] of Object.entries(constraints)) {
|
||||
const unknownVarsMap = {}
|
||||
for (const [varIndex, ] of Object.entries(constraint.vars)) {
|
||||
const unknownVarsMap = {};
|
||||
for (const [varIndex] of Object.entries(constraint.vars)) {
|
||||
if (vars[varIndex] === null) {
|
||||
unknownVarsMap[varIndex] = true
|
||||
unknownVarsMap[varIndex] = true;
|
||||
if (Object.keys(unknownVarsMap).length > 1) {
|
||||
break
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const unknownVars = Object.keys(unknownVarsMap).map(v => +v)
|
||||
if (unknownVars.length >= 2 || unknownVars.length === 0) continue
|
||||
const unknownVars = Object.keys(unknownVarsMap).map((v) => +v);
|
||||
if (unknownVars.length >= 2 || unknownVars.length === 0) continue;
|
||||
// otherwise solve
|
||||
const cc = constraint.copy()
|
||||
cc.evaluatePartial(vars)
|
||||
const cc = constraint.copy();
|
||||
cc.evaluatePartial(vars);
|
||||
// we should end up with either
|
||||
// 0 = x + c
|
||||
// or
|
||||
// 0 = x^2 + x
|
||||
// or a constraint with c = 0
|
||||
// in which case we solve a and b
|
||||
let out
|
||||
let out;
|
||||
if (cc.expMap.size === 3) {
|
||||
const [a, b, c] = constraint.originalConstraint
|
||||
const [a, b, c] = constraint.originalConstraint;
|
||||
// check that c = 0
|
||||
if (Object.keys(c).length !== 0) {
|
||||
throw new Error('expected c = 0 in quadratic polynomial constraint')
|
||||
throw new Error("expected c = 0 in quadratic polynomial constraint");
|
||||
}
|
||||
// solve for the unknown in a and b
|
||||
const polyA = new Polynomial(baseField)
|
||||
const polyB = new Polynomial(baseField)
|
||||
const polyA = new Polynomial(baseField);
|
||||
const polyB = new Polynomial(baseField);
|
||||
for (const [key, val] of Object.entries(a)) {
|
||||
if (+unknownVars[0] === +key) {
|
||||
polyA.term({ coef: val, exp: 1n })
|
||||
polyA.term({ coef: val, exp: 1n });
|
||||
} else {
|
||||
polyA.term({ coef: baseField.mul(val, vars[+key]), exp: 0n })
|
||||
polyA.term({ coef: baseField.mul(val, vars[+key]), exp: 0n });
|
||||
}
|
||||
}
|
||||
for (const [key, val] of Object.entries(b)) {
|
||||
if (+unknownVars[0] === +key) {
|
||||
polyB.term({ coef: val, exp: 1n })
|
||||
polyB.term({ coef: val, exp: 1n });
|
||||
} else {
|
||||
polyB.term({ coef: baseField.mul(val, vars[+key]), exp: 0n })
|
||||
polyB.term({ coef: baseField.mul(val, vars[+key]), exp: 0n });
|
||||
}
|
||||
}
|
||||
out = polyA.solve() ?? polyB.solve()
|
||||
out = polyA.solve() ?? polyB.solve();
|
||||
} else {
|
||||
const _expKey = Array(unknownVars[0]).fill(0n)
|
||||
_expKey.push(1n)
|
||||
const expKey = MultiPolynomial.expVectorToString(_expKey)
|
||||
const _expKey = Array(unknownVars[0]).fill(0n);
|
||||
_expKey.push(1n);
|
||||
const expKey = MultiPolynomial.expVectorToString(_expKey);
|
||||
if (cc.expMap.size !== 2) {
|
||||
continue
|
||||
throw new Error('expected exactly 2 remaining terms')
|
||||
continue;
|
||||
throw new Error("expected exactly 2 remaining terms");
|
||||
}
|
||||
if (!cc.expMap.has(expKey)) {
|
||||
throw new Error('cannot find remaining variable')
|
||||
throw new Error("cannot find remaining variable");
|
||||
}
|
||||
if (cc.expMap.has(expKey.replace('1', '2'))) {
|
||||
if (cc.expMap.has(expKey.replace("1", "2"))) {
|
||||
// we're in the case of 0 = x^2 + x
|
||||
// reduce by dividing an x out
|
||||
cc.expMap.set('0', cc.expMap.get(expKey))
|
||||
cc.expMap.set(expKey, cc.expMap.get(expKey.replace('1', '2')))
|
||||
cc.expMap.delete(expKey.replace('1', '2'))
|
||||
cc.expMap.set("0", cc.expMap.get(expKey));
|
||||
cc.expMap.set(expKey, cc.expMap.get(expKey.replace("1", "2")));
|
||||
cc.expMap.delete(expKey.replace("1", "2"));
|
||||
}
|
||||
if (!cc.expMap.has('0')) {
|
||||
throw new Error('exactly one term should be a constant')
|
||||
if (!cc.expMap.has("0")) {
|
||||
throw new Error("exactly one term should be a constant");
|
||||
}
|
||||
out = cc.field.div(cc.field.neg(cc.expMap.get('0')), cc.expMap.get(expKey))
|
||||
out = cc.field.div(
|
||||
cc.field.neg(cc.expMap.get("0")),
|
||||
cc.expMap.get(expKey),
|
||||
);
|
||||
}
|
||||
vars[unknownVars[0]] = out
|
||||
solved.push(constraint)
|
||||
vars[unknownVars[0]] = out;
|
||||
solved.push(constraint);
|
||||
}
|
||||
for (const c of solved) {
|
||||
const i = constraints.indexOf(c)
|
||||
constraints.splice(i, 1)
|
||||
const i = constraints.indexOf(c);
|
||||
constraints.splice(i, 1);
|
||||
}
|
||||
if (solved.length === 0)
|
||||
throw new Error('Unable to solve for remaining variables')
|
||||
throw new Error("Unable to solve for remaining variables");
|
||||
}
|
||||
// attempt to evaluate the constraints using the provided inputs
|
||||
for (const c of allConstraints) {
|
||||
if (c.evaluate(vars) !== 0n) {
|
||||
throw new Error('invalid inputs')
|
||||
throw new Error("invalid inputs");
|
||||
}
|
||||
}
|
||||
return vars
|
||||
return vars;
|
||||
}
|
||||
|
||||
Binary file not shown.
BIN
test/epochKeyLite_main.r1cs.bak
Normal file
BIN
test/epochKeyLite_main.r1cs.bak
Normal file
Binary file not shown.
@@ -1,39 +1,39 @@
|
||||
import test from 'ava'
|
||||
import fs from 'fs/promises'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { compile, buildTrace } from '../src/compiler.mjs'
|
||||
import * as wasm from 'rstark'
|
||||
import test from "ava";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { compile, buildTrace } from "../src/compiler.mjs";
|
||||
// import * as wasm from 'rstark'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
function serializeBigint(v) {
|
||||
let _v = v
|
||||
const out = []
|
||||
let _v = v;
|
||||
const out = [];
|
||||
while (_v > 0n) {
|
||||
out.push(Number(_v & ((1n << 32n) - 1n)))
|
||||
_v >>= 32n
|
||||
out.push(Number(_v & ((1n << 32n) - 1n)));
|
||||
_v >>= 32n;
|
||||
}
|
||||
return out
|
||||
return out;
|
||||
}
|
||||
|
||||
test('should compile and prove example1', async t => {
|
||||
const asm = await fs.readFile(path.join(__dirname, './example1.asm'))
|
||||
const compiled = compile(asm.toString())
|
||||
const trace = buildTrace(compiled.program)
|
||||
test("should compile and prove example1", async (t) => {
|
||||
const asm = await fs.readFile(path.join(__dirname, "./example1.asm"));
|
||||
const compiled = compile(asm.toString());
|
||||
const trace = buildTrace(compiled.program);
|
||||
for (const line of trace) {
|
||||
t.is(line.length, compiled.program.registerCount)
|
||||
t.is(line.length, compiled.program.registerCount);
|
||||
}
|
||||
const proof = wasm.prove({
|
||||
transition_constraints: compiled.constraints.map(v => v.serialize()),
|
||||
boundary: compiled.boundary.map(v => [v[0], v[1], serializeBigint(v[2])]),
|
||||
trace: trace.map(t => t.map(v => serializeBigint(v))),
|
||||
})
|
||||
wasm.verify(proof, {
|
||||
trace_len: trace.length,
|
||||
register_count: compiled.program.registerCount,
|
||||
transition_constraints: compiled.constraints.map(v => v.serialize()),
|
||||
boundary: compiled.boundary.map(v => [v[0], v[1], serializeBigint(v[2])]),
|
||||
})
|
||||
t.pass()
|
||||
})
|
||||
// const proof = wasm.prove({
|
||||
// transition_constraints: compiled.constraints.map((v) => v.serialize()),
|
||||
// boundary: compiled.boundary.map((v) => [v[0], v[1], serializeBigint(v[2])]),
|
||||
// trace: trace.map((t) => t.map((v) => serializeBigint(v))),
|
||||
// });
|
||||
// wasm.verify(proof, {
|
||||
// trace_len: trace.length,
|
||||
// register_count: compiled.program.registerCount,
|
||||
// transition_constraints: compiled.constraints.map((v) => v.serialize()),
|
||||
// boundary: compiled.boundary.map((v) => [v[0], v[1], serializeBigint(v[2])]),
|
||||
// });
|
||||
t.pass();
|
||||
});
|
||||
|
||||
@@ -1,113 +1,20 @@
|
||||
import test from 'ava'
|
||||
import { compileR1cs } from '../src/r1csCompiler.mjs'
|
||||
import * as wasm from 'rstark'
|
||||
// import { compile, buildTrace } from '../src/compiler.mjs'
|
||||
import fs from 'fs/promises'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { compile } from '../src/r1csStark.mjs'
|
||||
import test from "ava";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { buildWitness } from "../src/witnessBuilder.mjs";
|
||||
import { R1CS } from "../src/r1csParser.mjs";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
function serializeBigint(v) {
|
||||
let _v = v
|
||||
const out = []
|
||||
while (_v > 0n) {
|
||||
out.push(Number(_v & ((1n << 32n) - 1n)))
|
||||
_v >>= 32n
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
test.skip('should compile and prove bits r1cs', async () => {
|
||||
// the number to be bitified, should fit in 60 bits
|
||||
const input = [100n]
|
||||
const file = path.join(__dirname, 'bits.r1cs')
|
||||
const fileData = await fs.readFile(file)
|
||||
const compiled = compile(fileData.buffer, input)
|
||||
const proof = wasm.prove({
|
||||
transition_constraints: compiled.constraints.map(v => v.serialize()),
|
||||
boundary: compiled.boundary.map(v => [v[0], v[1], serializeBigint(v[2])]),
|
||||
trace: compiled.trace.map(t => t.map(v => serializeBigint(v))),
|
||||
})
|
||||
wasm.verify(proof, {
|
||||
trace_len: compiled.trace.length,
|
||||
register_count: compiled.program.registerCount,
|
||||
transition_constraints: compiled.constraints.map(v => v.serialize()),
|
||||
boundary: compiled.boundary.map(v => [v[0], v[1], serializeBigint(v[2])]),
|
||||
})
|
||||
|
||||
t.pass()
|
||||
})
|
||||
|
||||
test('should compile and prove unirep epoch key r1cs', async t => {
|
||||
const input = Array(7).fill(0n)
|
||||
const file = path.join(__dirname, 'epochKeyLite_main.r1cs')
|
||||
const fileData = await fs.readFile(file)
|
||||
const compiled = compile(fileData.buffer, input)
|
||||
const _ = +new Date()
|
||||
const proof = wasm.prove({
|
||||
transition_constraints: compiled.constraints.map(v => v.serialize()),
|
||||
boundary: compiled.boundary.map(v => [v[0], v[1], serializeBigint(v[2])]),
|
||||
trace: compiled.trace.map(t => t.map(v => serializeBigint(v))),
|
||||
})
|
||||
// console.log(`proved in ${+new Date() - _} ms`)
|
||||
wasm.verify(proof, {
|
||||
trace_len: compiled.trace.length,
|
||||
register_count: compiled.program.registerCount,
|
||||
transition_constraints: compiled.constraints.map(v => v.serialize()),
|
||||
boundary: compiled.boundary.map(v => [v[0], v[1], serializeBigint(v[2])]),
|
||||
})
|
||||
|
||||
t.pass()
|
||||
})
|
||||
|
||||
test('should compile and prove r1cs', async t => {
|
||||
const input = [
|
||||
12n,
|
||||
]
|
||||
const file = path.join(__dirname, 'example.r1cs')
|
||||
const fileData = await fs.readFile(file)
|
||||
const compiled = compile(fileData.buffer, input)
|
||||
const proof = wasm.prove({
|
||||
transition_constraints: compiled.constraints.map(v => v.serialize()),
|
||||
boundary: compiled.boundary.map(v => [v[0], v[1], serializeBigint(v[2])]),
|
||||
trace: compiled.trace.map(t => t.map(v => serializeBigint(v))),
|
||||
})
|
||||
wasm.verify(proof, {
|
||||
trace_len: compiled.trace.length,
|
||||
register_count: compiled.program.registerCount,
|
||||
transition_constraints: compiled.constraints.map(v => v.serialize()),
|
||||
boundary: compiled.boundary.map(v => [v[0], v[1], serializeBigint(v[2])]),
|
||||
})
|
||||
|
||||
t.pass()
|
||||
})
|
||||
|
||||
test('should fail to prove invalid input', async t => {
|
||||
// if the circuit is properly constrained changing any of these
|
||||
// values by any amount should cause the proof to fail
|
||||
const inputMemory = [
|
||||
1n,
|
||||
12n,
|
||||
90n,
|
||||
11n, // change by 1
|
||||
1080n
|
||||
]
|
||||
const file = path.join(__dirname, 'example.r1cs')
|
||||
const fileData = await fs.readFile(file)
|
||||
const compiled = compile(fileData.buffer, null, inputMemory)
|
||||
const proof = wasm.prove({
|
||||
transition_constraints: compiled.constraints.map(v => v.serialize()),
|
||||
boundary: compiled.boundary.map(v => [v[0], v[1], serializeBigint(v[2])]),
|
||||
trace: compiled.trace.map(t => t.map(v => serializeBigint(v))),
|
||||
})
|
||||
t.throws(() => {
|
||||
wasm.verify(proof, {
|
||||
trace_len: compiled.trace.length,
|
||||
register_count: compiled.program.registerCount,
|
||||
transition_constraints: compiled.constraints.map(v => v.serialize()),
|
||||
boundary: compiled.boundary.map(v => [v[0], v[1], serializeBigint(v[2])]),
|
||||
})
|
||||
})
|
||||
})
|
||||
test("should compile and prove unirep epoch key r1cs", async (t) => {
|
||||
const input = Array(7).fill(0n);
|
||||
const file = path.join(__dirname, "epochKeyLite_main.r1cs");
|
||||
const fileData = await fs.readFile(file);
|
||||
// const compiled = compile(fileData.buffer, input);
|
||||
const { data } = new R1CS(fileData.buffer);
|
||||
const witness = buildWitness(data, input);
|
||||
// console.log(witness);
|
||||
/// TODO: confirm the witness fulfills the r1cs
|
||||
t.pass();
|
||||
});
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
import test from 'ava'
|
||||
import { compile, buildTrace, field } from '../src/compiler.mjs'
|
||||
import * as wasm from 'rstark'
|
||||
import test from "ava";
|
||||
import { compile, buildTrace, field } from "../src/compiler.mjs";
|
||||
// import * as wasm from 'rstark'
|
||||
|
||||
function serializeBigint(v) {
|
||||
let _v = v
|
||||
const out = []
|
||||
let _v = v;
|
||||
const out = [];
|
||||
while (_v > 0n) {
|
||||
out.push(Number(_v & ((1n << 32n) - 1n)))
|
||||
_v >>= 32n
|
||||
out.push(Number(_v & ((1n << 32n) - 1n)));
|
||||
_v >>= 32n;
|
||||
}
|
||||
return out
|
||||
return out;
|
||||
}
|
||||
|
||||
async function proveAndVerify(asm, inputs) {
|
||||
const compiled = compile(asm)
|
||||
const trace = buildTrace(compiled.program, inputs)
|
||||
const proof = wasm.prove({
|
||||
transition_constraints: compiled.constraints.map(v => v.serialize()),
|
||||
boundary: compiled.boundary.map(v => [v[0], v[1], serializeBigint(v[2])]),
|
||||
trace: trace.map(t => t.map(v => serializeBigint(v))),
|
||||
})
|
||||
wasm.verify(proof, {
|
||||
trace_len: trace.length,
|
||||
register_count: compiled.program.registerCount,
|
||||
transition_constraints: compiled.constraints.map(v => v.serialize()),
|
||||
boundary: compiled.boundary.map(v => [v[0], v[1], serializeBigint(v[2])]),
|
||||
})
|
||||
const compiled = compile(asm);
|
||||
const trace = buildTrace(compiled.program, inputs);
|
||||
// const proof = wasm.prove({
|
||||
// transition_constraints: compiled.constraints.map(v => v.serialize()),
|
||||
// boundary: compiled.boundary.map(v => [v[0], v[1], serializeBigint(v[2])]),
|
||||
// trace: trace.map(t => t.map(v => serializeBigint(v))),
|
||||
// })
|
||||
// wasm.verify(proof, {
|
||||
// trace_len: trace.length,
|
||||
// register_count: compiled.program.registerCount,
|
||||
// transition_constraints: compiled.constraints.map(v => v.serialize()),
|
||||
// boundary: compiled.boundary.map(v => [v[0], v[1], serializeBigint(v[2])]),
|
||||
// })
|
||||
}
|
||||
|
||||
test('should set using named value', async t => {
|
||||
const v = field.random()
|
||||
test("should set using named value", async (t) => {
|
||||
const v = field.random();
|
||||
const asm = `
|
||||
set 0x0 input1
|
||||
set 0x1 ${v.toString()}
|
||||
eq 0x0 0x1
|
||||
`
|
||||
`;
|
||||
await proveAndVerify(asm, {
|
||||
input1: v
|
||||
})
|
||||
t.pass()
|
||||
})
|
||||
input1: v,
|
||||
});
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should add subtract eq', async t => {
|
||||
test("should add subtract eq", async (t) => {
|
||||
const asm = `
|
||||
set 0x0 0x124129821400
|
||||
set 0x1 0x1247912469
|
||||
@@ -49,138 +49,138 @@ neg 0x2 0x1
|
||||
add 0x3 0x0 0x2
|
||||
add 0x1 0x3 0x1
|
||||
eq 0x0 0x1
|
||||
`
|
||||
await proveAndVerify(asm)
|
||||
t.pass()
|
||||
})
|
||||
`;
|
||||
await proveAndVerify(asm);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should add registers', async t => {
|
||||
const v1 = field.random()
|
||||
const v2 = field.random()
|
||||
const sum = field.add(v1, v2)
|
||||
test("should add registers", async (t) => {
|
||||
const v1 = field.random();
|
||||
const v2 = field.random();
|
||||
const sum = field.add(v1, v2);
|
||||
const asm = `
|
||||
set 0x0 ${v1.toString()}
|
||||
set 0x1 ${v2.toString()}
|
||||
add 0x2 0x0 0x1
|
||||
set 0x3 ${sum.toString()}
|
||||
eq 0x2 0x3
|
||||
`
|
||||
await proveAndVerify(asm)
|
||||
t.pass()
|
||||
})
|
||||
`;
|
||||
await proveAndVerify(asm);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should add registers in place', async t => {
|
||||
const v1 = field.random()
|
||||
const v2 = field.random()
|
||||
const sum = field.add(v1, v2)
|
||||
test("should add registers in place", async (t) => {
|
||||
const v1 = field.random();
|
||||
const v2 = field.random();
|
||||
const sum = field.add(v1, v2);
|
||||
const asm = `
|
||||
set 0x0 ${v1.toString()}
|
||||
set 0x1 ${v2.toString()}
|
||||
add 0x0 0x0 0x1
|
||||
set 0x1 ${sum.toString()}
|
||||
eq 0x0 0x1
|
||||
`
|
||||
await proveAndVerify(asm)
|
||||
t.pass()
|
||||
})
|
||||
`;
|
||||
await proveAndVerify(asm);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should check equality of registers', async t => {
|
||||
test("should check equality of registers", async (t) => {
|
||||
const asm = `
|
||||
set 0x0 0x124129821400
|
||||
set 0x1 0x124129821400
|
||||
eq 0x0 0x1
|
||||
`
|
||||
await proveAndVerify(asm)
|
||||
t.pass()
|
||||
})
|
||||
`;
|
||||
await proveAndVerify(asm);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should fail to prove equality', async t => {
|
||||
test("should fail to prove equality", async (t) => {
|
||||
const asm = `
|
||||
set 0x0 0x124129821400
|
||||
set 0x1 0x124129821401
|
||||
eq 0x0 0x1
|
||||
`
|
||||
await t.throwsAsync(() => proveAndVerify(asm))
|
||||
})
|
||||
`;
|
||||
await t.throwsAsync(() => proveAndVerify(asm));
|
||||
});
|
||||
|
||||
test('should negate register', async t => {
|
||||
const v = field.random()
|
||||
const vNeg = field.neg(v)
|
||||
test("should negate register", async (t) => {
|
||||
const v = field.random();
|
||||
const vNeg = field.neg(v);
|
||||
const asm = `
|
||||
set 0x0 ${v.toString()}
|
||||
set 0x1 ${vNeg.toString()}
|
||||
neg 0x2 0x0
|
||||
eq 0x2 0x1
|
||||
`
|
||||
await proveAndVerify(asm)
|
||||
t.pass()
|
||||
})
|
||||
`;
|
||||
await proveAndVerify(asm);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should negate in place', async t => {
|
||||
const v = field.random()
|
||||
const vNeg = field.neg(v)
|
||||
test("should negate in place", async (t) => {
|
||||
const v = field.random();
|
||||
const vNeg = field.neg(v);
|
||||
const asm = `
|
||||
set 0x0 ${v.toString()}
|
||||
set 0x1 ${vNeg.toString()}
|
||||
neg 0x0 0x0
|
||||
eq 0x0 0x1
|
||||
`
|
||||
await proveAndVerify(asm)
|
||||
t.pass()
|
||||
})
|
||||
`;
|
||||
await proveAndVerify(asm);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should multiply registers', async t => {
|
||||
const v1 = field.random()
|
||||
const v2 = field.random()
|
||||
const prod = field.mul(v1, v2)
|
||||
test("should multiply registers", async (t) => {
|
||||
const v1 = field.random();
|
||||
const v2 = field.random();
|
||||
const prod = field.mul(v1, v2);
|
||||
const asm = `
|
||||
set 0x0 ${v1.toString()}
|
||||
set 0x1 ${v2.toString()}
|
||||
mul 0x2 0x0 0x1
|
||||
set 0x3 ${prod.toString()}
|
||||
eq 0x2 0x3
|
||||
`
|
||||
await proveAndVerify(asm)
|
||||
t.pass()
|
||||
})
|
||||
`;
|
||||
await proveAndVerify(asm);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should multiply in place', async t => {
|
||||
const v1 = field.random()
|
||||
const v2 = field.random()
|
||||
const prod = field.mul(v1, v2)
|
||||
test("should multiply in place", async (t) => {
|
||||
const v1 = field.random();
|
||||
const v2 = field.random();
|
||||
const prod = field.mul(v1, v2);
|
||||
const asm = `
|
||||
set 0x0 ${v1.toString()}
|
||||
set 0x1 ${v2.toString()}
|
||||
mul 0x0 0x0 0x1
|
||||
set 0x1 ${prod.toString()}
|
||||
eq 0x0 0x1
|
||||
`
|
||||
await proveAndVerify(asm)
|
||||
t.pass()
|
||||
})
|
||||
`;
|
||||
await proveAndVerify(asm);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should mod inverse in place (0x2)', async t => {
|
||||
const v = field.random()
|
||||
const vInv = field.inv(v)
|
||||
test("should mod inverse in place (0x2)", async (t) => {
|
||||
const v = field.random();
|
||||
const vInv = field.inv(v);
|
||||
const asm = `
|
||||
set 0x0 ${v.toString()}
|
||||
set 0x1 ${vInv.toString()}
|
||||
inv 0x2 0x0
|
||||
eq 0x2 0x1
|
||||
`
|
||||
await proveAndVerify(asm)
|
||||
t.pass()
|
||||
})
|
||||
`;
|
||||
await proveAndVerify(asm);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
test('should mod inverse in place (0x0)', async t => {
|
||||
const v = field.random()
|
||||
const vInv = field.inv(v)
|
||||
test("should mod inverse in place (0x0)", async (t) => {
|
||||
const v = field.random();
|
||||
const vInv = field.inv(v);
|
||||
const asm = `
|
||||
set 0x0 ${v.toString()}
|
||||
set 0x1 ${vInv.toString()}
|
||||
inv 0x0 0x0
|
||||
eq 0x0 0x1
|
||||
`
|
||||
await proveAndVerify(asm)
|
||||
t.pass()
|
||||
})
|
||||
`;
|
||||
await proveAndVerify(asm);
|
||||
t.pass();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user