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

Efficient comparisons #1523

Merged
merged 32 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
9c6fa34
bigint assert less than
mitschabaude Mar 26, 2024
e274fcd
bigint assert less than or equal
mitschabaude Mar 26, 2024
ffae661
unit tests for comparison assertions
mitschabaude Mar 26, 2024
0af69c1
helper in field stub module
mitschabaude Mar 27, 2024
41f75d1
more efficient comparison assertions which are generic over the range
mitschabaude Mar 27, 2024
ea60f08
refine assumptions of comparison gadget
mitschabaude Mar 27, 2024
9b5c337
use generic comparison assertion for uint, improve efficiency
mitschabaude Mar 27, 2024
79e610d
generic boolean comparisons
mitschabaude Mar 27, 2024
65e21bd
use generic boolean comparison for field
mitschabaude Mar 27, 2024
71a1b0d
use generic boolean comparisons for uint, improve efficiency
mitschabaude Mar 27, 2024
8726900
full field comparison
mitschabaude Mar 27, 2024
34bbd56
fixup uint lessthan
mitschabaude Mar 27, 2024
17fbdb6
get rid of circular dependencies from using foreign field gadgets for…
mitschabaude Mar 27, 2024
49f04f1
constant spec
mitschabaude Mar 27, 2024
53e5b11
make failing foreign field additions throw, so we can trust unit tests
mitschabaude Mar 27, 2024
9e2357c
full less than or equal
mitschabaude Mar 27, 2024
e703bdd
use full comparison assertions for field, massive efficiency gain
mitschabaude Mar 27, 2024
87be871
full boolean comparisons
mitschabaude Mar 27, 2024
1b80114
use full boolean comparisons
mitschabaude Mar 27, 2024
9531b51
remove 253-bits limitation from tests and docs
mitschabaude Mar 27, 2024
9606b23
fix edge case in bigint assert less than
mitschabaude Mar 28, 2024
76f261c
explicit unsafe bool creation and optimization for constants
mitschabaude Mar 28, 2024
10138fa
more efficient isEven that always works; assertBool returns a Bool
mitschabaude Mar 28, 2024
f04c396
it was a lovely trick, but this is more efficient
mitschabaude Mar 28, 2024
af78122
fixup diverse zkprogram types
mitschabaude Mar 28, 2024
9a362d2
use fast isOdd gadget in signature gadgets
mitschabaude Mar 28, 2024
0b56545
cleanup and changelog
mitschabaude Mar 28, 2024
a7808e2
final changelog tweaks
mitschabaude Mar 28, 2024
b973423
bindings
mitschabaude Mar 28, 2024
d2dc857
force recompile changes the vk
mitschabaude Mar 28, 2024
5348010
unrelated: fix ./run-in-browser on scripts that import o1js
mitschabaude Mar 28, 2024
e15f77d
typos
mitschabaude Mar 28, 2024
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Remove `CircuitValue`, `prop`, `arrayProp` and `matrixProp` https://github.com/o1-labs/o1js/pull/1507
- Remove `Mina.accountCreationFee()`, `Mina.BerkeleyQANet`, all APIs which accept private keys for feepayers, `Token`, `AccountUpdate.tokenSymbol`, `SmartContract.{token, setValue, setPermissions}`, "assert" methods for preconditions, `MerkleTee.calculateRootSlow()`, `Scalar.fromBigInt()`, `UInt64.lt()` and friends, deprecated static methods on `Group`, utility methods on `Circuit` like `Circuit.if()`, `Field.isZero()`, `isReady` and `shutdown()` https://github.com/o1-labs/o1js/pull/1515
- Remove `privateKey` from the accepted arguments of `SmartContract.deploy()` https://github.com/o1-labs/o1js/pull/1515
- **Efficient comparisons**. Support arbitrary bit lengths for `Field` comparisons and massively reduce their constraints https://github.com/o1-labs/o1js/pull/1523
- `Field.assertLessThan()` goes from 510 to 24 constraints, `Field.lessThan()` from 509 to 38
Copy link
Member

Choose a reason for hiding this comment

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

nice!

- Moderately improve other comparisons: `UInt64.assertLessThan()` from 27 to 14, `UInt64.lessThan()` from 27 to 15, `UInt32` similar.
- Massively improve `Field.isEven()`, add `Field.isOdd()`
- `PrivateKey.toPublicKey()` from 358 to 119 constraints thanks to `isOdd()`
- Add `Gadgets.ForeignField.assertLessThanOrEqual()` and support two variables as input to `ForeignField.assertLessThan()`
- Remove `this.sender` which unintuitively did not prove that its value was the actual sender of the transaction https://github.com/o1-labs/o1js/pull/1464 [@julio4](https://github.com/julio4)
Replaced by more explicit APIs:
- `this.sender.getUnconstrained()` which has the old behavior of `this.sender`, and returns an unconstrained value (which means that the prover can set it to any value they want)
Expand All @@ -53,6 +59,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Internal benchmarking tooling to keep track of performance https://github.com/o1-labs/o1js/pull/1481
- Add `toInput` method for `Group` instance https://github.com/o1-labs/o1js/pull/1483

### Changed

- `field.assertBool()` now also returns the `Field` as a `Bool` for ergonomics https://github.com/o1-labs/o1js/pull/1523

## [0.17.0](https://github.com/o1-labs/o1js/compare/1ad7333e9e...74948acac) - 2024-03-06

