Skip to content

Commit

Permalink
Merge pull request #935 from o1-labs/refactor/scalar
Browse files Browse the repository at this point in the history
Move Scalar to JS
  • Loading branch information
mitschabaude committed Jun 8, 2023
2 parents 7b5eb01 + a5bcb22 commit 9f08936
Show file tree
Hide file tree
Showing 21 changed files with 582 additions and 361 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Rewrite of `Provable.if()` causes breaking changes to all deployed contracts https://github.com/o1-labs/snarkyjs/pull/889
- Remove all deprecated methods and properties on `Field` https://github.com/o1-labs/snarkyjs/pull/902
- The `Field(x)` constructor and other Field methods no longer accept a `boolean` as input. Instead, you can now pass in a `bigint` to all Field methods. https://github.com/o1-labs/snarkyjs/pull/902
- Remove redundant `signFeePayer()` method https://github.com/o1-labs/snarkyjs/pull/935

### Added

- Add `field.assertNotEquals()` to assert that a field element does not equal some value https://github.com/o1-labs/snarkyjs/pull/902
- More efficient than `field.equals(x).assertFalse()`
- Add `scalar.toConstant()`, `scalar.toBigInt()`, `Scalar.from()`, `privateKey.toBigInt()`, `PrivateKey.fromBigInt()` https://github.com/o1-labs/snarkyjs/pull/935

### Changed

Expand Down
2 changes: 1 addition & 1 deletion src/bindings
7 changes: 2 additions & 5 deletions src/examples/api_exploration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,12 @@ console.assert(x0.equals(x1).toBoolean());
x1 = Field(37);
console.assert(x0.equals(x1).toBoolean());

// When initializing with booleans, true corresponds to the field element 1, and false corresponds to 0
const b = Field(true);
console.assert(b.equals(Field(1)).toBoolean());

/* You can perform arithmetic operations on field elements.
The arithmetic methods can take any "fieldy" values as inputs:
Field, number, string, or boolean
*/
const z = x0.mul(x1).add(b).div(234).square().neg().sub('67').add(false);
const b = Field(1);
const z = x0.mul(x1).add(b).div(234).square().neg().sub('67').add(0);

/* Field elements can be converted to their full, little endian binary representation. */
let bits: Bool[] = z.toBits();
Expand Down
1 change: 0 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export {
SmartContract,
method,
DeployArgs,
signFeePayer,
declareMethods,
Account,
VerificationKey,
Expand Down
53 changes: 11 additions & 42 deletions src/lib/account_update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { hashWithPrefix, packToFields } from './hash.js';
import { prefixes } from '../bindings/crypto/constants.js';
import { Context } from './global-context.js';
import { assert } from './errors.js';
import { Ml } from './ml/conversion.js';

// external API
export { AccountUpdate, Permissions, ZkappPublicInput };
Expand All @@ -40,7 +41,6 @@ export {
ZkappCommand,
addMissingSignatures,
addMissingProofs,
signJsonTransaction,
ZkappStateLength,
Events,
Actions,
Expand Down Expand Up @@ -1870,11 +1870,15 @@ function addMissingSignatures(
// there is a change signature will be added by the wallet
// if not, error will be thrown by verifyAccountUpdate
// while .send() execution
return { body, authorization: Ledger.dummySignature() }
return { body, authorization: Ledger.dummySignature() };
}
privateKey = additionalKeys[i];
}
let signature = Ledger.signFieldElement(fullCommitment, privateKey, false);
let signature = Ledger.signFieldElement(
fullCommitment,
Ml.fromPrivateKey(privateKey),
false
);
return { body, authorization: signature };
}

Expand All @@ -1894,7 +1898,9 @@ function addMissingSignatures(
// if not, error will be thrown by verifyAccountUpdate
// while .send() execution
Authorization.setSignature(accountUpdate, Ledger.dummySignature());
return accountUpdate as AccountUpdate & { lazyAuthorization: undefined };
return accountUpdate as AccountUpdate & {
lazyAuthorization: undefined;
};
}
privateKey = additionalKeys[i];
}
Expand All @@ -1903,7 +1909,7 @@ function addMissingSignatures(
: commitment;
let signature = Ledger.signFieldElement(
transactionCommitment,
privateKey,
Ml.fromPrivateKey(privateKey),
false
);
Authorization.setSignature(accountUpdate, signature);
Expand Down Expand Up @@ -2042,40 +2048,3 @@ async function addMissingProofs(
proofs,
};
}

