Skip to content

Commit

Permalink
Merge pull request #1523 from o1-labs/feature/efficient-comparisons
Browse files Browse the repository at this point in the history
Efficient comparisons
  • Loading branch information
mitschabaude authored Mar 28, 2024
2 parents e34d28f + e15f77d commit 8dde2c3
Show file tree
Hide file tree
Showing 25 changed files with 733 additions and 394 deletions.
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
- 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

0 comments on commit 8dde2c3

Please sign in to comment.