From cc4d3fce0155f73540ecd17f8fbc02cb3632e7bb Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 10:38:34 +0100 Subject: [PATCH 1/4] add range check 8 gadget --- src/lib/gadgets/gadgets.ts | 11 +++++++++++ src/lib/gadgets/range-check.ts | 20 ++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index b67ef58fd9..93f64cd482 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -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'; @@ -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. + */ + 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. diff --git a/src/lib/gadgets/range-check.ts b/src/lib/gadgets/range-check.ts index 1e44afdaf9..147ef1a6a4 100644 --- a/src/lib/gadgets/range-check.ts +++ b/src/lib/gadgets/range-check.ts @@ -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 }; /** @@ -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); +} From 2edfa18bf75642eb5bf17922dc457c186e5d83dd Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 10:38:45 +0100 Subject: [PATCH 2/4] range check 8 unit test --- src/lib/gadgets/range-check.unit-test.ts | 31 +++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/lib/gadgets/range-check.unit-test.ts b/src/lib/gadgets/range-check.unit-test.ts index 47aafbf592..1caea18f17 100644 --- a/src/lib/gadgets/range-check.unit-test.ts +++ b/src/lib/gadgets/range-check.unit-test.ts @@ -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] }, @@ -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) { @@ -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; @@ -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'); @@ -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'); From 5312b014616f1ec914e67517d0c181c0473f0f63 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 10:38:59 +0100 Subject: [PATCH 3/4] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfd3e03aac..0f52c5178e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. From 02f5f270d2b3a913b1d42057610d1bfe7e75a9fb Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 4 Dec 2023 11:04:43 +0100 Subject: [PATCH 4/4] fix doccomment --- src/lib/gadgets/gadgets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/gadgets/gadgets.ts b/src/lib/gadgets/gadgets.ts index 93f64cd482..7ee5f6c7c7 100644 --- a/src/lib/gadgets/gadgets.ts +++ b/src/lib/gadgets/gadgets.ts @@ -44,7 +44,7 @@ const Gadgets = { /** * Asserts that the input value is in the range [0, 2^8). * - * See {@link Gadgets.rangeCheck8} for analogous details and usage examples. + * See {@link Gadgets.rangeCheck64} for analogous details and usage examples. */ rangeCheck8(x: Field) { return rangeCheck8(x);