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

Looking up values in runtime tables #1858

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [1.9.1](https://github.com/o1-labs/o1js/compare/f15293a69...7e9394) - 2024-10-15

### Added

- Configuration of runtime tables https://github.com/o1-labs/o1js/pull/1858

### Fixes

- Performance regression when compiling recursive circuits is fixed https://github.com/o1-labs/o1js/pull/1874
Expand Down
5 changes: 3 additions & 2 deletions src/lib/proof-system/feature-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ async function featureFlagsfromFlatMethodIntfs(
xor: false,
rot: false,
lookup: false,
runtimeTables: false,
runtimeTables: undefined, //
};

// if there's only one method that means it defines the feature flags for the entire program
Expand Down Expand Up @@ -152,7 +152,8 @@ function featureFlagsFromGates(gates: Gate[]): FeatureFlags {
xor: false,
rot: false,
lookup: false,
runtimeTables: false,
// Runtime tables are Maybe because it cannot be inferred from gates alone
runtimeTables: undefined,
};
for (let gate of gates) {
let flag = gateToFlag[gate.type];
Expand Down
32 changes: 27 additions & 5 deletions src/lib/provable/gadgets/gadgets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
import { divMod32, addMod32, divMod64, addMod64 } from './arithmetic.js';
import { SHA256 } from './sha256.js';
import { BLAKE2B } from './blake2b.js';
import { rangeCheck3x12 } from './lookup.js';
import { rangeCheck3x12, inTable } from './lookup.js';
import { arrayGet } from './basic.js';

export { Gadgets, Field3, ForeignFieldSum };
Expand Down Expand Up @@ -439,7 +439,7 @@ const Gadgets = {
* Bitwise AND gadget on {@link Field} elements. Equivalent to the [bitwise AND `&` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND).
* The AND gate works by comparing two bits and returning `1` if both bits are `1`, and `0` otherwise.
*
* It can be checked by a double generic gate that verifies the following relationship between the values
* It can be checked by a double generic gate that verifies the following relationship between the values
* below (in the process it also invokes the {@link Gadgets.xor} gadget which will create additional constraints depending on `length`).
*
* The generic gate verifies:\
Expand All @@ -452,7 +452,7 @@ const Gadgets = {
* You can find more details about the implementation in the [Mina book](https://o1-labs.github.io/proof-systems/specs/kimchi.html?highlight=gates#and)
*
* The `length` parameter lets you define how many bits should be compared. `length` is rounded
* to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values
* to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values
* are constrained to fit into `paddedLength` bits. The output is guaranteed to have at most `paddedLength` bits as well.
*
* **Note:** Specifying a larger `length` parameter adds additional constraints.
Expand All @@ -476,8 +476,8 @@ const Gadgets = {
* Bitwise OR gadget on {@link Field} elements. Equivalent to the [bitwise OR `|` operator in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_OR).
* The OR gate works by comparing two bits and returning `1` if at least one bit is `1`, and `0` otherwise.
*
* The `length` parameter lets you define how many bits should be compared. `length` is rounded
* to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values
* The `length` parameter lets you define how many bits should be compared. `length` is rounded
* to the nearest multiple of 16, `paddedLength = ceil(length / 16) * 16`, and both input values
* are constrained to fit into `paddedLength` bits. The output is guaranteed to have at most `paddedLength` bits as well.
*
* **Note:** Specifying a larger `length` parameter adds additional constraints.
Expand Down Expand Up @@ -577,6 +577,28 @@ const Gadgets = {
return rangeCheck3x12(v0, v1, v2);
},

/**
* In-circuit check that up to 3 pairs of index and value are in the runtime
* table given by the identifier. Each given pair is a tuple composed of a
* bigint and a Field.
*
* Internally, it creates a lookup gate for the three pairs. If fewer pairs are
* given, the remaining pairs are duplicates of the first one.
*
* @param id
* @param pair0
* @param pair1
* @param pair2
*/
inTable(
id: number,
pair0: [bigint, Field],
pair1?: [bigint, Field] | undefined,
pair2?: [bigint, Field] | undefined
) {
return inTable(id, pair0, pair1, pair2);
},

/**
* Gadgets for foreign field operations.
*
Expand Down
33 changes: 32 additions & 1 deletion src/lib/provable/gadgets/lookup.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Field } from '../field.js';
import { Gates } from '../gates.js';

export { rangeCheck3x12 };
export { rangeCheck3x12, inTable };

function rangeCheck3x12(v0: Field, v1: Field, v2: Field) {
// Checks that all three input values exist in the RANGE_CHECK_TABLE (tableId: 1)
Expand All @@ -18,3 +18,34 @@ function rangeCheck3x12(v0: Field, v1: Field, v2: Field) {
Field.from(0)
);
}

/**
* In-circuit check that up to 3 pairs of index and value are in the runtime
* table given by the identifier. Each given pair is a tuple composed of a
* bigint and a Field.
*
* @param id
* @param pair0
* @param pair1
* @param pair2
*/
function inTable(
id: number,
pair0: [bigint, Field],
pair1?: [bigint, Field] | undefined,
pair2?: [bigint, Field] | undefined
) {
let [idx0, v0] = pair0;
let [idx1, v1] = pair1 === undefined ? pair0 : pair1;
let [idx2, v2] = pair2 === undefined ? pair0 : pair2;

Gates.lookup(
Field.from(id),
Field.from(idx0),
v0,
Field.from(idx1),
v1,
Field.from(idx2),
v2
);
}
9 changes: 9 additions & 0 deletions src/lib/provable/gates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export {
foreignFieldAdd,
foreignFieldMul,
KimchiGateType,
addRuntimeTableConfig,
};

export { fieldVar };
Expand All @@ -32,6 +33,7 @@ const Gates = {
foreignFieldAdd,
foreignFieldMul,
raw,
addRuntimeTableConfig,
};

function rangeCheck0(
Expand Down Expand Up @@ -289,6 +291,13 @@ function raw(kind: KimchiGateType, values: Field[], coefficients: bigint[]) {
);
}

function addRuntimeTableConfig(id: number, first_column: bigint[]) {
Snarky.gates.addRuntimeTableConfig(
id,
MlArray.to(first_column.map((x) => FieldConst.fromBigint(x)))
);
}

enum KimchiGateType {
Zero,
Generic,
Expand Down
129 changes: 95 additions & 34 deletions src/lib/provable/test/lookup.unit-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
import { Random } from '../../testing/property.js';
import { assert } from '../gadgets/common.js';
import { Gadgets } from '../gadgets/gadgets.js';
import { Gates } from '../gates.js';
import { constraintSystem, contains } from '../../testing/constraint-system.js';
import { FeatureFlags } from 'o1js';

let uint = (n: number | bigint): Spec<bigint, Field> => {
return fieldWithRng(Random.bignat((1n << BigInt(n)) - 1n));
Expand All @@ -23,39 +25,98 @@
);
};

let Lookup = ZkProgram({
name: 'lookup',
methods: {
three12Bit: {
privateInputs: [Field, Field, Field],
async method(v0: Field, v1: Field, v2: Field) {
// Dummy range check to make sure the lookup table is initialized
// It should never fail because 64 > 12
Gadgets.rangeCheck64(v0);
Gadgets.rangeCheck3x12(v0, v1, v2);
/*
// Range-check tests
{
let RangeCheck = ZkProgram({
name: 'range-check',
methods: {
three12Bit: {
privateInputs: [Field, Field, Field],
async method(v0: Field, v1: Field, v2: Field) {
// Dummy range check to make sure the lookup table is initialized
// It should never fail because 64 > 12
Gadgets.rangeCheck64(v0);
Gadgets.rangeCheck3x12(v0, v1, v2);
},
},
},
},
});

// constraint system sanity check

constraintSystem.fromZkProgram(Lookup, 'three12Bit', contains(['Lookup']));

await Lookup.compile();

await equivalentAsync(
{ from: [uint(12), uint(12), maybeUint(12)], to: boolean },
{ runs: 3 }
)(
(x, y, z) => {
assert(x < 1n << 12n);
assert(y < 1n << 12n);
assert(z < 1n << 12n);
return true;
},
async (x, y, z) => {
let { proof } = await Lookup.three12Bit(x, y, z);
return await Lookup.verify(proof);
}
);
});

// constraint system sanity check

constraintSystem.fromZkProgram(
RangeCheck,
'three12Bit',
contains(['Lookup'])
);

await RangeCheck.compile();

await equivalentAsync(
{ from: [uint(12), uint(12), maybeUint(12)], to: boolean },
{ runs: 3 }
)(
(x, y, z) => {
assert(x < 1n << 12n);
assert(y < 1n << 12n);
assert(z < 1n << 12n);
return true;
},
async (x, y, z) => {
let proof = await RangeCheck.three12Bit(x, y, z);
return await RangeCheck.verify(proof);
}
);
}
*/

// Runtime table tests
{
let RuntimeTable = ZkProgram({
name: 'runtime-table',
methods: {
runtimeTable: {
privateInputs: [Field, Field, Field],
async method(v0: Field, v1: Field, v2: Field) {
let tableId = 2;
let indices = [0n, 1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n];
Gates.addRuntimeTableConfig(tableId, indices);
Gadgets.inTable(tableId, [5n, v0], [6n, v1], [7n, v2]);
Gadgets.inTable(tableId, [6n, v1]);
},
},
},
});

// constraint system sanity check

constraintSystem.fromZkProgram(
RuntimeTable,
'runtimeTable',
contains(['Lookup'])
);

// check feature flags are set up correctly
const featureFlags = await FeatureFlags.fromZkProgram(RuntimeTable);
assert(featureFlags.lookup === true);
//assert(featureFlags.runtimeTables === true);

await RuntimeTable.compile();

await equivalentAsync(
{ from: [uint(12), uint(12), uint(12)], to: boolean },
{ runs: 1 }
)(
(x, y, z) => {
assert(x < 1n << 12n);
assert(y < 1n << 12n);
assert(z < 1n << 12n);
return true;
},
async (x, y, z) => {
let proof = await RuntimeTable.runtimeTable(x, y, z);
return await RuntimeTable.verify(proof);

Check failure on line 119 in src/lib/provable/test/lookup.unit-test.ts

View workflow job for this annotation

GitHub Actions / benchmarks (20, sdk-self-hosted-linux-amd64)

Argument of type '{ proof: Proof<undefined, void>; auxiliaryOutput: undefined; }' is not assignable to parameter of type 'Proof<undefined, void>'.

Check failure on line 119 in src/lib/provable/test/lookup.unit-test.ts

View workflow job for this annotation

GitHub Actions / Prepare

Argument of type '{ proof: Proof<undefined, void>; auxiliaryOutput: undefined; }' is not assignable to parameter of type 'Proof<undefined, void>'.

Check failure on line 119 in src/lib/provable/test/lookup.unit-test.ts

View workflow job for this annotation

GitHub Actions / compatible

Argument of type '{ proof: Proof<undefined, void>; auxiliaryOutput: undefined; }' is not assignable to parameter of type 'Proof<undefined, void>'.

Check failure on line 119 in src/lib/provable/test/lookup.unit-test.ts

View workflow job for this annotation

GitHub Actions / benchmarks (20, sdk-self-hosted-linux-arm64)

Argument of type '{ proof: Proof<undefined, void>; auxiliaryOutput: undefined; }' is not assignable to parameter of type 'Proof<undefined, void>'.

Check failure on line 119 in src/lib/provable/test/lookup.unit-test.ts

View workflow job for this annotation

GitHub Actions / master

Argument of type '{ proof: Proof<undefined, void>; auxiliaryOutput: undefined; }' is not assignable to parameter of type 'Proof<undefined, void>'.
}
);
}
Loading