Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased](https://github.com/o1-labs/o1js/compare/1ad7333e9e...HEAD)

### Added

- `Gadgets.rangeCheck8()` to assert that a value fits in 8 bits https://github.com/o1-labs/o1js/pull/1288

### Changed

- Change precondition APIs to use "require" instead of "assert" as the verb, to distinguish them from provable assertions.
Expand Down
11 changes: 11 additions & 0 deletions src/lib/gadgets/gadgets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
compactMultiRangeCheck,
multiRangeCheck,
rangeCheck64,
rangeCheck8,
} from './range-check.js';
import { not, rotate, xor, and, leftShift, rightShift } from './bitwise.js';
import { Field } from '../core.js';
Expand Down Expand Up @@ -39,6 +40,16 @@ const Gadgets = {
rangeCheck64(x: Field) {
return rangeCheck64(x);
},

/**
* Asserts that the input value is in the range [0, 2^8).
*
* See {@link Gadgets.rangeCheck8} for analogous details and usage examples.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this points to the same Gadget - did you mean Gadgets.rangeCheck32?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah I meant rangeCheck64 thanks - stupid copilot 🤖

*/
rangeCheck8(x: Field) {
return rangeCheck8(x);
},

/**
* A (left and right) rotation operates similarly to the shift operation (`<<` for left and `>>` for right) in JavaScript,
* with the distinction that the bits are circulated to the opposite end of a 64-bit representation rather than being discarded.
Expand Down
20 changes: 18 additions & 2 deletions src/lib/gadgets/range-check.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Field } from '../field.js';
import { Gates } from '../gates.js';
import { bitSlice, exists, toVar, toVars } from './common.js';
import { assert, bitSlice, exists, toVar, toVars } from './common.js';

export { rangeCheck64, multiRangeCheck, compactMultiRangeCheck };
export { rangeCheck64, rangeCheck8, multiRangeCheck, compactMultiRangeCheck };
export { l, l2, l3, lMask, l2Mask };

/**
Expand Down Expand Up @@ -207,3 +207,19 @@ function rangeCheck1Helper(inputs: {
[z20, z18, z16, x76, x64, y76, y64, z14, z12, z10, z8, z6, z4, z2, z0]
);
}

function rangeCheck8(x: Field) {
if (x.isConstant()) {
assert(
x.toBigInt() < 1n << 8n,
`rangeCheck8: expected field to fit in 8 bits, got ${x}`
);
return;
}

// check that x fits in 16 bits
x.rangeCheckHelper(16).assertEquals(x);
// check that 2^8 x fits in 16 bits
let x256 = x.mul(1 << 8).seal();
x256.rangeCheckHelper(16).assertEquals(x256);
}
31 changes: 28 additions & 3 deletions src/lib/gadgets/range-check.unit-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ constraintSystem(
ifNotAllConstant(withoutGenerics(equals(['RangeCheck0'])))
);

constraintSystem(
'range check 8',
{ from: [Field] },
Gadgets.rangeCheck8,
ifNotAllConstant(withoutGenerics(equals(['EndoMulScalar', 'EndoMulScalar'])))
);

constraintSystem(
'multi-range check',
{ from: [Field, Field, Field] },
Expand Down Expand Up @@ -72,6 +79,12 @@ let RangeCheck = ZkProgram({
Gadgets.rangeCheck64(x);
},
},
check8: {
privateInputs: [Field],
method(x) {
Gadgets.rangeCheck8(x);
},
},
checkMulti: {
privateInputs: [Field, Field, Field],
method(x, y, z) {
Expand All @@ -91,8 +104,9 @@ let RangeCheck = ZkProgram({
await RangeCheck.compile();

// TODO: we use this as a test because there's no way to check custom gates quickly :(
const runs = 2;

await equivalentAsync({ from: [maybeUint(64)], to: boolean }, { runs: 3 })(
await equivalentAsync({ from: [maybeUint(64)], to: boolean }, { runs })(
(x) => {
assert(x < 1n << 64n);
return true;
Expand All @@ -103,9 +117,20 @@ await equivalentAsync({ from: [maybeUint(64)], to: boolean }, { runs: 3 })(
}
);

await equivalentAsync({ from: [maybeUint(8)], to: boolean }, { runs })(
(x) => {
assert(x < 1n << 8n);
return true;
},
async (x) => {
let proof = await RangeCheck.check8(x);
return await RangeCheck.verify(proof);
}
);

await equivalentAsync(
{ from: [maybeUint(l), uint(l), uint(l)], to: boolean },
{ runs: 3 }
{ runs }
)(
(x, y, z) => {
assert(!(x >> l) && !(y >> l) && !(z >> l), 'multi: not out of range');
Expand All @@ -119,7 +144,7 @@ await equivalentAsync(

await equivalentAsync(
{ from: [maybeUint(2n * l), uint(l)], to: boolean },
{ runs: 3 }
{ runs }
)(
(xy, z) => {
assert(!(xy >> (2n * l)) && !(z >> l), 'compact: not out of range');
Expand Down