### Breaking changes
Expand Down
2 changes: 1 addition & 1 deletion src/bindings
Submodule bindings updated 0 files
14 changes: 13 additions & 1 deletion src/build/build-example.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async function build(srcPath, isWeb = false) {
resolveExtensions: ['.node.js', '.ts', '.js'],
logLevel: 'error',
plugins: isWeb
? [typescriptPlugin(tsConfig)]
? [typescriptPlugin(tsConfig), makeO1jsExternal()]
: [
typescriptPlugin(tsConfig),
makeNodeModulesExternal(),
Expand Down Expand Up @@ -123,6 +123,18 @@ function makeNodeModulesExternal() {
};
}

function makeO1jsExternal() {
return {
name: 'plugin-external',
setup(build) {
build.onResolve({ filter: /^o1js$/ }, () => ({
path: './index.js',
external: true,
}));
},
};
}

function makeJsooExternal() {
let isJsoo = /(bc.cjs|plonk_wasm.cjs)$/;
return {
Expand Down
20 changes: 19 additions & 1 deletion src/lib/provable/core/field-constructor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ import type { FieldVar, FieldConst } from './fieldvar.js';
export {
createField,
createBool,
createBoolUnsafe,
isField,
isBool,
getField,
getBool,
setFieldConstructor,
setBoolConstructor,
};
Expand All @@ -34,12 +37,16 @@ function createField(
return new fieldConstructor(value);
}

function createBool(value: boolean | Bool | FieldVar): Bool {
function createBool(value: boolean | Bool): Bool {
if (boolConstructor === undefined)
throw Error('Cannot construct a Bool before the class was defined.');
return new boolConstructor(value);
}

function createBoolUnsafe(value: Field): Bool {
return getBool().Unsafe.fromField(value);
}

function isField(x: unknown): x is Field {
if (fieldConstructor === undefined)
throw Error(
Expand All @@ -55,3 +62,14 @@ function isBool(x: unknown): x is Bool {
);
return x instanceof boolConstructor;
}

function getField(): typeof Field {
if (fieldConstructor === undefined)
throw Error('Field class not defined yet.');
return fieldConstructor;
}

function getBool(): typeof Bool {
if (boolConstructor === undefined) throw Error('Bool class not defined yet.');
return boolConstructor;
}
31 changes: 13 additions & 18 deletions src/lib/provable/crypto/signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,14 @@ class PublicKey extends CircuitValue {
*/
toGroup(): Group {
// compute y from elliptic curve equation y^2 = x^3 + 5
// TODO: we have to improve constraint efficiency by using range checks
let { x, isOdd } = this;
let ySquared = x.mul(x).mul(x).add(5);
let someY = ySquared.sqrt();
let isTheRightY = isOdd.equals(someY.toBits()[0]);
let y = isTheRightY
.toField()
.mul(someY)
.add(isTheRightY.not().toField().mul(someY.neg()));
let y = x.square().mul(x).add(5).sqrt();

// negate y if its parity is different from the public key's
let sameParity = y.isOdd().equals(isOdd).toField();
let sign = sameParity.mul(2).sub(1); // (2*sameParity - 1) == 1 if same parity, -1 if different parity
y = y.mul(sign);

return new Group({ x, y });
}

Expand All @@ -156,8 +155,7 @@ class PublicKey extends CircuitValue {
* @returns a {@link PublicKey}.
*/
static fromGroup({ x, y }: Group): PublicKey {
let isOdd = y.toBits()[0];
return PublicKey.fromObject({ x, isOdd });
return PublicKey.fromObject({ x, isOdd: y.isOdd() });
}

/**
Expand Down Expand Up @@ -260,12 +258,12 @@ class Signature extends CircuitValue {
deriveNonce(
{ fields: msg.map((f) => f.toBigInt()) },
{ x: publicKey.x.toBigInt(), y: publicKey.y.toBigInt() },
BigInt(d.toJSON()),
d.toBigInt(),
'testnet'
)
);
let { x: r, y: ry } = Group.generator.scale(kPrime);
const k = ry.toBits()[0].toBoolean() ? kPrime.neg() : kPrime;
const k = ry.isOdd().toBoolean() ? kPrime.neg() : kPrime;
let h = hashWithPrefix(
signaturePrefix('testnet'),
msg.concat([publicKey.x, publicKey.y, r])
Expand Down Expand Up @@ -294,25 +292,22 @@ class Signature extends CircuitValue {
// therefore we have to use scaleShifted which is very inefficient
let e = Scalar.fromBits(h.toBits());
let r = scaleShifted(point, e).neg().add(Group.generator.scale(this.s));
return Bool.and(r.x.equals(this.r), r.y.toBits()[0].equals(false));
return r.x.equals(this.r).and(r.y.isEven());
}

/**
* Decodes a base58 encoded signature into a {@link Signature}.
*/
static fromBase58(signatureBase58: string) {
let { r, s } = SignatureBigint.fromBase58(signatureBase58);
return Signature.fromObject({
r: Field(r),
s: Scalar.fromJSON(s.toString()),
});
return Signature.fromObject({ r: Field(r), s: Scalar.from(s) });
}
/**
* Encodes a {@link Signature} in base58 format.
*/
toBase58() {
let r = this.r.toBigInt();
let s = BigInt(this.s.toJSON());
let s = this.s.toBigInt();
return SignatureBigint.toBase58({ r, s });
}
}
Expand Down
Loading
Loading