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

Add rot gadget to o1js #1182

Merged
merged 84 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
4afed25
chore(bindings): update submodule for rot gate
MartinMinkov Oct 12, 2023
6bdcbbf
feat(snarky.d.ts): add 'rot' function to Snarky interface to support …
MartinMinkov Oct 12, 2023
ddb9200
feat(gates.ts): add rot function
MartinMinkov Oct 16, 2023
65d97be
feat(rot.ts): add new rotation function for bitwise operations
MartinMinkov Oct 16, 2023
e69a93f
chore(bindings): update bindings subproject to latest commit
MartinMinkov Oct 16, 2023
e040fe7
refactor(snarky.d.ts): replace hardcoded arrays with MlArray<FieldVar…
MartinMinkov Oct 16, 2023
82dfa33
feat(gates.ts): add MlArray import to convert limbs and crumbs arrays…
MartinMinkov Oct 16, 2023
a63c0d3
feat(rot.ts): implement provable rotation function
MartinMinkov Oct 16, 2023
19bfedf
refactor(rot.ts): simplify rot function by removing unnecessary check…
MartinMinkov Oct 16, 2023
1909678
feat(field.ts): add rot64 method to Field class for bit rotation
MartinMinkov Oct 16, 2023
a94394f
feat(field.unit-test.ts, int.ts): add rotation test
MartinMinkov Oct 16, 2023
eed078c
fix(rot.ts): correct condition to check if word is more than 64 bits
MartinMinkov Oct 16, 2023
60517f3
docs(CHANGELOG.md): add entry for bitwise ROT operation support for n…
MartinMinkov Oct 16, 2023
b8cc337
refactor(rot.ts): replace hardcoded value 64 with constant MAX_BITS
MartinMinkov Oct 16, 2023
3c8dd48
refactor(rot.ts): extract rotation logic into separate rotate function
MartinMinkov Oct 16, 2023
eba9209
fix(rot.ts): correct error message to match function name 'rot'
MartinMinkov Oct 16, 2023
8945d7f
refactor(rot.ts): simplify computation and conversion of rotated, exc…
MartinMinkov Oct 16, 2023
6745559
refactor: rename rot64 method to rot in field.ts, field.unit-test.ts,…
MartinMinkov Oct 16, 2023
e0bcb0f
feat(field.ts, int.ts): add default values for bits parameter in rot …
MartinMinkov Oct 16, 2023
e8e0c51
fix(field.ts, int.ts): correct the documentation for the rot function
MartinMinkov Oct 16, 2023
6f3a6c5
feat(gadgets.ts): add example e2e test
MartinMinkov Oct 16, 2023
cd024e5
refactor(rot.ts): split rot function into rot and rotate
MartinMinkov Oct 17, 2023
704c2d9
feat(gadgets.ts): add more test cases
MartinMinkov Oct 17, 2023
fb347bd
chore(rot.ts): minor refactors
MartinMinkov Oct 17, 2023
98bd547
feat(gadgets.ts): enhance testRot function to log output for better d…
MartinMinkov Oct 17, 2023
cc2d158
refactor(gadgets.ts): change testRot function parameters from number …
MartinMinkov Oct 17, 2023
b7698a6
refactor(rot.ts): update comment for clarity on the role of the prove…
MartinMinkov Oct 17, 2023
1e1f46e
chore(bindings): update subproject commit hash to latest version for …
MartinMinkov Oct 17, 2023
09585d2
Merge branch 'main' into feat/ROT-gadget
MartinMinkov Oct 17, 2023
b7b01a3
feat(gadgets.ts): add rot function to Gadgets namespace for bit rotation
MartinMinkov Oct 17, 2023
3f1de40
docs(field.ts, int.ts): rearrange and add more details to the rot met…
MartinMinkov Oct 17, 2023
c0210c3
feat(gadgets.unit-test.ts): add ROT ZkProgram and its equivalentAsync…
MartinMinkov Oct 17, 2023
22050e1
feat: add rot tests to primitive_constraint_system and vk_regression
MartinMinkov Oct 17, 2023
ea73c6a
feat(regression_test.json): add 'Bitwise Primitive' test case to exte…
MartinMinkov Oct 17, 2023
95a4559
Revert "feat(field.ts, int.ts): add default values for bits parameter…
MartinMinkov Oct 17, 2023
8f53db3
feat(rot): fix unit tests by only using Gadgets namespace for public API
MartinMinkov Oct 17, 2023
d86a4c8
chore(rot): minor doc and vk updates
MartinMinkov Oct 17, 2023
39328bf
chore(bindings): update bindings for compiled ROT artifacts
MartinMinkov Oct 17, 2023
300427c
feat(rot): minor refactor to rot gate checks and add range check to r…
MartinMinkov Oct 17, 2023
cd2c344
fix(gadgets.unit-test.ts): add await keyword to equivalentAsync funct…
MartinMinkov Oct 17, 2023
7de9b16
fix(regression_test.json): update 'rows' and 'digest' values in 'rot'…
MartinMinkov Oct 17, 2023
a77d0a8
refactor(rot.ts): simplify witnessSlices function
MartinMinkov Oct 18, 2023
fdc1061
Revert "refactor(rot.ts): simplify witnessSlices function"
MartinMinkov Oct 18, 2023
e795fb4
chore(bindings): update subproject commit hash to aa7f880 for latest …
MartinMinkov Oct 21, 2023
ca4be42
Merge branch 'main' into feat/ROT-gadget
MartinMinkov Oct 21, 2023
0442672
refactor(gadgets.ts): move testRot function and related tests to gadg…
MartinMinkov Oct 22, 2023
5b0de01
refactor(gadgets.unit-test.ts): convert inputs for rot to strings
MartinMinkov Oct 22, 2023
de2c091
refactor(gadgets.ts): simplify witness creation and rotation operations
MartinMinkov Oct 22, 2023
e3264ec
refactor(rot.ts): modify witnessSlices and change inputs to rot gate
MartinMinkov Oct 22, 2023
97af12c
refactor(rot.ts): simplify direction param usage
MartinMinkov Oct 23, 2023
20f10a0
refactor(field.unit-test.ts): remove rotation test from field.unit-te…
MartinMinkov Oct 23, 2023
9696ff0
fix(gadgets.ts): change expectedRight from integer to Field object to…
MartinMinkov Oct 23, 2023
27efd59
fix(rot.ts): use BigInt for comparison to handle large numbers correctly
MartinMinkov Oct 23, 2023
ef864c5
refactor(rot.ts): extract max bits check into a separate function to …
MartinMinkov Oct 23, 2023
c4a261e
refactor(gadgets.ts, gadgets.unit-test.ts, rot.ts, gates.ts, snarky.d…
MartinMinkov Oct 23, 2023
0f31fee
fix(rot.ts): replace 'rotated' with 'field' in rangeCheck64 function …
MartinMinkov Oct 23, 2023
10b8be9
fix(regression_test.json): update digest value in rot method to ensur…
MartinMinkov Oct 23, 2023
358105d
chore(bindings): update subproject commit hash to fcda5090bc6bf433188…
MartinMinkov Oct 25, 2023
87d5601
feat(gadgets.ts): update doc comment
MartinMinkov Oct 25, 2023
7dc93e1
Merge branch 'feat/ROT-gadget' of github.com:o1-labs/snarkyjs into fe…
MartinMinkov Oct 25, 2023
33aff5c
refactor(rot.ts): remove direction check in rot function
MartinMinkov Oct 25, 2023
00530af
refactor(rot.ts, gates.ts): replace Field type with bigint for two_to…
MartinMinkov Oct 25, 2023
1948ea4
fix(rot.ts): remove unnecessary range check on 'field' variable
MartinMinkov Oct 25, 2023
c188a56
refactor(gadgets.ts): change numeric literals to binary
MartinMinkov Oct 25, 2023
8a98da9
docs(CHANGELOG.md): update method description for bitwise rotation op…
MartinMinkov Oct 25, 2023
1f9fb24
feat(gadgets): rename rot to rotate
MartinMinkov Oct 25, 2023
b395950
refactor(rot.ts): simplify comments for better readability
MartinMinkov Oct 25, 2023
142e30f
fix(rot.ts): adjust validation range for rotation bits to exclude 0 a…
MartinMinkov Oct 25, 2023
b9ef95e
refactor(gadgets.unit-test.ts): simplify testRot function by removing…
MartinMinkov Oct 25, 2023
fbe8aa5
docs(gadgets.ts): enhance explanation of rotation operation and its c…
MartinMinkov Oct 25, 2023
8c50792
Revert "fix(rot.ts): adjust validation range for rotation bits to exc…
MartinMinkov Oct 26, 2023
36415ad
refactor(rot.ts): remove redundant checkMaxBits function call to impr…
MartinMinkov Oct 26, 2023
2f62bb1
Merge branch 'main' into feat/ROT-gadget
MartinMinkov Oct 26, 2023
b077078
fix(regression_test.json): update 'rot' method's rows and digest valu…
MartinMinkov Oct 26, 2023
9075a94
refactor(gadgets.ts): rename 'rot' function to 'rotate'
MartinMinkov Oct 26, 2023
03b4988
docs(CHANGELOG.md): update description for XOR operation to match for…
MartinMinkov Oct 26, 2023
e2ff992
refactor(bitwise.ts): merge rotate function from rot.ts into bitwise.ts
MartinMinkov Oct 26, 2023
6405b75
docs(gadgets.ts): enhance documentation for XOR gadget function
MartinMinkov Oct 26, 2023
131bce2
refactor(bitwise.ts, common.ts): extract common functions and constan…
MartinMinkov Oct 26, 2023
e950076
Merge branch 'main' into feat/ROT-gadget
MartinMinkov Oct 26, 2023
eeabe7c
feat(range-check.unit-test.ts): add name property to ROT ZkProgram
MartinMinkov Oct 26, 2023
6f2a0dd
fix(range-check.unit-test.ts): change Field value from string to BigInt
MartinMinkov Oct 26, 2023
e48c5e4
fix(bitwise.ts): adjust rotation bits range check to include 0 and MA…
MartinMinkov Oct 26, 2023
7cd98ee
refactor(bitwise.unit-test.ts): move rot tests from range-check.unit-…
MartinMinkov Oct 26, 2023
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
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,15 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added

- `Lightnet` namespace to interact with the account manager provided by the [lightnet Mina network](https://hub.docker.com/r/o1labs/mina-local-network). https://github.com/o1-labs/o1js/pull/1167

- 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
- Added bitwise `XOR` operation support for native field elements. https://github.com/o1-labs/o1js/pull/1177

- `Gadgets.rotate()`, new provable method to support bitwise rotation for native field elements. https://github.com/o1-labs/o1js/pull/1182

- `Gadgets.xor()`, new provable method to support bitwise xor for native field elements. https://github.com/o1-labs/o1js/pull/1177

- `Proof.dummy()` to create dummy proofs https://github.com/o1-labs/o1js/pull/1188
- You can use this to write ZkPrograms that handle the base case and the inductive case in the same method.

Expand Down
2 changes: 1 addition & 1 deletion src/bindings
46 changes: 42 additions & 4 deletions src/examples/gadgets.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,39 @@
import { Field, Provable, Gadgets, ZkProgram } from 'o1js';

let cs = Provable.constraintSystem(() => {
let f = Provable.witness(Field, () => Field(12));

let res1 = Gadgets.rotate(f, 2, 'left');
let res2 = Gadgets.rotate(f, 2, 'right');

res1.assertEquals(Field(48));
res2.assertEquals(Field(3));

Provable.log(res1);
Provable.log(res2);
});
console.log('constraint system: ', cs);

const ROT = ZkProgram({
name: 'rot-example',
methods: {
baseCase: {
privateInputs: [],
method: () => {
let a = Provable.witness(Field, () => Field(48));
let actualLeft = Gadgets.rotate(a, 2, 'left');
let actualRight = Gadgets.rotate(a, 2, 'right');

let expectedLeft = Field(192);
actualLeft.assertEquals(expectedLeft);

let expectedRight = Field(12);
actualRight.assertEquals(expectedRight);
MartinMinkov marked this conversation as resolved.
Show resolved Hide resolved
},
},
},
});

const XOR = ZkProgram({
name: 'xor-example',
methods: {
Expand All @@ -19,14 +53,18 @@ const XOR = ZkProgram({
console.log('compiling..');

console.time('compile');
await ROT.compile();
await XOR.compile();
console.timeEnd('compile');

console.log('proving..');

console.time('prove');
let proof = await XOR.baseCase();
console.timeEnd('prove');
console.time('rotation prove');
let rotProof = await ROT.baseCase();
console.timeEnd('rotation prove');
if (!(await ROT.verify(rotProof))) throw Error('rotate: Invalid proof');

console.time('xor prove');
let proof = await XOR.baseCase();
console.timeEnd('xor prove');
if (!(await XOR.verify(proof))) throw Error('Invalid proof');
else console.log('proof valid');
7 changes: 7 additions & 0 deletions src/examples/primitive_constraint_system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ const GroupMock = {
};

const BitwiseMock = {
rot() {
let a = Provable.witness(Field, () => new Field(12));
Gadgets.rotate(a, 2, 'left');
Gadgets.rotate(a, 2, 'right');
Gadgets.rotate(a, 4, 'left');
Gadgets.rotate(a, 4, 'right');
},
xor() {
let a = Provable.witness(Field, () => new Field(5n));
let b = Provable.witness(Field, () => new Field(5n));
Expand Down
4 changes: 4 additions & 0 deletions src/examples/regression_test.json
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@
"Bitwise Primitive": {
"digest": "Bitwise Primitive",
"methods": {
"rot": {
"rows": 13,
"digest": "2c0dadbba96fd7ddb9adb7d643425ce3"
},
"xor": {
"rows": 15,
"digest": "b3595a9cc9562d4f4a3a397b6de44971"
Expand Down
97 changes: 83 additions & 14 deletions src/lib/gadgets/bitwise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@ import { Provable } from '../provable.js';
import { Field as Fp } from '../../provable/field-bigint.js';
import { Field } from '../field.js';
import * as Gates from '../gates.js';
import {
MAX_BITS,
assert,
witnessSlices,
witnessNextValue,
divideWithRemainder,
} from './common.js';

export { xor };
export { xor, rotate };

function xor(a: Field, b: Field, length: number) {
// check that both input lengths are positive
Expand Down Expand Up @@ -111,21 +118,83 @@ function buildXor(
zero.assertEquals(expectedOutput);
}

function assert(stmt: boolean, message?: string) {
if (!stmt) {
throw Error(message ?? 'Assertion failed');
function rotate(
field: Field,
bits: number,
direction: 'left' | 'right' = 'left'
) {
// Check that the rotation bits are in range
assert(
bits >= 0 && bits <= MAX_BITS,
`rotation: expected bits to be between 0 and 64, got ${bits}`
);

if (field.isConstant()) {
assert(
field.toBigInt() < 2n ** BigInt(MAX_BITS),
`rotation: expected field to be at most 64 bits, got ${field.toBigInt()}`
);
return new Field(Fp.rot(field.toBigInt(), bits, direction));
}
const [rotated] = rot(field, bits, direction);
return rotated;
}

function witnessSlices(f: Field, start: number, length: number) {
if (length <= 0) throw Error('Length must be a positive number');

return Provable.witness(Field, () => {
let n = f.toBigInt();
return new Field((n >> BigInt(start)) & ((1n << BigInt(length)) - 1n));
});
}
function rot(
field: Field,
bits: number,
direction: 'left' | 'right' = 'left'
): [Field, Field, Field] {
const rotationBits = direction === 'right' ? MAX_BITS - bits : bits;
const big2Power64 = 2n ** BigInt(MAX_BITS);
const big2PowerRot = 2n ** BigInt(rotationBits);

const [rotated, excess, shifted, bound] = Provable.witness(
Provable.Array(Field, 4),
() => {
const f = field.toBigInt();

// Obtain rotated output, excess, and shifted for the equation:
// f * 2^rot = excess * 2^64 + shifted
const { quotient: excess, remainder: shifted } = divideWithRemainder(
f * big2PowerRot,
big2Power64
);

// Compute rotated value as: rotated = excess + shifted
const rotated = shifted + excess;
// Compute bound to check excess < 2^rot
const bound = excess + big2Power64 - big2PowerRot;
return [rotated, excess, shifted, bound].map(Field.from);
}
);

function witnessNextValue(current: Field) {
return Provable.witness(Field, () => new Field(current.toBigInt() >> 16n));
// Compute current row
Gates.rotate(
field,
rotated,
excess,
[
witnessSlices(bound, 52, 12), // bits 52-64
witnessSlices(bound, 40, 12), // bits 40-52
witnessSlices(bound, 28, 12), // bits 28-40
witnessSlices(bound, 16, 12), // bits 16-28
],
[
witnessSlices(bound, 14, 2), // bits 14-16
witnessSlices(bound, 12, 2), // bits 12-14
witnessSlices(bound, 10, 2), // bits 10-12
witnessSlices(bound, 8, 2), // bits 8-10
witnessSlices(bound, 6, 2), // bits 6-8
witnessSlices(bound, 4, 2), // bits 4-6
witnessSlices(bound, 2, 2), // bits 2-4
witnessSlices(bound, 0, 2), // bits 0-2
],
big2PowerRot
);
// Compute next row
Gates.rangeCheck64(shifted);
// Compute following row
Gates.rangeCheck64(excess);
return [rotated, excess, shifted];
}
61 changes: 58 additions & 3 deletions src/lib/gadgets/bitwise.unit-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import {
fieldWithRng,
} from '../testing/equivalent.js';
import { Fp, mod } from '../../bindings/crypto/finite_field.js';
import { Field } from '../field.js';
import { Field } from '../core.js';
import { Gadgets } from './gadgets.js';
import { Random } from '../testing/property.js';
import { test, Random } from '../testing/property.js';
import { Provable } from '../provable.js';

let Bitwise = ZkProgram({
name: 'bitwise',
Expand All @@ -21,6 +22,12 @@ let Bitwise = ZkProgram({
return Gadgets.xor(a, b, 64);
},
},
rot: {
privateInputs: [Field],
method(a: Field) {
return Gadgets.rotate(a, 12, 'left');
},
},
},
});

Expand All @@ -35,14 +42,28 @@ let uint = (length: number) => fieldWithRng(Random.biguint(length));
);
});

test(
Random.uint64,
Random.nat(64),
Random.boolean,
(x, n, direction, assert) => {
let z = Field(x);
let r1 = Fp.rot(x, n, direction ? 'left' : 'right');
Provable.runAndCheck(() => {
let f = Provable.witness(Field, () => z);
let r2 = Gadgets.rotate(f, n, direction ? 'left' : 'right');
Provable.asProver(() => assert(r1 === r2.toBigInt()));
});
}
);

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
await equivalentAsync(
{ from: [maybeUint64, maybeUint64], to: field },
{ runs: 3 }
Expand All @@ -57,3 +78,37 @@ await equivalentAsync(
return proof.publicOutput;
}
);

await equivalentAsync({ from: [field], to: field }, { runs: 3 })(
(x) => {
if (x >= 2n ** 64n) throw Error('Does not fit into 64 bits');
return Fp.rot(x, 12, 'left');
},
async (x) => {
let proof = await Bitwise.rot(x);
return proof.publicOutput;
}
);

function testRot(
field: Field,
bits: number,
mode: 'left' | 'right',
result: Field
) {
Provable.runAndCheck(() => {
let output = Gadgets.rotate(field, bits, mode);
output.assertEquals(result, `rot(${field}, ${bits}, ${mode})`);
});
}

testRot(Field(0), 0, 'left', Field(0));
testRot(Field(0), 32, 'right', Field(0));
testRot(Field(1), 1, 'left', Field(2));
testRot(Field(1), 63, 'left', Field(9223372036854775808n));
testRot(Field(256), 4, 'right', Field(16));
testRot(Field(1234567890), 32, 'right', Field(5302428712241725440));
testRot(Field(2651214356120862720), 32, 'right', Field(617283945));
testRot(Field(1153202983878524928), 32, 'right', Field(268500993));
testRot(Field(6510615555426900570n), 4, 'right', Field(11936128518282651045n));
testRot(Field(6510615555426900570n), 4, 'right', Field(11936128518282651045n));
37 changes: 37 additions & 0 deletions src/lib/gadgets/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Provable } from '../provable.js';
import { Field } from '../field.js';

const MAX_BITS = 64 as const;

export {
MAX_BITS,
assert,
witnessSlices,
witnessNextValue,
divideWithRemainder,
};

function assert(stmt: boolean, message?: string) {
if (!stmt) {
throw Error(message ?? 'Assertion failed');
}
}

function witnessSlices(f: Field, start: number, length: number) {
if (length <= 0) throw Error('Length must be a positive number');

return Provable.witness(Field, () => {
let n = f.toBigInt();
return new Field((n >> BigInt(start)) & ((1n << BigInt(length)) - 1n));
});
}

function witnessNextValue(current: Field) {
return Provable.witness(Field, () => new Field(current.toBigInt() >> 16n));
}

function divideWithRemainder(numerator: bigint, denominator: bigint) {
const quotient = numerator / denominator;
const remainder = numerator - denominator * quotient;
return { quotient, remainder };
}
Loading
Loading