/**
* Sign all accountUpdates of a transaction which belong to the account
* determined by [[ `privateKey` ]].
* @returns the modified transaction JSON
*/
function signJsonTransaction(
transactionJson: string,
privateKey: PrivateKey | string
) {
if (typeof privateKey === 'string')
privateKey = PrivateKey.fromBase58(privateKey);
let publicKey = privateKey.toPublicKey().toBase58();
let zkappCommand: Types.Json.ZkappCommand = JSON.parse(transactionJson);
let feePayer = zkappCommand.feePayer;
if (feePayer.body.publicKey === publicKey) {
zkappCommand = JSON.parse(
Ledger.signFeePayer(JSON.stringify(zkappCommand), privateKey)
);
}
for (let i = 0; i < zkappCommand.accountUpdates.length; i++) {
let accountUpdate = zkappCommand.accountUpdates[i];
if (
accountUpdate.body.publicKey === publicKey &&
accountUpdate.authorization.proof === null
) {
zkappCommand = JSON.parse(
Ledger.signOtherAccountUpdate(
JSON.stringify(zkappCommand),
privateKey,
i
)
);
}
}
return JSON.stringify(zkappCommand);
}
29 changes: 2 additions & 27 deletions src/lib/core.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { bytesToBigInt } from '../bindings/crypto/bigint-helpers.js';
import { defineBinable } from '../bindings/lib/binable.js';
import { sizeInBits } from '../provable/field-bigint.js';
import { Bool, Scalar } from '../snarky.js';
import { Bool } from '../snarky.js';
import { Field as InternalField } from './field.js';
import { Group as InternalGroup } from './group.js';
import { Scalar as ScalarBigint } from '../provable/curve-bigint.js';
import { mod } from '../bindings/crypto/finite_field.js';
import { Scalar } from './scalar.js';

export { Field, Bool, Scalar, Group };

Expand Down Expand Up @@ -69,7 +67,6 @@ type InferReturn<T> = T extends new (...args: any) => infer Return
// patching ocaml classes

Bool.toAuxiliary = () => [];
Scalar.toAuxiliary = () => [];

