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

Expose and test rangeCheck64 gadget #1181

Merged
merged 17 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from 14 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

- Internal support for several custom gates (range check, bitwise operations, foreign field operations) and lookup tables https://github.com/o1-labs/o1js/pull/1176

- `Gadgets.rangeCheck64()`, new provable method to do efficient 64-bit range checks using lookup tables https://github.com/o1-labs/o1js/pull/1181

## [0.13.1](https://github.com/o1-labs/o1js/compare/c2f392fe5...045faa7)

### Breaking changes
Expand All @@ -41,6 +43,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

- Improve prover performance by ~25% https://github.com/o1-labs/o1js/pull/1092
- Change internal representation of field elements to be JS bigint instead of Uint8Array
- Consolidate internal framework for testing equivalence of two implementations

## [0.13.0](https://github.com/o1-labs/o1js/compare/fbd4b2717...c2f392fe5)

Expand Down
2 changes: 1 addition & 1 deletion src/bindings
3 changes: 2 additions & 1 deletion src/examples/ex02_root.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Field, Circuit, circuitMain, public_, UInt64 } from 'o1js';
import { Field, Circuit, circuitMain, public_, UInt64, Gadgets } from 'o1js';

/* Exercise 2:

Expand All @@ -10,6 +10,7 @@ Prove:
class Main extends Circuit {
@circuitMain
static main(@public_ y: Field, x: UInt64) {
Gadgets.rangeCheck64(x.value);
let y3 = y.square().mul(y);
y3.assertEquals(x.value);
}
Expand Down
10 changes: 2 additions & 8 deletions src/examples/ex02_root_program.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import {
Field,
Circuit,
circuitMain,
public_,
UInt64,
Experimental,
} from 'o1js';
import { Field, UInt64, Experimental, Gadgets } from 'o1js';

let { ZkProgram } = Experimental;

Expand All @@ -15,6 +8,7 @@ const Main = ZkProgram({
main: {
privateInputs: [UInt64],
method(y: Field, x: UInt64) {
Gadgets.rangeCheck64(x.value);
let y3 = y.square().mul(y);
y3.assertEquals(x.value);
},
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export {
export { Provable } from './lib/provable.js';
export { Circuit, Keypair, public_, circuitMain } from './lib/circuit.js';
export { UInt32, UInt64, Int64, Sign } from './lib/int.js';
export { Gadgets } from './lib/gadgets/gadgets.js';
export { Types } from './bindings/mina-transaction/types.js';

export * as Mina from './lib/mina.js';
Expand Down
101 changes: 56 additions & 45 deletions src/lib/field.unit-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@ import { Provable } from './provable.js';
import { Binable } from '../bindings/lib/binable.js';
import { ProvableExtended } from './circuit_value.js';
import { FieldType } from './field.js';
import { createEquivalenceTesters, throwError } from './testing/equivalent.js';
import {
equivalentProvable as equivalent,
oneOf,
field,
bigintField,
throwError,
unit,
bool,
Spec,
} from './testing/equivalent.js';

// types
Field satisfies Provable<Field>;
Expand Down Expand Up @@ -56,73 +65,75 @@ test(Random.field, Random.int(-5, 5), (x, k) => {
deepEqual(Field(x + BigInt(k) * Field.ORDER), Field(x));
});

// Field | bigint parameter
let fieldOrBigint = oneOf(field, bigintField);

// special generator
let SmallField = Random.reject(
Random.field,
(x) => x.toString(2).length > Fp.sizeInBits - 2
);

let { equivalent1, equivalent2, equivalentVoid1, equivalentVoid2 } =
createEquivalenceTesters(Field, Field);
let smallField: Spec<bigint, Field> = { ...field, rng: SmallField };
let smallBigint: Spec<bigint, bigint> = { ...bigintField, rng: SmallField };
let smallFieldOrBigint = oneOf(smallField, smallBigint);

// arithmetic, both in- and outside provable code
equivalent2((x, y) => x.add(y), Fp.add);
equivalent1((x) => x.neg(), Fp.negate);
equivalent2((x, y) => x.sub(y), Fp.sub);
equivalent2((x, y) => x.mul(y), Fp.mul);
let equivalent1 = equivalent({ from: [field], to: field });
let equivalent2 = equivalent({ from: [field, fieldOrBigint], to: field });

equivalent2(Fp.add, (x, y) => x.add(y));
equivalent1(Fp.negate, (x) => x.neg());
equivalent2(Fp.sub, (x, y) => x.sub(y));
equivalent2(Fp.mul, (x, y) => x.mul(y));
equivalent1(
(x) => x.inv(),
(x) => Fp.inverse(x) ?? throwError('division by 0')
(x) => Fp.inverse(x) ?? throwError('division by 0'),
(x) => x.inv()
);
equivalent2(
(x, y) => x.div(y),
(x, y) => Fp.div(x, y) ?? throwError('division by 0')
(x, y) => Fp.div(x, y) ?? throwError('division by 0'),
(x, y) => x.div(y)
);
equivalent1((x) => x.square(), Fp.square);
equivalent1(Fp.square, (x) => x.square());
equivalent1(
(x) => x.sqrt(),
(x) => Fp.sqrt(x) ?? throwError('no sqrt')
(x) => Fp.sqrt(x) ?? throwError('no sqrt'),
(x) => x.sqrt()
);
equivalent2(
(x, y) => x.equals(y).toField(),
(x, y) => BigInt(x === y)
equivalent({ from: [field, fieldOrBigint], to: bool })(
(x, y) => x === y,
(x, y) => x.equals(y)
);
equivalent2(
(x, y) => x.lessThan(y).toField(),
(x, y) => BigInt(x < y),
SmallField

equivalent({ from: [smallField, smallFieldOrBigint], to: bool })(
(x, y) => x < y,
(x, y) => x.lessThan(y)
);
equivalent2(
(x, y) => x.lessThanOrEqual(y).toField(),
(x, y) => BigInt(x <= y),
SmallField
equivalent({ from: [smallField, smallFieldOrBigint], to: bool })(
(x, y) => x <= y,
(x, y) => x.lessThanOrEqual(y)
);
equivalentVoid2(
(x, y) => x.assertEquals(y),
(x, y) => x === y || throwError('not equal')
equivalent({ from: [field, fieldOrBigint], to: unit })(
(x, y) => x === y || throwError('not equal'),
(x, y) => x.assertEquals(y)
);
equivalentVoid2(
(x, y) => x.assertNotEquals(y),
(x, y) => x !== y || throwError('equal')
equivalent({ from: [field, fieldOrBigint], to: unit })(
(x, y) => x !== y || throwError('equal'),
(x, y) => x.assertNotEquals(y)
);
equivalentVoid2(
(x, y) => x.assertLessThan(y),
equivalent({ from: [smallField, smallFieldOrBigint], to: unit })(
(x, y) => x < y || throwError('not less than'),
SmallField
(x, y) => x.assertLessThan(y)
);
equivalentVoid2(
(x, y) => x.assertLessThanOrEqual(y),
equivalent({ from: [smallField, smallFieldOrBigint], to: unit })(
(x, y) => x <= y || throwError('not less than or equal'),
SmallField
(x, y) => x.assertLessThanOrEqual(y)
);
equivalentVoid1(
(x) => x.assertBool(),
(x) => x === 0n || x === 1n || throwError('not boolean')
equivalent({ from: [field], to: unit })(
(x) => x === 0n || x === 1n || throwError('not boolean'),
(x) => x.assertBool()
);
equivalent1(
(x) => x.isEven().toField(),
(x) => BigInt((x & 1n) === 0n),
SmallField
equivalent({ from: [smallField], to: bool })(
(x) => (x & 1n) === 0n,
(x) => x.isEven()
);

// non-constant field vars
Expand Down
7 changes: 7 additions & 0 deletions src/lib/gadgets/gadgets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { rangeCheck64 } from './range-check.js';

export { Gadgets };

const Gadgets = {
rangeCheck64,
};
47 changes: 47 additions & 0 deletions src/lib/gadgets/gadgets.unit-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { mod } from '../../bindings/crypto/finite_field.js';
import { Field } from '../field.js';
import { ZkProgram } from '../proof_system.js';
import {
Spec,
boolean,
equivalentAsync,
field,
} from '../testing/equivalent.js';
import { Random } from '../testing/random.js';
import { Gadgets } from './gadgets.js';

// TODO: make a ZkFunction or something that doesn't go through Pickles

let RangeCheck64 = ZkProgram({
methods: {
run: {
privateInputs: [Field],
method(x) {
Gadgets.rangeCheck64(x);
},
},
},
});

await RangeCheck64.compile();

let maybeUint64: Spec<bigint, Field> = {
...field,
rng: Random.map(Random.oneOf(Random.uint64, Random.uint64.invalid), (x) =>
mod(x, Field.ORDER)
),
};

// do a couple of proofs
// TODO: we use this as a test because there's no way to check custom gates quickly :(

equivalentAsync({ from: [maybeUint64], to: boolean }, { runs: 20 })(
(x) => {
if (x >= 1n << 64n) throw Error('expected 64 bits');
return true;
},
async (x) => {
let proof = await RangeCheck64.run(x);
return await RangeCheck64.verify(proof);
}
);
4 changes: 4 additions & 0 deletions src/lib/gadgets/range-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import * as Gates from '../gates.js';

export { rangeCheck64 };

/**
mitschabaude marked this conversation as resolved.
Show resolved Hide resolved
* Asserts that x is in the range [0, 2^64)
* @param x field element
*/
function rangeCheck64(x: Field) {
if (x.isConstant()) {
if (x.toBigInt() >= 1n << 64n) {
Expand Down
Loading
Loading