Bool.toInput = function (x) {
return { packed: [[x.toField(), 1] as [Field, number]] };
Expand All @@ -88,25 +85,3 @@ Bool.toBytes = BoolBinable.toBytes;
Bool.fromBytes = BoolBinable.fromBytes;
Bool.readBytes = BoolBinable.readBytes;
Bool.sizeInBytes = () => 1;

Scalar.toFieldsCompressed = function (s: Scalar) {
let isConstant = s.toFields().every((x) => x.isConstant());
let constantValue: Uint8Array | undefined = (s as any).constantValue;
if (!isConstant || constantValue === undefined)
throw Error(
`Scalar.toFieldsCompressed is not available in provable code.
That means it can't be called in a @method or similar environment, and there's no alternative implemented to achieve that.`
);
let x = bytesToBigInt(constantValue);
let lowBitSize = BigInt(sizeInBits - 1);
let lowBitMask = (1n << lowBitSize) - 1n;
return {
field: Field(x & lowBitMask),
highBit: Bool(x >> lowBitSize === 1n),
};
};

Scalar.fromBigInt = function (scalar: bigint) {
scalar = mod(scalar, ScalarBigint.modulus);
return Scalar.fromJSON(scalar.toString());
};
3 changes: 1 addition & 2 deletions src/lib/encryption.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Scalar } from '../snarky.js';
import { Field, Group } from './core.js';
import { Field, Scalar, Group } from './core.js';
import { Poseidon } from './hash.js';
import { Provable } from './provable.js';
import { PrivateKey, PublicKey } from './signature.js';
Expand Down
9 changes: 9 additions & 0 deletions src/lib/field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ function constFromBigint(x: Fp) {
const FieldConst = {
fromBigint: constFromBigint,
toBigint: constToBigint,
equal(x: FieldConst, y: FieldConst) {
for (let i = 0, n = Fp.sizeInBytes(); i < n; i++) {
if (x[i] !== y[i]) return false;
}
return true;
},
[0]: constFromBigint(0n),
[1]: constFromBigint(1n),
[-1]: constFromBigint(Fp(-1n)),
Expand Down Expand Up @@ -59,6 +65,9 @@ const FieldVar = {
let x0 = typeof x === 'bigint' ? FieldConst.fromBigint(x) : x;
return [FieldType.Constant, x0];
},
isConstant(x: FieldVar): x is ConstantFieldVar {
return x[0] === FieldType.Constant;
},
// TODO: handle (special) constants
add(x: FieldVar, y: FieldVar): FieldVar {
return [FieldType.Add, x, y];
Expand Down
16 changes: 8 additions & 8 deletions src/lib/group.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ describe('group', () => {
it('x*g+y*g = (x+y)*g', () => {
expect(() => {
Provable.runAndCheck(() => {
const x = Scalar.fromJSON(2)!;
const y = Scalar.fromJSON(3)!;
const x = Scalar.from(2);
const y = Scalar.from(3);
const left = g.scale(x).add(g.scale(y));
const right = g.scale(x.add(y));
left.assertEquals(right);
Expand All @@ -87,8 +87,8 @@ describe('group', () => {
it('x*(y*g) = (x*y)*g', () => {
expect(() => {
Provable.runAndCheck(() => {
const x = Scalar.fromJSON(2)!;
const y = Scalar.fromJSON(3)!;
const x = Scalar.from(2);
const y = Scalar.from(3);
const left = g.scale(y).scale(x);
const right = g.scale(y.mul(x));
left.assertEquals(right);
Expand Down Expand Up @@ -187,16 +187,16 @@ describe('group', () => {
});

it('x*g+y*g = (x+y)*g', () => {
const x = Scalar.fromJSON(2)!;
const y = Scalar.fromJSON(3)!;
const x = Scalar.from(2);
const y = Scalar.from(3);
const left = g.scale(x).add(g.scale(y));
const right = g.scale(x.add(y));
expect(left).toEqual(right);
});

it('x*(y*g) = (x*y)*g', () => {
const x = Scalar.fromJSON(2)!;
const y = Scalar.fromJSON(3)!;
const x = Scalar.from(2);
const y = Scalar.from(3);
const left = g.scale(y).scale(x);
const right = g.scale(y.mul(x));
expect(left).toEqual(right);
Expand Down
21 changes: 8 additions & 13 deletions src/lib/group.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Scalar } from './core.js';
import { Field, FieldVar, isField } from './field.js';
import { Scalar } from './scalar.js';
import { Bool, Snarky } from '../snarky.js';
import { Field as Fp } from '../provable/field-bigint.js';
import { Pallas } from '../bindings/crypto/elliptic_curve.js';
Expand Down Expand Up @@ -124,20 +124,15 @@ class Group {
* ```
*/
scale(s: Scalar | number | bigint) {
let scalar =
typeof s === 'bigint' || typeof s === 'number'
? Scalar.fromBigInt(BigInt(s))
: s;
let fields = scalar.toFields();

if (this.#isConstant() && fields.every((f) => f.isConstant())) {
let g_proj = Pallas.scale(this.#toProjective(), BigInt(scalar.toJSON()));
let scalar = Scalar.from(s);

if (this.#isConstant() && scalar.isConstant()) {
let g_proj = Pallas.scale(this.#toProjective(), scalar.toBigInt());
return Group.#fromProjective(g_proj);
} else {
let [, x, y] = Snarky.group.scale(this.#toTuple(), [
0,
...fields.map((f) => f.value).reverse(),
]);
let [, ...bits] = scalar.value;
bits.reverse();
let [, x, y] = Snarky.group.scale(this.#toTuple(), [0, ...bits]);
return new Group({ x, y });
}
}
Expand Down
24 changes: 24 additions & 0 deletions src/lib/ml/consistency.unit-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Test } from '../../snarky.js';
import { Random, test } from '../testing/property.js';
import { PrivateKey } from '../signature.js';
import { Ml } from './conversion.js';
import { expect } from 'expect';

// PrivateKey.toBase58, fromBase58

test(Random.privateKey, (s) => {
// private key to/from bigint
let sk = PrivateKey.fromBigInt(s);
expect(sk.toBigInt()).toEqual(s);

let skMl = Ml.fromPrivateKey(sk);

// toBase58 - check consistency with ml
let ml = Test.encoding.privateKeyToBase58(skMl);
let js = sk.toBase58();
expect(js).toEqual(ml);

// fromBase58 - check consistency with where we started
expect(PrivateKey.fromBase58(js)).toEqual(sk);
expect(Test.encoding.privateKeyOfBase58(ml)).toEqual(skMl);
});
29 changes: 29 additions & 0 deletions src/lib/ml/conversion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* this file contains conversion functions between JS and OCaml
*/

import { Scalar, ScalarConst } from '../scalar.js';
import { PrivateKey } from '../signature.js';

export { Ml };

const Ml = {
fromScalar,
toScalar,
fromPrivateKey,
toPrivateKey,
};

function fromScalar(s: Scalar) {
return s.toConstant().constantValue;
}
function toScalar(s: ScalarConst) {
return Scalar.from(s);
}

function fromPrivateKey(sk: PrivateKey) {
return fromScalar(sk.s);
}
function toPrivateKey(sk: ScalarConst) {
return new PrivateKey(Scalar.from(sk));
}
Loading

0 comments on commit 9f08936

Please sign in to comment.